h1 (h1 v0.7.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.
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.
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()}.
-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 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_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_server(server_ref()) -> ok.
-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()}.