Skip to main content

CHANGELOG.md

# Changelog

All notable changes to this project are 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).

## [Unreleased]

## [0.4.4] - 2026-06-18

Maintenance release: dependency update.

### Changed

- Bump `hackney` 4.4.3 -> 4.4.5.

## [0.4.3] - 2026-06-17

Maintenance release: a disconnect-handling fix and a dependency bump.

### Fixed

- Handle a peer disconnect mid-response gracefully. A client closing the
  connection before the response was written crashed the request worker
  twice and logged ERROR and CRASH reports for a normal disconnect. The
  H1 and H3 send paths now map a dead connection to `{error, closed}` (as
  H2 already did), and the worker treats a closed connection as a benign
  early termination instead of a handler crash.

### Changed

- Bump `hackney` 4.4.2 -> 4.4.3.

## [0.4.2] - 2026-06-17

Adds a cookie jar client layer.

### Added

- Cookie jar client layer (`livery_client:cookie_jar/0,1`). Stores the
  cookies a response sets and sends the matching ones (host, path, secure)
  on later requests through the layer, following the RFC 6265 client
  subset. Backed by a pluggable `livery_client_cookie_store` (default
  in-memory ETS); `max_cookies` caps the jar before the oldest are evicted.

## [0.4.1] - 2026-06-16

Maintenance release: dependency updates.

### Changed

- Bump `webtransport` 0.4.0 -> 0.4.1, `hackney` 4.4.0 -> 4.4.2, and
  `barrel_mcp` 2.2.3 -> 2.2.4.

## [0.4.0] - 2026-06-16

Adds early-response draining on HTTP/1.1 and request authority/scheme on
HTTP/2, with wire-dependency bumps.

### Added

- Early-response inbound drain on HTTP/1.1. When a handler commits a full
  response before the request body is read (for example a 413 rejecting an
  oversized upload), the leftover inbound body is drained before the socket
  closes, so the client reads the response instead of a connection reset.
  New per-response `early_response_drain` field on the response, with
  `livery_resp:with_early_response_drain/2` and `early_response_drain/1`,
  and `livery_resp:json/4` and `text/4` taking a response-options map. New
  `early_response_drain` and `lingering_timeout` listener options. The
  per-response override applies to full responses; streaming responses use
  the listener budget.
- HTTP/2 requests populate `livery_req:authority/1` and `livery_req:scheme/1`
  from the `:authority` and `:scheme` pseudo-headers, and synthesize a
  `host` header from the authority when the client omits one, so host-based
  routing works without a `host` header. The pseudo-headers are stripped
  from the application-visible headers.

### Changed

- Bump `h1` 0.6.2 -> 0.7.0 and `h2` 0.10.1 -> 0.10.2.
- HTTP/1.1 over TLS records `tls` on the request, so `livery_req:tls/1`
  reflects a secure connection.

### Fixed

- `wss://` (HTTPS WebSocket) upgrades on HTTP/1.1. The adapter now hands the
  ws session the TLS transport for an accepted SSL socket instead of always
  using the plain-TCP transport.
- An HTTP/1.1 request body that exceeds the listener `max_body` no longer
  resets the stream: the handler's response is delivered and the rest of the
  inbound body is drained before closing.

## [0.3.2] - 2026-06-14

Maintenance release: dependency updates.

### Changed

- Bump `h2` 0.9.0 -> 0.10.1, `hackney` 4.3.0 -> 4.4.0, and `barrel_mcp`
  2.2.2 -> 2.2.3.
- Require the wire dependencies (`h1`, `h2`, `quic`, `ws`, `instrument`,
  `webtransport`, `hackney`, `barrel_mcp`) with `~>` so patch releases
  are accepted automatically.
- Bump the cowboy comparison harness to cowboy 2.16.1 and cowlib 2.17.1,
  and `proper` to 1.5.0, in the test and bench profiles.

## [0.3.1] - 2026-06-14

Maintenance release: an `instrument` bump and a documentation pass.

### Changed

- Upgrade `instrument` 1.1.3 -> 1.1.4.

### Documentation

- Rewrote the guides, tutorials, and concept pages in a task-oriented
  style. Each page opens with what it is and when you need it, then the
  concrete steps.
- Added an Ecosystem page linking the sibling libraries `livery_grpc`,
  `livery_s3`, and `livery_stripe`, on the site and in the API reference.

## [0.3.0] - 2026-06-13

Adds deferred responses, per-SNI certificate selection on HTTP/3, and
per-listener TLS options, with wire-dependency bumps.

### Added

- `livery_resp:stream_deferred/1`. The resolver fun runs at emit time, in
  the worker, before any header is written, and chooses the response shape:
  `{stream|sse|ndjson, Status, Headers, Producer}` or `{full, Status,
  Headers, Body}`. This lets a streaming handler reply with a non-2xx JSON
  error if admission fails before the first byte, instead of `200 OK` + an
  in-band error frame. Headers added by wrapping middleware merge under the
  decision's headers (decision wins on conflict).
- Per-SNI certificate selection on HTTP/3. `livery_h3` forwards a
  `sni_callback` (carried inside `quic_opts`) so an H3 listener picks its
  certificate per connection from the ClientHello SNI, mirroring the
  `ssl_opts`/`sni_fun` path on H1/H2. See
  `docs/guides/serve-multiple-certs-sni.md`.
- Per-listener TLS `ssl_opts` are forwarded to the underlying listeners.

### Changed

- Bump `h1` 0.6.1 -> 0.6.2, `quic` 1.6.4 -> 1.6.5, `webtransport`
  0.3.3 -> 0.4.0, `hackney` 4.2.3 -> 4.3.0.

## [0.2.7] - 2026-06-10

Maintenance release: a Hex resolution fix, an HTTP/1.1 WebSocket header
fix, and a push-streaming mode for the HTTP client.

### Added

- `livery_client` push streaming. Pass `stream_to => Pid` (with `stream
  => true`) and the response is delivered to `Pid` as ordered messages
  (`{livery_response, Ref, {status, Status, Headers}}`, `{chunk, Binary}`,
  `done`, `{error, Reason}`), so one process can interleave body chunks
  with its own control messages in a selective receive instead of
  dedicating a process to a blocking read loop. `flow => manual` sends one
  chunk per `stream_next/1` for backpressure; `stop_stream/1` cancels and
  drops the connection. The `livery_client_adapter` behaviour gains
  optional `stream/3`, `stream_next/1`, and `stop_stream/1` callbacks. The
  pull-based `{stream, Reader}` + `read/2` API is unchanged.

### Fixed

- The package now resolves on Hex (#49). The published dependency pins
  conflicted: `barrel_mcp` 2.2.0 required `hackney` 4.0.3 / `h2` 0.6.1 /
  `erlang_h1` ~>0.2.3, and `hackney` 4.2.2 required `h2` 0.8.0, neither
  of which agrees with Livery's own pins. The dependency bumps below put
  the whole graph on `h2` 0.9.0. (Local builds use git deps, which skip
  Hex version resolution, so the conflict never showed up in CI.)
- HTTP/1.1 WebSocket upgrade: the `101 Switching Protocols` response no
  longer carries duplicate `Connection`/`Upgrade` headers, which
  spec-strict clients (Safari, undici) reject. Fixed upstream in
  `erlang_h1` 0.6.1, which now owns those framing headers and strips any
  caller-supplied copies.

### Changed

- Bump dependencies onto a mutually compatible set:
  - `barrel_mcp` 2.2.0 -> 2.2.2 (now requires `hackney` 4.2.3 / `h2`
    0.9.0 / `erlang_h1` ~>0.6.1)
  - `erlang_h1` 0.6.0 -> 0.6.1
  - `hackney` 4.2.2 -> 4.2.3 (now requires `h2` 0.9.0)
  - `webtransport` 0.3.2 -> 0.3.3

## [0.2.6] - 2026-06-09

Maintenance release: a `barrel_mcp` bump that threads the authenticated
principal into MCP tool handlers. No Livery API change.

### Changed

- Bump `barrel_mcp` 2.1.0 -> 2.2.0. Arity-2 MCP tool handlers
  (`Mod:Fun(Args, Ctx)`) now receive the authenticated principal in
  `Ctx` under `auth_info`, so owner-scoped tools can identify the
  caller. The handler passes it through unchanged; no Livery API change.

## [0.2.5] - 2026-06-07

Maintenance release: HTTP client fixes and a `Retry-After`-aware retry
layer. No API change beyond the new retry option.

### Added

- The retry layer honors a `Retry-After` (delta-seconds) header on a
  retryable response, sleeping that long instead of the computed backoff,
  capped by the new `retry_after_max` option (default 120000 ms). An
  HTTP-date `Retry-After` falls back to backoff.

### Fixed

- The hackney client adapter no longer crashes on a bodyless response
  (HEAD, 204, 304), where hackney returns a three-tuple with no body.

### Changed

- Bump `hackney` 4.2.1 -> 4.2.2.

## [0.2.4] - 2026-06-06

Maintenance release: an HTTP/2 write-path optimization, an HTTP/2
disconnect fix, and a runtime concurrency-cap setter. No API change
beyond the added setter.

### Changed

- Bump `h2` to 0.9.0, which coalesces a response's frames into a single
  socket write instead of one write per frame. On the loopback benchmark
  this roughly doubles large HTTP/2 response throughput (100 KiB over TLS:
  ~28k -> ~66k req/s) and lifts smaller bodies ~7-9%.

### Fixed

- HTTP/2: a client that disconnects mid-response is reported as a normal
  disconnect (`{error, closed}`, matching HTTP/1.1's `gen_tcp:send`)
  instead of crashing the request. The crash path error-logged the full
  stacktrace, which carries the response body as a send argument, so a
  disconnect during a large response pretty-printed the whole body per
  request - a throughput sink and a log-hygiene leak.

### Added

- `livery_req_sup:set_max_concurrent_requests/1` changes the in-flight
  request cap at runtime. The cap is resolved once at startup and cached
  in `persistent_term` rather than read from the application environment
  on every request.

### Benchmarks

- `bench/compare.sh` compares livery, cowboy, and bandit over HTTP/1.1
  (`wrk`) and HTTP/2 over TLS (`h2load`) across realistic workloads (tiny
  GET, 1/10/100 KiB sized responses served from a cached payload, JSON
  echo `POST`), with a per-protocol summary table and an optional
  concurrency sweep.

## [0.2.3] - 2026-06-05

Maintenance release: dependency bumps and benchmark tooling. No API change.

### Changed

- Bump wire dependencies: `h1` (erlang_h1) 0.5.0 -> 0.6.0 (cuts
  per-request allocations, single-scan header parsing, one-pass response
  header block, header-block size cap), `quic` 1.6.3 -> 1.6.4, `hackney`
  4.2.0 -> 4.2.1, `webtransport` 0.3.1 -> 0.3.2.

### Added

- HTTP/3 in the cross-server benchmark (`bench/compare.sh`), measured with
  livery's in-VM `quic_h3` driver (livery only; cowboy and bandit have no
  HTTP/3), alongside the HTTP/1.1 (`wrk`) and HTTP/2-over-TLS (`h2load`)
  comparisons.

## [0.2.2] - 2026-06-05

Maintenance release: an H1 throughput optimization and benchmark tooling.
No API change.

### Changed

- H1 full responses coalesce into a single `content-length` socket write
  (`livery_h1:send_full/5` via the new `h1:respond/5`) instead of chunked
  framing over two writes, lifting H1 throughput about 24% in the loopback
  benchmark. Requires erlang_h1 0.5.0 (bumped from 0.4.0).

### Added

- Cross-server benchmark (`bench/compare.sh`) comparing livery, cowboy,
  and bandit over HTTP/1.1 (`wrk`) and HTTP/2 over TLS (`h2load`).

## [0.2.1] - 2026-06-04

Maintenance release: tests, docs, and internal layout. No API or
behaviour change.

### Added

- End-to-end test suite (`livery_e2e_SUITE`): boots the example notes
  service over H1, H2, and H3 and runs the same CRUD + middleware + SSE +
  WebSocket journey against each protocol.

### Changed

- Source tree grouped into domain subdirectories (`src/client`,
  `src/middleware`, `src/auth`, `src/codec`); the core runtime stays flat
  in `src/`. Pure relocation, no module renamed.
- README rewritten around runnable snippets.
- The example service registers its `/ws` route for any method, so the
  WebSocket upgrade works over H2/H3 extended CONNECT as documented.

## [0.2.0] - 2026-06-04

Closes the structural gap with Axum + Tower + Hyper: router composition,
first-class shared state, and a composable HTTP client that mirrors the
middleware model outbound, including load balancing across endpoints.

### Added

- Router composition. `livery_router:nest/2,3` mounts a sub-router under a
  path prefix and `livery_router:merge/1,2` combines routers, so an area
  (for example an MCP mount) can be assembled on its own and grafted in.
- First-class service config. `livery:start_service/1` takes a `config`
  map shared by every handler and middleware, read with
  `livery_req:config/1,2,3` (the `with_state` analogue).
- Composable HTTP client (`livery_client`): the outbound twin of the
  middleware. Build a client with a transport adapter, base URL, default
  headers, and a layer stack, then call it. Ships timeout, retry,
  concurrency-limit, and circuit-breaker layers, streamed request and
  response bodies, and a `livery_client_adapter` behaviour (default
  `livery_client_hackney`, covering HTTP/1.1, HTTP/2, and HTTP/3).
- Client load balancing. A `livery_client:balance/1` layer spreads
  requests across a pool of endpoints with power-of-two-choices or
  round-robin selection, passive outlier ejection, and lazy half-open
  recovery. Pools are seeded from a static list or a
  `livery_client_discover` provider and can be changed at runtime with
  `add_endpoint/2` and `remove_endpoint/2`.
- Bind to a specific listen address, including IPv6 (`livery_inet`), and
  reduced per-request overhead.
- Cowboy cutover validation. `examples/livery_example_migration.erl`
  expresses the common Cowboy patterns (plain handler, REST resource,
  SSE, a `cowboy_loop`-style streaming endpoint, WebSocket echo) in
  Livery, and `test/livery_cowboy_parity_SUITE.erl` runs that handler set
  behind both a live Cowboy listener and Livery, diffing the observable
  behaviour over H1, then drives the same Livery handlers over H2 and H3.

### Changed

- Wire dependencies moved to hex and bumped: `quic` 1.6.3, `h2` 0.8.0,
  `webtransport` 0.3.1, `hackney` 4.2.0, `instrument` 1.1.3.

### Fixed

- H1 query string handling.
- Low-severity security hardening across the adapters.

## [0.1.0] - 2026-05-26

First public release. Livery is a BEAM-native web framework that serves
one handler set over HTTP/1.1, HTTP/2, and HTTP/3 from a single service
runtime, in the spirit of Axum + Tower + Hyper. This is an early (0.x)
release; the framework is still under active development.

### Core

- Multi-protocol service runtime (`livery:start_service/1`) that brings
  H3 (UDP), H2 (TLS), and H1 (TCP) up together under one router and
  middleware stack and advertises `Alt-Svc` for H3. Single-protocol
  listeners via `livery:start_listener/2`.
- Thin H1/H2/H3 adapters over the sibling wire libraries (`h1`, `h2`,
  `quic`); externally observable behaviour is locked across all adapters
  by a parity test suite.
- Per-request worker model (`livery_req_proc`) with a per-stream
  translator that forwards wire events, so handlers may block and
  receive.
- Value-based Tower/Axum middleware (`call(Req, Next, State) -> Resp`),
  with global and per-route stacks.
- Immutable request/response values (`livery_req`, `livery_resp`) and a
  radix-trie router (`livery_router`).
- Response body variants: full, chunked, SSE, file, empty, and upgrade.
- Graceful shutdown via `livery_drain` and cancel-on-disconnect across
  H1/H2/H3.

### Protocols and streaming

- WebSocket over H1, H2, and H3.
- WebTransport upgrade bridge over H3.
- Server-Sent Events and file responses streamed over every adapter.
- MCP Streamable HTTP handler over `barrel_mcp` 2.0.

### Middleware and helpers

- CORS (`livery_cors`) and security headers (`livery_security_headers`).
- Response compression (`livery_compress`) over a pluggable
  `livery_codec` registry, with gzip and deflate built in.
- Multipart and streaming form-body parsing (`livery_multipart`,
  `livery_ext:read_form/1,2`).
- Concurrency-limit load shedding (`livery_concurrency`) and per-key
  rate limiting (`livery_ratelimit`).
- HTTP caching: automatic ETag and conditional GET (`livery_etag`) plus
  `livery_resp:with_etag/2` and `with_cache_control/2`.
- Static-directory serving (`livery_static`) with MIME by extension,
  weak ETag, Range, directory index, and strict path confinement.
- Health and readiness endpoints (`livery_health`) and a Prometheus
  `/metrics` handler (`livery_metrics`).

### Auth and API tooling

- Signed session cookies and RFC 7662 token introspection.
- Bearer middleware with OIDC discovery and JWKS fetch, cache, and
  rotation.
- OpenAPI request validation with inline Redoc and Swagger UI handlers.

### Observability

- OpenTelemetry-style tracing and HTTP server metrics over the
  `instrument` library, with a logger bridge that carries trace context
  into log events.
- Metrics middleware (`livery_instrument_metrics`) is best-effort and
  resolves instruments from the `instrument` registry on each request, so
  it never fails a request and self-heals after a registry restart
  (requires `instrument` 1.1.2).

### Notes

- In the bundled in-VM benchmark harness, H3 throughput is bounded by the
  QUIC round trip because the client and server share one BEAM. Measure
  H3 with an external native QUIC client.

[0.3.2]: https://github.com/benoitc/livery/releases/tag/v0.3.2
[0.3.1]: https://github.com/benoitc/livery/releases/tag/v0.3.1
[0.3.0]: https://github.com/benoitc/livery/releases/tag/v0.3.0
[0.2.7]: https://github.com/benoitc/livery/releases/tag/v0.2.7
[0.2.6]: https://github.com/benoitc/livery/releases/tag/v0.2.6
[0.2.5]: https://github.com/benoitc/livery/releases/tag/v0.2.5
[0.2.4]: https://github.com/benoitc/livery/releases/tag/v0.2.4
[0.2.3]: https://github.com/benoitc/livery/releases/tag/v0.2.3
[0.2.2]: https://github.com/benoitc/livery/releases/tag/v0.2.2
[0.2.1]: https://github.com/benoitc/livery/releases/tag/v0.2.1
[0.2.0]: https://github.com/benoitc/livery/releases/tag/v0.2.0
[0.1.0]: https://github.com/benoitc/livery/releases/tag/v0.1.0