This page defines the Chappe vocabulary. The names line up with the HTTP RFCs (9110/9112/9113/9114) and modern Java 25 APIs (ScopedValue, virtual threads). No in-house jargon — every term should ring a bell to a regular HTTP developer.
Server, Router, Handler
Three interfaces hold the entire public API.
| Type | Role |
|---|---|
|
Lifecycle: |
|
Request dispatcher by method + path. Fluent builder. Supports exact routes, path params ( |
|
Functional interface: |
Request and Response
Both types are immutable at the API surface. Response is built through a builder; Request is provided by the engine.
// Request — read-only access
req.method() // HttpMethod: GET, POST, ...
req.path() // String relative to the mount, no query string
req.queryParams() // Map<String, List<String>>
req.pathParams() // Map<String, String>
req.headers() // Headers (case-insensitive)
req.body() // Body (asInputStream(), asBytes(), ...)
req.contextPath() // mount prefix, "" otherwise
req.pathInfo() // alias of path() for mounts
req.remoteAddress() // client SocketAddress
req.isSecure() // true if TLS
req.scheme() // "http" or "https"
req.attribute(k, v) // mutable per-request attributes (Servlet-compat)
Body
Body abstracts the request or response body. Several implementations exist, all sealed inside chappe-api:
| Type | Use case |
|---|---|
|
204, 304, body-less requests. |
|
Short responses (text, inline JSON). Single allocation. |
|
Wraps an |
|
Push streaming: |
|
Zero-copy via |
Filters
A Filter is a declarative middleware. It turns a Handler into another Handler:
Filter logging = next -> req -> {
System.out.println(req.method() + " " + req.path());
return next.handle(req);
};
Helpers shipped on Filter:
-
Filter.addHeader(name, value)— add a header to every response. -
Filter.addHeaderIf(predicate, name, value)— conditional header (predicate evaluated per request). -
Filter.addHeaderIfEnv(envVar, expected, name, value)— gate on an environment variable (typical: staging). -
Filter.gzip()/Filter.gzip(threshold)— on-the-fly compression negotiated throughAccept-Encoding.
Connection, frame, multiplexing
HTTP/2 vocabulary (RFC 9113), reused by HTTP/3 (RFC 9114) over a different transport.
| Term | Meaning in Chappe |
|---|---|
Connection |
A TCP socket (HTTP/1.1, HTTP/2) or a QUIC stream-set (HTTP/3). One virtual thread per connection. |
Frame |
HTTP/2 transport unit: |
Stream |
Logical sub-flow multiplexed over an HTTP/2 connection — one request + one response. |
Multiplexing |
Several streams in flight on one connection. HTTP/1.1 cannot do it (pipelining ≠ multiplexing); HTTP/2 and HTTP/3 can. |
HPACK |
HTTP/2 header compression (RFC 7541) — static table + dynamic table + Huffman. Implemented in |
Flow control |
Per-stream and per-connection byte windows (HTTP/2); |
Virtual thread per request
Chappe enforces a strict rule: one virtual thread per connection. No platform pool, no EventLoopGroup, no application-side Selector.
-
Executors.newVirtualThreadPerTaskExecutor()is the only executor used. -
The Java 25 VM multiplexes virtual threads onto a handful of carrier threads.
-
Application code writes synchronous blocking style:
body.asInputStream().readAllBytes()is fine, never a scalability regression. -
No
ThreadLocal— seeRequestContextbelow.
RequestContext and Scoped Values
RequestContext.CURRENT is a ScopedValue<RequestContext> (JEP 506, finalized in Java 25). It carries request attributes, tenant, authenticated user — everything ThreadLocal carried in classic servers, only cleaner:
-
explicit propagation via
ScopedValue.where(…).run(…); -
automatic cleanup when the scope ends;
-
virtual-thread compatible without pinning risk.
// Read it from anywhere in the call chain
Request req = RequestContext.currentRequest();
Mount, contextPath, pathInfo
Router#mount(prefix, handler) delegates a whole path prefix, all verbs included. The prefix is stripped before the call:
-
request.path()is relative to the mount (/dashboard), -
request.contextPath()is the prefix (/admin), -
request.pathInfo()is an alias ofpath()for mounts.
Resolution order inside a Router:
-
exact routes (first match by path + method),
-
405 Method Not Allowedif the path matches but the method does not (sets theAllowheader), -
mounts (first matching prefix, registration order),
-
notFound(404fallback).