h2 (h2 v0.11.0)

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.

Return the address of the connection's peer.

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.

peername(Conn)

-spec peername(connection()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, term()}.

Return the address of the connection's peer.

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()}.