From f13160eda607b01118359704527f5ed06e2de0bf Mon Sep 17 00:00:00 2001 From: stringnick Date: Sat, 13 Jan 2024 13:23:54 +0100 Subject: [PATCH 1/3] simple router with clojure --- build.zig | 1 + examples/hello3/hello3.zig | 108 +++++++++++++++++++++++++++++++++++++ src/router.zig | 77 ++++++++++++++++++++++++++ src/zap.zig | 2 + 4 files changed, 188 insertions(+) create mode 100644 examples/hello3/hello3.zig create mode 100644 src/router.zig diff --git a/build.zig b/build.zig index 1a2193a..c7efd86 100644 --- a/build.zig +++ b/build.zig @@ -34,6 +34,7 @@ pub fn build(b: *std.build.Builder) !void { .{ .name = "hello", .src = "examples/hello/hello.zig" }, .{ .name = "https", .src = "examples/https/https.zig" }, .{ .name = "hello2", .src = "examples/hello2/hello2.zig" }, + .{ .name = "hello3", .src = "examples/hello3/hello3.zig" }, .{ .name = "routes", .src = "examples/routes/routes.zig" }, .{ .name = "serve", .src = "examples/serve/serve.zig" }, .{ .name = "hello_json", .src = "examples/hello_json/hello_json.zig" }, diff --git a/examples/hello3/hello3.zig b/examples/hello3/hello3.zig new file mode 100644 index 0000000..5c7675d --- /dev/null +++ b/examples/hello3/hello3.zig @@ -0,0 +1,108 @@ +const std = @import("std"); +const zap = @import("zap"); +const Allocator = std.mem.Allocator; + +fn on_request_verbose(r: zap.Request) void { + if (r.path) |the_path| { + std.debug.print("PATH: {s}\n", .{the_path}); + } + + if (r.query) |the_query| { + std.debug.print("QUERY: {s}\n", .{the_query}); + } + r.sendBody("

Hello from ZAP!!!

") catch return; +} + +pub const SomePackage = struct { + const Self = @This(); + + allocator: Allocator, + a: i8, + b: i8, + + pub fn init(allocator: Allocator, a: i8, b: i8) Self { + return .{ + .allocator = allocator, + .a = a, + .b = b, + }; + } + + pub fn getA(self: *Self, req: zap.Request) void { + std.log.warn("get_a_requested", .{}); + + const string = std.fmt.allocPrint( + self.allocator, + "A value is {d}\n", + .{self.a}, + ) catch return; + defer self.allocator.free(string); + + req.sendBody(string) catch return; + } + + pub fn getB(self: *Self, req: zap.Request) void { + std.log.warn("get_b_requested", .{}); + + const string = std.fmt.allocPrint( + self.allocator, + "B value is {d}\n", + .{self.b}, + ) catch return; + defer self.allocator.free(string); + + req.sendBody(string) catch return; + } + + pub fn incrementA(self: *Self, req: zap.Request) void { + std.log.warn("increment_a_requested", .{}); + + self.a += 1; + + req.sendBody("incremented A") catch return; + } +}; + +fn not_found(req: zap.Request) void { + std.debug.print("not found handler", .{}); + + req.sendBody("Not found") catch return; +} + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{ + .thread_safe = true, + }){}; + var allocator = gpa.allocator(); + + var simpleRouter = zap.Router.init(allocator, .{ + .not_found = not_found, + }); + defer simpleRouter.deinit(); + + var somePackage = SomePackage.init(allocator, 1, 2); + + try simpleRouter.handle_func("/", on_request_verbose); + + try simpleRouter.handle_func("/geta", zap.RequestHandler(&somePackage, SomePackage.getA)); + + try simpleRouter.handle_func("/getb", zap.RequestHandler(&somePackage, SomePackage.getB)); + + try simpleRouter.handle_func("/inca", zap.RequestHandler(&somePackage, SomePackage.incrementA)); + + var listener = zap.HttpListener.init(.{ + .port = 3000, + .on_request = zap.RequestHandler(&simpleRouter, &zap.Router.serve), + .log = true, + .max_clients = 100000, + }); + try listener.listen(); + + std.debug.print("Listening on 0.0.0.0:3000\n", .{}); + + // start worker threads + zap.start(.{ + .threads = 2, + .workers = 2, + }); +} diff --git a/src/router.zig b/src/router.zig new file mode 100644 index 0000000..387e4da --- /dev/null +++ b/src/router.zig @@ -0,0 +1,77 @@ +const std = @import("std"); +const zap = @import("zap.zig"); + +const Allocator = std.mem.Allocator; +const RouterError = error{ + AlreadyExists, + EmptyPath, +}; + +// inline closure for RequestFn with self argument +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); +} + +pub const RouterOptions = struct { + not_found: ?zap.HttpRequestFn = null, +}; + +pub const Router = struct { + const Self = @This(); + + routes: std.StringHashMap(zap.HttpRequestFn), + not_found: ?zap.HttpRequestFn, + + pub fn init(allocator: Allocator, options: RouterOptions) Self { + return .{ + .routes = std.StringHashMap(zap.HttpRequestFn).init(allocator), + + .not_found = options.not_found, + }; + } + + pub fn deinit(self: *Self) void { + self.routes.deinit(); + } + + pub fn handle_func(self: *Self, path: []const u8, h: zap.HttpRequestFn) !void { + if (path.len == 0) { + return RouterError.EmptyPath; + } + + const route = self.routes.get(path); + + if (route != null) { + return RouterError.AlreadyExists; + } + + try self.routes.put(path, h); + } + + pub fn serve(self: *Self, r: zap.Request) void { + var route = self.routes.get(r.path.?); + + if (route) |handler| { + handler(r); + } else if (self.not_found) |handler| { + // not found handler + handler(r); + } else { + // default 404 output + r.setStatus(.not_found); + r.sendBody("404 Not Found") catch return; + } + } +}; diff --git a/src/zap.zig b/src/zap.zig index 35f7bf2..5d9a91f 100644 --- a/src/zap.zig +++ b/src/zap.zig @@ -52,6 +52,8 @@ pub const Tls = @import("tls.zig"); /// ``` pub const Endpoint = @import("endpoint.zig"); +pub usingnamespace @import("router.zig"); + pub usingnamespace @import("util.zig"); pub usingnamespace @import("http.zig"); From 5e76e17baf5b84010ca373b73723bc6faadb8963 Mon Sep 17 00:00:00 2001 From: stringnick Date: Thu, 18 Jan 2024 21:33:26 +0100 Subject: [PATCH 2/3] some refactoring --- build.zig | 2 +- .../simple_router.zig} | 0 src/router.zig | 87 ++++++++----------- src/util.zig | 18 ++++ src/zap.zig | 2 +- 5 files changed, 55 insertions(+), 54 deletions(-) rename examples/{hello3/hello3.zig => simple_router/simple_router.zig} (100%) diff --git a/build.zig b/build.zig index c7efd86..0ccbb6f 100644 --- a/build.zig +++ b/build.zig @@ -34,7 +34,7 @@ pub fn build(b: *std.build.Builder) !void { .{ .name = "hello", .src = "examples/hello/hello.zig" }, .{ .name = "https", .src = "examples/https/https.zig" }, .{ .name = "hello2", .src = "examples/hello2/hello2.zig" }, - .{ .name = "hello3", .src = "examples/hello3/hello3.zig" }, + .{ .name = "simple_router", .src = "examples/simple_router/simple_router.zig" }, .{ .name = "routes", .src = "examples/routes/routes.zig" }, .{ .name = "serve", .src = "examples/serve/serve.zig" }, .{ .name = "hello_json", .src = "examples/hello_json/hello_json.zig" }, diff --git a/examples/hello3/hello3.zig b/examples/simple_router/simple_router.zig similarity index 100% rename from examples/hello3/hello3.zig rename to examples/simple_router/simple_router.zig diff --git a/src/router.zig b/src/router.zig index 387e4da..25886ea 100644 --- a/src/router.zig +++ b/src/router.zig @@ -7,71 +7,54 @@ const RouterError = error{ EmptyPath, }; -// inline closure for RequestFn with self argument -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; - } +const Self = @This(); - fn run(req: zap.Request) void { - hidden_func(hidden_self, req); - } - }).init(self, func); -} - -pub const RouterOptions = struct { +pub const Options = struct { not_found: ?zap.HttpRequestFn = null, }; -pub const Router = struct { - const Self = @This(); +routes: std.StringHashMap(zap.HttpRequestFn), +not_found: ?zap.HttpRequestFn, - routes: std.StringHashMap(zap.HttpRequestFn), - not_found: ?zap.HttpRequestFn, +pub fn init(allocator: Allocator, options: Options) Self { + return .{ + .routes = std.StringHashMap(zap.HttpRequestFn).init(allocator), - pub fn init(allocator: Allocator, options: RouterOptions) Self { - return .{ - .routes = std.StringHashMap(zap.HttpRequestFn).init(allocator), + .not_found = options.not_found, + }; +} - .not_found = options.not_found, - }; - } +pub fn deinit(self: *Self) void { + self.routes.deinit(); +} - pub fn deinit(self: *Self) void { - self.routes.deinit(); +pub fn handle_func(self: *Self, path: []const u8, h: zap.HttpRequestFn) !void { + if (path.len == 0) { + return RouterError.EmptyPath; } - pub fn handle_func(self: *Self, path: []const u8, h: zap.HttpRequestFn) !void { - if (path.len == 0) { - return RouterError.EmptyPath; - } + const route = self.routes.get(path); - const route = self.routes.get(path); + if (route != null) { + return RouterError.AlreadyExists; + } - if (route != null) { - return RouterError.AlreadyExists; - } + try self.routes.put(path, h); +} - try self.routes.put(path, h); - } +pub fn serve(self: *Self, r: zap.Request) void { + const path = if (r.path) |p| p else "/"; - pub fn serve(self: *Self, r: zap.Request) void { - var route = self.routes.get(r.path.?); + var route = self.routes.get(path); - if (route) |handler| { - handler(r); - } else if (self.not_found) |handler| { - // not found handler - handler(r); - } else { - // default 404 output - r.setStatus(.not_found); - r.sendBody("404 Not Found") catch return; - } + if (route) |handler| { + handler(r); + } else if (self.not_found) |handler| { + // not found handler + handler(r); + } else { + // default 404 output + r.setStatus(.not_found); + r.sendBody("404 Not Found") catch return; } -}; +} diff --git a/src/util.zig b/src/util.zig index d9ff0db..21e9eaf 100644 --- a/src/util.zig +++ b/src/util.zig @@ -1,5 +1,23 @@ 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 diff --git a/src/zap.zig b/src/zap.zig index 5d9a91f..0414d68 100644 --- a/src/zap.zig +++ b/src/zap.zig @@ -52,7 +52,7 @@ pub const Tls = @import("tls.zig"); /// ``` pub const Endpoint = @import("endpoint.zig"); -pub usingnamespace @import("router.zig"); +pub const Router = @import("router.zig"); pub usingnamespace @import("util.zig"); pub usingnamespace @import("http.zig"); From 4058854a129997768bc91fcc778677be8741e6bf Mon Sep 17 00:00:00 2001 From: stringnick Date: Thu, 18 Jan 2024 21:39:05 +0100 Subject: [PATCH 3/3] update readme.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e9f3459..7e9ba8c 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,8 @@ what's necessary to show a feature. - run it like this: `ZAP_USE_OPENSSL=true zig build run-https` OR like this: `zig build -Dopenssl=true run-https` - it will tell you how to generate certificates - +- [**Router support**](examples/simple_router/simple_router.zig): You can now use + simple router by path. Also example with self capture helper func. I'll continue wrapping more of facil.io's functionality and adding stuff to zap to a point where I can use it as the JSON REST API backend for real research projects, serving thousands of concurrent clients.