Skip to content

Commit

Permalink
re-write of zap.Router, fix #83
Browse files Browse the repository at this point in the history
  • Loading branch information
renerocksai committed Mar 23, 2024
1 parent 8cf48dd commit b2f4e29
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 33 deletions.
10 changes: 5 additions & 5 deletions examples/simple_router/simple_router.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down
79 changes: 68 additions & 11 deletions src/router.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,110 @@ const std = @import("std");
const zap = @import("zap.zig");

const Allocator = std.mem.Allocator;

/// Errors returnable by init()
const RouterError = error{
AlreadyExists,
EmptyPath,
};

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);
}

const 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);
Expand Down
17 changes: 0 additions & 17 deletions src/util.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit b2f4e29

Please sign in to comment.