Full networking stack for Mojo 🔥 HTTP/1.1, HTTP/2, and HTTP/3 server and client (HTTP/3 over QUIC), WebSocket, TLS, TCP, UDP, Unix sockets, DNS, all in one library on top of one non-blocking reactor. Drop to raw sockets when HTTP isn't the right shape. The codecs and protocol state machines are proved against the RFCs in Lean 4.
from flare.prelude import *
def hello(req: Request) -> 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=2)And a version-aware client (negotiates HTTP/2 via ALPN, opt into HTTP/3):
from flare.prelude import *
def main() raises:
with HttpClient("https://example.com", prefer_http3=True) as c:
var r = c.get("/")
print(r.status, r.text())- Batteries included: HTTP/1.1, HTTP/2 and HTTP/3 (server and client), WebSocket (also over HTTP/2 and HTTP/3), gRPC, TLS and mTLS with SNI certificates, streaming, sessions, compression, CORS, static files, SSE, templates, caching and an OpenAPI emitter. Full inventory in
docs/features.md. - Composable by types, not callbacks:
Handleris a trait;Router, middleware and typed extractors compose by nesting structs, monomorphised into one direct call sequence per request type. - Hard to misuse under load: per-request
Canceltokens, graceful drain, sanitized 4xx/5xx, TLS cert hot reload, structured logging, Prometheus metrics and an in-processTestClient[H]. - Fast, with a tight tail: thread-per-core reactor (
kqueue/epoll, opt-inio_uring). At 4 workers the handler path reaches0.96-1.00xofactix_weband the static path1.08-1.16x, with p99 about 3 ms at the peak; see Performance. - Fuzzed and formally modelled: 63 fuzz harnesses (9M+ runs, zero known crashes) and Lean 4 models of the codecs, state machines and scheduler, proved against the RFCs; see Formal verification.
[workspace]
channels = ["https://conda.modular.com/max", "conda-forge"]
preview = ["pixi-build"]
[dependencies]
flare = { git = "https://github.com/ehsanmok/flare.git", tag = "<latest-release>" }pixi installRequires pixi (pulls Mojo automatically). Pin to a released tag for reproducible builds.
To track unreleased work (breaking changes possible between tags):
[dependencies]
flare = { git = "https://github.com/ehsanmok/flare.git", branch = "main" }from flare.prelude import *
def home(req: Request) -> Response:
return ok("flare is up")
def health(req: Request) -> Response:
return ok_json('{"status":"ok"}')
def greet(req: Request) raises -> Response: # raises: req.param() can
return ok("hello, " + req.param("name"))
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^)Handler fields are the inputs; a bad value becomes a sanitized 400 before your code runs.
from flare.prelude import *
@fieldwise_init
struct GetUser(Copyable, Defaultable, Handler):
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("/users/:id", Extracted[GetUser]())
var srv = HttpServer.bind(SocketAddr.localhost(8080))
srv.serve(r^, num_workers=4)Cancel-aware handlers get a token the reactor flips on peer FIN, deadline or drain:
@fieldwise_init
struct SlowHandler(CancelHandler, Copyable):
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 unroll dispatch per route, with no runtime trie walk:
comptime ROUTES: List[ComptimeRoute] = [
ComptimeRoute(Method.GET, "/", home),
ComptimeRoute(Method.GET, "/users/:id", get_user),
ComptimeRoute(Method.GET, "/files/*", files),
]
var r = ComptimeRouter[ROUTES]()Middleware and shared state are structs wrapping a Handler, monomorphised into one call sequence per request type:
@fieldwise_init
struct WithHits[Inner: Handler](Handler):
var inner: Self.Inner
var hits: Int
def serve(self, req: Request) raises -> Response:
var resp = self.inner.serve(req).lower()
resp.headers.set("X-Hits", String(self.hits))
return resp^
srv.serve(WithHits(inner=router^, hits=37))Every tier has runnable, tested equivalents:
| I want to... | Go to |
|---|---|
| Start with routing, JSON, a client, raw TCP/UDP/TLS/WebSocket | examples/basic/ |
| Add middleware, state, sessions, forms, SSE, caching, CORS, static files | examples/intermediate/ |
| Go to HTTP/2, HTTP/3, gRPC, mTLS, compile-time routing, streaming proxies, the raw reactor | examples/advanced/ |
| Find the example for a use case | docs/cookbook.md |
| Browse the API | https://ehsanmok.github.io/flare/ |
TFB plaintext, wrk2 -t8 -c256 -d30s, 48-vCPU Xeon Platinum 8488C, Linux 6.12, one run for all rows, flare workers pinned, all servers built without debug asserts (mojo build -D ASSERT=none, cargo build --release --locked). Each row is the highest rate that survives the harness's sustainable-peak finder.
| Server (4 workers) | Req/s | p99 (ms) |
|---|---|---|
| flare_mc_static (fixed-response path) | 705,569 | 2.82 |
| actix_web | 610,408 | 2.78 |
| flare_mc (handler path) | 607,940 | 3.00 |
| hyper | 556,619 | 2.67 |
| axum | 489,422 | 2.71 |
On one worker flare does 168k req/s against nginx's 212k and Go net/http's 99k, with the lowest p50 and p99 of the three. Absolute numbers move by about 5 % between runs on this shared box, so compare rows within one run. Methodology, every workload, HTTP/2 and HTTP/3 results, and how to reproduce are in docs/benchmark.md. Production builds use -D ASSERT=none; see docs/build.md.
The primitives the HTTP server is built on are public, for when HTTP isn't the right shape: flare.tcp, flare.tls, flare.udp, flare.uds, flare.ws, flare.dns and the flare.runtime.Reactor. Runnable examples are in examples/basic/ and examples/advanced/reactor.mojo.
flare.http / http2 / http3 servers, clients, Router, middleware, extractors
flare.ws grpc openapi WebSocket, gRPC, OpenAPI
flare.quic QUIC v1 (sans-I/O codec + live UDP reactor, ECN, PMTU discovery)
flare.tls crypto TLS 1.2/1.3 (OpenSSL, rustls for QUIC), HMAC, base64url
flare.tcp udp uds dns sockets
flare.net io addresses, buffered readers
flare.runtime reactor (kqueue / epoll / io_uring), scheduler, timers
Each layer imports only from layers below it. The per-module inventory, the request lifecycle and the per-connection state machine are in docs/architecture.md.
formal/ holds Lean 4 models of flare, from the byte codecs up through HTTP/1.1, HTTP/2, QUIC and HTTP/3 to the scheduler, each proved against a separate spec written from the RFC (core Lean only, no sorry, no extra axioms). Anchors, a spec lock and differential tests keep the models in sync with the Mojo code, and the 0.12 audit found 138 issues, all fixed. The design is in formal/README.md, the audit in formal/REPORT.md.
Per-layer posture, limits and the sanitized-error policy are in docs/security.md, the threat model in docs/threat-model.md. To report a vulnerability, open a private security advisory on GitHub or email the maintainer.
git clone https://github.com/ehsanmok/flare.git && cd flare
pixi install
pixi run tests # full suite plus every example
pixi run tests-gen # after adding a test file
pixi run format # mojo format (needs `pixi install -e dev`)Environments, the task list and the structural lints are in docs/development.md; every task is defined in pixi.toml.
