Current section
Files
Jump to
Current section
Files
CHANGELOG.md
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.3.0] - 2026-10-19
### Added
- `timeouts => #{hibernate => timeout()}`: the quiet time after which a connection with nothing in flight hibernates. Default 1000 ms. `0` hibernates on every quiet moment, `infinity` never
### Changed
- A connection hibernates after the `hibernate` timeout of quiet, not after every request or WebSocket frame. The idle timeout still fires on time: the quiet wait consumes part of the idle budget and the hibernation timer gets the rest
- Requires `nhttp_lib` 2.0.0
- WebSocket frames on HTTP/1.1, HTTP/2 and HTTP/3 are decoded with `nhttp_ws:feed/2`. The decoder keeps the partial frame, so the server holds no WebSocket buffer of its own
- A WebSocket session on HTTP/1.1 reads with `{active, 100}` instead of `{active, once}`, and sends the replies to all frames of one read in one socket write. A 64 B echo costs about 30% fewer reductions
- A WebSocket session checks which optional `handle_ws_*` callbacks the handler exports once, when it opens. A callback added by a code reload applies to sessions opened after the reload
### Fixed
- HTTP/3: a WebSocket frame or push chunk that QUIC flow control refuses waits per stream and leaves in order on `stream_writable`. A Close+FIN waits in the same queue. Before, it was dropped and the send returned `ok`
- A WebSocket handler reply that holds a CLOSE ends the session, and the frames after it are not sent. A reply or close with a reserved close code, a reason over 123 bytes or a control payload over 125 bytes closes the session with 1011
- HTTP/1.1: `nhttp_ws:send/2` on a closed socket returns `{error, gone}`, not `{error, closed}`
- The `opts()` type declares the request limit options (`max_body_size`, `max_headers`, `max_header_size`, `max_header_name_length`, `max_header_value_length`, `max_uri_length`). The listener already read them, but a caller that set one failed dialyzer
- `get_port/1`, `get_port/2` and `get_ports/1` took about one second to return the TCP port on an idle listener, because they waited for an acceptor to leave `accept`. The server now reads the port that the listener registers at startup
## [1.2.1] - 2026-09-30
### Changed
- Requires `nhttp_lib` 1.2.1
### Fixed
- A `{stream, Spec, State}` result with an invalid `Spec` on HTTP/2 ended the whole connection. The server now resets only that stream
## [1.2.0] - 2026-09-28
### Added
- Listener options `h2_initial_window_size` and `h2_max_frame_size`, aliases of the `h2_settings` keys
- Listener option `h2_response_delay`, a fixed or uniform delay before the response headers of a `{reply, _, _}` result on HTTP/2
- Listener options `h2_connection_window_policy` and `h2_stream_window_policy`, credit policies for the HTTP/2 receive windows with the shapes `eager`, `{threshold, N}`, `{delay, Ms}` and `never`
- The `on_response` shape of both credit policies, which sends the credit for a request ahead of the HEADERS of its `{reply, _, _}` response, and the listener option `h2_credit_batch` that batches the connection credit
### Changed
- Updates `nhttp_lib` reference
- Response bodies, producer chunks and WebSocket frames on HTTP/2 go through the codec send queue. The server keeps no remainder buffer of its own, and one connection grant reaches every blocked stream, one frame per stream per turn
- Every RST_STREAM the server sends goes through the codec, which closes the stream and purges the octets it holds for it
- `h2_settings` accepts `max_send_buffer` and `max_queued_streams`, the bounds of the codec send queue. The server defaults both to `infinity`. A response body, producer chunk or WebSocket frame that the bound refuses ends its stream with RST_STREAM(ENHANCE_YOUR_CALM). A producer sees `{error, closed}` and a WebSocket session closes with `{transport, send_buffer_full}`
### Fixed
- A WebSocket-over-HTTP/2 frame that outran the send window was dropped without a record
- One large response was able to take a whole connection credit ahead of smaller ones
- The NO_ERROR RST_STREAM that asks a client to stop sending a request body went out before the response was complete, and the buffered body then followed it on a closed stream (RFC 9113 Section 5.1 and Section 8.1)
## [1.1.1] - 2026-09-10
### Changed
- Requires `nhttp_lib` 1.1.1
## [1.1.0] - 2026-09-10
### Changed
- Requires `nhttp_lib` 1.1.0
- Socket re-arming for the next read is centralized. A `setopts` error on a peer that closed ends the HTTP/1.1, HTTP/2, and WebSocket loops as a normal close
- The `nhttp.http.request.duration` metric records native time units. The consumer converts them
### Fixed
- `t:nhttp:start_error/0` includes `{already_started, pid()}`
## [1.0.4] - 2026-08-24
### Fixed
- HTTP/1.1 connections no longer crash when the peer closes the socket. A `setopts` call that gives an error now closes the connection normally. A body read that gives an error tells the handler that the peer closed the connection
## [1.0.3] - 2026-08-20
### Changed
- Requires `nhttp_lib` 1.0.4 for correct HTTP/1.1 `Content-Length` handling. An empty-body response now emits `Content-Length: 0`, a 1xx, 204, or 304 response emits no `Content-Length` field, and a 2xx response to a `CONNECT` request can suppress it (RFC 9110 Section 8.6)
## [1.0.2] - 2026-08-13
### Added
- Support for Erlang/OTP 29.
## [1.0.1] - 2026-08-12
### Fixed
- WebSocket fragmented messages on HTTP/1.1. The connection keeps decoder state across reads, so a message split over several frames is reassembled and delivered once (RFC 6455 section 5.4)
- WebSocket CLOSE codes for decode errors. A text payload or CLOSE reason that is not valid UTF-8 gives 1007, a message over `max_message_size` gives 1009, and other framing errors keep 1002. Before, every decode error gave 1002
### Changed
- Requires `nhttp_lib` 1.0.3 for the stateful frame decoder and incremental UTF-8 validation
### Added
- WebSocket RFC 6455 compliance tests with Autobahn. Run them with `make ws-compliance`
## [1.0.0] - 2026-06-01
Initial public release.
### Added
- HTTP/1.1 and HTTP/2 server with automatic protocol selection via TLS ALPN (RFC 9112, RFC 9113)
- HTTP/3 server over QUIC via in-process `nquic` library mode (RFC 9114)
- Handler behaviour (`nhttp_handler`) with a small callback set for request handling, streaming, and WebSocket sessions
- WebSocket support across HTTP/1.1, HTTP/2, and HTTP/3 with one callback set (RFC 6455, RFC 8441, RFC 9220)
- Streaming responses via producer funs with end-to-end backpressure
- Streaming request bodies with handler-driven backpressure
- Server-Sent Events helpers (`nhttp_sse`)
- CORS preflight and response header helpers (`nhttp_cors`)
- Response compression (gzip / deflate) with MIME-aware defaults
- PROXY protocol v1/v2 for HAProxy / AWS NLB deployments
- SNI with static lookup table and dynamic callback
- OpenTelemetry spans and metrics (opt-in)
- Graceful shutdown with connection draining
- One process per connection, with HTTP/2 and HTTP/3 streams multiplexed inside it
- Per-stream handler workers on HTTP/2 to isolate slow handlers