·
32 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
[1.5.0] - 2026-08-20
Breaking changes
- HTTP CONNECT tunneling is now disabled by default: an empty
HttpServerConfig::connectAllowlistnow rejects every
CONNECT target with403 Forbiddeninstead of allowing every resolved host. This closes an unauthenticated SSRF/open
proxy in default configurations across HTTP/1.1 and HTTP/2. Applications that intentionally provide CONNECT tunnels
must explicitly configure every trusted target hostname or IP withwithConnectAllowlist()(or the
connectAllowlistJSON/YAML setting). Setting the allowlist to["*"]deliberately restores the previous unrestricted
behavior, allowing every host and port including loopback, private-network, link-local, and cloud metadata targets. - HttpRequestView::headerValue, headerValueOrEmpty, trailerValue, trailerValueOrEmpty, hasHeader and hasTrailer are now all expecting lower ASCII case keys. This is enforced by the new parameter
LowerAsciiKeythat will fail to compile for constant strings (for instance,headerValue("Host")does not compile, butheaderValue("host")does) - HttpRequestView::headers() && trailers() now return a case-sensitive map with lower case keys: header names are normalized to lower-case when parsed, but the returned map have case sensitive look-ups. For instance, the code
req.headers().find("X-Header")is now wrong (it can never match) and should be replaced withreq.headers().find("x-header"). For simple lookups, prefer above methods that are safer and simpler. - http::Connection, http::ContentType, http::Host, etc. are now
LowerAsciiKeyinstead ofstd::string_view(compile-time validated once at their definition instead of at every call site). Fully backward-compatible for typical usage (implicit conversion back to std::string_view is available) -- only generic/template code asserting the exact typestd::string_viewwould need adjustment. Pure formatting helpers (http::ContentTypeHeaderSepand similar*Sepconstants, which are not header-name lookup keys) are unaffected and remainstd::string_view. - File::Identity becomes private and File::identity() has been replaced with File::appendIdentityData():
File::Identityis now a private nested type, and the publicFile::identity()method has been removed. The newFile::appendIdentityData(char* pData)method writes the file's current descriptor identity and metadata to the provided buffer, returning a pointer past the last written byte. The buffer must be at leastFile::kIdentitySizebytes long. - Global header values should be trimmed of OWS. This is to save some work on the http message finalization path.
Bug Fixes
- Client: heap-buffer-overflow when adding a header to a body-less request built with reserved header capacity:
HttpClient::makeRequest(additionalCapacity, method, url)(the overload without a body) was incorrectly computing indexes to its internal buffer. - HTTP/1.1 request is now rejected if it does not contain a Host header: the server now returns
400 Bad Requestfor HTTP/1.1 requests that do not include aHostheader, per RFC 9112 §3.2. - Router updates could race server startup: a route update submitted while the server was preparing to run could mutate the router directly while startup clamped route configuration, causing intermittent literal-route assertions. Startup now publishes a synchronized
Startingstate before launching its thread, so subsequent updates are queued for the event-loop thread. - Predicate and stop-token shutdown could leave a listener open without an event loop: when cancellation arrived between event-loop iterations,
runUntil()could reset its lifecycle without closing the listener or active connections. New TCP connections then succeeded but were never serviced, most visibly as intermittent 10-second Windows CI stalls. Predicate-driven exits now perform teardown on the event-loop thread, and lifecycle tests use a bounded listener-closure check. Tests:tests/http-server-lifecycle_test.cpp(StartAndStopWhen,StartWithStopToken),tests/multi-http-server_test.cpp(StartDetachedWithStopTokenStopsOnRequest). - Async handlers: heap-use-after-free when a connection was closed during a long
deferWork(): a request whose async handler ran slow background work (e.g. a multi-second database query) could have its connection swept by the keep-alive idle timeout while the work was still in flight. When the background thread finished it wrote its result into the coroutine frame / connection memory that had already been freed by the event-loop thread, which AddressSanitizer reported as aheap-use-after-free. The fix is threefold: (1)DeferredWorkAwaitablenow stores the result/exception in ashared_ptrstate co-owned by the background thread and copies the event-loop post-callback at construction, so the worker never dereferences theHttpRequestViewor the coroutine frame after completion; (2) connections with an active async handler are excluded from the keep-alive deadline sweep and re-armed once the response is flushed, so an in-flight request is no longer closed as if idle; (3) each connection carries a monotonicgenerationtoken that is validated before a posted async completion runs its pre-resume work or resumes the coroutine, so a stale completion can never resume a different connection that reused the same fd. Applies to both HTTP/1.x and HTTP/2 async paths. Tests:aeronet/http/test/http-request-view_test.cpp(DeferredWorkCompletionOutlivesAwaitableStorage),tests/http-routing_test.cpp(DeferredWorkIsNotSweptByKeepAliveTimeout). - Fixed HPACK dynamic table desynchronization: entries are now always added to the dynamic table on incremental indexing, even when the resulting header is rejected as malformed, keeping compression state in sync with the peer per RFC 9113 §4.3.
- HTTP/1.1 now rejects field names that aren't valid
tokens (RFC 9110 §5.6.2), and field values containing NUL, bare CR, or bare LF (RFC 9112 §2.2), instead of accepting them silently. - HTTP/2 now rejects field names containing uppercase ASCII or invalid bytes, and field values containing NUL/CR/LF, per RFC 9113 §8.2.1, instead of accepting them silently.
- HTTP/2 now rejects malformed pseudo-header field sections: duplicate, misordered, undefined, response-only, and
missing request pseudo-headers, plus invalid CONNECT pseudo-header shapes, produce a stream-level
RST_STREAM(PROTOCOL_ERROR)without closing the connection. Invalid HPACK encoding remains a connection-level
COMPRESSION_ERROR, and malformed field sections are still decoded in full to keep the HPACK dynamic table in sync.
Extended CONNECT's defined:protocolfield is rejected contextually because its setting is not supported. Unsupported
extension methods now return501 Not Implementedinstead of being dispatched asGET. - HTTP/2 now enforces
SETTINGS_MAX_HEADER_LIST_SIZEfor decoded header fields: initial and trailing header blocks whose RFC 9113 header-list size exceeds the configuredHttp2Config::maxHeaderListSizeare rejected withRST_STREAM(ENHANCE_YOUR_CALM)before reaching request handling. - A handler slower than
keepAliveTimeoutcould get its own response swept away as idle: the keep-alive deadline is armed when the request is read, so a handler that took longer thankeepAliveTimeout(a multi-second database query, say) returned with the deadline already expired and the next maintenance sweep closed the connection as if it had been idle - while the response it had just produced was still going out. HTTP/1.1 hands its whole response to the kernel in one go and rarely noticed, but HTTP/2 parks whatever exceeds the peer's flow-control window until a WINDOW_UPDATE arrives: that tail was dropped, and since a downloading HTTP/2 peer keeps sending frames the kernel answeredclose()with RST rather than FIN, so clients got a successful 200 with fewer bytes thancontent-length(unexpected EOF) instead of the response the handler returned.keepAliveTimeoutbounds idleness between requests (seeHttpServerConfig::keepAliveTimeout), so the idle window now restarts when the work completes. Active HTTP/2 streams are excluded from keep-alive reaping until their responses complete, including while waiting for peer flow-control credit; normal idle expiry resumes once no streams remain active. Tests:tests/http2-core_test.cpp(SlowHandlerDoesNotGetItsOwnResponseSweptAsIdle),aeronet/client/test/http-client-http2-e2e_test.cpp(SlowHandlerLargeResponseSurvivesKeepAliveSweep). - Fixed macOS
EventLoop::add()potentially masking a failed filter registration when adding bothEVFILT_READandEVFILT_WRITEin one call; each filter's result is now checked individually viaEV_RECEIPT.
Improvements
- Faster and smaller router dispatch: literal-route open-addressing slots now store compact side-table indices, and
RoutingResultreferences immutable route metadata instead of copying configuration and middleware ranges. The result shrank from 96 to 48 bytes, the hotRouter::matchsymbol shrank by 39%, and pinned internal benchmarks show about 45% lower median latency for deterministic literal hits. - Faster HTTP/1.1 client response capture: completed chunked and decompressed bodies now transfer suitably sized scratch allocations into
HttpResponseinstead of making a final body copy, while oversized scratch remains reusable for small bodies. The 256 KiB chunked parser microbenchmark is about 39% faster. - Faster, stricter HTTP/1.1 client response parsing: chunk-size scanning, hexadecimal decoding, OWS/extension handling, overflow checks, and CRLF consumption now run in one forward pass; status/header/trailer lines use SIMD-accelerated
SearchCRLF, and chunk-data delimiters use direct validation. The client now rejects bare-LF framing and consistently requires CRLF, matching the server parser. Chunked-response benchmarks are about 25% faster for 1 KiB chunks and 68% faster for 16-byte chunks. - Added a Material for MkDocs documentation site with structured English navigation, local/CI validation, and GitHub Pages deployment alongside the live benchmark dashboards.
- Replace std::to_chars(int) with faster custom WriteInt
- **Get rid of
<aeronet/stringconv.hpp>: removed internal functionStringToTimeISO8601UTCthat now becomes useless. - Remove limit of number of settings in SETTINGS frame header in HTTP2: The HTTP/2 spec does not limit the number of settings in a SETTINGS frame, but aeronet previously limited it to 16. This limit has been removed, and the SETTINGS frame is now parsed according to the spec without any arbitrary limit.
- Fix MSVC harmless warnings of unreachable code in ndigits.hpp
- Fix gcc harmless warnings of sign conversion in ndigits.hpp
- Optimized HPACK Huffman decoding with a smaller lookup table, canonical fallback, and correct padding handling, significantly improving encode/decode performance. Reworked static header lookup using a collision-free perfect hash and optimized dynamic table insertion/eviction to eliminate unnecessary memory moves. Removed redundant Huffman length computation during encoding and reduced memory usage, yielding up to 46% faster decoding, 30% faster round-trips, and 63% faster static header lookups.
- Benchmark profiling workflow:
scripts/profile_benchmark.shnow supports reliable command/PID recording, existingperf.datapost-processing, SVG flamegraphs, and Hotspot AppImage discovery. Scripted server and client benchmarks can profile each measured workload directly with--profile. - Simplify and optimize ParseHeaderLine: use
std::string_view::findthat decays tostd::memchrfor faster colon search, and added force inline to gain an additional 7 % speedup (and no extra code generation). Total expected speedup are roughly ~40% for typical browser headers, ~30% for API proxy headers, and ~0% for short names headers. Seebenchmarks/internal/init-try-set-head_bench.cppfor the new benchmark coverage. - Faster HTTP/1 CRLF search:
SearchCRLFnow scans the first 128 bytes with baseline SSE2 on x86 before falling back to libcmemchr, improving the realistic request-corpus microbenchmark by about 10%. Non-SSE2 targets retain the portablememchrpath. Seebenchmarks/internal/search-crlf_bench.cppandaeronet/tech/test/memory-utils_test.cpp. - Faster mime mappings lookup:
DetermineMIMETypeStrnow uses a binary search on extension codes on 64 bits instead of comparing std::string_views to lowercase. Function gains around 40% efficiency on average. - Further request head buffer parsing optimizations: faster CI hashing for headers, faster request method parsing. Measured gains of ~10% for requests with very few headers, and up to ~23% for requests with many headers. See
benchmarks/internal/init-try-set-head_bench.cppfor the new benchmark coverage. - Improved header & trailer lookups by normalizing keys to lower case in HttpRequestView.
Others
- Bumped
glazeversion to8.1.0.