2024-03-25 14:49:43 -06:00
|
|
|
const std = @import("std");
|
|
|
|
const mem = std.mem;
|
|
|
|
const net = std.net;
|
|
|
|
const http = std.http;
|
|
|
|
const heap = std.heap;
|
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
const App = Listener(Router);
|
|
|
|
|
2024-03-25 14:49:43 -06:00
|
|
|
pub fn main() !void {
|
|
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
|
|
defer _ = gpa.deinit();
|
|
|
|
const allocator = gpa.allocator();
|
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
var router = Router.init(allocator, &handle, &handleError);
|
2024-03-25 15:58:40 -06:00
|
|
|
defer router.deinit();
|
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
try router.addRoute("/", &handle);
|
|
|
|
|
2024-03-25 14:49:43 -06:00
|
|
|
const address = try net.Address.parseIp("0.0.0.0", 8080);
|
2024-03-25 16:33:38 -06:00
|
|
|
var listener = App.init(.{
|
|
|
|
.address = address,
|
|
|
|
.allocator = allocator,
|
|
|
|
.context = router,
|
|
|
|
.handler = router.handle,
|
|
|
|
});
|
2024-03-25 14:49:43 -06:00
|
|
|
defer listener.deinit();
|
|
|
|
try listener.listen();
|
|
|
|
}
|
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub fn handle(_: *Router, event: *App.Event) anyerror!void {
|
2024-03-25 14:49:43 -06:00
|
|
|
try event.res.body.appendSlice("hello there");
|
|
|
|
}
|
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub fn handleError(_: Router, event: *App.Event) anyerror!void {
|
|
|
|
try event.res.body.appendSlice("ahoy, an error occurred");
|
|
|
|
}
|
2024-03-25 14:49:43 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub fn Listener(comptime Context: type) type {
|
|
|
|
return struct {
|
|
|
|
address: net.Address,
|
|
|
|
arena: heap.ArenaAllocator,
|
|
|
|
ctx: *Context,
|
|
|
|
handlerFn: HandlerFn,
|
|
|
|
|
|
|
|
pub const Options = struct {
|
|
|
|
address: net.Address,
|
|
|
|
allocator: mem.Allocator,
|
|
|
|
context: *Context,
|
|
|
|
handler: HandlerFn,
|
2024-03-25 14:49:43 -06:00
|
|
|
};
|
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub fn init(options: Options) Listener {
|
|
|
|
return .{
|
|
|
|
.address = options.address,
|
|
|
|
.arena = heap.ArenaAllocator.init(options.allocator),
|
|
|
|
.ctx = options.context,
|
|
|
|
.handlerFn = options.handler,
|
2024-03-25 14:49:43 -06:00
|
|
|
};
|
2024-03-25 16:33:38 -06:00
|
|
|
}
|
2024-03-25 14:49:43 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub fn deinit(self: *Listener) void {
|
|
|
|
self.arena.deinit();
|
|
|
|
}
|
2024-03-25 14:49:43 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub fn listen(self: *Listener) !void {
|
|
|
|
var tcp = try self.address.listen(.{});
|
|
|
|
defer tcp.deinit();
|
|
|
|
std.debug.print("listening at: {any}\n", .{self.address});
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
const read_buf = try self.arena.allocator().alloc(u8, 1024 * 32);
|
|
|
|
const connection = try tcp.accept();
|
|
|
|
var server = http.Server.init(connection, read_buf);
|
|
|
|
try self.handle(&server);
|
|
|
|
_ = self.arena.reset(.retain_capacity);
|
|
|
|
}
|
|
|
|
}
|
2024-03-25 14:49:43 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
fn handle(self: *Listener, server: *http.Server) !void {
|
|
|
|
handler: while (true) {
|
|
|
|
var req = server.receiveHead() catch |e| if (e == error.HttpConnectionClosing) break :handler else return e;
|
|
|
|
|
|
|
|
var event = Event{
|
|
|
|
.req = .{
|
|
|
|
.uri = try std.Uri.parseWithoutScheme(req.head.target),
|
|
|
|
.headers = std.ArrayList(*const http.Header).init(self.arena.allocator()),
|
|
|
|
},
|
|
|
|
.res = .{
|
|
|
|
.status = .ok,
|
|
|
|
.headers = std.StringHashMap(*http.Header).init(self.arena.allocator()),
|
|
|
|
.body = std.ArrayList(u8).init(self.arena.allocator()),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
var header_it = req.iterateHeaders();
|
|
|
|
while (header_it.next()) |header| try event.req.headers.append(&header);
|
|
|
|
|
|
|
|
try self.handlerFn(self.ctx, &event);
|
|
|
|
|
|
|
|
try self.respondFromEvent(&event, &req);
|
|
|
|
}
|
2024-03-25 14:49:43 -06:00
|
|
|
}
|
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
fn respondFromEvent(self: *Listener, event: *Event, req: *http.Server.Request) !void {
|
|
|
|
const res_body = try event.res.body.toOwnedSlice();
|
|
|
|
const res_headers = try self.arena.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,
|
|
|
|
});
|
|
|
|
}
|
2024-03-25 14:49:43 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub const Event = struct {
|
|
|
|
req: Request,
|
|
|
|
res: Response,
|
2024-03-25 14:49:43 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
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),
|
|
|
|
};
|
2024-03-25 14:49:43 -06:00
|
|
|
};
|
2024-03-25 16:33:38 -06:00
|
|
|
|
|
|
|
pub const HandlerFn = *const fn (ctx: *Context, event: *Event) anyerror!void;
|
2024-03-25 14:49:43 -06:00
|
|
|
};
|
2024-03-25 16:33:38 -06:00
|
|
|
}
|
2024-03-25 15:58:40 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub const Router = struct {
|
|
|
|
allocator: mem.Allocator,
|
2024-03-25 15:58:40 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
root_node: Node,
|
|
|
|
static_routes: std.StringHashMap(*Node),
|
2024-03-25 15:58:40 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
error_handler: HandlerFn,
|
2024-03-25 15:58:40 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub fn init(allocator: mem.Allocator, root_handler: HandlerFn, error_handler: HandlerFn) Router {
|
|
|
|
return .{
|
|
|
|
.allocator = allocator,
|
|
|
|
.root_node = .{
|
|
|
|
.type = .normal,
|
|
|
|
.max_depth = 64,
|
|
|
|
.children = std.StringHashMap(*Node).init(allocator),
|
|
|
|
.handler = root_handler,
|
|
|
|
.placeholder_children = std.ArrayList(*Node).init(allocator),
|
|
|
|
},
|
|
|
|
.static_routes = std.StringHashMap(*Node).init(allocator),
|
|
|
|
.error_handler = error_handler,
|
|
|
|
};
|
|
|
|
}
|
2024-03-25 15:58:40 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub fn deinit(self: *Router) void {
|
|
|
|
self.static_routes.deinit();
|
|
|
|
}
|
2024-03-25 15:58:40 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub fn handle(self: *Router, event: *Listener.Event) anyerror!void {
|
|
|
|
try self.route(event.req.uri.path)(self, event);
|
|
|
|
}
|
2024-03-25 15:58:40 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub fn addRoute(self: *Router, path: []const u8, handler: HandlerFn) !void {
|
|
|
|
try self.addStaticRoute(path, handler);
|
|
|
|
}
|
2024-03-25 15:58:40 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
fn addStaticRoute(self: *Router, path: []const u8, handler: HandlerFn) !void {
|
|
|
|
try self.static_routes.put(path, .{
|
|
|
|
.type = .normal,
|
|
|
|
.max_depth = 1,
|
|
|
|
.children = std.StringHashMap(*Node).init(self.allocator),
|
|
|
|
.handler = handler,
|
|
|
|
.placeholder_children = std.ArrayList(*Node).init(self.allocator),
|
|
|
|
});
|
|
|
|
}
|
2024-03-25 15:58:40 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub fn route(self: *Router, path: []const u8) HandlerFn {
|
|
|
|
if (self.static_routes.get(path)) |rt| return rt.handler;
|
|
|
|
if (self.root_node.wildcard_child_node) |node| return node.handler;
|
|
|
|
return self.error_handler;
|
|
|
|
}
|
2024-03-25 15:58:40 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub const Node = struct {
|
|
|
|
type: Type,
|
|
|
|
max_depth: usize, // TODO: what is best here
|
|
|
|
parent: ?*Node = null,
|
|
|
|
children: std.StringHashMap(*Node),
|
|
|
|
handler: HandlerFn,
|
|
|
|
wildcard_child_node: ?*Node = null,
|
|
|
|
placeholder_children: std.ArrayList(*Node),
|
2024-03-25 15:58:40 -06:00
|
|
|
|
2024-03-25 16:33:38 -06:00
|
|
|
pub const Type = enum { normal, wildcard, placeholder };
|
2024-03-25 15:58:40 -06:00
|
|
|
};
|
2024-03-25 16:33:38 -06:00
|
|
|
|
|
|
|
pub const HandlerFn = *const fn (self: *Router, event: *App.Event) anyerror!void;
|
|
|
|
};
|