A Mojo HTTP server you can put in front of users, plus the raw TCP, UDP, TLS, DNS, HTTP/2, and WebSocket primitives it's built on. One reactor per worker (kqueue on macOS, epoll on Linux with EPOLLEXCLUSIVE shared listener for multi-worker), a per-connection state machine, an RFC 7230 parser with extensive fuzz coverage, and a Handler trait that takes plain def functions or compiled-down structs.
from flare import HttpServer, Router, Request, Response, ok, SocketAddr
def hello(req: Request) raises -> Response:
return ok("hello")
def main() raises:
var r = Router()
r.get("/", hello)
var srv = HttpServer.bind(SocketAddr.localhost(8080))
srv.serve(r^, num_workers=4)The bar isn't "is it fast", it's is it hard to misuse under load and easy to operate.
- Full networking stack: HTTP/1.1 (client + server), HTTP/2 frame codec + HPACK + h2c upgrade, WebSocket (RFC 6455), TLS 1.2/1.3 (client + server, OpenSSL with ALPN), TCP, UDP, DNS, all layered so each module imports only from below.
- One reactor per worker (
kqueueon macOS,epollon Linux +EPOLLEXCLUSIVEshared listener) with a per-connection state machine + timer wheel; thread-per-core viaHttpServer.serve(handler, num_workers=N). Optional cross-workerWorkerHandoffPool(FLARE_SOAK_WORKERS=on) for skewed-keepalive workloads. - Composable handlers:
Handlertrait,Routerwith path params,App[S]for shared state, typed extractors (PathInt/QueryInt/HeaderStr/Form/Multipart/Cookies/ ...), middleware stack (Logger,RequestId,Compress,CatchPanic),Cors,FileServerwith HEAD + Range;ComptimeRouter[ROUTES]unrolls dispatch at compile time. - Sessions + signed cookies: HMAC-SHA256 (
flare.crypto) under typedSession[T]withCookieSessionStore+InMemorySessionStoreand thesigned_cookie_*lower-level codec. - Content negotiation: gzip + brotli content-encoding (RFC 9110 §12.5.3 q-value parser), urlencoded + multipart form parsing, RFC 6265 cookie jars, RFC 9110 Range support.
- Production hygiene: per-request
Canceltoken (peer FIN, timeout, drain unified), server-side TLS with cert reload + mTLS + ALPN, streaming bodies with backpressure, sanitised 4xx/5xx, graceful drain with per-workerShutdownReports, 24 h soak harness, 22 fuzz harnesses (5M+ runs, zero known crashes).
TFB plaintext (GET /plaintext → 13-byte Hello, World!), wrk2 calibrated-peak harness with --latency (coordinated-omission corrected). Linux AWS EPYC 7R32, wrk2 -t8 -c256 -d30s, 5x30 s measurement runs at 90 %-of-peak. Worker counts spelled out per row. Full methodology + tables in docs/benchmark.md.
| Server | Workers | Req/s (median) | p99 (ms) | p99.99 (ms) |
|---|---|---|---|---|
| actix_web (tokio) | 4 | 264,691 | 2.80 | 21.61 |
| hyper (tokio multi-thread) | 4 | 221,349 | 2.82 | 3.67 |
| axum (tokio multi-thread) | 4 | 201,042 | 2.82 | 3.65 |
| flare_mc (shared listener) | 4 | 170,305 | 2.38 | 3.11 |
nginx (worker_processes 1) |
1 | 63,764 | 2.29 | 3.03 |
| flare (reactor) | 1 | 56,086 | 2.70 | 3.54 |
Go net/http (GOMAXPROCS=1) |
1 | 35,940 | 2.92 | 5.47 |
- Apples-to-apples 4-worker comparison.
flare_mc,hyper,axum,actix_weball run 4 OS worker threads. flare_mc holds the best p99 / p99.9 / p99.99 of the four (2.38 / 2.73 / 3.11 ms) and lands at 64 % of the throughput leader (actix_web). - Per-core baseline.
flare,nginx,go_nethttpall run 1 OS worker thread. flare 1w is 88 % of nginx 1w throughput and 1.56x Go 1w, with comparable tail latency. - Multi-worker scaling. flare_mc 4w / flare 1w = 3.04x at the same workload.
- Apple M-series, single-worker: ~157K req/s, ~1.10x Go
net/http.
[workspace]
channels = ["https://conda.modular.com/max-nightly", "conda-forge"]
preview = ["pixi-build"]
[dependencies]
flare = { git = "https://github.com/ehsanmok/flare.git", tag = "<latest-release>" }pixi installRequires pixi (pulls Mojo nightly automatically). Released tags are listed on GitHub Releases. Pin to one for reproducible builds.
To track unreleased work (breaking changes possible between tags):
[dependencies]
flare = { git = "https://github.com/ehsanmok/flare.git", branch = "main" }The tour below grows the snippet at the top of this README out, one persona at a time. Each level adds roughly one concept; everything compiles, and the runnable equivalents live under examples/ (every one is part of pixi run tests). docs/cookbook.md maps "I want to..." to the right example, and the rendered package docstring is at https://ehsanmok.github.io/flare/.
Two routes, one with a path parameter, a JSON-shaped response. This is where most apps start: def handlers, a Router, HttpServer.bind, num_workers. No traits, no generics, no extractors yet.
from flare import HttpServer, Router, Request, Response, ok, SocketAddr
def home(req: Request) raises -> Response:
return ok("flare is up")
def greet(req: Request) raises -> Response:
return ok("hello, " + req.param("name"))
def health(req: Request) raises -> Response:
var resp = ok('{"status":"ok"}')
resp.headers.set("Content-Type", "application/json")
return resp^
def main() raises:
var r = Router()
r.get("/", home)
r.get("/hi/:name", greet)
r.get("/health", health)
var srv = HttpServer.bind(SocketAddr.localhost(8080))
srv.serve(r^, num_workers=4)What you get for free: 404 on unknown paths, 405 with Allow on wrong method, sanitised 4xx / 5xx bodies, peer-FIN cancellation, RFC 7230 size limits, the per-worker reactor with kqueue / epoll.
For request bodies, query strings, cookies, sessions, multipart forms, gzip / brotli, TLS, HTTP/2, and WebSocket: all under examples/ (05_http_get, 13_cookies, 28_forms, 29_multipart_upload, 30_sessions, 34_brotli, 12_tls, 35_http2, 06_websocket_echo).
Once your handlers need to read structured input (path params as integers, query strings as bools, headers as strings), promote each Handler from a def into a struct whose fields are the inputs. PathInt["id"] / PathStr / QueryInt / HeaderStr / Form[T] / Multipart / Cookies / ... parse and validate at extraction time; Extracted[H] reflects on the struct's fields and pulls each one in before serve runs. Missing or malformed values become a 400 with a sanitised body, so your serve only sees well-typed values.
from flare.http import (
Router, ok, Request, Response, HttpServer,
Extracted, PathInt, Handler,
)
from flare.net import SocketAddr
def home(req: Request) raises -> Response:
return ok("home")
@fieldwise_init
struct GetUser(Copyable, Defaultable, Handler, Movable):
var id: PathInt["id"]
def __init__(out self):
self.id = PathInt["id"]()
def serve(self, req: Request) raises -> Response:
return ok("user=" + String(self.id.value))
def main() raises:
var r = Router()
r.get("/", home)
r.get[Extracted[GetUser]]("/users/:id", Extracted[GetUser]())
HttpServer.bind(SocketAddr.localhost(8080)).serve(r^, num_workers=4)Middleware is the same shape: a Handler that wraps another Handler. The stock layers (Logger, RequestId, Compress, CatchPanic, Cors, FileServer) all compose by nesting structs, no callback chain. examples/18_middleware.mojo walks through the production-shaped pipeline (RequestID → Logger → Timing → Recover → RequireAuth → Router).
Three patterns the production server leans on. Each is independent; pick the one your workload needs.
Cancel-aware handlers. CancelHandler.serve(req, cancel) gets a token the reactor flips on peer FIN, deadline elapse, or graceful drain. Long-running handlers poll between expensive steps and return early; plain Handlers ignore the token and run to completion. The reactor still tears down the connection if the peer goes away; the token just lets your handler do partial work cleanly.
from flare.http import CancelHandler, Cancel, Request, Response, ok
@fieldwise_init
struct SlowHandler(CancelHandler, Copyable, Movable):
def serve(self, req: Request, cancel: Cancel) raises -> Response:
for i in range(100):
if cancel.cancelled():
return ok("partial: " + String(i))
# ...one expensive step...
return ok("done")Compile-time route tables. When the route table is known at build time, ComptimeRouter[ROUTES] parses the path patterns at compile time and unrolls the dispatch loop per route. No runtime trie walk, no per-request handler-table indirection. Same path-param + wildcard syntax as the runtime Router, same 404 / 405-with-Allow semantics; the only difference is when the dispatch is decided.
from flare.http import (
ComptimeRoute, ComptimeRouter, HttpServer,
Request, Response, Method, ok,
)
from flare.net import SocketAddr
def home(req: Request) raises -> Response:
return ok("home")
def get_user(req: Request) raises -> Response:
return ok("user=" + req.param("id"))
def files(req: Request) raises -> Response:
return ok("files=" + req.param("*"))
comptime ROUTES: List[ComptimeRoute] = [
ComptimeRoute(Method.GET, "/", home),
ComptimeRoute(Method.GET, "/users/:id", get_user),
ComptimeRoute(Method.GET, "/files/*", files),
]
def main() raises:
var r = ComptimeRouter[ROUTES]()
HttpServer.bind(SocketAddr.localhost(8080)).serve(r^, num_workers=4)App state + middleware composition. App[S] carries shared state alongside an inner handler; state_view() hands out a borrow that middleware can read or mutate. The compiler monomorphises the whole nested chain into one direct call sequence per request type, with no virtual dispatch and no per-request allocation.
from flare.http import App, Router, Request, Response, Handler, State, ok, HttpServer
from flare.net import SocketAddr
@fieldwise_init
struct Counters(Copyable, Movable):
var hits: Int
def home(req: Request) raises -> Response:
return ok("home")
@fieldwise_init
struct WithHits[Inner: Handler](Handler):
var inner: Self.Inner
var snapshot: State[Counters]
def serve(self, req: Request) raises -> Response:
var resp = self.inner.serve(req)
resp.headers.set("X-Hits", String(self.snapshot.get().hits))
return resp^
def main() raises:
var router = Router()
router.get("/", home)
var app = App(state=Counters(hits=0), handler=router^)
var view = app.state_view()
var srv = HttpServer.bind(SocketAddr.localhost(8080))
srv.serve(WithHits(inner=app^, snapshot=view^))For the static-response fast path (serve_static), serve_comptime[handler, config] with build-time invariant checks, the multi-worker shared-listener mode (HttpServer.serve(handler, num_workers=N)), and the cross-worker WorkerHandoffPool (FLARE_SOAK_WORKERS=on), see docs/cookbook.md and the linked examples.
flare ships the primitives the HTTP server is built on, so you can drop down a layer when HTTP isn't the right shape: custom binary protocols, raw TLS, UDP, or running the reactor directly.
from flare.tcp import TcpStream
from flare.tls import TlsStream, TlsConfig
from flare.udp import UdpSocket
from flare.ws import WsClient
from flare.dns import resolve
from flare.runtime import Reactor, INTEREST_READRound-trip examples for each (04_tcp_echo, 06_websocket_echo, 11_udp, 12_tls, 14_reactor) live under examples/ and the rendered package docstring at https://ehsanmok.github.io/flare/ walks the layered API top-down. Use cases: a custom protocol over TLS, a UDP client / server, a WebSocket client driven from a CLI tool, or a hand-rolled non-HTTP server on top of the same reactor that powers HttpServer.
flare.io BufReader (Readable trait, generic buffered reader)
flare.ws WebSocket client + server (RFC 6455)
flare.http HTTP/1.1 client + reactor server + Cancel + Handler / Router / App
flare.tls TLS 1.2/1.3 (OpenSSL, both client and server; reactor-loop integration follow-up)
flare.tcp TcpStream + TcpListener (IPv4 + IPv6)
flare.udp UdpSocket (IPv4 + IPv6)
flare.dns getaddrinfo (dual-stack)
flare.net IpAddr, SocketAddr, RawSocket
flare.runtime Reactor (kqueue/epoll), TimerWheel, Scheduler, Pool[T]
Each layer imports only from layers below it. No circular dependencies. The full request lifecycle, including the Cancel injection point and the per-connection state machine, lives in docs/architecture.md.
Headline numbers live in the Numbers block above; full single/multi-worker tables, tail percentiles, methodology, and the soak harness for long-running operational gates live in docs/benchmark.md. Multi-worker cross-server ratios are gated on matched-worker baselines (Go GOMAXPROCS=N, nginx worker_processes N, Rust hyper / axum N-worker); flare publishes its own scaling claim and does not publish a "vs Go" multicore ratio against single-worker baselines.
We do not lead on speed. The position is plain: speed claims in networking are mostly architecture and kernel, not language. flare's job is to be operationally honest under load. Numbers are a corollary, not the headline.
Per-layer security posture and the sanitised-error-response policy live in docs/security.md. Highlights: RFC 7230 token validation, configurable size limits, sanitised 4xx/5xx bodies, TLS 1.2+ only, WebSocket frame masking + UTF-8 validation, 19 fuzz harnesses with 4M+ runs and zero known crashes.
For security issues, please open a private security advisory on GitHub or email the maintainer directly.
git clone https://github.com/ehsanmok/flare.git && cd flare
pixi install # lean: tests, examples, microbench, format-check
pixi install -e dev # adds mojodoc + pre-commitflare uses four pixi environments, layered:
| Env | Adds | What it unlocks |
|---|---|---|
default |
nothing | tests, examples, microbenchmarks, format-check |
dev |
mojodoc, pre-commit |
docs, docs-build, format (with hook install) |
fuzz |
dev + mozz |
fuzz-* / prop-* |
bench |
dev + go, nginx, wrk, wrk2 |
bench-vs-baseline*, bench-tail-quick, bench-mixed-keepalive |
pixi run tests # full suite + 21+ examples
pixi run --environment fuzz fuzz-all # 19 harnesses
pixi run --environment bench bench-vs-baseline-quick # ~7 minPer-component task list lives in pixi.toml. The full architecture / benchmark / security / cookbook tour is under docs/.
