Skip to content

docs: update README positioning and 0.3.0 changelog heading - #93

Merged
ZhuchkaTriplesix merged 52 commits into
mainfrom
chore-readme-positioning-main-pr
Apr 27, 2026
Merged

docs: update README positioning and 0.3.0 changelog heading#93
ZhuchkaTriplesix merged 52 commits into
mainfrom
chore-readme-positioning-main-pr

Conversation

@ZhuchkaTriplesix

Copy link
Copy Markdown
Member

Summary

  • replace the README opening sentence with neutral, performance-focused wording for Granian RSGI
  • avoid the awkward "RSGI-first" phrasing while keeping the single-worker throughput emphasis
  • move 0.3.0 changelog notes out of unreleased into a dated release section

Test plan

  • docs-only change
  • verified rendered markdown wording in README.md

ZhuchkaTriplesix and others added 30 commits April 27, 2026 12:28
…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.
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.
ZhuchkaTriplesix and others added 15 commits April 27, 2026 15:16
- 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.

@ZhuchkaTriplesix ZhuchkaTriplesix left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

123

@ZhuchkaTriplesix ZhuchkaTriplesix self-assigned this Apr 27, 2026
@ZhuchkaTriplesix ZhuchkaTriplesix added the enhancement New feature or request label Apr 27, 2026
@ZhuchkaTriplesix
ZhuchkaTriplesix merged commit aa81acf into main Apr 27, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment