const std = @import("std"); const mem = std.mem; const net = std.net; const http = std.http; const heap = std.heap; const App = Listener(Router); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var router = Router.init(allocator, &handle, &handleError); defer router.deinit(); try router.addRoute("/", &handle); const address = try net.Address.parseIp("0.0.0.0", 8080); var listener = App.init(.{ .address = address, .allocator = allocator, .context = router, .handler = router.handle, }); defer listener.deinit(); try listener.listen(); } pub fn handle(_: *Router, event: *App.Event) anyerror!void { try event.res.body.appendSlice("hello there"); } pub fn handleError(_: Router, event: *App.Event) anyerror!void { try event.res.body.appendSlice("ahoy, an error occurred"); } pub fn Listener(comptime Context: type) type { return struct { address: net.Address, arena: heap.ArenaAllocator, ctx: *Context, handlerFn: HandlerFn, pub const Options = struct { address: net.Address, allocator: mem.Allocator, context: *Context, handler: HandlerFn, }; pub fn init(options: Options) Listener { return .{ .address = options.address, .arena = heap.ArenaAllocator.init(options.allocator), .ctx = options.context, .handlerFn = options.handler, }; } pub fn deinit(self: *Listener) void { self.arena.deinit(); } pub fn listen(self: *Listener) !void { var tcp = try self.address.listen(.{}); defer tcp.deinit(); std.debug.print("listening at: {any}\n", .{self.address}); while (true) { const read_buf = try self.arena.allocator().alloc(u8, 1024 * 32); const connection = try tcp.accept(); var server = http.Server.init(connection, read_buf); try self.handle(&server); _ = self.arena.reset(.retain_capacity); } } fn handle(self: *Listener, server: *http.Server) !void { handler: while (true) { var req = server.receiveHead() catch |e| if (e == error.HttpConnectionClosing) break :handler else return e; var event = Event{ .req = .{ .uri = try std.Uri.parseWithoutScheme(req.head.target), .headers = std.ArrayList(*const http.Header).init(self.arena.allocator()), }, .res = .{ .status = .ok, .headers = std.StringHashMap(*http.Header).init(self.arena.allocator()), .body = std.ArrayList(u8).init(self.arena.allocator()), }, }; var header_it = req.iterateHeaders(); while (header_it.next()) |header| try event.req.headers.append(&header); try self.handlerFn(self.ctx, &event); try self.respondFromEvent(&event, &req); } } fn respondFromEvent(self: *Listener, event: *Event, req: *http.Server.Request) !void { const res_body = try event.res.body.toOwnedSlice(); const res_headers = try self.arena.allocator().alloc(http.Header, event.res.headers.count()); var i: usize = 0; var header_it = event.res.headers.iterator(); while (header_it.next()) |header_ptr| : (i += 1) res_headers[i] = header_ptr.value_ptr.*.*; try req.respond(res_body, .{ .status = event.res.status, .extra_headers = res_headers, }); } pub const Event = struct { req: Request, res: Response, pub const Request = struct { uri: std.Uri, // head: http.Server.Request.Head, headers: std.ArrayList(*const http.Header), }; pub const Response = struct { status: http.Status, headers: std.StringHashMap(*http.Header), body: std.ArrayList(u8), }; }; pub const HandlerFn = *const fn (ctx: *Context, event: *Event) anyerror!void; }; } pub const Router = struct { allocator: mem.Allocator, root_node: Node, static_routes: std.StringHashMap(*Node), error_handler: HandlerFn, pub fn init(allocator: mem.Allocator, root_handler: HandlerFn, error_handler: HandlerFn) Router { return .{ .allocator = allocator, .root_node = .{ .type = .normal, .max_depth = 64, .children = std.StringHashMap(*Node).init(allocator), .handler = root_handler, .placeholder_children = std.ArrayList(*Node).init(allocator), }, .static_routes = std.StringHashMap(*Node).init(allocator), .error_handler = error_handler, }; } pub fn deinit(self: *Router) void { self.static_routes.deinit(); } pub fn handle(self: *Router, event: *Listener.Event) anyerror!void { try self.route(event.req.uri.path)(self, event); } pub fn addRoute(self: *Router, path: []const u8, handler: HandlerFn) !void { try self.addStaticRoute(path, handler); } fn addStaticRoute(self: *Router, path: []const u8, handler: HandlerFn) !void { try self.static_routes.put(path, .{ .type = .normal, .max_depth = 1, .children = std.StringHashMap(*Node).init(self.allocator), .handler = handler, .placeholder_children = std.ArrayList(*Node).init(self.allocator), }); } pub fn route(self: *Router, path: []const u8) HandlerFn { if (self.static_routes.get(path)) |rt| return rt.handler; if (self.root_node.wildcard_child_node) |node| return node.handler; return self.error_handler; } pub const Node = struct { type: Type, max_depth: usize, // TODO: what is best here parent: ?*Node = null, children: std.StringHashMap(*Node), handler: HandlerFn, wildcard_child_node: ?*Node = null, placeholder_children: std.ArrayList(*Node), pub const Type = enum { normal, wildcard, placeholder }; }; pub const HandlerFn = *const fn (self: *Router, event: *App.Event) anyerror!void; };