Skip to content

Serve HTTP/2 and HTTP/1.1 on one TLS port by ALPN - #82

Merged
benoitc merged 1 commit into
mainfrom
alpn-listener
Aug 12, 2026
Merged

Serve HTTP/2 and HTTP/1.1 on one TLS port by ALPN#82
benoitc merged 1 commit into
mainfrom
alpn-listener

Conversation

@benoitc

@benoitc benoitc commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Adds https => #{..., alpn => [h2, http1]}, one TLS port serving both protocols, picked per connection. Livery owns the listen socket (livery_h1h2), handshakes off the accept path so a stalled client costs a process rather than an acceptor slot, reads the negotiated protocol, and hands the socket to h2:serve_socket/2 or h1:serve_socket/2. A client that offered no ALPN goes to HTTP/1.1 rather than being dropped. The list is server preference order, so [h2, http1] means h2 wins when a client offers both. It defaults to [h2], which is the current h2-only behaviour, so existing users see no change.

Two breaking changes come with it. which_listeners/1 now maps each protocol to a list of ports, because one port can serve two protocols and one protocol can sit on two ports; maps:get(h1, L) becomes hd(maps:get(h1, L)). And the H1/H2 stream handle is {Conn, StreamId, Alpn}, so peer_info/1 can report what ALPN actually settled on instead of a constant, undefined on a cleartext listener or for a TLS client that offered none. H2 requests also carry tls => #{} on a TLS listener, as H1 requests already did.

Separately, the H1 listener now forwards h1's parser size limits (max_line_length, max_request_line_size, max_empty_lines, the header caps, pipeline), which h1 accepted all along but livery never passed on, and verify for mutual TLS now that h1 0.9.0 honours it. The service host is finally read: with it set, alt_svc => advertise emits an authority-qualified Alt-Svc. livery_service:listener_opts() declares the options that worked but went undeclared, and says the rest of the map is forwarded to the adapter's own listen_opts().

Deps: h1 0.9.0 and h2 0.12.0 for serve_socket/2, webtransport 0.4.5 so it agrees with us on h2, hackney 4.7.4 for the pool fixes under livery_client.

Ships as 0.8.0.

Livery owns the listen socket for this one rather than delegating it
to h1 or h2, because only the process that ran the handshake knows
which protocol the client asked for. A client that offered no ALPN is
served HTTP/1.1: ALPN is what makes h2 over TLS possible, so its
absence is HTTP/1.1, not an error.

which_listeners/1 maps each protocol to a list of ports. One port can
serve two protocols and one protocol can sit on two ports, neither of
which the old one-port-per-protocol shape could express.

The H1/H2 stream handle carries the negotiated ALPN as a third
element. peer_info/1 returned a constant before, and the connection
process is the only one that ever sees the real value.

Also forwards h1's parser size limits, which h1 accepted but livery
never passed on, and uses the service host to qualify the advertised
Alt-Svc authority, which was documented but read by nothing.
@benoitc
benoitc merged commit 150dc0a into main Aug 12, 2026
12 checks passed
@benoitc
benoitc deleted the alpn-listener branch August 12, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant