Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simple router with handler clojures support #68

Merged
merged 3 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "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" },
Expand Down
108 changes: 108 additions & 0 deletions examples/simple_router/simple_router.zig
Original file line number Diff line number Diff line change
@@ -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("<html><body><h1>Hello from ZAP!!!</h1></body></html>") 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,
});
}
60 changes: 60 additions & 0 deletions src/router.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const std = @import("std");
const zap = @import("zap.zig");

const Allocator = std.mem.Allocator;
const RouterError = error{
AlreadyExists,
EmptyPath,
};

const Self = @This();

pub const Options = struct {
not_found: ?zap.HttpRequestFn = null,
};

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),

.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 {
const path = if (r.path) |p| p else "/";

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;
}
}
18 changes: 18 additions & 0 deletions src/util.zig
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/zap.zig
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pub const Tls = @import("tls.zig");
/// ```
pub const Endpoint = @import("endpoint.zig");

pub const Router = @import("router.zig");

pub usingnamespace @import("util.zig");
pub usingnamespace @import("http.zig");

Expand Down