Skip to content
45 changes: 45 additions & 0 deletions docs/en/reference/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,51 @@ Methods are called automatically by `HTTPClient`.

---

## `app.middleware(middleware_type)`

Registers a function-based middleware, FastAPI-style. See the
[Function-based Middleware](../tutorial/middleware/function.md) tutorial
for the full guide.

```python
@app.middleware("http")
async def mw(request: Request, call_next):
return await call_next(request)
```

| Parameter | Type | Description |
|-----------|------|--------------|
| `middleware_type` | `Literal["http"]` | Only `"http"` is currently supported — raises `ValueError` otherwise |

The decorated function receives `(request: Request, call_next)` and must
return (or replace) the result of `await call_next(request)`. Multiple
registered middleware nest like a stack — the first registered is
outermost.

## Request

Passed to `@app.middleware("http")` handlers. Represents the outgoing
request before it's sent.

```python
from fasthttp import Request
```

| Attribute | Type | Description |
|-----------|------|--------------|
| `method` | `str` | HTTP method being sent |
| `url` | `httpx.URL` | Structured URL (`.host`, `.port`, `.scheme`, `.path`, `.params`) |
| `host` | `str \| None` | Shortcut for `request.url.host` |
| `headers` | `dict[str, str]` | Mutable — reaches the outgoing request |
| `query_params` | `dict[str, Any]` | Read-only snapshot |
| `json` | `dict \| None` | Read-only snapshot |
| `content` | `Any` | Read-only snapshot |
| `route` | `Route` | The `Route` being executed |
| `app` | `FastHTTP` | The application instance |
| `state` | `SimpleNamespace` | Per-request scratch space |

---

## CookieJar

Cookie storage passed to `FastHTTP(cookie_jar=...)`. Captures `Set-Cookie` headers from responses and injects cookies into subsequent requests.
Expand Down
137 changes: 137 additions & 0 deletions docs/en/tutorial/middleware/function.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Function-based Middleware

`@app.middleware("http")` registers a plain async function as middleware, FastAPI-style — no `BaseMiddleware` subclass required.

```python
import time
from fasthttp import FastHTTP, Request
from fasthttp.response import Response

app = FastHTTP()


@app.middleware("http")
async def timing(request: Request, call_next):
start = time.monotonic()
response = await call_next(request)
if response is not None:
response.headers["X-Elapsed"] = str(time.monotonic() - start)
return response


@app.get(url="https://httpbin.org/get")
async def get_data(resp: Response) -> dict:
return resp.json()


app.run()
```

!!! note "Only `"http"` is supported for now"
WebSocket (`@app.ws`) and GraphQL (`@app.graphql`) requests don't run
through `HTTPClient`, so they don't go through this decorator yet.
Calling `app.middleware("ws")` or `app.middleware("graphql")` raises
`ValueError`. Use [`BaseMiddleware`](creating.md) or
[event hooks](../../middleware.md#event-hooks) for those in the meantime.

## The `call_next` pattern

Unlike `BaseMiddleware`, which splits `request()`/`response()` into two
separate methods, a function middleware wraps the **entire** call in one
coroutine — including retries, redirects, class-based `BaseMiddleware`,
and the route handler itself:

```
add_trace_id ─┐ ┌─ (everything after call_next)
await call_next(request) ──▶ retries/redirects/BaseMiddleware/handler
(everything before call_next) ◀──────┘
```

Everything **before** `await call_next(request)` runs before the request is
sent. Everything **after** runs once the response (or `None`, on failure)
comes back — including the route handler's return value already being
processed.

Register several and they nest like a stack — the **first registered is
outermost**:

```python
@app.middleware("http")
async def outer(request: Request, call_next):
print("outer: before")
response = await call_next(request)
print("outer: after")
return response


@app.middleware("http")
async def inner(request: Request, call_next):
print("inner: before")
response = await call_next(request)
print("inner: after")
return response
```

Order: `outer: before` → `inner: before` → request sent → `inner: after` → `outer: after`.

## Short-circuiting

Return without calling `call_next(request)` to skip the request entirely
— useful for auth guards or cache short-circuits:

```python
@app.middleware("http")
async def block_blank_host(request: Request, call_next):
if not request.host:
return None
return await call_next(request)
```

## `Request`

`Request` describes the outgoing request *before* it's sent. Unlike an
incoming ASGI request, the body is already fully built — there's nothing
to stream — so only `.headers` is mutable and actually reaches the
request. `.json` / `.content` / `.query_params` are read-only snapshots
for inspection (logging, tracing) — mutating them has no effect, matching
how `BaseMiddleware.request()`'s `kwargs["json"]`/`kwargs["data"]` already
behave.

| Attribute | Type | Description |
|-----------|------|--------------|
| `method` | `str` | HTTP method being sent |
| `url` | `httpx.URL` | Structured URL — `.host`, `.port`, `.scheme`, `.path`, `.params` |
| `host` | `str \| None` | Shortcut for `request.url.host` |
| `headers` | `dict[str, str]` | **Mutable** — changes are sent with the request |
| `query_params` | `dict[str, Any]` | Read-only snapshot of the query params that will be sent |
| `json` | `dict \| None` | Read-only snapshot of the JSON body, if any |
| `content` | `Any` | Read-only snapshot of the raw body/form data, if any |
| `route` | `Route` | The `Route` being executed — `tags`, `response_model`, etc. |
| `app` | `FastHTTP` | The application instance — access app-level config from middleware |
| `state` | `SimpleNamespace` | Per-request scratch space — stash data before `call_next` and read it after |

### Using `.state` to pass data across the call

```python
@app.middleware("http")
async def timing(request: Request, call_next):
request.state.start = time.monotonic()
response = await call_next(request)
elapsed = time.monotonic() - request.state.start
if response is not None:
response.headers["X-Elapsed"] = f"{elapsed:.3f}"
return response
```

## Mixing with class-based middleware

Function middleware (`@app.middleware("http")`) and `BaseMiddleware`
(`FastHTTP(middleware=[...])`) can be used together. Function middleware
always wraps **outside** — it sees the request first and the response
last, with all `BaseMiddleware` instances and the route handler running
in between.

## See also

- [Creating Middleware](creating.md) — class-based `BaseMiddleware` API
- [Middleware Reference](../../reference/middleware.md) — full API reference
1 change: 1 addition & 0 deletions docs/en/tutorial/middleware/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Output:
## Next steps

- [Creating Middleware](creating.md) — BaseMiddleware API, class attributes, pipe chaining
- [Function-based Middleware](function.md) — `@app.middleware("http")`, FastAPI-style
- [Examples](examples.md) — auth, logging, timing, method filtering, toggle

## Comparison with dependencies
Expand Down
45 changes: 45 additions & 0 deletions docs/ru/reference/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,51 @@ manager = MiddlewareManager([AuthMiddleware(), LoggingMiddleware()])

---

## `app.middleware(middleware_type)`

Регистрирует function-based middleware в стиле FastAPI. Полное
руководство — в туториале
[Function-based Middleware](../tutorial/middleware/function.md).

```python
@app.middleware("http")
async def mw(request: Request, call_next):
return await call_next(request)
```

| Параметр | Тип | Описание |
|----------|-----|----------|
| `middleware_type` | `Literal["http"]` | Пока поддерживается только `"http"` — иначе `ValueError` |

Декорируемая функция принимает `(request: Request, call_next)` и должна
вернуть (или заменить) результат `await call_next(request)`. Несколько
зарегистрированных middleware вкладываются как стек — первая
зарегистрированная становится самой внешней.

## Request

Передаётся в обработчики `@app.middleware("http")`. Представляет
исходящий запрос до отправки.

```python
from fasthttp import Request
```

| Атрибут | Тип | Описание |
|---------|-----|----------|
| `method` | `str` | HTTP-метод отправляемого запроса |
| `url` | `httpx.URL` | Структурированный URL (`.host`, `.port`, `.scheme`, `.path`, `.params`) |
| `host` | `str \| None` | Шорткат для `request.url.host` |
| `headers` | `dict[str, str]` | Мутабельно — уходит вместе с запросом |
| `query_params` | `dict[str, Any]` | Read-only снимок |
| `json` | `dict \| None` | Read-only снимок |
| `content` | `Any` | Read-only снимок |
| `route` | `Route` | Выполняемый `Route` |
| `app` | `FastHTTP` | Экземпляр приложения |
| `state` | `SimpleNamespace` | Scratch-пространство на один запрос |

---

## CookieJar

Хранилище кук, передаётся в `FastHTTP(cookie_jar=...)`. Перехватывает `Set-Cookie` из ответов и подставляет куки в последующие запросы.
Expand Down
138 changes: 138 additions & 0 deletions docs/ru/tutorial/middleware/function.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Function-based Middleware

`@app.middleware("http")` регистрирует обычную async-функцию как middleware — в стиле FastAPI, без наследования от `BaseMiddleware`.

```python
import time
from fasthttp import FastHTTP, Request
from fasthttp.response import Response

app = FastHTTP()


@app.middleware("http")
async def timing(request: Request, call_next):
start = time.monotonic()
response = await call_next(request)
if response is not None:
response.headers["X-Elapsed"] = str(time.monotonic() - start)
return response


@app.get(url="https://httpbin.org/get")
async def get_data(resp: Response) -> dict:
return resp.json()


app.run()
```

!!! note "Пока поддерживается только `"http"`"
WebSocket (`@app.ws`) и GraphQL (`@app.graphql`) запросы не проходят
через `HTTPClient`, поэтому через этот декоратор пока не идут.
Вызов `app.middleware("ws")` или `app.middleware("graphql")`
выбрасывает `ValueError`. Пока используйте
[`BaseMiddleware`](creating.md) или
[event hooks](../../middleware.md#event-hooks).

## Паттерн `call_next`

В отличие от `BaseMiddleware`, где `request()`/`response()` — два разных
метода, function-middleware оборачивает **весь** вызов одной корутиной —
включая retry, редиректы, class-based `BaseMiddleware` и сам обработчик
роута:

```
add_trace_id ─┐ ┌─ (всё после call_next)
await call_next(request) ──▶ retries/redirects/BaseMiddleware/handler
(всё до call_next) ◀──────────────────┘
```

Всё, что **до** `await call_next(request)`, выполняется до отправки
запроса. Всё, что **после**, — уже после того как пришёл ответ (или
`None` при ошибке), причём результат обработчика роута к этому моменту
уже обработан.

Зарегистрируй несколько — они вложатся как стек, **первая
зарегистрированная — самая внешняя**:

```python
@app.middleware("http")
async def outer(request: Request, call_next):
print("outer: before")
response = await call_next(request)
print("outer: after")
return response


@app.middleware("http")
async def inner(request: Request, call_next):
print("inner: before")
response = await call_next(request)
print("inner: after")
return response
```

Порядок: `outer: before` → `inner: before` → запрос отправлен → `inner: after` → `outer: after`.

## Короткое замыкание

Верни значение, не вызывая `call_next(request)`, чтобы полностью
пропустить запрос — полезно для auth-guard'ов или кэш-шорткатов:

```python
@app.middleware("http")
async def block_blank_host(request: Request, call_next):
if not request.host:
return None
return await call_next(request)
```

## `Request`

`Request` описывает исходящий запрос *до* отправки. В отличие от
входящего ASGI-запроса, тело уже полностью собрано — стримить нечего,
поэтому мутабелен только `.headers`, и только он реально влияет на
отправляемый запрос. `.json` / `.content` / `.query_params` — это
read-only снимки для инспекции (логирование, трейсинг) — их изменение
ни на что не влияет, ровно как и `kwargs["json"]`/`kwargs["data"]` в
`BaseMiddleware.request()` уже сегодня.

| Атрибут | Тип | Описание |
|---------|-----|----------|
| `method` | `str` | HTTP-метод отправляемого запроса |
| `url` | `httpx.URL` | Структурированный URL — `.host`, `.port`, `.scheme`, `.path`, `.params` |
| `host` | `str \| None` | Шорткат для `request.url.host` |
| `headers` | `dict[str, str]` | **Мутабельно** — изменения уходят вместе с запросом |
| `query_params` | `dict[str, Any]` | Read-only снимок query-параметров, которые будут отправлены |
| `json` | `dict \| None` | Read-only снимок JSON-тела, если есть |
| `content` | `Any` | Read-only снимок сырого тела/form-data, если есть |
| `route` | `Route` | Выполняемый `Route` — `tags`, `response_model` и т.д. |
| `app` | `FastHTTP` | Экземпляр приложения — доступ к конфигу уровня app из middleware |
| `state` | `SimpleNamespace` | Scratch-пространство на один запрос — положи данные до `call_next`, прочитай после |

### `.state` для передачи данных между фазами

```python
@app.middleware("http")
async def timing(request: Request, call_next):
request.state.start = time.monotonic()
response = await call_next(request)
elapsed = time.monotonic() - request.state.start
if response is not None:
response.headers["X-Elapsed"] = f"{elapsed:.3f}"
return response
```

## Совмещение с class-based middleware

Function middleware (`@app.middleware("http")`) и `BaseMiddleware`
(`FastHTTP(middleware=[...])`) можно использовать вместе. Function
middleware всегда оборачивает **снаружи** — видит запрос первой и ответ
последней, а все `BaseMiddleware` и обработчик роута выполняются между
этими точками.

## Смотри также

- [Создание Middleware](creating.md) — API class-based `BaseMiddleware`
- [Middleware Reference](../../reference/middleware.md) — полный справочник API
1 change: 1 addition & 0 deletions docs/ru/tutorial/middleware/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ app = FastHTTP(middleware=[LoggingMiddleware()])
## Далее

- [Создание Middleware](creating.md) — API BaseMiddleware, атрибуты класса, pipe-чейнинг
- [Function-based Middleware](function.md) — `@app.middleware("http")` в стиле FastAPI
- [Примеры](examples.md) — auth, логирование, тайминги, фильтр по методам, toggle

## Сравнение с зависимостями
Expand Down
Loading
Loading