You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 44ab0d4
Browse filesBrowse the repository at this point in the historyBrowse files
fix(http): Use ScopedValue for per-request state, not HttpExchange attributes
HttpExchange.setAttribute writes to a context-shared map; concurrent
requests overwrote each other's body bytes/operation-id/path-parameters,
which manifested in k6 as ~30% empty 200 response bodies under modest
keep-alive load. The original master code avoided this via a private
attribute map on its BodyHandler.RequestBodyWrapper, which the refactor
inadvertently dropped.
Replaces the four exchange attributes with a single immutable
RequestContext bound to ScopedValue.CONTEXT for the duration of the
request. Request.bytes/parsed/operationId/pathParams are now no-arg
accessors on the bound value; they throw NoSuchElementException if read
outside a request scope.
Verified with the curl-equivalent k6 profile: 30 sustained VUs / 45 s /
1,436,394 requests / 2,393,990 checks → 100% pass, 0% HTTP failures.
README updated to retract the keep-alive caveat (it was a misdiagnosis)
and document the ScopedValue ⇒ off-thread capture rule for handlers.
Copy file name to clipboardExpand all lines: README.md
+5-5Lines changed: 5 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -124,13 +124,13 @@ Schemas are located under test resources folder.
124
124
125
125
The library wraps the JDK's bundled `com.sun.net.httpserver.HttpServer` and uses a virtual-thread-per-request executor. On a developer laptop (Apple Silicon, single instance, default JVM flags) it sustains roughly:
126
126
127
-
-**20k–35k requests/second** for small JSON GETs and POSTs (~300 byte bodies), measured both via parallelised `curl` (no connection reuse) and via `k6` at moderate VU counts.
128
-
- The HTTP layer reports **0% request failures** even when sustained at 30 concurrent virtual users for 45 seconds (~1.5M requests).
127
+
-**~32k requests/second** for small JSON GETs and POSTs (~300 byte bodies), measured via `k6` at 30 sustained VUs over 45 seconds (1.4M requests, **100% of checks passing**, 0% HTTP failures).
129
128
130
-
That said, the underlying JDK HttpServer is documented as a low-throughput / dev-test server, and a few caveats are worth knowing:
129
+
A few things to know:
131
130
132
-
-**Keep-alive reuse at high concurrency.** k6 and other clients that aggressively reuse HTTP/1.1 connections will occasionally observe empty response bodies (with the correct `Content-Length` header) when running tens of VUs against this server. Curl-style clients that open a fresh connection per request do not see this. If you need consistent throughput above a few thousand RPS with keep-alive clients, consider deploying behind a real HTTP server (Jetty, Helidon Níma, Netty) — this library's filter/validator stack is independent of the underlying server and could be ported.
133
131
-**Single-process model.** No horizontal scaling primitives are bundled; run multiple instances behind a load balancer for production scale.
134
-
-**`HttpExchange.sendResponseHeaders(rCode, length)` semantics.** When a handler has no response body, pass `-1` (no body, `Content-Length: 0`); passing `0` produces a chunked response with zero chunks, which is technically non-conformant.
132
+
-**JDK HttpServer is the throughput ceiling.** It's documented as a low-throughput / dev-test server. If you need to go materially above the rates above, deploy the same filter/validator/router stack on Jetty, Helidon Níma, or Netty — the spec and validation code is server-agnostic.
133
+
-**Per-request state uses `ScopedValue`** (Java 25, JEP 506), not `HttpExchange.setAttribute`. This matters if a handler offloads work to an executor that's not a `StructuredTaskScope`-managed child thread: the `ScopedValue` is not visible there, so the handler must capture the values it needs (e.g. `byte[] body = Request.bytes();`) before submitting.
134
+
-**`HttpExchange.sendResponseHeaders(rCode, length)` gotcha.** When a handler has no response body, pass `-1` (`Content-Length: 0`, no body); passing `0` produces a chunked response with zero chunks, which is technically non-conformant.
0 commit comments