h1 (h1 v0.8.0)
View SourceHTTP/1.1 public API.
Mirrors the surface of h2 (HTTP/2) and quic_h3 (HTTP/3) so applications can swap protocols without rewriting call sites.
Client
{ok, Conn} = h1:connect("example.com", 80, #{}).
{ok, StreamId} = h1:request(Conn, <<"GET">>, <<"/">>,
[{<<"host">>, <<"example.com">>}]).
receive
{h1, Conn, {response, StreamId, Status, _Headers}} -> ok
end.
ok = h1:close(Conn).Server
{ok, S} = h1:start_server(8080, #{
transport => tcp,
handler => fun(Conn, Id, _Method, _Path, _Hs) ->
h1:send_response(Conn, Id, 200, [{<<"content-length">>, <<"2">>}]),
h1:send_data(Conn, Id, <<"ok">>, true)
end}).
Summary
Functions
Server: reply 200 Connection Established to a classic HTTP/1.1 CONNECT (RFC 9110 §9.3.6, RFC 9112 §3.2.3 authority-form request-target) and take ownership of the raw socket. Mirror of accept_upgrade/3 for the 101 Switching Protocols case, but writes status 200 and injects no Connection/Upgrade headers so bytes after CRLF belong to the tunnel.
Server: reply 101 Switching Protocols to an upgrade request and take ownership of the raw socket. Injects the Connection: Upgrade and Upgrade: <proto> framing headers itself and strips any caller-supplied copies (case-insensitive), so the 101 carries exactly one of each. Pass only protocol-specific extras in ExtraHeaders (e.g. sec-websocket-accept).
Server: send 100 Continue to a client waiting on Expect.
Address and port of the connected peer. Works in both modes: the remote client on a server connection, the remote server on a client connection.
Toggle request pipelining on a client connection.
Send a request using h2-compatible pseudo-headers. The list may contain :method, :path, :authority; they're translated into the HTTP/1.1 request line + Host header.
Server: send a complete response (status, headers, and body) in a single socket write and end the stream. A Content-Length is added when the headers carry neither Content-Length nor Transfer-Encoding, so the body is sent fixed-length rather than chunked. Use this for fully-known bodies; use send_response/4 + send_data/4 for streaming.
As respond/5, with per-response options. early_response_drain overrides the listener's drain budget for this response only: pass a larger {MaxBytes, MaxMs} to a known-large upload endpoint, or 0 to close immediately without draining.
Send a body chunk. With EndStream = true the stream is ended; if the request body was not fully received first (server side), h1 advertises Connection: close and drains the remaining inbound body before closing, as for respond/5.
Server: send an interim (1xx) response, e.g. 103 Early Hints, ahead of the final response. May be called several times per stream, until the final response headers are sent. 101 is rejected (use the Upgrade machinery), and so are HTTP/1.0 clients (RFC 9110 §15.2).
Stop accepting new connections while continuing to serve the established ones (graceful drain). Synchronous. Call stop_server/1 afterwards to close the remaining connections.
Stop a server. Synchronous: closes the listen socket, the acceptor pool, and every accepted connection (kept-alive and in-flight included) before returning.
Client: send an Upgrade request and wait for 101 Switching Protocols.
Types
-type connect_opts() :: #{transport => tcp | ssl, ssl_opts => [ssl:tls_client_option()], connect_timeout => timeout(), timeout => timeout(), pipeline => boolean(), max_keepalive_requests => pos_integer(), max_header_block_size => pos_integer(), idle_timeout => timeout(), request_timeout => timeout()}.
-type connection() :: pid().
-type early_response_drain() :: 0 | {non_neg_integer() | infinity, non_neg_integer() | infinity}.
-type respond_opts() :: #{early_response_drain => early_response_drain()}.
-type server_opts() :: #{transport => tcp | ssl, cert => binary() | string(), key => binary() | string(), cacerts => [binary()], ssl_opts => [ssl:tls_option()], ip => inet:ip_address(), inet6 => boolean(), handler := fun((connection(), stream_id(), binary(), binary(), headers()) -> any()) | module(), acceptors => pos_integer(), handshake_timeout => timeout(), idle_timeout => timeout(), request_timeout => timeout(), early_response_drain => early_response_drain(), max_keepalive_requests => pos_integer(), max_header_block_size => pos_integer(), max_body_size => pos_integer() | infinity}.
-type server_ref() :: {pid(), reference(), inet:port_number()}.
-type status() :: 100..599.
-type stream_id() :: non_neg_integer().
Functions
-spec accept_connect(connection(), stream_id(), headers()) -> {ok, gen_tcp | ssl, term(), binary()} | {error, term()}.
Server: reply 200 Connection Established to a classic HTTP/1.1 CONNECT (RFC 9110 §9.3.6, RFC 9112 §3.2.3 authority-form request-target) and take ownership of the raw socket. Mirror of accept_upgrade/3 for the 101 Switching Protocols case, but writes status 200 and injects no Connection/Upgrade headers so bytes after CRLF belong to the tunnel.
-spec accept_upgrade(connection(), stream_id(), headers()) -> {ok, term(), binary()} | {error, term()}.
Server: reply 101 Switching Protocols to an upgrade request and take ownership of the raw socket. Injects the Connection: Upgrade and Upgrade: <proto> framing headers itself and strips any caller-supplied copies (case-insensitive), so the 101 carries exactly one of each. Pass only protocol-specific extras in ExtraHeaders (e.g. sec-websocket-accept).
-spec cancel(connection(), stream_id()) -> ok | {error, term()}.
-spec cancel(connection(), stream_id(), term()) -> ok | {error, term()}.
-spec cancel_stream(connection(), stream_id()) -> ok | {error, term()}.
-spec cancel_stream(connection(), stream_id(), term()) -> ok | {error, term()}.
-spec close(connection()) -> ok.
-spec connect(string() | binary(), inet:port_number()) -> {ok, connection()} | {error, term()}.
-spec connect(string() | binary(), inet:port_number(), connect_opts()) -> {ok, connection()} | {error, term()}.
-spec continue(connection(), stream_id()) -> ok | {error, term()}.
Server: send 100 Continue to a client waiting on Expect.
-spec controlling_process(connection(), pid()) -> ok | {error, term()}.
-spec get_peer_settings(connection()) -> map().
-spec get_settings(connection()) -> map().
-spec goaway(connection()) -> ok | {error, term()}.
-spec goaway(connection(), term()) -> ok | {error, term()}.
-spec peername(connection()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, term()}.
Address and port of the connected peer. Works in both modes: the remote client on a server connection, the remote server on a client connection.
-spec pipeline(connection(), boolean()) -> ok | {error, term()}.
Toggle request pipelining on a client connection.
-spec request(connection(), headers()) -> {ok, stream_id()} | {error, term()}.
Send a request using h2-compatible pseudo-headers. The list may contain :method, :path, :authority; they're translated into the HTTP/1.1 request line + Host header.
-spec request(connection(), headers(), map()) -> {ok, stream_id()} | {error, term()}.
Server: send a complete response (status, headers, and body) in a single socket write and end the stream. A Content-Length is added when the headers carry neither Content-Length nor Transfer-Encoding, so the body is sent fixed-length rather than chunked. Use this for fully-known bodies; use send_response/4 + send_data/4 for streaming.
If the request body has not been fully received when this is called (an early response, e.g. rejecting an oversized upload with 413), h1 adds Connection: close, sends the response, then drains and discards the rest of the inbound body before closing the socket. The response is delivered cleanly and the connection is not reused. The drain is bounded by the listener's early_response_drain budget (default {infinity, 30000}).
-spec respond(connection(), stream_id(), status(), headers(), iodata(), respond_opts()) -> ok | {error, term()}.
As respond/5, with per-response options. early_response_drain overrides the listener's drain budget for this response only: pass a larger {MaxBytes, MaxMs} to a known-large upload endpoint, or 0 to close immediately without draining.
-spec send_data(connection(), stream_id(), binary()) -> ok | {error, term()}.
-spec send_data(connection(), stream_id(), binary(), boolean()) -> ok | {error, term()}.
Send a body chunk. With EndStream = true the stream is ended; if the request body was not fully received first (server side), h1 advertises Connection: close and drains the remaining inbound body before closing, as for respond/5.
-spec send_informational(connection(), stream_id(), 100..199, headers()) -> ok | {error, term()}.
Server: send an interim (1xx) response, e.g. 103 Early Hints, ahead of the final response. May be called several times per stream, until the final response headers are sent. 101 is rejected (use the Upgrade machinery), and so are HTTP/1.0 clients (RFC 9110 §15.2).
-spec send_response(connection(), stream_id(), status(), headers()) -> ok | {error, term()}.
-spec send_trailers(connection(), stream_id(), headers()) -> ok | {error, term()}.
-spec server_port(server_ref()) -> inet:port_number().
-spec set_stream_handler(connection(), stream_id(), pid()) -> ok | {error, term()}.
-spec set_stream_handler(connection(), stream_id(), pid(), map()) -> ok | {error, term()}.
-spec start_server(inet:port_number(), server_opts()) -> {ok, server_ref()} | {error, term()}.
-spec start_server(atom(), inet:port_number(), server_opts()) -> {ok, server_ref()} | {error, term()}.
-spec stop_accepting(server_ref()) -> ok.
Stop accepting new connections while continuing to serve the established ones (graceful drain). Synchronous. Call stop_server/1 afterwards to close the remaining connections.
-spec stop_server(server_ref()) -> ok.
Stop a server. Synchronous: closes the listen socket, the acceptor pool, and every accepted connection (kept-alive and in-flight included) before returning.
-spec unset_stream_handler(connection(), stream_id()) -> ok.
-spec upgrade(connection(), binary(), headers()) -> {ok, stream_id(), term(), binary(), headers()} | {error, term()}.
Client: send an Upgrade request and wait for 101 Switching Protocols.
-spec wait_connected(connection()) -> ok | {error, term()}.
-spec wait_connected(connection(), timeout()) -> ok | {error, term()}.