Hey folks,
Wanted to share a project I’ve been working on for a while: Roadrunner, a pure-Erlang HTTP/1.1 + HTTP/2 + WebSocket server, built from scratch with TDD as the HTTP layer for Arizona.
Why another HTTP server? Mostly because I wanted something that fits better with Arizona, faster, and with:
- A small, easy-to-predict API (a handler behaviour, request/response accessors, listener controls, and a few opt-in helpers like cookies, qs, multipart, SSE, WebSocket)
- Parsing that follows the RFCs: RFC 9110/9112 for HTTP/1.1, RFC 9113 + RFC 7541 (HPACK) for HTTP/2, RFC 6455 for WebSocket, RFC 7692 permessage-deflate. h2spec strict 100 % and Autobahn fuzzingclient strict 100 %, no skipped tests.
- Modern OTP style: sigils,
maybe, body recursion, binary keys for wire data,-doc/-moduledocmarkdown, dialyzer-clean - Built-in graceful shutdown, telemetry events, per-request
request_idinlogger:set_process_metadata/1, andproc_lib:set_label/1per-listener / per-acceptor / per-conn soobservertrees are easy to read - Real numbers. There’s a full bench grid in the repo against cowboy and elli. Roadrunner is usually 30 to 80 % faster than cowboy. Versus elli, it’s about even or a bit faster on simple GETs and clearly wins when you need something elli doesn’t ship (router, gzip, h2, WebSocket, pipelining, etc.)
Quick taste of the numbers (req/s, median of 3 runs, 50 clients, loopback, 12th-gen i9):
| scenario | roadrunner | cowboy | elli |
|---|---|---|---|
hello |
298 k | 179 k | 278 k |
headers_heavy |
235 k | 118 k | 211 k |
cookies_heavy |
247 k | 154 k | n/a |
pipelined_h1 |
501 k | 329 k | 4.9 k |
gzip_response |
127 k | 100 k | n/a |
websocket_msg_throughput |
199 k | 155 k | n/a |
Bold marks the row winner. n/a means elli’s test fixture doesn’t support that workload (no router, no gzip, no WebSocket). Full grid with p50/p99 plus h2 and memory shape lives in docs/bench_results.md.
A few things to know up front:
- It’s
0.x. The core works and is fully tested, but the API may change between minor versions - Needs OTP 29 (currently RC). Why? Performance and modern Erlang
- Not on Hex yet.
rebar3 hex publishneeds runtime deps from Hex, andtelemetryis still a git dep locally because an OTP 29 RC3 + Fastly TLS bug blocksrebar3 update. The fix is landing in OTP 29 RC4 (I guess), and v0.1.0 will go up on Hex right after
% deps (git for now, hex once OTP 29 RC4 lands):
{deps, [
{roadrunner, {git, "https://github.com/arizona-framework/roadrunner.git", {branch, "main"}}}
]}.
% boot a listener:
roadrunner:start_listener(my_listener, #{
port => 8080,
routes => [{~"/", my_handler, #{greeting => ~"hello"}}]
}).
Feedback is very welcome: bug reports, doc gaps, perf checks on your hardware, anything. The README has the full conformance, perf, and hardening notes, plus a docs/comparison.md with the honest take.
Beep beep.