Roadrunner

Erlang CIHex.pmHex DocsLicense

roadrunner logo

Pure-Erlang HTTP/1.1 + HTTP/2 + 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 (e.g. {roadrunner, "0.1.0"}) if you need stability across upgrades.

βœ… Conformance

Eunit + Common Test (incl. PropEr) suites with 100 % line coverage, dialyzer-clean, h2spec strict 100 %, Autobahn fuzzingclient strict 100 % 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, 5 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
hello287 k 189 k 281 k
json 290 k 194 k 316 k
echo 284 k 153 k 294 k
headers_heavy254 k 143 k 249 k
large_response 121 k 95 k 129 k
multi_request_body 271 k 120 k 275 k
varied_paths_router292 k 168 k β€”
post_4kb_form174 k 95 k β€”
large_post_streaming19 k 7.0 k β€”
pipelined_h1572 k 362 k 4.8 k
websocket_msg_throughput231 k 171 k β€”
gzip_response137 k 108 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 270 k req/s at p50 1.06 ms, p99 2.26 ms, p99.99 3.34 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 βœ“ βœ“ βœ—
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.1.0"}
]}.

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 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.