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/1.1 (RFC 9112) and HTTP/2 (RFC 9113) with automatic protocol selection via TLS ALPN
  • HTTP/3 over QUIC (RFC 9114), in-process nquic library mode
  • WebSocket over HTTP/1.1 (RFC 6455), HTTP/2 (RFC 8441), and HTTP/3 (RFC 9220) with one callback set
  • Server-Sent Events helpers (W3C SSE)
  • CORS preflight and response header helpers (Fetch)
  • PROXY protocol v1/v2 for HAProxy and AWS NLB deployments
  • SNI with static lookup table and/or dynamic callback
  • Streaming responses via producer funs with end-to-end backpressure
  • Streaming request bodies with handler-driven backpressure
  • Response compression (gzip / deflate) with MIME-aware defaults
  • OpenTelemetry spans and metrics (opt-in)
  • Graceful shutdown with connection draining
  • One process per connection, with HTTP/2 and HTTP/3 streams multiplexed inside it

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.

OptionDefaultEffect
h2_initial_window_size65535Alias of initial_window_size in h2_settings: the receive window that each new stream grants the peer.
h2_max_frame_size16384Alias of max_frame_size in h2_settings: the largest frame payload the server accepts.
h2_response_delay0Milliseconds to hold the response headers of a {reply, _, _} result. {uniform, MinMs, MaxMs} draws a value per response.
h2_connection_window_policyeagerCredit policy for the connection receive window.
h2_stream_window_policyeagerCredit policy for each stream receive window.
h2_credit_batch0Octets 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 WINDOWUPDATE 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