Changelog
View SourceAll notable changes to this project are documented here. Format loosely follows Keep a Changelog; versions follow Semantic Versioning.
[0.2.3] - 2026-05-28
Changed
- Build and dialyzer clean on OTP 27, 28 and 29. Replaces the legacy
catch Exproperator (removed in OTP 29) withtry ... catch _:_ -> ok end, retypesupgrade_fromasgen_statem:from(), and drops a handful of unreachable clauses surfaced byunmatched_returns.
Tests / CI
- Interop suite's
docker_runnow picks the last non-empty stdout line as the container id so cold image pulls don't confuse it. - New GitHub Actions matrix runs build, xref, dialyze and tests on OTP 27, 28 and 29 (rebar3 3.27.0).
0.2.2 - 2026-05-20
Security
- Reject chunked bodies whose declared chunk size exceeds
max_body_sizebefore buffering, and cap the chunk-extension scan at 4096 bytes. Both paths previously let a peer grow the parser buffer without bound. - Enforce
max_empty_linesusing the parser's persistent counter so a peer can no longer bypass the limit by dripping one blank line per packet (bare-CRLF lines are now counted too). - Keep the socket passive after an Upgrade / CONNECT is detected until the handler accepts, so tunnel bytes are no longer re-parsed as HTTP.
recv_capsule/4now honors the overall timeout across reads and caps the partial buffer at 16 MB (capsule_too_large).- Acceptor backs off on unknown accept errors instead of spinning at 100% CPU.
Fixed
wait_connected/1,2could hang: waiters were stored with a malformed reply tag, sogen_statem:replynever reached the caller.- Server stream map leaked one closed-stream entry per keep-alive request; streams are now dropped once both directions finish.
- Chunked response framing over a non-chunked
Transfer-Encodingnow appendschunkedto the header so it matches the wire bytes. - Partial response status line returns
moreinstead ofbad_request. - Connection policy (keep-alive / close) is resolved before the
requestevent is emitted, so handlers see consistent state. - Server loop notifies the handler with
stream_resetwhen the connection dies mid-stream, preventing an orphaned handler from hanging. stop_server/1erases thepersistent_termentry created bystart_server/3.set_active_oncesynthesizes a close event when re-arming the socket fails, so the connection shuts down promptly instead of stalling.
0.2.1 - 2026-04-19
Fixed
h1:upgrade/4crash when the:pathpseudo-header is supplied (dead-code path inhandle_client_upgrade/4eagerly encoded pseudo-headers beforeupgrade_wire/1stripped them).
0.2.0 - 2026-04-19
Added
h1:accept_connect/3,4: server-side reply of200 Connection Establishedto a classic HTTP/1.1 CONNECT with atomic raw-socket handoff (RFC 9110 §9.3.6, RFC 9112 §3.2.3). Mirrorsh1:accept_upgrade/3but writes status 200 and injects no Connection/Upgrade/framing headers, so bytes past the terminating CRLF belong to the tunnel.
0.1.1 — 2026-04-19
Changed
- Hex package name is
erlang_h1(the shorth1is already taken on hex.pm). The OTP application, module atom, and public API are unchanged — call sites continue to useh1:connect/2etc.
0.1.0 — 2026-04-19
Initial release.
HTTP/1.1 core
- Streaming pure-Erlang parser covering RFC 9110 / RFC 9112: request and response lines, chunked transfer, trailers, obs-fold, 100-continue, absolute-form / asterisk / authority request targets.
- Request / response / chunk / trailer encoder with CRLF-injection guards on header names, methods, paths, and reason phrases.
- RFC 9297 capsule codec (
h1_capsule) wire-compatible with the equivalent module inerlang_h2. h1_connectiongen_statemrunning in both client and server modes with keep-alive, pipelining (in-order response delivery on the server),Expect: 100-continue, and Upgrade / 101 Switching Protocols with socket handoff.
Public API
h1module mirrors the surface ofh2andquic_h3so callers can swap protocols:connect,request,send_data,send_trailers,cancel,goaway,close,start_server,stop_server,send_response, plus H1-specificupgrade,accept_upgrade,continue,pipeline.- Event messages (
{h1, Conn, Event}) match theh2/h3shape, with an extra{upgrade, ...}/{upgraded, ...}pair for the 101 handoff.
Hardening
- Smuggling guards (RFC 9112 §6.1). Reject messages carrying both
Content-LengthandTransfer-Encoding: chunked; reject differingContent-Lengthvalues across duplicates or in a comma-list; rejectTransfer-Encodingon HTTP/1.0. - DoS guards. Chunk-size hex capped at 16 digits; configurable
max_body_sizeenforced per stream; idle and request timers armed asgen_statemtimeouts (slowloris guard). - Field validation. Encoder rejects CRLF in header names, methods,
paths, and reason phrases; parser rejects forbidden fields in
trailers per RFC 9110 §6.5.1; obs-fold re-validates the unfolded
value against
max_header_value_size. - Response framing (RFC 9110 §6.3). HEAD / 1xx / 204 / 304 responses are body-less regardless of framing headers; close-delimited bodies finalise on socket close.
- TLS defaults. Client connects with
verify_peer+ OS CA trust + hostname check + automatic SNI; user-suppliedssl_optswin on every key. - Host enforcement. Client auto-adds the
Host:header from the connect hostname; server rejects HTTP/1.1 requests missingHostwith 400 and closes the connection.
Listener + client integration
- Built-in acceptor pool + listener (
h1_acceptor,h1_listener) and per-connection server loop (h1_server) that preserves pipelined response byte order on the wire (RFC 9112 §9.3). - Client connect helper (
h1_client) drives TCP / TLS handshake, socket ownership, andwait_connectedsynchronisation. - Reference
ranch_protocolmodule + docs covering drop-in Ranch integration and ALPN multiplexing withh2.
Tests
- 52 EUnit tests + 4 PropEr roundtrip properties.
- 149 Common Test cases across parser, encoder, capsule codec,
connection state machine, end-to-end client/server, Upgrade +
capsule exchange, Ranch integration, compliance vectors
(smuggling / framing / chunked / DoS) and interop (curl, plus
python:3-alpineandnginx:alpineunder Docker).
Documentation
README.md— install, quickstart, full client & server walkthroughs, TLS guidance, tuning, events and error reference, Ranch snippet.docs/features.md— RFC coverage, in-scope vs. intentionally out-of-scope, internal module map.docs/ranch.md— production-shape protocol module, ALPN multiplex, graceful drain, gotchas.