@@ -24,7 +24,7 @@ Cross-platform • Zero allocations
2424- 🌍 ** Cross-platform** support via Zig std.Io backends
2525- 💾 ** Zero runtime allocations** - all memory allocated at compile time
2626- 🔒 ** Lock-free** atomic operations for maximum concurrency
27- - 📦 ** Simple API ** - just configure and run
27+ - 🧩 ** Bring your own router ** - plug in any dispatcher or router you want
2828- ✅ ** HTTP/2 RFC 7540** compliant
2929
3030## Quick Start
@@ -63,11 +63,13 @@ exe.root_module.addImport("http2", http2_dep.module("http2"));
6363const std = @import("std");
6464const http2 = @import("http2");
6565
66- fn indexHandler(ctx: *const http2.Context) !http2.Response {
67- return ctx.response.text(.ok, "hello from http2.zig\n");
68- }
66+ fn handleRequest(ctx: *const http2.Context) !http2.Response {
67+ if (ctx.method == .get) {
68+ if (std.mem.eql(u8, ctx.path, "/")) {
69+ return ctx.response.text(.ok, "hello from http2.zig\n");
70+ }
71+ }
6972
70- fn notFoundHandler(ctx: *const http2.Context) !http2.Response {
7173 return ctx.response.text(.not_found, "not found\n");
7274}
7375
@@ -80,29 +82,112 @@ pub fn main() !void {
8082 try http2.init(allocator);
8183 defer http2.deinit();
8284
83- // Configure the request router
84- var router = http2.Router.init(allocator);
85- defer router.deinit();
86-
87- try router.get("/", indexHandler);
88- router.setFallback(notFoundHandler);
89-
9085 // Configure and create server
9186 const config = http2.Server.Config{
9287 .address = try std.Io.net.IpAddress.parse("127.0.0.1", 3000),
93- .router = &router ,
88+ .dispatcher = http2.RequestDispatcher.fromHandler(handleRequest) ,
9489 };
9590
9691 var server = try http2.Server.init(allocator, config);
9792 defer server.deinit();
9893
99- std.log.info("HTTP/2 server listening on {}", .{config.address});
94+ std.log.info("HTTP/2 server listening on {f }", .{config.address});
10095
10196 // Run the server
10297 try server.run();
10398}
10499```
105100
101+ ### Bring Your Own Router With turboapi-core
102+
103+ The core library stays router-agnostic. If you want radix-tree routing, path params, and HTTP
104+ helpers, add ` turboapi-core ` to your application:
105+
106+ ``` bash
107+ zig fetch --save=turboapi_core " git+https://github.com/justrach/turboapi-core.git#main"
108+ ```
109+
110+ Wire it into ` build.zig ` :
111+
112+ ``` zig
113+ const core_dep = b.dependency("turboapi_core", .{
114+ .target = target,
115+ .optimize = optimize,
116+ });
117+ const core_mod = core_dep.module("turboapi-core");
118+ exe.root_module.addImport("turboapi-core", core_mod);
119+ ```
120+
121+ Then bridge your router into ` http2.zig ` with a typed dispatcher:
122+
123+ ``` zig
124+ const std = @import("std");
125+ const core = @import("turboapi-core");
126+ const http2 = @import("http2");
127+
128+ const App = struct {
129+ router: core.Router,
130+
131+ fn init(target: *App, allocator: std.mem.Allocator) !void {
132+ target.* = .{
133+ .router = core.Router.init(allocator),
134+ };
135+ errdefer target.deinit();
136+
137+ try target.router.addRoute("GET", "/", "index");
138+ try target.router.addRoute("GET", "/users/{id}", "user_show");
139+ }
140+
141+ fn deinit(self: *App) void {
142+ self.router.deinit();
143+ }
144+
145+ fn dispatch(self: *const App, ctx: *const http2.Context) !http2.Response {
146+ if (self.router.findRoute(ctx.method.bytes(), ctx.path)) |match_result| {
147+ var match = match_result;
148+ defer match.deinit();
149+
150+ if (std.mem.eql(u8, match.handler_key, "index")) {
151+ return ctx.response.text(.ok, "hello\n");
152+ }
153+ if (std.mem.eql(u8, match.handler_key, "user_show")) {
154+ _ = match.params.get("id");
155+ return ctx.response.text(.ok, "user\n");
156+ }
157+ }
158+
159+ return ctx.response.text(.not_found, "not found\n");
160+ }
161+ };
162+
163+ pub fn main() !void {
164+ var gpa: std.heap.DebugAllocator(.{}) = .init;
165+ defer _ = gpa.deinit();
166+ const allocator = gpa.allocator();
167+
168+ try http2.init(allocator);
169+ defer http2.deinit();
170+
171+ var app: App = undefined;
172+ try App.init(&app, allocator);
173+ defer app.deinit();
174+
175+ const config = http2.Server.Config{
176+ .address = try std.Io.net.IpAddress.parse("127.0.0.1", 3000),
177+ .dispatcher = http2.RequestDispatcher.bind(App, &app, App.dispatch),
178+ };
179+
180+ var server = try http2.Server.init(allocator, config);
181+ defer server.deinit();
182+ try server.run();
183+ }
184+ ```
185+
186+ Repository examples:
187+
188+ - ` examples/basic_tls.zig ` shows the same dispatcher API with a small custom Zig router.
189+ - ` examples/turboapi.zig ` shows the ` turboapi-core ` integration with TLS.
190+
106191## Performance
107192
108193TBD
@@ -116,8 +201,8 @@ pub const Server.Config = struct {
116201 /// Address to bind to
117202 address: std.Io.net.IpAddress,
118203
119- /// Request router for handling HTTP requests
120- router: *Router ,
204+ /// Request dispatcher for application routing or request handling
205+ dispatcher: RequestDispatcher ,
121206
122207 /// Maximum concurrent connections (default: 1000)
123208 max_connections: u32 = 1000,
@@ -127,25 +212,25 @@ pub const Server.Config = struct {
127212};
128213```
129214
130- ### Router
215+ ### Request Dispatcher
216+
217+ ` http2.zig ` no longer ships with a built-in router. Instead, ` Server.Config ` takes a
218+ ` RequestDispatcher ` , which is just a function pointer plus optional typed state.
131219
132- The server expects a router in ` Server.Config ` , and requests are dispatched through it.
220+ For stateless handling:
133221
134222``` zig
135- try router.get("/", indexHandler);
136- try router.post("/api/messages", createMessageHandler);
137- try router.getPrefix("/assets", staticAssetsHandler);
138- router.setFallback(notFoundHandler);
223+ .dispatcher = http2.RequestDispatcher.fromHandler(handleRequest),
139224```
140225
141- Current routing behavior:
226+ For stateful apps, middleware stacks, or third-party routers:
227+
228+ ``` zig
229+ .dispatcher = http2.RequestDispatcher.bind(App, &app, App.dispatch),
230+ ```
142231
143- - ` get ` , ` post ` , ` put ` , ` delete ` , ` head ` , ` options ` , and ` patch ` register exact routes.
144- - ` getPrefix ` and ` postPrefix ` register prefix routes.
145- - Prefix routes are ordered by longest path first.
146- - Prefix matching is segment-aware: ` /api ` matches ` /api ` and ` /api/users ` , but not ` /apix ` .
147- - A matching path with the wrong method returns ` 405 Method Not Allowed ` .
148- - A missing path falls through to the fallback handler when configured; otherwise it returns ` 404 Not Found ` .
232+ This keeps transport, request parsing, and response building inside ` http2.zig ` , while letting the
233+ application decide how routing, params, middleware, and fallback behavior should work.
149234
150235The request context passed to handlers exposes:
151236
@@ -207,11 +292,14 @@ zig build -Doptimize=ReleaseFast
207292### Running Examples
208293
209294``` bash
210- # Run the hello world example
211- zig build run-hello
295+ # Run the basic TLS example with the local Zig router
296+ zig build run
297+
298+ # Run the turboapi-core example
299+ zig build run-turboapi
212300
213301# Run the benchmark server
214- cd benchmarks && zig build run
302+ zig build benchmark
215303```
216304
217305### Benchmarking
0 commit comments