Skip to content

awksedgreep/rocket

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rocket

High-performance HTTP/1.1 server for the BEAM.

Rocket uses OTP 28's :socket module with a picohttpparser NIF for request parsing. No middleware layers, no protocol abstractions — just raw TCP sockets and pattern-matched routing compiled at build time.

Status

Experimental / early-stage — Rocket is under active development and not yet production-ready.

Current limitations:

  • No TLS/HTTPS
  • No HTTP/2
  • No WebSockets
  • No middleware or Plug compatibility
  • No chunked/streaming responses
  • Requires OTP 28 (pre-release)

Performance

~30x lower latency than Bandit+Plug, translating to ~3x higher throughput under load.

Latency (in-process bench, p50):

Endpoint Rocket Bandit Improvement
GET /health 68μs 2.1ms 30x
GET /json 74μs 2.2ms 30x
POST 1KB body 81μs 2.3ms 28x

Throughput (hey -n 100000 -c 128, single machine):

Endpoint Rocket Bandit Speedup
GET /health 275,918 rps 84,949 rps 3.2x
GET /json 262,089 rps 80,765 rps 3.2x
POST 1KB body 278,130 rps 91,789 rps 3.0x

The throughput gap is narrower because external bench tools (hey, wrk) spend most of their time in client-side overhead — connection management, response parsing, scheduling — which both servers share equally. Latency measures what Rocket actually controls: parsing, routing, and response construction.

Requirements

  • Elixir ~> 1.18
  • OTP 28+
  • C compiler (for the picohttpparser NIF)

Installation

Add rocket to your dependencies:

def deps do
  [
    {:rocket, "~> 0.2"}
  ]
end

Quick Start

Define a router:

defmodule MyApp.Router do
  use Rocket.Router

  get "/health" do
    send_resp(req, 200, "ok")
  end

  get "/api/users/:id" do
    id = req.path_params["id"]
    json(req, 200, %{id: id, name: "Alice"})
  end

  post "/api/users" do
    body = req.body
    json(req, 201, %{status: "created"})
  end

  match _ do
    send_resp(req, 404, "not found")
  end
end

Add Rocket to your supervision tree:

children = [
  {Rocket, port: 4000, handler: MyApp.Router}
]

Supervisor.start_link(children, strategy: :one_for_one)

Router

use Rocket.Router gives you route macros that compile to pattern-match clauses at build time. Every route receives a req variable — a %Rocket.Request{} struct.

HTTP Methods

get "/path" do ... end
post "/path" do ... end
put "/path" do ... end
delete "/path" do ... end
patch "/path" do ... end
head "/path" do ... end
options "/path" do ... end

Path Parameters

Segments prefixed with : become path params:

get "/api/v1/label/:name/values" do
  name = req.path_params["name"]
  # ...
end

Catch-All

match _ do
  send_resp(req, 404, "not found")
end

If omitted, Rocket returns a default 404.

Request

The %Rocket.Request{} struct:

Field Type Description
method atom :get, :post, etc.
path binary "/api/v1/query"
path_segments list ["api", "v1", "query"]
query_string binary Raw query string
headers [{name, value}] Raw header tuples
body binary Request body
path_params map Matched :name segments

Helpers

# Lazy-parsed query params (cached after first call)
{params, req} = Rocket.Request.query_params(req)

# Get a single query param
value = Rocket.Request.get_query_param(req, "metric")

# Get a header
token = Rocket.Request.get_header(req, "authorization")

Response

Response functions are auto-imported in router modules:

# Plain text / binary response
send_resp(req, 200, "hello")

# Empty response (204, etc.)
send_resp(req, 204)

# JSON response (encoded with :json.encode/1)
json(req, 200, %{status: "ok"})

# Custom headers + iodata body
Rocket.Response.send_iodata(req, 200, [{"content-type", "text/csv"}], csv_data)

Configuration

{Rocket,
  port: 8080,             # default: 8080
  handler: MyApp.Router,  # required
  num_acceptors: 16,      # default: System.schedulers_online()
  max_connections: 10_000, # default: 10_000
  max_body: 1_048_576,    # default: 1 MB
  backlog: 1024           # default: 1024
}

Architecture

Rocket.Supervisor
  └── Rocket.Listener (GenServer)
        ├── Rocket.Acceptor 1  ─┐
        ├── Rocket.Acceptor 2  ─┤  accept loop → spawn Connection
        ├── ...                 ─┤
        └── Rocket.Acceptor N  ─┘
              └── Rocket.Connection (per-client process)
                    ├── :socket.recv
                    ├── Rocket.HTTP.parse_request (NIF)
                    ├── Router.handle (your code)
                    └── :socket.send
  • Listener opens the TCP socket with SO_REUSEADDR / SO_REUSEPORT and spawns the acceptor pool
  • Acceptors call :socket.accept/1 in a tight loop, hand off each socket to a new Connection process
  • Connection owns one client socket for its lifetime — handles keep-alive, pipelining, Expect: 100-continue, max body enforcement, and connection backpressure via a shared :counters ref
  • Rocket.HTTP is a NIF wrapping picohttpparser — parses method, path, query string, and headers in C, returns Elixir terms

License

MIT — see LICENSE.

About

Rocket: High-performance HTTP Server for Elixir

Resources

License

Stars

Watchers

Forks

Packages