HttpProxy and Http2Client (719 LOC) shipped a reverse-proxy adapter and outbound HTTP/2
client from flash core with zero callers anywhere in the server itself — only each
other and their own tests. An HTTP/1.1+2 server framework has no business bundling an
outbound client; that capability belongs in its own flash-extensions/flash-ext-*
module if/when it's needed. Removed, along with the now-dead src/bench load driver
that depended on Http2Client (no replacement client written here — flagged as
follow-up work, not silently dropped).
docs/http2/ had accumulated core, cross-protocol documentation alongside genuine
HTTP/2-protocol internals: HTTP1-HARDENING, TRANSPORT, MESSAGE-MODEL,
TRAILERS-AND-STREAMING and BYTES all describe machinery HTTP/1.1 and HTTP/2 share, not
HTTP/2 specifically. Moved to a new docs/core/, leaving docs/http2/ to the protocol
layers, wire internals and operational docs that are actually HTTP/2-specific.
CLEARTEXT-AND-PROXY.md renamed to CLEARTEXT.md and its now-removed upstream-client
section cut, matching the source removal above.
README.md: removed the "HTTP/2 upstream proxy" section (documented the deleted
HttpProxy/Http2Client), the flash-bench module row and build command (not a module
that exists in this repo), and fixed every doc link to the new docs/core/ paths.
Added the new FlashConfiguration.maxConnections field to the configuration reference.
src/bench/ (a load-test harness distinct from the JMH suite, not wired into any Maven
profile or CI) is committed here for the first time.
Pools Request/RequestBody/RequestLine/Response per connection (EX-20..EX-24),
following the same reset()/dev-mode-guard idiom Http1HeaderMap already used.
HeaderMap splits into HeaderView (interface) + Http1HeaderMap (impl, DEC-22).
Response gains byte-level structured headers, PreEncodedHeader, and
ResponseSerializer as the single source of truth for a response's header
sequence, consumed by Http1ResponseWriter's single-bulk-write rewrite (EX-27).
ByteTemplate gets O(1) slot lookup plus a buffer-writing overload (EX-28).
Multipart audited: three resource-exhaustion gaps found and fixed — unbounded
buffered part size, part count, and per-part header parsing (EX-38..EX-40) —
and boundary length confirmed already bounded (EX-41).
Re-measuring RequestPipelineBenchmark after the pooling work surfaced one more
per-request allocation underneath it (RequestParser building fresh
RequestByteViews every call) and, while checking the phase's own DoD text, an
unbounded Response.header(...) loop hazard neither had a limit — both fixed
(EX-42, EX-43). The h1 zero-alloc contract now holds: parseAndRoute measures
0.008 B/op (JMH noise floor), down from Phase 4's 120.008 B/op (DEC-20, DEC-23).
MESSAGE-MODEL.md records the pooling model; README gains an "Object lifetime"
section documenting the do-not-retain-past-the-handler contract. 503/503 tests
green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements HTTP/2 frame reading, validation, and writing: FrameType (the
10 RFC 9113 types + per-type validation descriptor), FrameFlags (with
the deliberate END_STREAM/ACK bit collision documented), FrameHeader (a
flyweight, never allocated per frame), Http2FrameReader (length-prefixed
reader over BufferedByteSource, mirroring RequestParser's buffer/
compaction discipline), FrameValidator (table-driven, specific RFC error
code per violation -- not a uniform code per type), Padding (RFC 9113
6.1/6.2), and FrameWriteBuffer (beginFrame/endFrame length back-patching
over Phase 4's ByteWriter).
All 10 frame types round-trip correctly; every RFC-mandated rejection
has its own test asserting the specific error code; the reader is
fuzz-tested against 10,000,000 random inputs (~14s). The zero-alloc
contract is measured, not asserted: reading + validating + consuming a
frame is 0.002 B/op, writing one is ~10^-4 B/op -- both indistinguishable
from zero (DEC-21).
Found and fixed EX-37 while writing Http2FrameReaderTest: BufferedByteSource's
deadline mechanism (EX-07's actual fix) NPE'd against a null socket, which
every isolated unit test in this codebase uses -- it had zero dedicated
test coverage of its own. Fixed to treat a null socket as "no OS-level
timeout to bound" rather than a misuse, and given BufferedByteSourceTest,
which did not exist before.
449/449 tests green, both with and without -Pjmh.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Builds dev.relism.flash.bytes: ByteScan (scanning/comparison/hashing,
scalar + SWAR, property-tested against each other on every boundary and
20,000 random fuzz trials each), ArrayBackedByteView/SegmentedByteView
capability hierarchy, PooledSlice/SlicePool, ByteWriter, Pairs.
Cashes in the allocation and scanning wins the existing code left on the
table: EX-04 (word-at-a-time router matching, verified directly against
fpr-core's own ByteCompare), EX-05 (pooled views replacing per-call
anonymous ByteView allocations in HeaderMap/QueryParams/PathParams),
EX-09 (HeaderMap index built once per reset() instead of rescanning per
lookup), EX-19 (reusable PathParams on the router's per-connection
scratch), EX-25/EX-26 (single-allocation String construction), EX-33
(SWAR header-terminator scan in RequestParser).
Also closes EX-06's router half, missing from this phase's own EX-item
list in the plan (same class of omission DEC-12 recorded for Phase 1):
FastPathRouterImpl/FastPathWsRouterImpl's ThreadLocals (unbounded under
one-virtual-thread-per-connection) are replaced by an opaque,
caller-owned per-connection scratch object (AbstractRouter#newScratch),
not by extending ConnectionScratch as its own Javadoc originally assumed
-- that would have created transport's first dependency on routing in
the reverse direction. Full rationale in DEC-19.
Every optimization is measured, not asserted (DEC-20): SWAR scan 35.4%
faster than scalar, kept; EX-04's word-path 32.1% faster than
byte-at-a-time at the mechanism level, kept for its real future
consumers even though today's router doesn't yet route through it
(MethodPathByteView stays deliberately non-array-backed, per the plan's
own text). Router matching itself is ~0 B/op including parametric
routes. The full h1 pipeline is not literally 0 B/op yet -- 120 B/op is
Request/RequestBody/RequestLine construction, honestly attributed to
Phase 6's explicit scope rather than hidden.
395/395 tests green, both with and without -Pjmh.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements the connection-level serialized frame writer per the plan's
go/no-go gate: tryLock() fast path with an intrusive Vyukov-style MPSC
fallback under contention, ReentrantLock throughout (never synchronized),
and a scan-based write-timeout reaper.
All four gate criteria met and measured: N=1 0 B/op and 42.6 ns overhead
(<=50 ns budget); N=64 65.5% throughput retention (>=60%) and 11.8-14.2 us
p999 (<1 ms); no carrier pinning; stress test 10,000/10,000 green across
1000 iterations x 5 concurrency levels x 2 scheduler configs. Compared
against plain-lock and dedicated-thread designs with real benchmark
numbers, not assertion. Full methodology and results in WRITER.md, DEC-09.
Also fixes a real regression found while resuming this work: the JMH
benchmark broke plain `mvn test` (no -Pjmh) because it lived in
src/test/java, which Surefire's test discovery loads regardless of
whether a class is ultimately selected as a test. Moved to a dedicated
src/jmh/java source root registered only under the jmh profile
(build-helper-maven-plugin), per DEC-17.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Fixes the request-smuggling and resource-exhaustion debt in the existing
HTTP/1.1 parser, and adds the ALPN/h2c-preface negotiation seam so a
connection's protocol is decided once, before any request is parsed, per
flash/docs/http2/IMPLEMENTATION-PLAN.md Phase 1.
Existing-code defects fixed (EX-nn):
- EX-02: reject Content-Length + Transfer-Encoding together (RFC 9112 6.1
CL.TE/TE.CL smuggling), and conflicting duplicate Content-Length values.
- EX-03: strict, overflow-safe Content-Length parsing, replacing a parser
that silently skipped non-digit bytes ("5abc" -> 5, "-1" -> 1).
- EX-07: header-read / idle-keep-alive / body-read timeouts enforced by an
absolute deadline (dev.relism.flash.transport.BufferedByteSource), not
merely Socket#setSoTimeout, which never trips against a peer trickling
one byte per read within the window.
- EX-08: header count / name length / value length / request-line length
bounds (Http1Limits), 431 on violation.
- EX-10: ChunkedInputStream now reads through BufferedByteSource instead
of the raw unbuffered socket stream, and the header-parser's read-ahead
bytes are handed over via a zero-copy prependOnce() instead of a
SequenceInputStream/ByteArrayInputStream pair.
- EX-17: HttpStatus's status-code bound is computed from values() instead
of a hand-maintained constant that silently threw
ArrayIndexOutOfBoundsException when a code above it was added; added
421, 431, 505, 507, 511 and others HTTP/2 and this hardening need.
- EX-18: bare-CR desync and obsolete line folding rejected.
- EX-30: the TLS handshake is forced explicitly, under a timeout, before
any protocol decision -- SSLSocket#getApplicationProtocol() returned
null until the handshake had run, and nothing previously forced it.
- EX-31: TLS 1.2 cipher suites on the RFC 9113 Appendix A blocklist are
filtered out of a listener's enabled set whenever it offers h2 via ALPN.
- EX-35 (found in this phase): Transfer-Encoding values listing multiple
codings ("gzip, chunked") were silently treated as not chunked at all,
corrupting the message boundary -- only the whole value was compared.
- EX-36 (found in this phase): a header line with no ':' was silently
skipped instead of rejected.
New:
- dev.relism.flash.transport.BufferedByteSource: the single buffered,
deadline-aware, peekable view over a connection's inbound bytes.
- dev.relism.flash.transport.ProtocolNegotiator/NegotiatedProtocol: ALPN
and h2c prior-knowledge detection. In this phase an H2 result is always
closed cleanly -- there is no Http2Connection to hand off to until
Phase 8. FlashConfiguration.http2Enabled gates the h2c preface peek.
- dev.relism.flash.exceptions.MalformedRequestException: a typed,
status-carrying rejection distinct from HttpException, caught at the
parse site so a malformed request never reaches the handler chain or
the user's exception handler, and the connection is always closed.
Two small plan-document corrections recorded as DEC-12 (Phase 1's Files
list omitted BufferedByteSource.java and MalformedRequestException.java;
the request-line-length check description pointed at the wrong offset).
DEC-13/DEC-14 record the deadline and exception-hierarchy designs.
277/277 tests green (flash module), run twice for stability of the new
wall-clock-based HttpServerTimeoutTest cases. Whole-repo build green.
h1 benchmark regression check is left unverified in the plan's DoD: no
JMH harness exists yet (Phase 3 deliverable).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Establishes the package layout, limits/error model and decision-log
convention that every later HTTP/2 phase depends on, per
flash/docs/http2/IMPLEMENTATION-PLAN.md Phase 0.
- dev.relism.flash.h2: package-info (architecture overview), Http2ErrorCode
(the 14 RFC 9113 §7 codes with precomputed 4-byte wire encodings),
Http2Exception (connection error -> GOAWAY) and Http2StreamException
(stream error -> RST_STREAM), neither extending IOException, both with
stack-trace capture disabled on the hot rejection path.
- Http2Limits: every bound Phase 0 requires (concurrent streams, frame
size, header list size, CONTINUATION/reset/settings/ping rate bounds,
flow-control windows, HPACK table size/string length, assembly and idle
timeouts), each documented with the attack or RFC clause it addresses.
- dev.relism.flash.http.Http1Limits: the h1 bounds needed by EX-03 (strict
Content-Length) and EX-08 (header count/size limits).
- flash/docs/http2/DECISIONS.md seeded with DEC-01..DEC-11 (the ten
decisions implied by the plan itself, plus DEC-11 recording that commits
keep scope `core` rather than adding `h2` to AGENTS.md).
- flash/docs/http2/IMPLEMENTATION-PLAN.md: added the Progress Ledger
(tracks phase status across sessions) and checked off Phase 0's DoD.
19 new tests, full flash module suite green (226/226).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>