Skip to content

Feat/http middleware - #89

Merged
NEFORCEO merged 13 commits into
masterfrom
feat/http-middleware
Jul 25, 2026
Merged

Feat/http middleware#89
NEFORCEO merged 13 commits into
masterfrom
feat/http-middleware

Conversation

@NEFORCEO

Copy link
Copy Markdown
Member

🚀 Description

Adds @app.middleware("http") — a function-based middleware decorator, FastAPI-style, as an alternative to subclassing BaseMiddleware.

The decorated function receives (request: Request, call_next) and wraps the entire per-request pipeline (retries, redirects, existing class-based BaseMiddleware, the route handler) in a single coroutine — everything before await call_next(request) runs before the request is sent, everything after runs once the response (or None, on failure) comes back. Multiple registered middleware nest like a stack, first registered = outermost.

A new Request class (fasthttp/request.py, exported as fasthttp.Request) represents the outgoing request: .method, .url (httpx.URL), .host, mutable .headers, read-only .query_params/.json/.content snapshots, .route, .app, and .state (a SimpleNamespace scratch space for passing data from before call_next to after it).

@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

Only "http" is supported for now — @app.graphql and @app.ws don't run through HTTPClient at all (GraphQL builds its own httpx.AsyncClient internally, WebSocket connects directly via websockets.connect), so app.middleware("ws")/app.middleware("graphql") raise ValueError rather than silently doing nothing.

Implementation notes

  • HTTPClient.send() gained an optional extra_headers kwarg, merged into the request config before class-based middleware runs — this is how request.headers mutations from function middleware actually reach the outgoing request.
  • FastHTTP._run_route() builds the call_next chain around the existing self.client.send(...) call site — the only place HTTPClient.send is invoked — so class-based BaseMiddleware, retries, and redirects are untouched.
  • CallNext/HTTPMiddlewareFunc type aliases live in fasthttp/types.py (TYPE_CHECKING-only, Request/Response imported there only for typing to avoid circular imports).
  • .json/.content/.query_params on Request are intentionally read-only: HTTPClient._execute_request already reads the body straight from route.json/route.data/route.files rather than from the mutable config dict, so mutating them wouldn't affect what's sent — same pre-existing limitation BaseMiddleware.request()'s kwargs["json"] already has today. Documented rather than silently misleading.

🧩 Type of Change

  • feat: New feature

✅ Checklist

  • Tests added or updated
  • Documentation updated
  • No breaking changes
  • Code follows project style

🔗 Related Issues

Fixes #


📸 Additional Context

Tests (tests/test_app.py::TestFastHTTPMiddlewareDecorator, 5 new):

  • rejects unsupported middleware_type
  • registers the function
  • headers mutated in middleware reach the outgoing request; response headers set after call_next are preserved
  • registration order is outer-to-inner (outer:before → inner:before → inner:after → outer:after)
  • returning without calling call_next short-circuits the request entirely (client.request never called)

Docs: new tutorial page docs/{en,ru}/tutorial/middleware/function.md (linked from the middleware nav and middleware/index.md), plus an app.middleware() / Request section in docs/{en,ru}/reference/middleware.md. mkdocs build passes clean, no new broken links.

Full suite: 577 passed, 1 skipped. ruff check clean on all touched files.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Thanks for your contribution, @NEFORCEO!

Please make sure:

  • Code is clean and formatted
  • Tests are passing
  • PR description is clear

Maintainers will review it soon.

@codspeed-hq

codspeed-hq Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will improve performance by 41.42%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 6 improved benchmarks
✅ 6 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
test_response_req_text_from_json 213.2 µs 134.7 µs +58.28%
test_response_repr 196.4 µs 133.7 µs +46.88%
test_response_property_access 194.5 µs 133.7 µs +45.45%
test_response_creation 201.3 µs 139.3 µs +44.44%
test_response_json_parsing 205.6 µs 143.5 µs +43.28%
test_middleware_after_response 403.1 µs 352.7 µs +14.29%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feat/http-middleware (6bcf0d0) with master (6f3d441)1

Open in CodSpeed

Footnotes

  1. No successful run was found on master (49e1774) during the generation of this report, so 6f3d441 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@NEFORCEO
NEFORCEO merged commit 33c4899 into master Jul 25, 2026
17 checks passed
@NEFORCEO
NEFORCEO deleted the feat/http-middleware branch July 25, 2026 06:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants