From c81a211bd3e647d05293c17d4c49072403ba6683 Mon Sep 17 00:00:00 2001 From: Rene Schallner Date: Sat, 23 Mar 2024 22:35:49 +0100 Subject: [PATCH] re-write of zap.Router, fix #83 --- examples/simple_router/simple_router.zig | 10 +-- src/router.zig | 79 ++++++++++++++++++++---- src/util.zig | 17 ----- 3 files changed, 73 insertions(+), 33 deletions(-) diff --git a/examples/simple_router/simple_router.zig b/examples/simple_router/simple_router.zig index 94838ab..9d58b99 100644 --- a/examples/simple_router/simple_router.zig +++ b/examples/simple_router/simple_router.zig @@ -82,17 +82,17 @@ pub fn main() !void { var somePackage = SomePackage.init(allocator, 1, 2); - try simpleRouter.handle_func("/", on_request_verbose); + try simpleRouter.handle_func_unbound("/", on_request_verbose); - try simpleRouter.handle_func("/geta", zap.RequestHandler(&somePackage, SomePackage.getA)); + try simpleRouter.handle_func("/geta", &somePackage, &SomePackage.getA); - try simpleRouter.handle_func("/getb", zap.RequestHandler(&somePackage, SomePackage.getB)); + try simpleRouter.handle_func("/getb", &somePackage, &SomePackage.getB); - try simpleRouter.handle_func("/inca", zap.RequestHandler(&somePackage, SomePackage.incrementA)); + try simpleRouter.handle_func("/inca", &somePackage, &SomePackage.incrementA); var listener = zap.HttpListener.init(.{ .port = 3000, - .on_request = zap.RequestHandler(&simpleRouter, &zap.Router.serve), + .on_request = simpleRouter.on_request_handler(), .log = true, .max_clients = 100000, }); diff --git a/src/router.zig b/src/router.zig index 25886ea..c9ca7d5 100644 --- a/src/router.zig +++ b/src/router.zig @@ -2,6 +2,8 @@ const std = @import("std"); const zap = @import("zap.zig"); const Allocator = std.mem.Allocator; + +/// Errors returnable by init() const RouterError = error{ AlreadyExists, EmptyPath, @@ -9,46 +11,101 @@ const RouterError = error{ const Self = @This(); +/// This is a singleton +var _instance: *Self = undefined; + +/// Options to pass to init() pub const Options = struct { + /// an optional zap request function for 404 not found case not_found: ?zap.HttpRequestFn = null, }; -routes: std.StringHashMap(zap.HttpRequestFn), +const CallbackTag = enum { bound, unbound }; +const BoundHandler = *fn (*const anyopaque, zap.Request) void; +const Callback = union(CallbackTag) { + bound: struct { instance: usize, handler: usize }, + unbound: zap.HttpRequestFn, +}; + +routes: std.StringHashMap(Callback), not_found: ?zap.HttpRequestFn, +/// Create a new Router pub fn init(allocator: Allocator, options: Options) Self { return .{ - .routes = std.StringHashMap(zap.HttpRequestFn).init(allocator), + .routes = std.StringHashMap(Callback).init(allocator), .not_found = options.not_found, }; } +/// Deinit the router pub fn deinit(self: *Self) void { self.routes.deinit(); } -pub fn handle_func(self: *Self, path: []const u8, h: zap.HttpRequestFn) !void { +/// Call this to add a route with an unbound handler: a handler that is not member of a struct. +pub fn handle_func_unbound(self: *Self, path: []const u8, h: zap.HttpRequestFn) !void { if (path.len == 0) { return RouterError.EmptyPath; } - const route = self.routes.get(path); + if (self.routes.contains(path)) { + return RouterError.AlreadyExists; + } + + try self.routes.put(path, Callback{ .unbound = h }); +} + +/// Call this to add a route with a handler that is bound to an instance of a struct. +/// Example: +/// +/// ```zig +/// const HandlerType = struct { +/// pub fn getA(self: *HandlerType, r: zap.Request) void { +/// _ = self; +/// r.sendBody("hello\n\n") catch return; +/// } +/// } +/// var handler_instance = HandlerType{}; +/// +/// my_router.handle_func("/getA", &handler_instance, HandlerType.getA); +/// ``` +pub fn handle_func(self: *Self, path: []const u8, instance: *anyopaque, handler: anytype) !void { + // TODO: assert type of instance has handler - if (route != null) { + if (path.len == 0) { + return RouterError.EmptyPath; + } + + if (self.routes.contains(path)) { return RouterError.AlreadyExists; } - try self.routes.put(path, h); + try self.routes.put(path, Callback{ .bound = .{ + .instance = @intFromPtr(instance), + .handler = @intFromPtr(handler), + } }); } -pub fn serve(self: *Self, r: zap.Request) void { - const path = if (r.path) |p| p else "/"; +/// Get the zap request handler function needed for a listener +pub fn on_request_handler(self: *Self) zap.HttpRequestFn { + _instance = self; + return zap_on_request; +} + +fn zap_on_request(r: zap.Request) void { + return serve(_instance, r); +} - var route = self.routes.get(path); +fn serve(self: *Self, r: zap.Request) void { + const path = r.path orelse "/"; - if (route) |handler| { - handler(r); + if (self.routes.get(path)) |routeInfo| { + switch (routeInfo) { + .bound => |b| @call(.auto, @as(BoundHandler, @ptrFromInt(b.handler)), .{ @as(*anyopaque, @ptrFromInt(b.instance)), r }), + .unbound => |h| h(r), + } } else if (self.not_found) |handler| { // not found handler handler(r); diff --git a/src/util.zig b/src/util.zig index 21e9eaf..4b6a4c9 100644 --- a/src/util.zig +++ b/src/util.zig @@ -2,23 +2,6 @@ const std = @import("std"); const fio = @import("fio.zig"); const zap = @import("zap.zig"); -/// capture self for RequestFn signature support -pub inline fn RequestHandler(self: anytype, func: *const fn (@TypeOf(self), zap.Request) void) *const fn (zap.Request) void { - return (opaque { - var hidden_self: @TypeOf(self) = undefined; - var hidden_func: *const fn (@TypeOf(self), zap.Request) void = undefined; - pub fn init(h_self: @TypeOf(self), h_func: *const fn (@TypeOf(self), zap.Request) void) *const @TypeOf(run) { - hidden_self = h_self; - hidden_func = h_func; - return &run; - } - - fn run(req: zap.Request) void { - hidden_func(hidden_self, req); - } - }).init(self, func); -} - /// Used internally: convert a FIO object into its string representation. /// note: since this is called from within request functions, we don't make /// copies. Also, we return temp memory from fio. -> don't hold on to it outside