h2 (h2 v0.10.1)

View Source

HTTP/2 Public API

This module provides the public API for HTTP/2 client and server operations. It wraps the h2_connection state machine with a clean interface.

Client Usage

  %% Connect to a server
  {ok, Conn} = h2:connect("example.com", 443, #{}).
 
  %% Send a request
  {ok, StreamId} = h2:request(Conn, <<"GET">>, <<"/">>, [
      {<<"host">>, <<"example.com">>}
  ]).
 
  %% Receive response (messages sent to caller)
  receive
      {h2, Conn, {response, StreamId, Status, Headers}} ->
          io:format("Status: ~p~n", [Status]);
      {h2, Conn, {data, StreamId, Data, IsFin}} ->
          io:format("Data: ~p~n", [Data])
  end.
 
  %% Close connection
  ok = h2:close(Conn).

Server Usage

  %% Start server
  {ok, Server} = h2:start_server(8443, #{
      cert => "server.pem",
      key => "server-key.pem",
      handler => fun(Conn, StreamId, Method, Path, Headers) ->
          h2:send_response(Conn, StreamId, 200, [{<<"content-type">>, <<"text/plain">>}]),
          h2:send_data(Conn, StreamId, <<"Hello World!">>, true)
      end
  }).
 
  %% Stop server
  ok = h2:stop_server(Server).

Event Messages

The owner process receives the following messages:

Client: - {h2, Conn, {response, StreamId, Status, Headers}} - {h2, Conn, {data, StreamId, Data, IsFin}} - {h2, Conn, {trailers, StreamId, Trailers}} - {h2, Conn, {stream_reset, StreamId, ErrorCode}} - {h2, Conn, {goaway, LastStreamId, ErrorCode}} - {h2, Conn, closed}

Server handler receives direct calls.

Bidirectional streaming (gRPC)

A per-call process can own a single stream's events without owning the connection, so many calls multiplex one connection. Pass #{handler => Pid} to request/3 (or call set_stream_handler/3,4) to route every event for that stream to Pid; events that arrive before registration are buffered and replayed in order. send_data/4 can be called repeatedly and half-closes with send_data(Conn, Sid, <<>>, true) while the receive side stays open. For backpressure use #{flow_control => manual} plus consume/3 on receive, and send_data/5 with #{block => Timeout} on send. See the README for an example.

Summary

Functions

Cancel a stream.

Cancel a stream with a specific error code.

Close the connection immediately.

Connect to an HTTP/2 server. Uses TLS by default on port 443.

Connect to an HTTP/2 server with options.

Acknowledge consumption of ByteCount received bytes on a manual flow-control stream, replenishing its receive window. Receive-side backpressure: a handler calls this only after processing data, gating the peer's WINDOW_UPDATE on consumer progress. No-op on auto-mode streams.

Transfer connection ownership.

Get peer settings.

Get local settings.

Initiate graceful connection shutdown.

Initiate connection shutdown with error code.

Send an HTTP/2 request with pre-built headers (matches quic_h3:request/2). Headers should include pseudo-headers (:method, :path, :scheme, :authority).

Send an HTTP/2 request. For body-less requests (GET, HEAD, etc.), sends HEADERS with END_STREAM. For CONNECT (RFC 7540 §8.3) leaves the stream open so the caller can send tunnel bytes via send_data.

Send an HTTP/2 request with body. Sends HEADERS without END_STREAM, then sends DATA with END_STREAM.

Send a complete response (headers + full body) in a single call. This is the fast path for the common request/response case: one message to the connection and one socket write (HEADERS coalesced with DATA), versus the two round-trips of send_response/4 followed by send_data/4. Falls back to the granular path transparently when the response cannot be coalesced (oversized headers/body, CONNECT tunnels).

Send data on a stream.

Send data on a stream with end_stream flag.

Send data with per-call options. Pass #{block => Timeout} (ms or infinity) to block until the peer's window accepts the data, returning ok, or {error, timeout} if the window does not open in time (the data may still be queued; the caller should slow down or cancel the stream).

Send an HTTP/2 response (server mode).

Send trailers on a stream.

Return the TCP port the server is actually listening on.

Register a pid to receive a stream's events. Routes every event for the stream — {response,...}, {data,...}, {trailers,...}, {informational,...}, {stream_reset,...} — to the handler pid as {h2, Conn, Event}. By default the connection replays any events buffered before the handler was registered, in arrival order, so a response/trailers that raced ahead of registration is never dropped to the owner. The call returns ok. Pass #{drain_buffer => true} to instead get the buffered DATA back in the reply ({ok, [{Data, Fin}, ...]}) and forward it yourself (kept for the WebSocket/MASQUE tunnel callers).

Start an HTTP/2 server.

Start a named HTTP/2 server (matches quic_h3:start_server/3).

Stop an HTTP/2 server.

Wait for a client connection to reach the connected state.

Types

connect_opts/0

-type connect_opts() ::
          #{transport => tcp | ssl,
            ssl_opts => [ssl:tls_client_option()],
            cert => binary() | string(),
            key => binary() | string(),
            cacerts => [binary()],
            verify => verify_none | verify_peer,
            settings => h2_settings:settings(),
            timeout => timeout(),
            connect_timeout => timeout(),
            sync => boolean()}.

connection/0

-type connection() :: pid().

error_code/0

-type error_code() :: h2_error:error_code().

headers/0

-type headers() :: [{binary(), binary()}].

server_opts/0

-type server_opts() ::
          #{transport => ssl | tcp,
            cert => binary() | string(),
            key => binary() | string(),
            cacerts => [binary()],
            verify => verify_none | verify_peer,
            ssl_opts => [ssl:tls_option()],
            ip => inet:ip_address(),
            inet6 => boolean(),
            handler := fun((connection(), stream_id(), binary(), binary(), headers()) -> any()),
            settings => h2_settings:settings(),
            acceptors => pos_integer(),
            enable_connect_protocol => boolean()}.

server_ref/0

-type server_ref() :: {pid(), reference(), inet:port_number()}.

status/0

-type status() :: 100..599.

stream_id/0

-type stream_id() :: non_neg_integer().

Functions

cancel(Conn, StreamId)

-spec cancel(connection(), stream_id()) -> ok | {error, term()}.

Cancel a stream.

cancel(Conn, StreamId, ErrorCode)

-spec cancel(connection(), stream_id(), error_code()) -> ok | {error, term()}.

Cancel a stream with a specific error code.

cancel_stream(Conn, StreamId)

This function is deprecated. Use cancel/2 instead..
-spec cancel_stream(connection(), stream_id()) -> ok | {error, term()}.

cancel_stream(Conn, StreamId, ErrorCode)

This function is deprecated. Use cancel/3 instead..
-spec cancel_stream(connection(), stream_id(), error_code()) -> ok | {error, term()}.

close(Conn)

-spec close(connection()) -> ok.

Close the connection immediately.

connect(Host, Port)

-spec connect(string() | binary(), inet:port_number()) -> {ok, connection()} | {error, term()}.

Connect to an HTTP/2 server. Uses TLS by default on port 443.

connect(Host, Port, Opts)

-spec connect(string() | binary(), inet:port_number(), connect_opts()) ->
                 {ok, connection()} | {error, term()}.

Connect to an HTTP/2 server with options.

consume(Conn, StreamId, ByteCount)

-spec consume(connection(), stream_id(), non_neg_integer()) -> ok | {error, term()}.

Acknowledge consumption of ByteCount received bytes on a manual flow-control stream, replenishing its receive window. Receive-side backpressure: a handler calls this only after processing data, gating the peer's WINDOW_UPDATE on consumer progress. No-op on auto-mode streams.

controlling_process(Conn, NewOwner)

-spec controlling_process(connection(), pid()) -> ok | {error, term()}.

Transfer connection ownership.

get_peer_settings(Conn)

-spec get_peer_settings(connection()) -> h2_settings:settings().

Get peer settings.

get_settings(Conn)

-spec get_settings(connection()) -> h2_settings:settings().

Get local settings.

goaway(Conn)

-spec goaway(connection()) -> ok | {error, term()}.

Initiate graceful connection shutdown.

goaway(Conn, ErrorCode)

-spec goaway(connection(), error_code()) -> ok | {error, term()}.

Initiate connection shutdown with error code.

request(Conn, Headers)

-spec request(connection(), headers()) -> {ok, stream_id()} | {error, term()}.

Send an HTTP/2 request with pre-built headers (matches quic_h3:request/2). Headers should include pseudo-headers (:method, :path, :scheme, :authority).

request(Conn, Headers, Opts)

-spec request(connection(), headers(), map()) -> {ok, stream_id()} | {error, term()}.

request(Conn, Method, Path, Headers)

-spec request(connection(), binary(), binary(), headers()) -> {ok, stream_id()} | {error, term()}.

Send an HTTP/2 request. For body-less requests (GET, HEAD, etc.), sends HEADERS with END_STREAM. For CONNECT (RFC 7540 §8.3) leaves the stream open so the caller can send tunnel bytes via send_data.

request(Conn, Method, Path, Headers, Body)

-spec request(connection(), binary(), binary(), headers(), binary()) ->
                 {ok, stream_id()} | {error, term()}.

Send an HTTP/2 request with body. Sends HEADERS without END_STREAM, then sends DATA with END_STREAM.

respond(Conn, StreamId, Status, Headers, Body)

-spec respond(connection(), stream_id(), status(), headers(), binary()) -> ok | {error, term()}.

Send a complete response (headers + full body) in a single call. This is the fast path for the common request/response case: one message to the connection and one socket write (HEADERS coalesced with DATA), versus the two round-trips of send_response/4 followed by send_data/4. Falls back to the granular path transparently when the response cannot be coalesced (oversized headers/body, CONNECT tunnels).

send_data(Conn, StreamId, Data)

-spec send_data(connection(), stream_id(), binary()) -> ok | {error, term()}.

Send data on a stream.

send_data(Conn, StreamId, Data, EndStream)

-spec send_data(connection(), stream_id(), binary(), boolean()) -> ok | {error, term()}.

Send data on a stream with end_stream flag.

Non-blocking backpressure: when the peer's send window is exhausted the data is buffered, and {error, send_buffer_full} is returned once the buffer would exceed the per-stream cap, so the caller backs off instead of growing memory without bound. For a blocking variant see send_data/5.

send_data(Conn, StreamId, Data, EndStream, Opts)

-spec send_data(connection(), stream_id(), binary(), boolean(), map()) -> ok | {error, term()}.

Send data with per-call options. Pass #{block => Timeout} (ms or infinity) to block until the peer's window accepts the data, returning ok, or {error, timeout} if the window does not open in time (the data may still be queued; the caller should slow down or cancel the stream).

send_response(Conn, StreamId, Status, Headers)

-spec send_response(connection(), stream_id(), status(), headers()) -> ok | {error, term()}.

Send an HTTP/2 response (server mode).

send_trailers(Conn, StreamId, Trailers)

-spec send_trailers(connection(), stream_id(), headers()) -> ok | {error, term()}.

Send trailers on a stream.

server_port(_)

-spec server_port(server_ref()) -> inet:port_number().

Return the TCP port the server is actually listening on.

set_stream_handler(Conn, StreamId, Pid)

-spec set_stream_handler(connection(), stream_id(), pid()) ->
                            ok | {ok, [{binary(), boolean()}]} | {error, term()}.

Register a pid to receive a stream's events. Routes every event for the stream — {response,...}, {data,...}, {trailers,...}, {informational,...}, {stream_reset,...} — to the handler pid as {h2, Conn, Event}. By default the connection replays any events buffered before the handler was registered, in arrival order, so a response/trailers that raced ahead of registration is never dropped to the owner. The call returns ok. Pass #{drain_buffer => true} to instead get the buffered DATA back in the reply ({ok, [{Data, Fin}, ...]}) and forward it yourself (kept for the WebSocket/MASQUE tunnel callers).

To avoid the registration race entirely, set the handler at stream creation with h2:request(Conn, Headers, #{handler => Pid}).

Backpressure: by default incoming DATA replenishes the receive window on dispatch, so a slow handler's mailbox can grow unbounded. Pass #{flow_control => manual} (here or at request time) to gate window replenishment on consume/3 — the handler calls h2:consume(Conn, StreamId, N) after processing N bytes, bounding in-flight data to one window.

set_stream_handler(Conn, StreamId, Pid, Opts)

-spec set_stream_handler(connection(), stream_id(), pid(), map()) ->
                            ok | {ok, [{binary(), boolean()}]} | {error, term()}.

start_server(Port, Opts)

-spec start_server(inet:port_number(), server_opts()) -> {ok, server_ref()} | {error, term()}.

Start an HTTP/2 server.

start_server(Name, Port, Opts)

-spec start_server(atom(), inet:port_number(), server_opts()) -> {ok, server_ref()} | {error, term()}.

Start a named HTTP/2 server (matches quic_h3:start_server/3).

stop_server(_)

-spec stop_server(server_ref()) -> ok.

Stop an HTTP/2 server.

unset_stream_handler(Conn, StreamId)

-spec unset_stream_handler(connection(), stream_id()) -> ok.

wait_connected(Conn)

-spec wait_connected(connection()) -> ok | {error, term()}.

Wait for a client connection to reach the connected state.

wait_connected(Conn, Timeout)

-spec wait_connected(connection(), timeout()) -> ok | {error, term()}.