Files
Flash5/flash/docs/core/HTTP1-HARDENING.md
Zakaria El Orche a0dda8e47a refactor(core): remove out-of-scope HTTP/2 client/proxy, reorganize docs, refresh README
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.
2026-08-14 18:13:03 +00:00

93 lines
4.9 KiB
Markdown

# HTTP/1.1 hardening
Audience: operators. This is the document to read when a `400`/`413`/`414`/`431`/`501` shows up
in the logs and it isn't obvious why. Every rejection rule Flash's HTTP/1.1 parser enforces is
listed here with its RFC citation and the status it produces. Contributor-level detail (why each
check is implemented the way it is, the exact code paths) lives in the Javadoc of
`RequestParser`, `ChunkedInputStream`, and `dev.relism.flash.exceptions.MalformedRequestException`.
Every rejection in this document has one thing in common: **the connection is always closed
afterwards, never kept alive.** A rejected request is exactly the situation a smuggling attack
needs a reusable connection for, so none of these rejections offer one — see
`MalformedRequestException`'s Javadoc.
## Request-smuggling defenses (RFC 9112 §6.1)
| Rule | Status | Detail |
|---|---|---|
| `Content-Length` and `Transfer-Encoding` both present | `400` | The canonical CL.TE/TE.CL smuggling vector. Rejected regardless of which header appears first. |
| Multiple `Content-Length` lines with **differing** values | `400` | Identical repeated values are tolerated (RFC 9110 §8.6 permits treating them as one). |
| `Transfer-Encoding` whose **final** coding is not `chunked` | `501` | Flash implements only `chunked`; anything else (`gzip` alone, or `chunked, gzip` — chunked must be *last*) is unsupported. |
## Strict `Content-Length` parsing (RFC 9110 §8.6)
| Input | Status |
|---|---|
| Empty value | `400` |
| Any non-digit byte (including a leading `+` or `-`) | `400` |
| More than 19 digits | `400` |
| Value overflows `Long.MAX_VALUE` | `400` |
| Value exceeds `Http1Limits.MAX_CONTENT_LENGTH` (4 GiB by default) | `413` |
The previous parser silently skipped non-digit characters (`"5abc"` parsed as `5`; `"-1"` parsed
as `1`) instead of rejecting them — this is the fix.
## Header and request-line limits (`Http1Limits`)
| Limit | Default | Status when exceeded |
|---|---|---|
| `MAX_HEADER_COUNT` | 100 | `431 Request Header Fields Too Large` |
| `MAX_HEADER_NAME_LENGTH` | 256 B | `431` |
| `MAX_HEADER_VALUE_LENGTH` | 8192 B | `431` |
| `MAX_REQUEST_LINE_LENGTH` | 8192 B | `431` |
| Header block exceeds `maxHeaderBufferSize` (or the connection ends before it completes) | configurable, default 64 KiB | `431` |
## Line-terminator and header-syntax correctness (RFC 9112 §5)
| Rule | Status |
|---|---|
| A `\r` not immediately followed by `\n` (bare CR) | `400` — a known desynchronization/smuggling surface |
| A header line beginning with whitespace (obsolete line folding, RFC 9112 §5.2) | `400` |
| A header name containing a byte outside RFC 9110 §5.6.2's `tchar` set | `400` |
| A header line with no `:` | `400` |
## Chunked transfer safety (RFC 9112 §7.1, `Http1Limits`)
| Limit | Default | Status when exceeded |
|---|---|---|
| `MAX_CHUNK_SIZE` | 16 MiB | `413` |
| Chunk-size line longer than 16 hex digits | — | `400` |
| `MAX_CHUNK_EXT_LENGTH` (the optional `;name=value` after a chunk size) | 256 B | `400` |
| `MAX_CHUNKS_PER_BODY` | 100 000 | `413` |
| `MAX_TRAILER_COUNT` | 50 | `431` |
| A chunk's data not followed by `\r\n`, or a malformed chunk-size/trailer terminator | — | `400` |
Trailers are consumed within the bounds above and exposed through `Request.trailers()` on both
HTTP/1.1 and HTTP/2.
## Timeouts (`FlashConfiguration`)
| Setting | Default | Covers |
|---|---|---|
| `idleKeepAliveTimeoutMs` | 60 000 | How long a keep-alive connection may sit idle waiting for its next request. |
| `headerReadTimeoutMs` | 10 000 | Once the first byte of a request arrives, how long the full header block may take. |
| `bodyReadTimeoutMs` | 30 000 | How long reading the body (by the handler, or the automatic post-response drain) may take. |
| `shutdownDrainTimeoutMs` | 15 000 | How long graceful shutdown waits for in-flight requests before force-closing. |
These are enforced by an **absolute deadline**, not merely `Socket.setSoTimeout`. A per-read
socket timeout alone never trips against a peer that sends one byte just often enough to keep
each individual read alive (the classic slowloris shape) — see
`dev.relism.flash.transport.BufferedByteSource`'s Javadoc for how the absolute deadline is
implemented on top of the JDK's per-read-only timeout API.
## TLS (RFC 9113 §9.2.2, applies once a listener offers `h2` over ALPN)
- The TLS handshake is forced explicitly (not left to the JDK's lazy on-first-read trigger)
before any protocol decision is made, and is bounded by `headerReadTimeoutMs`.
- When a listener's `TlsConfig.applicationProtocols` includes `"h2"`, the enabled TLS 1.2 cipher
suite list is filtered against the RFC 9113 Appendix A blocklist
(`TlsConfig.TLS12_H2_BLOCKED_CIPHERS`, ~280 entries). TLS 1.3 is never affected — none of its
cipher suites are on that list.
- `FlashConfiguration.http2Enabled` advertises `h2` on TLS listeners. The independent
`http2CleartextEnabled` switch accepts the h2c prior-knowledge preface on plaintext listeners.