move to separate file
This commit is contained in:
parent
c774e56828
commit
cbadc880b1
2 changed files with 398 additions and 390 deletions
392
src/application.zig
Normal file
392
src/application.zig
Normal file
|
@ -0,0 +1,392 @@
|
|||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const net = std.net;
|
||||
const heap = std.heap;
|
||||
const http = std.http;
|
||||
|
||||
pub fn Application(comptime Context: type) type {
|
||||
return struct {
|
||||
allocator: mem.Allocator,
|
||||
|
||||
/// Listen address
|
||||
address: net.Address,
|
||||
/// Main router
|
||||
// router: Router,
|
||||
handler: HandlerFn,
|
||||
|
||||
/// Initialization options
|
||||
pub const Options = struct {
|
||||
address: net.Address,
|
||||
allocator: mem.Allocator,
|
||||
root_handler: HandlerFn,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(options: Options) Self {
|
||||
return .{
|
||||
.address = options.address,
|
||||
.allocator = options.allocator,
|
||||
.handler = options.root_handler,
|
||||
// .router = Router.init(options.allocator, options.root_handler),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(_: *Self) void {
|
||||
// self.router.deinit();
|
||||
}
|
||||
|
||||
/// Listens for new connections forever
|
||||
pub fn listen(self: *Self) !void {
|
||||
var arena = heap.ArenaAllocator.init(self.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
var tcp = try self.address.listen(.{ .reuse_address = true });
|
||||
defer tcp.deinit();
|
||||
std.debug.print("listening at: {any}\n", .{self.address});
|
||||
|
||||
while (true) {
|
||||
const read_buf = try arena.allocator().alloc(u8, 1024 * 32);
|
||||
const connection = try tcp.accept();
|
||||
var server = http.Server.init(connection, read_buf);
|
||||
try self.handle(&server, arena.allocator());
|
||||
_ = arena.reset(.retain_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(self: *Self, server: *http.Server, allocator: mem.Allocator) !void {
|
||||
handler: while (true) {
|
||||
var req = server.receiveHead() catch |e| if (e == error.HttpConnectionClosing) break :handler else return e;
|
||||
|
||||
var ctx = Context{};
|
||||
var event = Event{
|
||||
.ctx = &ctx,
|
||||
.req = .{
|
||||
.uri = try std.Uri.parseWithoutScheme(req.head.target),
|
||||
.headers = std.ArrayList(*const http.Header).init(allocator),
|
||||
},
|
||||
.res = .{
|
||||
.status = .ok,
|
||||
.headers = std.StringHashMap(*http.Header).init(allocator),
|
||||
.body = std.ArrayList(u8).init(allocator),
|
||||
},
|
||||
};
|
||||
|
||||
var header_it = req.iterateHeaders();
|
||||
while (header_it.next()) |header| try event.req.headers.append(&header);
|
||||
|
||||
// const route = try self.router.getRoute(event.req.uri.path);
|
||||
// try route.handler(&event);
|
||||
try self.handler(&event);
|
||||
|
||||
try respondFromEvent(&event, &req, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
fn respondFromEvent(event: *Event, req: *http.Server.Request, allocator: mem.Allocator) !void {
|
||||
const res_body = try event.res.body.toOwnedSlice();
|
||||
const res_headers = try 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,
|
||||
});
|
||||
}
|
||||
|
||||
/// Single request-response context
|
||||
pub const Event = struct {
|
||||
ctx: *Context,
|
||||
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 HandlerFn = *const fn (event: *Event) anyerror!void;
|
||||
};
|
||||
}
|
||||
/// HTTP router
|
||||
/// Some code taken from [unjs/radix3](https://github.com/unjs/radix3)
|
||||
pub fn Router(comptime Context: type) type {
|
||||
return struct {
|
||||
allocator: mem.Allocator,
|
||||
arena: heap.ArenaAllocator,
|
||||
|
||||
root_node: *Node,
|
||||
// static_routes: std.StringHashMap(*Node),
|
||||
|
||||
pub fn init(allocator: mem.Allocator, root_ctx: Context) Router {
|
||||
var arena = heap.ArenaAllocator.init(allocator);
|
||||
var node = Node.init(arena.allocator()) catch @panic("OOM");
|
||||
node.data = root_ctx;
|
||||
// std.debug.print("init router with root_node {any}\n", .{node});
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.arena = arena,
|
||||
.root_node = node,
|
||||
// .static_routes = std.StringHashMap(*Node).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Router) void {
|
||||
self.root_node.deinit(self.arena.allocator(), null);
|
||||
self.arena.deinit();
|
||||
// self.static_routes.deinit();
|
||||
}
|
||||
|
||||
// pub fn handle(self: *Router, event: *Listener.Event) !void {
|
||||
// const route = try self.getRoute(event.req.uri.path);
|
||||
// try route.handler(event);
|
||||
// }
|
||||
|
||||
/// Insert a route if the path is not already present, otherwise overwrite preexisting HandlerFn.
|
||||
pub fn putRoute(self: *Router, path: []const u8, ctx: Context) !void {
|
||||
std.debug.print("\npath {s}\n", .{path});
|
||||
// var is_static_route = true;
|
||||
var sections = mem.splitScalar(u8, path, '/');
|
||||
if (sections.peek()) |sec| if (sec.len == 0) {
|
||||
_ = sections.next();
|
||||
};
|
||||
var node = self.root_node;
|
||||
var unnamed_placeholder_ctr: usize = 0;
|
||||
var matched_nodes = std.ArrayList(*Node).init(self.allocator);
|
||||
defer matched_nodes.deinit();
|
||||
try matched_nodes.append(node);
|
||||
while (sections.next()) |section| {
|
||||
std.debug.print("adding section {s}\n", .{section});
|
||||
if (node.children.get(section)) |child| {
|
||||
std.debug.print("into child\n", .{});
|
||||
node = child;
|
||||
} else {
|
||||
var child_node = try Node.init(self.allocator);
|
||||
child_node.type = if (mem.startsWith(u8, section, "**"))
|
||||
.wildcard
|
||||
else if ((section.len > 0 and section[0] == ':') or mem.eql(u8, section, "*"))
|
||||
.placeholder
|
||||
else
|
||||
.normal;
|
||||
child_node.parent = node;
|
||||
// child_node.handler = handler;
|
||||
try node.children.put(section, child_node);
|
||||
|
||||
switch (child_node.type) {
|
||||
.normal => {},
|
||||
.wildcard => {
|
||||
std.debug.print("is wildcard\n", .{});
|
||||
node.wildcard_child_node = child_node;
|
||||
child_node.param_name = if (section.len > 3) section[3..] else "_";
|
||||
// is_static_route = false;
|
||||
},
|
||||
.placeholder => {
|
||||
std.debug.print("is placeholder\n", .{});
|
||||
child_node.param_name = if (mem.eql(u8, section, "*")) blk: {
|
||||
std.debug.print("is unnamed placeholder #{d}\n", .{unnamed_placeholder_ctr});
|
||||
const s = try std.fmt.allocPrint(self.arena.allocator(), "_{d}", .{unnamed_placeholder_ctr}); // TODO: this will leak
|
||||
unnamed_placeholder_ctr += 1;
|
||||
break :blk s;
|
||||
} else try self.arena.allocator().dupe(u8, section[1..]);
|
||||
try node.placeholder_children.append(child_node);
|
||||
// is_static_route = false;
|
||||
},
|
||||
}
|
||||
|
||||
// std.debug.print("added child node: {any}\n", .{child_node});
|
||||
|
||||
try matched_nodes.append(child_node);
|
||||
node = child_node;
|
||||
}
|
||||
}
|
||||
|
||||
node.data = ctx;
|
||||
|
||||
// if (is_static_route) std.debug.print("was static route\n", .{});
|
||||
|
||||
// if (is_static_route) try self.static_routes.put(path, node);
|
||||
}
|
||||
|
||||
/// Get the HandlerFn associated with path, otherwise get root_handler.
|
||||
pub fn getRoute(self: *Router, path: []const u8) !*Node {
|
||||
// if (self.static_routes.get(path)) |rt| return rt;
|
||||
std.debug.print("\nget path {s}\n", .{path});
|
||||
|
||||
var params = std.StringHashMap([]const u8).init(self.allocator);
|
||||
defer params.deinit();
|
||||
var params_found = false;
|
||||
var wildcard_node: ?*Node = null;
|
||||
var node: *Node = self.root_node;
|
||||
var wildcard_param: ?[]const u8 = null;
|
||||
|
||||
var remaining = mem.count(u8, path, "/") + 1;
|
||||
var sections = mem.splitScalar(u8, path, '/');
|
||||
if (sections.peek()) |sec| if (sec.len == 0) {
|
||||
_ = sections.next();
|
||||
};
|
||||
while (sections.next()) |section| : (remaining -= 1) {
|
||||
std.debug.print("finding section: {s}\n", .{section});
|
||||
if (node.wildcard_child_node) |wildcard_child_node| {
|
||||
std.debug.print("section has wildcard\n", .{});
|
||||
wildcard_node = wildcard_child_node;
|
||||
wildcard_param = sections.rest();
|
||||
}
|
||||
|
||||
const next_node = node.children.get(section);
|
||||
if (next_node) |child| {
|
||||
node = child;
|
||||
std.debug.print("found section {s} child_node {any}\n", .{ section, child.type });
|
||||
} else {
|
||||
var child_node: ?*Node = null;
|
||||
if (node.placeholder_children.items.len > 1) {
|
||||
// TODO
|
||||
for (node.placeholder_children.items) |child| place: {
|
||||
if (child.max_depth == remaining) {
|
||||
child_node = child;
|
||||
break :place;
|
||||
}
|
||||
}
|
||||
} else if (node.placeholder_children.items.len == 1)
|
||||
child_node = node.placeholder_children.items[0];
|
||||
|
||||
if (child_node) |n| {
|
||||
if (n.param_name) |name| try params.put(name, section);
|
||||
params_found = true;
|
||||
node = n;
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
std.debug.print("ended up with node {any}\nhas wildcard_node: {any}\nis root_node: {any}\n", .{ node.type, wildcard_node != null, node == self.root_node });
|
||||
|
||||
if (wildcard_node) |wildcard| {
|
||||
node = wildcard;
|
||||
try params.put("_", wildcard_param.?);
|
||||
params_found = true;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/// If there is a route with a matching path, it is deleted from the router, and this function return true. Otherwise it returns false.
|
||||
pub fn removeRoute(self: *Router, path: []const u8) bool {
|
||||
// _ = self.static_routes.remove(path);
|
||||
std.debug.print("\nremoving path {s}\n", .{path});
|
||||
|
||||
var opt_node: ?*Node = self.root_node;
|
||||
var sections = mem.splitScalar(u8, path, '/');
|
||||
if (sections.peek()) |sec| if (sec.len == 0) {
|
||||
_ = sections.next();
|
||||
};
|
||||
while (sections.next()) |section| {
|
||||
// TODO: reorder to be safe
|
||||
std.debug.print("section: {s}\n", .{section});
|
||||
opt_node = opt_node.?.children.get(section);
|
||||
if (opt_node == null) return false;
|
||||
}
|
||||
// TODO: should this node.parent be an assert instead?
|
||||
if (opt_node) |node| {
|
||||
std.debug.print("found node\n", .{});
|
||||
// if (node.children.count() == 0) {
|
||||
// std.debug.print("node has no children\n", .{});
|
||||
if (node.parent) |_| {
|
||||
std.debug.print("removing self from parent\n", .{});
|
||||
var rest_sections = mem.splitScalar(u8, path, '/');
|
||||
var last_section: []const u8 = undefined;
|
||||
while (rest_sections.peek()) |_| last_section = rest_sections.next().?;
|
||||
// parent.wildcard_child_node = null;
|
||||
// parent.placeholder_children.clearAndFree();
|
||||
// _ = parent.children.remove(last_section); // TODO assert this is true
|
||||
node.deinit(self.allocator, last_section);
|
||||
} else node.deinit(self.allocator, null);
|
||||
// } // else node.deinit(self.allocator);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub const Node = struct {
|
||||
type: Type,
|
||||
max_depth: usize, // TODO: what is best here
|
||||
parent: ?*Node = null,
|
||||
children: std.StringHashMap(*Node),
|
||||
data: Context,
|
||||
param_name: ?[]const u8 = null,
|
||||
wildcard_child_node: ?*Node = null,
|
||||
placeholder_children: std.ArrayList(*Node),
|
||||
|
||||
pub const Type = enum { normal, wildcard, placeholder };
|
||||
|
||||
/// Expects handler to be set later. Will cause problems otherwise!
|
||||
pub fn init(allocator: mem.Allocator) !*Node {
|
||||
var self = try allocator.create(Node);
|
||||
self.type = .normal;
|
||||
self.max_depth = 256;
|
||||
self.children = std.StringHashMap(*Node).init(allocator);
|
||||
self.placeholder_children = std.ArrayList(*Node).init(allocator);
|
||||
self.parent = null;
|
||||
self.param_name = null;
|
||||
self.wildcard_child_node = null;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Node, allocator: mem.Allocator, section: ?[]const u8) void {
|
||||
std.debug.print("deiniting node with {d} children.. and {d} placeholder_children\n", .{ self.children.count(), self.placeholder_children.items.len });
|
||||
if (section) |sec| if (self.parent) |parent| {
|
||||
_ = parent.children.remove(sec);
|
||||
};
|
||||
for (self.placeholder_children.items) |child| std.debug.print("child {any}\n", .{child}); // child.deinit(allocator);
|
||||
self.placeholder_children.deinit();
|
||||
var child_it = self.children.valueIterator();
|
||||
while (child_it.next()) |child| child.*.deinit(allocator, section);
|
||||
self.children.deinit();
|
||||
allocator.destroy(self);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// test "Router" {
|
||||
// var router = Listener.Router.init(std.testing.allocator, &dummyHandler);
|
||||
// defer router.deinit();
|
||||
|
||||
// try router.putRoute("/foo", &hummyDandler);
|
||||
// try router.putRoute("/foo/bar", &hummyDandler);
|
||||
// try router.putRoute("/foo/foobar", &tummyCandler);
|
||||
// try router.putRoute("/bar", &hummyDandler);
|
||||
|
||||
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/")).handler);
|
||||
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo")).handler);
|
||||
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo/bar")).handler);
|
||||
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/foobar")).handler);
|
||||
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/bar")).handler);
|
||||
|
||||
// try std.testing.expect(router.removeRoute("/foo"));
|
||||
|
||||
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/foo/bar")).handler);
|
||||
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/foo")).handler);
|
||||
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/")).handler);
|
||||
|
||||
// try router.putRoute("/foo", &hummyDandler);
|
||||
// try router.putRoute("/foo/**", &tummyCandler);
|
||||
// try router.putRoute("/bar/*", &tummyCandler);
|
||||
|
||||
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo")).handler);
|
||||
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/bar")).handler);
|
||||
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/bar/foo")).handler);
|
||||
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/foof")).handler);
|
||||
|
||||
// try std.testing.expect(router.removeRoute("/bar/*"));
|
||||
// }
|
||||
|
||||
// fn dummyHandler(_: *Listener.Event) anyerror!void {}
|
||||
// fn hummyDandler(_: *Listener.Event) anyerror!void {}
|
||||
// fn tummyCandler(_: *Listener.Event) anyerror!void {}
|
396
src/main.zig
396
src/main.zig
|
@ -4,6 +4,8 @@ const net = std.net;
|
|||
const http = std.http;
|
||||
const heap = std.heap;
|
||||
|
||||
pub const Application = @import("application.zig").Application;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
@ -25,399 +27,13 @@ pub fn main() !void {
|
|||
try listener.listen();
|
||||
}
|
||||
|
||||
pub const App = Listener(DummyContext);
|
||||
pub const DummyContext = struct {};
|
||||
const App = Application(DummyContext);
|
||||
const DummyContext = struct {};
|
||||
|
||||
pub fn handle(event: *App.Event) anyerror!void {
|
||||
fn handle(event: *App.Event) anyerror!void {
|
||||
try event.res.body.appendSlice("hello there");
|
||||
}
|
||||
|
||||
pub fn handleError(event: *App.Event) anyerror!void {
|
||||
fn handleError(event: *App.Event) anyerror!void {
|
||||
try event.res.body.appendSlice("ahoy, an error occurred");
|
||||
}
|
||||
|
||||
pub fn Listener(comptime Context: type) type {
|
||||
return struct {
|
||||
allocator: mem.Allocator,
|
||||
|
||||
/// Listen address
|
||||
address: net.Address,
|
||||
/// Main router
|
||||
// router: Router,
|
||||
handler: HandlerFn,
|
||||
|
||||
/// Initialization options
|
||||
pub const Options = struct {
|
||||
address: net.Address,
|
||||
allocator: mem.Allocator,
|
||||
root_handler: HandlerFn,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(options: Options) Self {
|
||||
return .{
|
||||
.address = options.address,
|
||||
.allocator = options.allocator,
|
||||
.handler = options.root_handler,
|
||||
// .router = Router.init(options.allocator, options.root_handler),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(_: *Self) void {
|
||||
// self.router.deinit();
|
||||
}
|
||||
|
||||
/// Listens for new connections forever
|
||||
pub fn listen(self: *Self) !void {
|
||||
var arena = heap.ArenaAllocator.init(self.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
var tcp = try self.address.listen(.{ .reuse_address = true });
|
||||
defer tcp.deinit();
|
||||
std.debug.print("listening at: {any}\n", .{self.address});
|
||||
|
||||
while (true) {
|
||||
const read_buf = try arena.allocator().alloc(u8, 1024 * 32);
|
||||
const connection = try tcp.accept();
|
||||
var server = http.Server.init(connection, read_buf);
|
||||
try self.handle(&server, arena.allocator());
|
||||
_ = arena.reset(.retain_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(self: *Self, server: *http.Server, allocator: mem.Allocator) !void {
|
||||
handler: while (true) {
|
||||
var req = server.receiveHead() catch |e| if (e == error.HttpConnectionClosing) break :handler else return e;
|
||||
|
||||
var ctx = Context{};
|
||||
var event = Event{
|
||||
.ctx = &ctx,
|
||||
.req = .{
|
||||
.uri = try std.Uri.parseWithoutScheme(req.head.target),
|
||||
.headers = std.ArrayList(*const http.Header).init(allocator),
|
||||
},
|
||||
.res = .{
|
||||
.status = .ok,
|
||||
.headers = std.StringHashMap(*http.Header).init(allocator),
|
||||
.body = std.ArrayList(u8).init(allocator),
|
||||
},
|
||||
};
|
||||
|
||||
var header_it = req.iterateHeaders();
|
||||
while (header_it.next()) |header| try event.req.headers.append(&header);
|
||||
|
||||
// const route = try self.router.getRoute(event.req.uri.path);
|
||||
// try route.handler(&event);
|
||||
try self.handler(&event);
|
||||
|
||||
try respondFromEvent(&event, &req, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
fn respondFromEvent(event: *Event, req: *http.Server.Request, allocator: mem.Allocator) !void {
|
||||
const res_body = try event.res.body.toOwnedSlice();
|
||||
const res_headers = try 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,
|
||||
});
|
||||
}
|
||||
|
||||
/// Single request-response context
|
||||
pub const Event = struct {
|
||||
ctx: *Context,
|
||||
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 HandlerFn = *const fn (event: *Event) anyerror!void;
|
||||
|
||||
/// HTTP router
|
||||
/// Some code taken from [unjs/radix3](https://github.com/unjs/radix3)
|
||||
pub const Router = struct {
|
||||
allocator: mem.Allocator,
|
||||
arena: heap.ArenaAllocator,
|
||||
|
||||
root_node: *Node,
|
||||
// static_routes: std.StringHashMap(*Node),
|
||||
|
||||
pub fn init(allocator: mem.Allocator, root_handler: HandlerFn) Router {
|
||||
var arena = heap.ArenaAllocator.init(allocator);
|
||||
var node = Node.init(arena.allocator()) catch @panic("OOM");
|
||||
node.handler = root_handler;
|
||||
// std.debug.print("init router with root_node {any}\n", .{node});
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.arena = arena,
|
||||
.root_node = node,
|
||||
// .static_routes = std.StringHashMap(*Node).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Router) void {
|
||||
self.root_node.deinit(self.arena.allocator(), null);
|
||||
self.arena.deinit();
|
||||
// self.static_routes.deinit();
|
||||
}
|
||||
|
||||
// pub fn handle(self: *Router, event: *Listener.Event) !void {
|
||||
// const route = try self.getRoute(event.req.uri.path);
|
||||
// try route.handler(event);
|
||||
// }
|
||||
|
||||
/// Insert a route if the path is not already present, otherwise overwrite preexisting HandlerFn.
|
||||
pub fn putRoute(self: *Router, path: []const u8, handler: HandlerFn) !void {
|
||||
std.debug.print("\npath {s}\n", .{path});
|
||||
// var is_static_route = true;
|
||||
var sections = mem.splitScalar(u8, path, '/');
|
||||
if (sections.peek()) |sec| if (sec.len == 0) {
|
||||
_ = sections.next();
|
||||
};
|
||||
var node = self.root_node;
|
||||
var unnamed_placeholder_ctr: usize = 0;
|
||||
var matched_nodes = std.ArrayList(*Node).init(self.allocator);
|
||||
defer matched_nodes.deinit();
|
||||
try matched_nodes.append(node);
|
||||
while (sections.next()) |section| {
|
||||
std.debug.print("adding section {s}\n", .{section});
|
||||
if (node.children.get(section)) |child| {
|
||||
std.debug.print("into child\n", .{});
|
||||
node = child;
|
||||
} else {
|
||||
var child_node = try Node.init(self.allocator);
|
||||
child_node.type = if (mem.startsWith(u8, section, "**"))
|
||||
.wildcard
|
||||
else if ((section.len > 0 and section[0] == ':') or mem.eql(u8, section, "*"))
|
||||
.placeholder
|
||||
else
|
||||
.normal;
|
||||
child_node.parent = node;
|
||||
// child_node.handler = handler;
|
||||
try node.children.put(section, child_node);
|
||||
|
||||
switch (child_node.type) {
|
||||
.normal => {},
|
||||
.wildcard => {
|
||||
std.debug.print("is wildcard\n", .{});
|
||||
node.wildcard_child_node = child_node;
|
||||
child_node.param_name = if (section.len > 3) section[3..] else "_";
|
||||
// is_static_route = false;
|
||||
},
|
||||
.placeholder => {
|
||||
std.debug.print("is placeholder\n", .{});
|
||||
child_node.param_name = if (mem.eql(u8, section, "*")) blk: {
|
||||
std.debug.print("is unnamed placeholder #{d}\n", .{unnamed_placeholder_ctr});
|
||||
const s = try std.fmt.allocPrint(self.arena.allocator(), "_{d}", .{unnamed_placeholder_ctr}); // TODO: this will leak
|
||||
unnamed_placeholder_ctr += 1;
|
||||
break :blk s;
|
||||
} else try self.arena.allocator().dupe(u8, section[1..]);
|
||||
try node.placeholder_children.append(child_node);
|
||||
// is_static_route = false;
|
||||
},
|
||||
}
|
||||
|
||||
// std.debug.print("added child node: {any}\n", .{child_node});
|
||||
|
||||
try matched_nodes.append(child_node);
|
||||
node = child_node;
|
||||
}
|
||||
}
|
||||
|
||||
node.handler = handler;
|
||||
|
||||
// if (is_static_route) std.debug.print("was static route\n", .{});
|
||||
|
||||
// if (is_static_route) try self.static_routes.put(path, node);
|
||||
}
|
||||
|
||||
/// Get the HandlerFn associated with path, otherwise get root_handler.
|
||||
pub fn getRoute(self: *Router, path: []const u8) !*Node {
|
||||
// if (self.static_routes.get(path)) |rt| return rt;
|
||||
std.debug.print("\nget path {s}\n", .{path});
|
||||
|
||||
var params = std.StringHashMap([]const u8).init(self.allocator);
|
||||
defer params.deinit();
|
||||
var params_found = false;
|
||||
var wildcard_node: ?*Node = null;
|
||||
var node: *Node = self.root_node;
|
||||
var wildcard_param: ?[]const u8 = null;
|
||||
|
||||
var remaining = mem.count(u8, path, "/") + 1;
|
||||
var sections = mem.splitScalar(u8, path, '/');
|
||||
if (sections.peek()) |sec| if (sec.len == 0) {
|
||||
_ = sections.next();
|
||||
};
|
||||
while (sections.next()) |section| : (remaining -= 1) {
|
||||
std.debug.print("finding section: {s}\n", .{section});
|
||||
if (node.wildcard_child_node) |wildcard_child_node| {
|
||||
std.debug.print("section has wildcard\n", .{});
|
||||
wildcard_node = wildcard_child_node;
|
||||
wildcard_param = sections.rest();
|
||||
}
|
||||
|
||||
const next_node = node.children.get(section);
|
||||
if (next_node) |child| {
|
||||
node = child;
|
||||
std.debug.print("found section {s} child_node {any}\n", .{ section, child.type });
|
||||
} else {
|
||||
var child_node: ?*Node = null;
|
||||
if (node.placeholder_children.items.len > 1) {
|
||||
// TODO
|
||||
for (node.placeholder_children.items) |child| place: {
|
||||
if (child.max_depth == remaining) {
|
||||
child_node = child;
|
||||
break :place;
|
||||
}
|
||||
}
|
||||
} else if (node.placeholder_children.items.len == 1)
|
||||
child_node = node.placeholder_children.items[0];
|
||||
|
||||
if (child_node) |n| {
|
||||
if (n.param_name) |name| try params.put(name, section);
|
||||
params_found = true;
|
||||
node = n;
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
std.debug.print("ended up with node {any}\nhas wildcard_node: {any}\nis root_node: {any}\n", .{ node.type, wildcard_node != null, node == self.root_node });
|
||||
|
||||
if (wildcard_node) |wildcard| {
|
||||
node = wildcard;
|
||||
try params.put("_", wildcard_param.?);
|
||||
params_found = true;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/// If there is a route with a matching path, it is deleted from the router, and this function return true. Otherwise it returns false.
|
||||
pub fn removeRoute(self: *Router, path: []const u8) bool {
|
||||
// _ = self.static_routes.remove(path);
|
||||
std.debug.print("\nremoving path {s}\n", .{path});
|
||||
|
||||
var opt_node: ?*Node = self.root_node;
|
||||
var sections = mem.splitScalar(u8, path, '/');
|
||||
if (sections.peek()) |sec| if (sec.len == 0) {
|
||||
_ = sections.next();
|
||||
};
|
||||
while (sections.next()) |section| {
|
||||
// TODO: reorder to be safe
|
||||
std.debug.print("section: {s}\n", .{section});
|
||||
opt_node = opt_node.?.children.get(section);
|
||||
if (opt_node == null) return false;
|
||||
}
|
||||
// TODO: should this node.parent be an assert instead?
|
||||
if (opt_node) |node| {
|
||||
std.debug.print("found node\n", .{});
|
||||
// if (node.children.count() == 0) {
|
||||
// std.debug.print("node has no children\n", .{});
|
||||
if (node.parent) |_| {
|
||||
std.debug.print("removing self from parent\n", .{});
|
||||
var rest_sections = mem.splitScalar(u8, path, '/');
|
||||
var last_section: []const u8 = undefined;
|
||||
while (rest_sections.peek()) |_| last_section = rest_sections.next().?;
|
||||
// parent.wildcard_child_node = null;
|
||||
// parent.placeholder_children.clearAndFree();
|
||||
// _ = parent.children.remove(last_section); // TODO assert this is true
|
||||
node.deinit(self.allocator, last_section);
|
||||
} else node.deinit(self.allocator, null);
|
||||
// } // else node.deinit(self.allocator);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub const Node = struct {
|
||||
type: Type,
|
||||
max_depth: usize, // TODO: what is best here
|
||||
parent: ?*Node = null,
|
||||
children: std.StringHashMap(*Node),
|
||||
handler: HandlerFn,
|
||||
param_name: ?[]const u8 = null,
|
||||
wildcard_child_node: ?*Node = null,
|
||||
placeholder_children: std.ArrayList(*Node),
|
||||
|
||||
pub const Type = enum { normal, wildcard, placeholder };
|
||||
|
||||
/// Expects handler to be set later. Will cause problems otherwise!
|
||||
pub fn init(allocator: mem.Allocator) !*Node {
|
||||
var self = try allocator.create(Node);
|
||||
self.type = .normal;
|
||||
self.max_depth = 256;
|
||||
self.children = std.StringHashMap(*Node).init(allocator);
|
||||
self.placeholder_children = std.ArrayList(*Node).init(allocator);
|
||||
self.parent = null;
|
||||
self.param_name = null;
|
||||
self.wildcard_child_node = null;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Node, allocator: mem.Allocator, section: ?[]const u8) void {
|
||||
std.debug.print("deiniting node with {d} children.. and {d} placeholder_children\n", .{ self.children.count(), self.placeholder_children.items.len });
|
||||
if (section) |sec| if (self.parent) |parent| {
|
||||
_ = parent.children.remove(sec);
|
||||
};
|
||||
for (self.placeholder_children.items) |child| std.debug.print("child {any}\n", .{child}); // child.deinit(allocator);
|
||||
self.placeholder_children.deinit();
|
||||
var child_it = self.children.valueIterator();
|
||||
while (child_it.next()) |child| child.*.deinit(allocator, section);
|
||||
self.children.deinit();
|
||||
allocator.destroy(self);
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// test "Router" {
|
||||
// var router = Listener.Router.init(std.testing.allocator, &dummyHandler);
|
||||
// defer router.deinit();
|
||||
|
||||
// try router.putRoute("/foo", &hummyDandler);
|
||||
// try router.putRoute("/foo/bar", &hummyDandler);
|
||||
// try router.putRoute("/foo/foobar", &tummyCandler);
|
||||
// try router.putRoute("/bar", &hummyDandler);
|
||||
|
||||
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/")).handler);
|
||||
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo")).handler);
|
||||
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo/bar")).handler);
|
||||
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/foobar")).handler);
|
||||
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/bar")).handler);
|
||||
|
||||
// try std.testing.expect(router.removeRoute("/foo"));
|
||||
|
||||
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/foo/bar")).handler);
|
||||
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/foo")).handler);
|
||||
// try std.testing.expectEqual(&dummyHandler, (try router.getRoute("/")).handler);
|
||||
|
||||
// try router.putRoute("/foo", &hummyDandler);
|
||||
// try router.putRoute("/foo/**", &tummyCandler);
|
||||
// try router.putRoute("/bar/*", &tummyCandler);
|
||||
|
||||
// try std.testing.expectEqual(&hummyDandler, (try router.getRoute("/foo")).handler);
|
||||
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/bar")).handler);
|
||||
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/bar/foo")).handler);
|
||||
// try std.testing.expectEqual(&tummyCandler, (try router.getRoute("/foo/foof")).handler);
|
||||
|
||||
// try std.testing.expect(router.removeRoute("/bar/*"));
|
||||
// }
|
||||
|
||||
// fn dummyHandler(_: *Listener.Event) anyerror!void {}
|
||||
// fn hummyDandler(_: *Listener.Event) anyerror!void {}
|
||||
// fn tummyCandler(_: *Listener.Event) anyerror!void {}
|
||||
|
|
Loading…
Add table
Reference in a new issue