From f0f072ebaba5f50a0aa48f0e0b34b147715fdfe2 Mon Sep 17 00:00:00 2001 From: Jeeves Date: Mon, 25 Mar 2024 16:33:38 -0600 Subject: [PATCH] this ain't it either, chief --- src/main.zig | 279 +++++++++++++++++++++++++++++---------------------- 1 file changed, 159 insertions(+), 120 deletions(-) diff --git a/src/main.zig b/src/main.zig index ad07c31..de99fbb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,158 +4,197 @@ 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(); - const router = BasicRouter.init(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 = Listener.init(address, allocator, &handle); + var listener = App.init(.{ + .address = address, + .allocator = allocator, + .context = router, + .handler = router.handle, + }); defer listener.deinit(); try listener.listen(); } -pub fn handle(event: *Listener.Event) anyerror!void { +pub fn handle(_: *Router, event: *App.Event) anyerror!void { try event.res.body.appendSlice("hello there"); } -pub const Listener = struct { - address: net.Address, - arena: heap.ArenaAllocator, - handlerFn: *const fn (event: *Event) anyerror!void, +pub fn handleError(_: Router, event: *App.Event) anyerror!void { + try event.res.body.appendSlice("ahoy, an error occurred"); +} - pub fn init(address: net.Address, allocator: mem.Allocator, handler: *const fn (event: *Event) anyerror!void) Listener { - return .{ - .address = address, - .arena = heap.ArenaAllocator.init(allocator), - .handlerFn = 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(&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 BasicRouter = Router(Listener.Event); - -pub fn Router(comptime Context: type) type { +pub fn Listener(comptime Context: type) type { return struct { - ctx: Context, - allocator: mem.Allocator, + address: net.Address, + arena: heap.ArenaAllocator, + ctx: *Context, + handlerFn: HandlerFn, - static_routes: std.StringHashMap(*Node), + pub const Options = struct { + address: net.Address, + allocator: mem.Allocator, + context: *Context, + handler: HandlerFn, + }; - pub fn init(allocator: mem.Allocator, ctx: Context) Self { + pub fn init(options: Options) Listener { return .{ - .ctx = ctx, - .allocator = allocator, - .static_routes = std.StringHashMap(*Node).init(allocator), + .address = options.address, + .arena = heap.ArenaAllocator.init(options.allocator), + .ctx = options.context, + .handlerFn = options.handler, }; } - pub fn deinit(self: *Self) void { - self.static_routes.deinit(); + pub fn deinit(self: *Listener) void { + self.arena.deinit(); } - pub fn addRoute(self: *Self, path: []const u8, handler: HandlerFn) !void { - try self.addStaticRoute(path, handler); + 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 addStaticRoute(self: *Self, 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), + 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 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 Event = struct { + req: Request, + res: Response, - pub const Type = enum { normal, wildcard, placeholder }; + 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) anyerror!void; - - const Self = @This(); + 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; +};