whitecap

View Source

High-performance Erlang HTTP/1.1 server.

Whitecap optimizes the hot path of bidder-style traffic: short-lived requests, high concurrency, sub-millisecond targets. It is not a fully RFC 7230 conformant server — see Non-goals.

Install

%% rebar.config
{deps, [
    {whitecap, "0.1.0"}
]}.

Usage

A handler is any module exporting handle/2:

-module(my_handler).
-export([handle/2]).

handle(_Req, _Opts) ->
    {ok, {200, [{<<"Content-Type">>, <<"text/plain">>}], <<"hello">>}}.

The return shape is {ok, {Status, Headers, Body}} where:

  • Status is an integer status code (e.g. 200) or {Code, ReasonBinary} for codes whitecap doesn't know about.
  • Headers is a list of {Key, Value} iodata pairs. Content-Length is added automatically (except for 204).
  • Body is iodata.

Then start one or more listeners (defaults to four SO_REUSEPORT acceptors):

{ok, _} = application:ensure_all_started(whitecap),
ok = whitecap:start_listeners(#{
    handler => my_handler,
    handler_opts => #{},
    port => 8080,
    ip => {0, 0, 0, 0}
}).

Configuration

Set via sys.config or application:set_env/3 before application:start(whitecap):

KeyDefaultMeaning
max_keepalive10000Requests served per connection before close.
receive_timeoutinfinitygen_tcp:recv timeout in ms. Set finite for slowloris protection.

Telemetry

Events emitted under the [whitecap, connections, ...] prefix:

EventMeasurementsMetadata
[whitecap, connections, accept]#{}#{}
[whitecap, connections, accept_error]#{}#{reason => term()}
[whitecap, connections, close]#{}#{}
[whitecap, connections, stats]#{duration => microseconds, keep_alive => integer()}#{}
[whitecap, connections, timeout]#{}#{}
[whitecap, connections, max_keepalive]#{}#{}

duration is microseconds (from os:system_time/0 deltas converted via erlang:convert_time_unit/3).

Non-goals

Whitecap intentionally trades HTTP/1.1 conformance for throughput. Clients are expected to be cooperative (the bidder use case). Known and deliberate deviations:

  • Case-sensitive header matching. Content-Length and Transfer-Encoding must use the exact casing Content-Length: / content-length: / Transfer-Encoding: / transfer-encoding:. Any other casing is silently ignored. Use canonical casing.
  • Strict OWS in header values. Exactly one space is allowed between the colon and the value.
  • No HTTP/1.0. Status lines must end with HTTP/1.1. 1.0 requests get 501 Not Implemented.
  • No Transfer-Encoding: chunked. Returns 501.
  • Limited verbs. GET, HEAD, POST, PUT only.
  • No size limits. Request lines, header sections, and bodies are not bounded. Run whitecap only with trusted clients or a fronting proxy.
  • Acceptor does not transfer socket ownership (gen_tcp:controlling_process/2 is skipped). Works because connection workers use {active, false} + synchronous recv. Don't switch to active mode without revisiting this.

Development

make compile       # debug_info + warn-everything
make eunit         # eunit + cover
make xref          # cross-reference checks
make dialyzer      # success-typing
make test          # xref + eunit + dialyzer
make profile       # fprofx  cachegrind

License

MIT.