Roadrunner

Erlang CIHex.pmHex DocsLicense

roadrunner logo

Pure-Erlang HTTP/1.1 + HTTP/2 + HTTP/3 + WebSocket server for OTP 29+. Built for low tail latency at sustained load. Beep beep.

Roadrunner is the HTTP backbone of the arizona-framework. Strict RFC 9110 / 9112 / 9113 parsing, with strict 100 % h2spec (HTTP/2 conformance) and strict 100 % Autobahn fuzzingclient (WebSocket, no exclusions). The user-facing API is a handler behaviour, request/response accessors, listener controls, and a handful of opt-in helpers (cookies, qs, multipart, SSE, WebSocket). Modern OTP idioms throughout, with predictable per-connection lifecycle observability.

Requirements

Requires OTP 29 or newer.

🚧 Status

Roadrunner is in 0.x. The core is functional and covered by tests, but the API may change between minor versions. Pin an exact version in your deps if you need stability across upgrades.

Conformance

Strict 100 % h2spec (HTTP/2) and Autobahn fuzzingclient across the full WebSocket matrix (no exclusions). HTTP/1.1 parsers stress-tested against the llhttp test corpus and the canonical PortSwigger request-smuggling vectors.

Standards conformance:

Performance at a glance

Median req/s over HTTP/1.1 on a 12th-gen i9-12900HX, 50 clients, 2 s warmup + 5 s measure, loopback. HTTP/2 numbers, p50 / p99 percentiles, and memory shape sit in docs/bench_results.md and docs/comparison.md.

scenario roadrunner cowboy elli
hello307 k 201 k 299 k
json 299 k 189 k 304 k
echo304 k 162 k 282 k
headers_heavy257 k 141 k 253 k
large_response124 k 98 k 123 k
multi_request_body 262 k 125 k 274 k
varied_paths_router290 k 175 k β€”
post_4kb_form193 k 98 k β€”
large_post_streaming20 k 6.9 k β€”
pipelined_h1580 k 371 k 4.8 k
websocket_msg_throughput232 k 179 k β€”
gzip_response138 k 111 k β€”

Bold = fastest in row. β€” means the elli fixture doesn't expose that workload (no router, no gzip middleware, no WebSocket, no streaming-POST endpoint). On simple GETs and small POSTs Roadrunner and elli are within the bench's ~15 % variance band on those rows; the comparison doc has the full honest framing.

Tail latency at sustained load

Open-loop, Coordinated-Omission-corrected (wrk2, hello, 8 threads, 50 connections, 3-run median): Roadrunner sustains 291 k req/s at p50 1.07 ms, p99 2.31 ms, p99.99 4.70 ms. Full per-scenario matrix with all four rate-points per server in docs/wrk2_results.md.

The throughput numbers above are from scripts/bench.escript (closed-loop); the comparison doc has the full methodology breakdown.

Comparison

If your workload needs a feature, the server has to ship it. β€” means achievable in user code but no helper / option built in; βœ— means out of scope for that server.

feature roadrunner cowboy elli
HTTP/1.1 βœ“ βœ“ βœ“
HTTP/2 + HPACK βœ“ βœ“ βœ—
HTTP/3 (QUIC, experimental) βœ“ βœ— βœ—
WebSocket (RFC 6455) βœ“ βœ“ β€”
permessage-deflate (RFC 7692) βœ“ βœ“ βœ—
Native router βœ“ βœ“ βœ—
gzip / deflate response negotiation βœ“ βœ“ β€”
Streaming request bodies βœ“ βœ“ β€”
Native qs / cookie / multipart βœ“ βœ“ β€”
Server-Sent Events helper βœ“ β€” β€”
Sendfile βœ“ βœ“ βœ“
Static handler (ETag / Range / IMS) βœ“ βœ“ β€”
Graceful drain with deadline + broadcast βœ“ β€” βœ—
Per-request request_id in logger meta βœ“ β€” βœ—

Quickstart

Add to rebar.config:

{deps, [
    {roadrunner, "0.2.2"}
]}.

Write a handler β€” the third route element is per-route state, threaded to the handler via roadrunner_req:state/1:

-module(hello_handler).
-behaviour(roadrunner_handler).
-export([handle/1]).

handle(Req) ->
    #{greeting := Greeting} = roadrunner_req:state(Req),
    {roadrunner_resp:text(200, <<Greeting/binary, ", roadrunner!">>), Req}.

Boot a listener:

1> application:ensure_all_started(roadrunner).
2> roadrunner:start_listener(my_listener, #{
       port => 8080,
       routes => [{~"/", hello_handler, #{greeting => ~"hello"}}]
   }).
$ curl -i localhost:8080
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 18

hello, roadrunner!

For HTTP/2 over TLS, add a cert and list both protocols. ALPN is derived from protocols automatically:

3> roadrunner:start_listener(my_tls_listener, #{
       port => 8443,
       protocols => [http1, http2],
       tls => [
           {certfile, "cert.pem"},
           {keyfile, "key.pem"}
       ],
       routes => [{~"/", hello_handler, #{greeting => ~"hello"}}]
   }).

ALPN routes h2 clients to the HTTP/2 path and http/1.1 clients (or no-ALPN) to the HTTP/1.1 path on the same listener. Drop http2 from the list to disable HTTP/2. For HTTP/2 on plain TCP (h2c prior-knowledge per RFC 7540 Β§3.4), use protocols => [http2] without the tls opt.

For HTTP/3 (experimental), add http3 to a TLS listener's protocols (e.g. protocols => [http1, http2, http3]). It serves h3 over UDP on the same port number and advertises Alt-Svc so browsers upgrade from TCP; the quic transport starts on demand, so h1/h2-only listeners never boot it.

For listeners that don't need routing, routes => Mod (or {Mod, State} to seed handler state) skips the router entirely and dispatches every request to Mod:handle/1:

roadrunner:start_listener(my_listener, #{
    port => 8080,
    routes => {hello_handler, #{greeting => ~"hello"}}
}).

Configuration

All listener options live in the roadrunner_listener:opts/0 type, with per-key defaults and tuning rationale. Beyond port, protocols, tls, and routes from the Quickstart, the type covers:

Features

Handlers

Routing

Middleware

Built-in handlers

Hardening

Observability

Lifecycle

Documentation

Design philosophy

Sponsors

Roadrunner is open source and maintained on personal time. If you or your company find it useful, consider sponsoring.

I also accept coffees β˜•

"Buy Me A Coffee"

<img src="https://raw.githubusercontent.com/williamthome/williamthome/sponsorkit/sponsors.svg" alt="Sponsors" />

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for development setup, testing guidelines, and contribution workflow.

Contributors

<img src="https://contrib.rocks/image?repo=arizona-framework/roadrunner&max=100&columns=10" width="15%" alt="Contributors" />

Star History

<picture> <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=arizona-framework/roadrunner&type=Date&theme=dark" /> <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=arizona-framework/roadrunner&type=Date" /> <img src="https://api.star-history.com/svg?repos=arizona-framework/roadrunner&type=Date" alt="Star History Chart" width="100%" /> </picture>

License

Copyright (c) 2026 William Fank ThomΓ©

Roadrunner is open-source under the Apache 2.0 License on GitHub.

See LICENSE.md for more information.