nhttp

Hex.pm CI

HTTP/1.1, HTTP/2, and HTTP/3 server for Erlang/OTP 27+.

Getting started

%% rebar.config
{deps, [nhttp]}.

Minimal server

-module(my_handler).
-behaviour(nhttp_handler).
-export([init/1, handle_request/2]).
init(_Args) ->
{ok, #{}}.
handle_request(#{method := get, path := <<"/">>}, State) ->
{reply, nhttp_resp:ok(<<"Hello, World!">>), State};
handle_request(_Req, State) ->
{reply, nhttp_resp:not_found(), State}.
{ok, Pid} = nhttp:start_link(my_server, #{
port => 8080,
handler => my_handler
}).

See the examples/ directory for runnable handler skeletons covering plain HTTP, streaming responses, WebSocket, SSE, CORS, and chunked uploads.

TLS with HTTP/1.1 + HTTP/2 (ALPN)

{ok, Pid} = nhttp:start_link(my_server, #{
port => 8443,
versions => [http1_1, http2],
handler => my_handler,
tls => #{certfile => "server.pem", keyfile => "server.key"}
}).

HTTP/3 over QUIC

HTTP/3 runs over QUIC on a UDP socket. HTTP/1.1 and HTTP/2 run over TCP on their own socket. List all three versions and a single listener opens both sockets for you, and sets Alt-Svc on the TCP side so clients know where to find HTTP/3.

{ok, Pid} = nhttp:start_link(my_server, #{
port => 8443,
versions => [http1_1, http2, http3],
handler => my_handler,
tls => #{certfile => "server.pem", keyfile => "server.key"}
}).
%% nhttp:get_ports(Pid) => #{tcp => 8443, quic => 8443}

Features

HTTP/2 test affordances

These listener options exist so that a client can be tested against a slow or stingy HTTP/2 peer. Each one defaults to the current behavior and changes nothing on HTTP/1.1 or HTTP/3.

Option Default Effect
h2_initial_window_size 65535 Alias of initial_window_size in h2_settings: the receive window that each new stream grants the peer.
h2_max_frame_size 16384 Alias of max_frame_size in h2_settings: the largest frame payload the server accepts.
h2_response_delay 0 Milliseconds to hold the response headers of a {reply, _, _} result. {uniform, MinMs, MaxMs} draws a value per response.
h2_connection_window_policy eager Credit policy for the connection receive window.
h2_stream_window_policy eager Credit policy for each stream receive window.
h2_credit_batch 0 Octets per WINDOW_UPDATE under the on_response connection policy. 0 sends the whole accumulator with each response. Valid only when a policy is on_response.

h2_settings also accepts max_send_buffer and max_queued_streams, the bounds of the codec send queue that holds response bodies while the peer withholds credit. The server defaults both to infinity.

An alias must equal the h2_settings key when both are present. The delay applies to {reply, _, _} results only. Error responses, producer streams and WebSocket upgrades go out at once.

A credit policy has five shapes. eager sends a WINDOW_UPDATE for each body chunk as soon as the handler consumed it. {threshold, N} holds the credit until the uncredited octets reach N, then sends the accumulated total. {delay, Ms} sends the credit for each chunk Ms milliseconds after the handler consumed it. on_response holds the credit of a request until the HEADERS of its {reply, _, _} result go out. The credit goes in the same socket write, ahead of the HEADERS. never sends no credit.

The two policies are independent. No policy credits more than the handler consumed. Error responses, {abort, _, _} results and stream resets release no on_response credit. A stream that closes before its delayed credit is due gets no stream WINDOW_UPDATE. The connection credit for those octets still goes out.

Under the on_response connection policy the responded octets move into one accumulator. Each time the accumulator reaches h2_credit_batch, one WINDOW_UPDATE of exactly that size goes out with the response, and the remainder carries. This example imitates APNs, which credits half of its window at a time, sends no stream credit, and answers 129 to 388 ms after the request:

#{
h2_connection_window_policy => on_response,
h2_credit_batch => 32830,
h2_stream_window_policy => never,
h2_response_delay => {uniform, 129, 388}
}

Documentation

nhttp on HexDocs

License

Apache License 2.0