New in the 0.4.0-SNAPSHOT development line — everything listed here landed after the 0.3.0 release and is not part of it. Every entry links to the section that documents it, and those sections carry the NEW badge. Each brick of the suite keeps its own page of this kind; the ecosystem index gathers them. When the next release train ships, this page is frozen with it and restarts empty on the dev line.
Fixes that change behaviour
-
writeTimeoutis applied —Server.Builder.writeTimeout(Duration)was accepted and never read (chappe#13). It now bounds each blocking write of an HTTP/1.1 response: when the client stops reading and the socket refuses the bytes for longer than the timeout (30 s by default), Chappe closes the connection and the client gets a truncated body, where the connection’s virtual thread used to stay blocked with no bound. It is not an inactivity bound: a Server-Sent Events stream that stays silent for minutes between events is not affected. If a client of yours legitimately stalls reading for longer than 30 s, raise the value. Reference — timeouts, Migration — upgrading from 0.3.0. -
The last response survives the close — when an HTTP/1.1 connection ends after a response (a
431, a400, orConnection: close) while request bytes are still unread, Chappe used to close at once; the TCP stack then answered with a reset, and the reset destroyed the response before the client read it ("Connection reset"). Chappe now closes in stages, as RFC 9112 §9.6 describes: it half-closes, drains what the client still sends for at most 2 s or 1 MiB, then closes, so the response ends with a FIN (chappe#19). Over TLS it sendsclose_notifyfirst and drains the ciphertext without decrypting it (chappe#21). A lost connection, a client that closed first, the idle timeout and a WebSocket takeover still close at once. Internals — threading model, HTTP capabilities. -
A failed request body read closes gracefully — when reading a request body failed (bad chunked framing, a body cut short), the connection used to close at once, so a client still uploading could get a reset that destroyed the response. It now gets the same staged close: the response,
close_notifyover TLS, a half-close, then a bounded drain. Chappe still never parses another request from such a connection (CHAPPE-013, CHAPPE-014). Internals — threading model. -
Response headers are validated — a header name must be an RFC 9110 token, and a value may hold only visible ASCII, obs-text (
U+0080–U+00FF), space and tab.Response.builder(),Filter.addHeader*, theWebSocketUpgradesubprotocol andGrpcCall.addHeader/addTrailernow throwIllegalArgumentExceptionfor anything else. A customResponsethat bypasses them gets a500over HTTP/1.1 and HTTP/2. Until now, the HTTP/1.1 writer truncated a char aboveU+00FFto its low byte, soU+010D U+010Abecame CR LF and injected a header line, and HTTP/2 sent such a char as?(CHAPPE-008). If you set header values from user input, encode them first (RFC 8187, percent-encoding). Concepts — header and trailer validation.