quic_h3 (quic v1.3.0)
View SourceHTTP/3 client and server API.
This module provides the public interface for HTTP/3 connections built on top of QUIC transport.
Client Usage
%% Connect to an HTTP/3 server
{ok, Conn} = quic_h3:connect("example.com", 443),
%% Send a request
Headers = [
{<<":method">>, <<"GET">>},
{<<":scheme">>, <<"https">>},
{<<":path">>, <<"/">>},
{<<":authority">>, <<"example.com">>}
],
{ok, StreamId} = quic_h3:request(Conn, Headers),
%% Receive response (async messages to owner)
receive
{quic_h3, Conn, {response, StreamId, Status, RespHeaders}} ->
io:format("Status: ~p~n", [Status])
end,
%% Close connection
ok = quic_h3:close(Conn).Server Usage
%% Start an HTTP/3 server
{ok, _} = quic_h3:start_server(my_server, 4433, #{
cert => CertDer,
key => KeyTerm,
handler => fun handle_request/5
}),
%% Handler function
handle_request(Conn, StreamId, Method, Path, Headers) ->
quic_h3:send_response(Conn, StreamId, 200, [{<<"content-type">>, <<"text/plain">>}]),
quic_h3:send_data(Conn, StreamId, <<"Hello, HTTP/3!">>, true).Server Push (RFC 9114 Section 4.6)
Server push allows a server to pre-emptively send resources to a client.
Server side:
%% In request handler, push associated resources
{ok, PushId} = quic_h3:push(Conn, StreamId, [
{<<":method">>, <<"GET">>},
{<<":scheme">>, <<"https">>},
{<<":authority">>, <<"example.com">>},
{<<":path">>, <<"/style.css">>}
]),
ok = quic_h3:send_push_response(Conn, PushId, 200,
[{<<"content-type">>, <<"text/css">>}]),
ok = quic_h3:send_push_data(Conn, PushId, CssBody, true).Client side:
%% Enable push after connecting
ok = quic_h3:set_max_push_id(Conn, 10),
%% Handle push notifications
receive
{quic_h3, Conn, {push_promise, PushId, ReqStreamId, Headers}} ->
%% Server announced it will push this resource
ok;
{quic_h3, Conn, {push_response, PushId, Status, Headers}} ->
%% Push response headers received
ok;
{quic_h3, Conn, {push_data, PushId, Data, Fin}} ->
%% Push response data received
ok
end.
Summary
Functions
Cancel a stream with H3_REQUEST_CANCELLED error.
Cancel a stream with a specific error code.
Cancel a push (client only).
Close the connection.
Connect to an HTTP/3 server.
Connect to an HTTP/3 server with options.
Get peer HTTP/3 settings.
Get the underlying QUIC connection for an H3 connection.
Get local HTTP/3 settings.
Initiate graceful shutdown.
Whether both sides negotiated RFC 9297 support and the extension is live on this connection.
Max payload we can fit in one H3 DATAGRAM for the given stream. Returns 0 if RFC 9297 isn't live or the peer advertised 0 for max_datagram_frame_size.
Open a client-initiated bidirectional stream outside the H3 request/response flow. Equivalent to open_bidi_stream(Conn, undefined) — the stream is a plain request stream and the caller is responsible for HEADERS/DATA framing (typically not useful on its own; prefer the /2 form below).
Open a client-initiated bidirectional stream and pre-claim it with an extension signal type (e.g. WebTransport's 16#41).
Initiate a server push (server only).
Send an HTTP request.
Send an HTTP request with options.
Send body data on a request stream.
Send body data with fin flag.
Send an HTTP Datagram bound to a request stream.
Send data on a push stream (server only).
Send response headers on a push stream (server only).
Send an HTTP response (server only).
Send trailers on a request stream.
Set the maximum push ID (client only).
Register a handler to receive stream body data.
Register a handler with options.
Start an HTTP/3 server.
Stop an HTTP/3 server.
Unregister a stream handler.
Wait for H3 connection to be ready.
Types
-type conn() :: pid().
-type connection_handler_fun() :: fun((QuicConnPid :: pid()) -> per_connection_opts()).
-type error_code() :: non_neg_integer().
-type push_id() :: non_neg_integer().
-type status() :: 100..599.
-type stream_id() :: non_neg_integer().
-type stream_type_handler() :: fun((uni | bidi, stream_id(), non_neg_integer()) -> claim | ignore).
Functions
Cancel a stream with H3_REQUEST_CANCELLED error.
-spec cancel(conn(), stream_id(), error_code()) -> ok.
Cancel a stream with a specific error code.
Cancel a push (client only).
Sends CANCEL_PUSH to tell the server we don't want this push. Can be called after receiving a push_promise notification.
-spec close(conn()) -> ok.
Close the connection.
Immediately closes the HTTP/3 connection and underlying QUIC connection.
-spec connect(Host, Port) -> {ok, conn()} | {error, term()} when Host :: binary() | string() | inet:ip_address(), Port :: inet:port_number().
Connect to an HTTP/3 server.
Establishes a QUIC connection with ALPN "h3" and starts the HTTP/3 connection layer.
The calling process becomes the owner and will receive HTTP/3 events as messages.
-spec connect(Host, Port, Opts) -> {ok, conn()} | {error, term()} when Host :: binary() | string() | inet:ip_address(), Port :: inet:port_number(), Opts :: connect_opts().
Connect to an HTTP/3 server with options.
Options:
sync- Iftrue, wait for H3 connection to be established before returning. This ensures requests can be made immediately. Default:false.connect_timeout- Timeout in ms for sync connect. Default: 5000.
Get peer HTTP/3 settings.
Returns undefined if SETTINGS has not been received yet.
Get the underlying QUIC connection for an H3 connection.
Needed for WebTransport which uses native QUIC streams alongside H3.
Get local HTTP/3 settings.
-spec goaway(conn()) -> ok.
Initiate graceful shutdown.
Sends a GOAWAY frame to the peer. No new requests will be accepted, but existing streams will complete.
Whether both sides negotiated RFC 9297 support and the extension is live on this connection.
-spec max_datagram_size(conn(), stream_id()) -> non_neg_integer().
Max payload we can fit in one H3 DATAGRAM for the given stream. Returns 0 if RFC 9297 isn't live or the peer advertised 0 for max_datagram_frame_size.
Open a client-initiated bidirectional stream outside the H3 request/response flow. Equivalent to open_bidi_stream(Conn, undefined) — the stream is a plain request stream and the caller is responsible for HEADERS/DATA framing (typically not useful on its own; prefer the /2 form below).
-spec open_bidi_stream(conn(), non_neg_integer() | undefined) -> {ok, stream_id()} | {error, term()}.
Open a client-initiated bidirectional stream and pre-claim it with an extension signal type (e.g. WebTransport's 16#41).
When SignalType is a non-negative integer, the stream is recorded in the H3 connection's claimed-bidi table. Subsequent inbound data on that stream bypasses the HTTP/3 request parser and is delivered to the connection owner as {quic_h3, Conn, {stream_type_data, bidi, StreamId, Data, Fin}}. The owner also receives {quic_h3, Conn, {stream_type_open, bidi, StreamId, SignalType}} at open time, mirroring the peer-initiated claimed-bidi path.
The caller sends the extension signal varint (and any session/header prefix) itself via send_data/4 — this API is extension-agnostic.
When SignalType is undefined, no claim is recorded and the stream behaves as a normal H3 request stream.
Initiate a server push (server only).
Sends a PUSH_PROMISE on the request stream and allocates a push ID. Returns the push ID for subsequent send_push_response/send_push_data calls.
The Headers should contain the pseudo-headers for the pushed request: :method, :scheme, :authority, and :path.
Send an HTTP request.
Opens a new request stream and sends the HEADERS frame. Returns the stream ID for tracking the response.
Required pseudo-headers:
:method- HTTP method (GET, POST, etc.):scheme- URL scheme (https):path- Request path:authority- Host authority
Send an HTTP request with options.
Send body data on a request stream.
For clients, this sends request body data. For servers, this sends response body data.
Send body data with fin flag.
Set Fin to true to indicate the end of the body.
Send an HTTP Datagram bound to a request stream.
Enabled by passing h3_datagram_enabled => true to connect/3 or start_server/3; the underlying QUIC connection must also advertise a non-zero max_datagram_frame_size (RFC 9221) for the H3 extension to go live.
Returns {error, h3_datagrams_disabled} when the extension was not negotiated, {error, unknown_stream} when the stream id is not a known request stream, or one of the RFC 9221 error atoms (datagrams_not_supported, datagram_too_large, datagram_too_large_for_path, congestion_limited) forwarded from the QUIC layer.
Payload for a WebTransport-style CONNECT session is typically handed over unmodified; the quarter-stream-id prefix is added automatically.
Send data on a push stream (server only).
Set Fin to true to indicate this is the last data.
Send response headers on a push stream (server only).
After push/3 returns a push ID, use this to send the response headers. The :status pseudo-header is added automatically.
Send an HTTP response (server only).
Sends the response status and headers. The body should be sent separately using send_data/4.
Send trailers on a request stream.
Trailers are sent after the body and signal the end of the stream.
Set the maximum push ID (client only).
This enables server push up to the specified push ID. Call this after connecting to allow the server to push resources. The MaxPushId cannot be decreased once set.
Example:
%% Enable push with up to 10 promised resources (push IDs 0-9)
ok = quic_h3:set_max_push_id(Conn, 9).
-spec set_stream_handler(conn(), stream_id(), pid()) -> ok | {ok, [{binary(), boolean()}]} | {error, term()}.
Register a handler to receive stream body data.
By default, body data messages are sent to the connection owner. For server handlers that need to receive body data (e.g., POST bodies), call this function to redirect data to the handler process.
The handler will receive messages of the form: {quic_h3, Conn, {data, StreamId, Data, Fin}}
If data arrived before registration, it is returned as a list of {Data, Fin} tuples that the handler should process.
Example:
handle_request(Conn, StreamId, <<"POST">>, _Path, _Headers) ->
case quic_h3:set_stream_handler(Conn, StreamId, self()) of
ok ->
receive_body(Conn, StreamId, <<>>);
{ok, BufferedChunks} ->
Body = process_chunks(BufferedChunks),
receive_body(Conn, StreamId, Body)
end.
-spec set_stream_handler(conn(), stream_id(), pid(), map()) -> ok | {ok, [{binary(), boolean()}]} | {error, term()}.
Register a handler with options.
Options:
drain_buffer- If true (default), returns buffered data. If false, sends buffered data as messages.
-spec start_server(Name, Port, Opts) -> {ok, pid()} | {error, term()} when Name :: atom(), Port :: inet:port_number(), Opts :: server_opts().
Start an HTTP/3 server.
The server listens on the given port and accepts HTTP/3 connections. Each incoming request triggers the handler with request details.
The handler can be:
- A function:
fun(Conn, StreamId, Method, Path, Headers) -> ok - A module implementing
handle_request/5
Example:
{ok, _} = quic_h3:start_server(my_server, 4433, #{
cert => CertDer,
key => KeyTerm,
handler => fun(Conn, StreamId, <<"GET">>, Path, _) ->
Body = <<"Hello from ", Path/binary>>,
quic_h3:send_response(Conn, StreamId, 200, []),
quic_h3:send_data(Conn, StreamId, Body, true)
end
}).Receiving datagrams / stream-type events: when h3_datagram_enabled or stream_type_handler is used, owner-addressed messages are emitted per connection. Supply a durable receiver by returning #{owner => Pid} from the per-connection connection_handler callback. Without an explicit owner those messages route to the listener process, where they are discarded.
Stop an HTTP/3 server.
Unregister a stream handler.
Future data will be sent to the connection owner.
Wait for H3 connection to be ready.
Blocks until the connection is established and SETTINGS exchanged, or until the timeout expires.