From 464dc81c4214960ed7eb3ddf9c3238755cf148a1 Mon Sep 17 00:00:00 2001 From: Nathan Reiner Date: Thu, 13 Nov 2025 17:23:58 +0100 Subject: add HandlerInfo --- build.zig.zon | 2 +- src/main.zig | 33 +++++++++++++++++++++++------- src/routes/login.zig | 17 ++++++++++++++++ src/routes/root.zig | 57 +++++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 src/routes/login.zig diff --git a/build.zig.zon b/build.zig.zon index 352c4f1..511d4c1 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -22,7 +22,7 @@ // original project's identity. Thus it is recommended to leave the comment // on the following line intact, so that it shows up in code reviews that // modify the field. - .fingerprint = 0x93de37723f3d5133, // Changing this has security and trust implications. + .fingerprint = 0xf901ac632f19fccc, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. .minimum_zig_version = "0.15.2", diff --git a/src/main.zig b/src/main.zig index 2566120..be43352 100644 --- a/src/main.zig +++ b/src/main.zig @@ -7,14 +7,36 @@ pub const std_options = std.Options { const log = std.log.scoped(.main); +var net_server: std.net.Server = undefined; + +fn signal_handler(signo: i32) callconv(.c) void { + if (signo == std.os.linux.SIG.INT) { + log.info("shutdown", .{}); + net_server.deinit(); + std.process.exit(0); + } +} + +fn register_sigaction() void { + var sa = std.os.linux.Sigaction{ + .handler = .{ .handler = signal_handler }, + .mask = std.mem.zeroes(std.os.linux.sigset_t), + .flags = 0, + }; + + _ = std.os.linux.sigaction(std.os.linux.SIG.INT, &sa, null); +} + pub fn main() !void { + register_sigaction(); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer std.debug.assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); - const address = try std.net.Address.parseIpAndPort("127.0.0.1:8080"); - var net_server = try address.listen(.{}); + const address = try std.net.Address.parseIpAndPort("0.0.0.0:8080"); + net_server = try address.listen(.{ .reuse_address = true }); defer net_server.deinit(); log.info("listening on {f}", .{address}); @@ -35,10 +57,7 @@ pub fn main() !void { var request = http_server.receiveHead() catch continue; log.info("{} {s}", .{request.head.method, request.head.target}); - const handler = memora.routes.get(request.head.target); - - handler(&request, allocator) catch |err| { - log.err("handler({s}): {}", .{request.head.target, err}); - }; + const handler_info = memora.routes.get(request.head.target); + try handler_info.handle(&request, allocator); } } diff --git a/src/routes/login.zig b/src/routes/login.zig new file mode 100644 index 0000000..29bf370 --- /dev/null +++ b/src/routes/login.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +pub fn handler( + request: *std.http.Server.Request, + allocator: std.mem.Allocator, +) anyerror!void { + var output = std.Io.Writer.Allocating.init(allocator); + var stringify = std.json.Stringify { .writer = &output.writer }; + + try stringify.write(.{ + .user = "nathan.reiner", + .name = "Nathan Reiner", + }); + + try output.writer.flush(); + try request.respond(output.written(), .{}); +} diff --git a/src/routes/root.zig b/src/routes/root.zig index 0330404..01952bb 100644 --- a/src/routes/root.zig +++ b/src/routes/root.zig @@ -1,15 +1,58 @@ const std = @import("std"); pub const fallback = @import("fallback.zig"); +pub const login = @import("login.zig"); -pub const Handler = *const fn ( - request: *std.http.Server.Request, - allocator: std.mem.Allocator, -) anyerror!void; +pub const HandlerInfo = struct { + handler: *const fn ( + request: *std.http.Server.Request, + allocator: std.mem.Allocator, + ) anyerror!void, + needs_auth: bool, + method: std.http.Method, -pub const handlers = std.StaticStringMap(Handler).initComptime(.{ + pub fn handle( + self: *const @This(), + request: *std.http.Server.Request, + allocator: std.mem.Allocator, + ) !void { + if (request.head.method != self.method) { + try request.respond("{ \"error\": \"Bad Request\" }", .{ .status = .bad_request }); + } + + self.handler(request, allocator) catch |err| { + const response, const status_code: std.http.Status = switch (err) { + error.BadRequest => .{ "{ \"error\": \"Bad Request\" }", .bad_request }, + error.Unauthorized => .{ "{ \"error\": \"Unauthorized\" }", .unauthorized }, + error.Forbidden => .{ "{ \"error\": \"Forbidden\" }", .forbidden }, + error.NotFound => .{ "{ \"error\": \"Not Found\" }", .not_found }, + else => .{ "{ \"error\": \"Internal Server Error\" }", .internal_server_error }, + }; + + try request.respond(response, .{ .status = status_code }); + }; + } +}; + +pub const handlers = std.StaticStringMap(HandlerInfo).initComptime(.{ + .{ + "", + HandlerInfo { + .handler = fallback.handler, + .needs_auth = false, + .method = .GET, + } + }, + .{ + "/api/login", + HandlerInfo { + .handler = login.handler, + .needs_auth = false, + .method = .POST, + } + }, }); -pub fn get(path: []const u8) Handler { - return handlers.get(path) orelse fallback.handler; +pub fn get(path: []const u8) HandlerInfo { + return (handlers.getLongestPrefix(std.mem.trimEnd(u8, path, "/")) orelse unreachable).value; } -- cgit v1.2.3-70-g09d2