docs: update README positioning and 0.3.0 changelog heading - #93
Merged
Conversation
…dy cap Switch ASGI body collection to linear bytearray accumulation and stop oversized chunked requests during read with a 413 response aligned to existing payload-too-large semantics.
Add regression tests for chunked JSON body reconstruction and early 413 behavior when chunked input exceeds the configured ASGI body limit.
Add non-http scope behavior checks and a dedicated suite for ASGI body-limit parsing, boundary handling, and disconnect semantics.
Add websocket edge-case tests and unit-level coverage for CORS, CSRF, HTTPException, security headers, and SSE helpers.
Issue 70 asgi body cap
Resolve route and method before consuming request body so invalid path/method traffic returns early and avoids unnecessary buffering work.
Add RSGI probe tests that assert protocol body reads are not triggered for 404 and 405 responses, protecting the new fast-fail behavior.
…ore-body-read perf(dispatch): fast-fail 404/405 before body read
Validate structured response headers and Set-Cookie lines at the framework boundary and fail safely instead of forwarding potentially injected header content.
Add regression tests that assert malicious header and cookie payloads are rejected with safe 500 responses, including HTTPException header payloads.
security(headers): validate response header/cookie values against CRLF
Replace per-request asyncio.run loop creation with a lazily initialized thread-local loop to reduce ASGI bridge fixed overhead under load.
Add regression coverage that fails if the blocking bridge path reintroduces per-request asyncio.run, with explicit loop cleanup for test hygiene.
perf(asgi): remove per-request fresh event-loop creation
Move the multipart parse path to consume the existing request buffer via mem::take instead of cloning the full body, reducing peak memory overhead for large uploads.
perf(form): eliminate multipart full-body clone
Build compiled route tables lazily on first HTTP request and invalidate stale snapshots when routes are added before freeze, keeping hot-path matching lock-free without forcing strict startup ordering.
Add regression tests that verify routes added after first request still resolve and 405/Allow behavior remains correct with lazy compiled snapshots.
Document first-request auto-compilation, freeze semantics, and snapshot invalidation/rebuild when routes are added before freeze.
perf(routing): auto-enable compiled snapshot on request path
Wrap RouteEntry's per-request-immutable fields (algs, dep_names, dep_factories, dep_is_async, dep_wants_request, handler_param_names) in Arc so the dispatcher can `Arc::clone` cheaply instead of copying a Vec or rebuilding a HashSet on every request. Refs #76.
Reduce the number of Python::with_gil blocks taken per RSGI request and defer expensive header/query/body extraction until the handler actually needs it: - Snapshot scope.proto/method/path/query_string and clone shared middleware/cors/security configs in a single GIL block at function entry, replacing ~4 separate getattr round-trips and a state.read. - Skip body, query, authorization, and cookie parsing for routes that don't need them; only build the kwargs dict when at least one dependency or named param is configured. - Reorder map_handler_return type checks so the hot-path PyString / PyBytes downcasts run before the more expensive __oxyroute_* attribute lookups. - Inline response mapping + header merging + RSGI send into a single GIL block via new sync helpers (send_*_sync) in src/response.rs, avoiding an additional GIL acquire/release per request. - Drop the now-unused async send_bytes / send_head_with_headers helpers. Behavior is unchanged; existing tests (and the new body-skip coverage in tests/test_dispatch_fast_fail.py) continue to pass. Refs #76.
Lightweight harness for tracking RSGI throughput vs FastAPI: - perf-test/app.py / perf-test/fastapi_app.py: identical hello endpoints - perf-test/bench.sh: tuned Granian launch + 3x wrk runs + summary - perf-test/README.md: prerequisites and run instructions Used for milestone v0.3.0 perf work to publish before/after deltas.
OxyRoute v0.3.0 supports a single transport (RSGI). The runtime ASGI compatibility bridge in oxyroute/asgi.py is removed: - delete oxyroute/asgi.py - drop App.__call__, App._asgi3 = build_asgi_caller(self), and the websocket / _handle_asgi_websocket plumbing from oxyroute/app.py - delete the ASGI-only test suites (test_asgi*, test_websocket_asgi) The unit/integration tests still drive the app in-process through httpx.ASGITransport for speed and isolation, so the same bridge code moves to tests/_rsgi_test_transport.py as a *test fixture only* and each affected test now does ``httpx.ASGITransport(app=asgi_test_app(app))``. Refs #88.
OxyRoute v0.3.0 only supports RSGI; the ASGI compatibility bridge and the ASGI websocket spike were removed. Documentation is updated to match: - delete docs/asgi.md and docs/websocket.md - remove the ASGI bridge entry from docs/index.md and the cross-link from docs/http2.md - README: drop the optional ASGI bullet and the "ASGI and other servers are covered..." paragraph; project layout no longer mentions an "optional ASGI bridge" - docs/rsgi.md: drop the "When to use ASGI instead" section - docs/feature.md: mark ASGI compatibility as removed and reflect the new WebSocket status (native RSGI WS upcoming) - docs/sse.md / docs/dependencies.md / docs/development.md: refer to the in-process httpx test transport instead of the removed bridge Refs #88.
Reflect the breaking removal of the ASGI bridge: - pyproject.toml / Cargo.toml / Cargo.lock / oxyroute.__version__ / src/state.rs OpenAPI version → 0.3.0. - Add CHANGELOG.md with a 0.3.0 entry describing the dropped ASGI surface (App.__call__, oxyroute.asgi, ASGI-based @app.websocket) and the test-time migration to tests/_rsgi_test_transport.asgi_test_app. Refs #88.
- New docs/websocket.md walks through @app.websocket(path), the oxyroute.WebSocket API surface (accept / receive* / send* / close), routing semantics, error handling and the in-process testing pattern. - Linked from docs/index.md table of contents. - CHANGELOG bumped: Added section now covers the native WebSocket support that supersedes the removed ASGI spike.
- Add perf-test/{app_oxyroute.py,app_fastapi.py,bench_hello.sh,README.md}:
Granian RSGI vs ASGI, same wrk settings, prints Requests/sec and delta %.
- Optional extra `[project.optional-dependencies] bench` (fastapi + httpx).
- tests/test_perf_hello_bench.py: runs the shell script when OXYROUTE_BENCH=1
(skipped in normal pytest; requires wrk).
- Server stdout/stderr silenced so RPS parse is not polluted by Granian logs.
…o_py Delegate async send helpers to sync implementations where safe. try_rsgi_sync_short_circuit in dispatch returns immediately for GET/HEAD openapi and 404/405 with no middleware; handle_rsgi uses it before the async run_rsgi path.
When native code returns None or a plain value, avoid await so Granian and tests work with the sync short-circuit path.
Add direct handle_rsgi coverage for openapi/404. Test transport shim matches App.__rsgi__ when handle_rsgi is non-awaitable.
- routes and websocket_routes as Arc for cheap pointer clone per request - HotSnapshot + match_*_compiled helpers; run_rsgi uses one read + GIL-safe Py clones - try_rsgi_sync_short_circuit uses snapshot and compiled match when possible
- bench.sh: dynamic port, venv python -m granian, setsid + process-group kill, tmp wrk logs, env tuning knobs, ratio/uplift summary - FastAPI apps: PlainTextResponse to match OxyRoute text responses - bench_hello: print ratio and uplift instead of ambiguous delta
Queue ASGI messages synchronously from the executor thread so Python 3.14 cannot enqueue the completion sentinel before response.start/body.
Document the current RSGI-only usage path, optional middleware layers, request/response behavior, WebSockets, production notes, and update stale v0.2/WebSocket references.
feat(rsgi): native websocket, sync short-circuit, and bench harness hardening
Use neutral performance-focused wording for Granian RSGI and move 0.3.0 changelog notes from unreleased to a dated release section.
This was
linked to
issues
Apr 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
0.3.0changelog notes out ofunreleasedinto a dated release sectionTest plan
README.md