Breaks HttpServer (563 lines, eleven responsibilities) into named, single-purpose components and introduces the ConnectionProtocol seam HTTP/2 plugs into starting Phase 8, per flash/docs/http2/IMPLEMENTATION-PLAN.md Phase 2. New packages: - dev.relism.flash.transport: TransportFactory (composition root, EX-34), ListenerBinder, BoundListener, TransportTuning, AcceptLoop, ConnectionRunner (per-connection setup/teardown), ConnectionProtocol (the h1/h2 seam), ConnectionContext, ConnectionScratch + ScratchPool (EX-06), ServerLifecycle (implements ServerHandle; start/stop/graceful shutdown, EX-32). - dev.relism.flash.http1: Http1Connection (the keep-alive request loop, implements ConnectionProtocol), Http1ResponseWriter, Http1KeepAlive (the shared Connection-header token-list scanner, EX-13). - dev.relism.flash.websocket additions: WebSocketUpgrade (detection + handshake), WebSocketLoop (session loop), WebSocketProtocolException. Existing-code defects fixed (EX-nn): - EX-01: WebSocketSession's two blocking-write sites use ReentrantLock instead of synchronized (out) -- a virtual thread blocking inside synchronized pins its carrier platform thread on Java 21. - EX-06: HttpServer's three ThreadLocals (SHA1, LONG_BUF, STREAM_RELAY_BUFFER) replaced by ConnectionScratch, pooled via ScratchPool instead of one-per-virtual-thread (i.e. one-per-connection) growth. The router's ThreadLocals are deliberately deferred to Phase 4 per this EX item's own phasing -- see DEC-15 for the plan-wording fix. - EX-11: WebSocketSession.readFrame's extended-length and mask-key bytes are now read in a single bounded readFully instead of one at a time. - EX-12: full RFC 6455 frame validation -- continuation-frame reassembly, mandatory masking-direction enforcement, opcode validation, control-frame constraints (not fragmented, <=125 bytes), and WebSocketProtocolException carrying the correct close code (1002 protocol error, 1009 message too big). - EX-13: Connection header token-list scanning shared between the keep-alive decision and the WebSocket upgrade check. - EX-14: HEAD responses report Content-Length but write no body. - EX-15: Content-Type omitted when empty; Content-Length and the body omitted entirely for 204/304/1xx responses. - EX-16: Date header (dev.relism.flash.http.DateHeader), refreshed once per second by a shared daemon thread; FlashConfiguration.sendDate. - EX-32: two-stage graceful shutdown -- stop accepting, force Connection: close on the response an in-flight handler is still producing (re-checked after the handler runs, not just before dispatch, so a shutdown beginning mid-handler is still honoured), drain up to shutdownDrainTimeoutMs, then force-close. - EX-34: ServerHandle.create delegates to TransportFactory instead of constructing HttpServer directly. Two plan corrections recorded: DEC-15 (Phase 2's "no ThreadLocal anywhere" DoD line contradicted EX-06's own multi-phase assignment -- corrected to match the registry) and DEC-16 (no separate WebSocketFrameCodec class this phase; the EX-11/EX-12 fixes stay inside WebSocketSession, which is one cohesive state machine under R6's own carve-out -- revisit at Phase 15 if RFC 8441 needs the decoupling for real). HttpServer.java deleted. 311/311 tests green (flash module), run three times for stability of the wall-clock-based timeout/shutdown tests. Whole-repo build green. h1 benchmark regression check remains unverified in the plan's DoD (no JMH harness until Phase 3, same caveat as Phase 1). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
57 lines
1.9 KiB
Java
57 lines
1.9 KiB
Java
package dev.relism.flash.http;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.time.ZoneOffset;
|
|
import java.time.ZonedDateTime;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
/**
|
|
* {@code EX-16}: RFC 9110 §6.6.1 — an origin server with a clock SHOULD send {@code Date}.
|
|
* Flash never emitted it. Rather than formatting a timestamp on every response, a single
|
|
* daemon thread refreshes a pre-encoded {@code "Date: ...\r\n"} field line once per second into
|
|
* a {@code volatile byte[]}; {@code Http1ResponseWriter} writes it with one
|
|
* {@code OutputStream#write(byte[])} — the cost per response is one volatile read and one
|
|
* write, never a format call (R4).
|
|
*
|
|
* <p>The parallel HPACK-encoded rendering for HTTP/2 responses is added in Phase 9.
|
|
*/
|
|
public final class DateHeader {
|
|
|
|
private DateHeader() {
|
|
}
|
|
|
|
private static final DateTimeFormatter FORMATTER =
|
|
DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC);
|
|
|
|
private static volatile byte[] current = encode();
|
|
|
|
static {
|
|
Thread refresher = new Thread(() -> {
|
|
while (true) {
|
|
try {
|
|
Thread.sleep(1000);
|
|
} catch (InterruptedException e) {
|
|
Thread.currentThread().interrupt();
|
|
return;
|
|
}
|
|
current = encode();
|
|
}
|
|
}, "flash-date-header");
|
|
refresher.setDaemon(true);
|
|
refresher.start();
|
|
}
|
|
|
|
private static byte[] encode() {
|
|
String line = "Date: " + FORMATTER.format(ZonedDateTime.now(ZoneOffset.UTC)) + "\r\n";
|
|
return line.getBytes(StandardCharsets.US_ASCII);
|
|
}
|
|
|
|
/**
|
|
* The current pre-encoded {@code "Date: ...\r\n"} field line, accurate to within one
|
|
* second. Never allocates — the same array is returned until the next refresh.
|
|
*/
|
|
public static byte[] bytes() {
|
|
return current;
|
|
}
|
|
}
|