const std = @import("std"); const web = @import("web"); const linux = std.os.linux; const errno = linux.E.init; const UUID = web.UUID; var running: std.atomic.Value(bool) = .init(true); fn interruptionHandler(signal: i32) callconv(.c) void { switch (signal) { linux.SIG.INT, linux.SIG.TERM => { running.store(false, .release); }, else => {}, } } const Router = struct { allocator: std.mem.Allocator, fn init(allocator: std.mem.Allocator) Router { return .{ .allocator = allocator, }; } fn interface(self: *Router) web.RequestRouter { return .{ .ptr = self, .vtable = &.{ .route = onRoute, }, }; } fn onRoute(ctx: *anyopaque, route: web.Route) !web.RequestHandler { const self: *Router = @ptrCast(@alignCast(ctx)); const handler = try self.allocator.create(Handler); handler.* = try .init(self.allocator, route); return handler.interface(); } }; const Handler = struct { allocator: std.mem.Allocator, route: web.Route, uuid: UUID, timer: std.time.Timer, accept: ?[]const u8 = null, accept_encoding: ?[]const u8 = null, accept_language: ?[]const u8 = null, user_agent: ?[]const u8 = null, fn init(allocator: std.mem.Allocator, route: web.Route) !Handler { return .{ .allocator = allocator, .route = route, .uuid = UUID.v7(), .timer = try .start(), }; } fn interface(self: *Handler) web.RequestHandler { return .{ .ptr = self, .vtable = &.{ .header = onHeader, .body = onBody, .finalize = onFinalize, }, }; } fn onHeader(ctx: *anyopaque, response: *web.Response, header: web.http.Header) !void { const self: *Handler = @ptrCast(@alignCast(ctx)); switch (header.name) { .known => |k| switch (k) { .Accept => { self.accept = header.value; }, .@"Accept-Encoding" => { self.accept_encoding = header.value; }, .@"Accept-Language" => { self.accept_language = header.value; }, .@"User-Agent" => { self.user_agent = header.value; }, else => {}, }, .other => {}, } _ = response; } fn onBody(ctx: *anyopaque, response: *web.Response, body: []const u8) !void { const self: *Handler = @ptrCast(@alignCast(ctx)); try response.sendResponse(.{ .media_type = "application/json", .response_body = "{\"ok\":true}\r\n", }); _ = self; _ = body; } fn onFinalize(ctx: *anyopaque) void { const self: *Handler = @ptrCast(@alignCast(ctx)); const time_ns = self.timer.read(); const time_us_ceil = (time_ns + std.time.ns_per_us - 1) / std.time.ns_per_us; std.log.info("{s} {s} ({} µs)", .{ @tagName(self.route.method), self.route.pathname, time_us_ceil }); self.allocator.destroy(self); } }; pub fn main() !void { var gpa: std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }) = .init; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var router: Router = .init(allocator); var server = try web.Server.init(allocator, .{ .request_router = router.interface(), .address = .initIp4(.{ 127, 0, 0, 1 }, 8000), }); defer server.deinit(allocator); const sigaction: linux.Sigaction = .{ .handler = .{ .handler = interruptionHandler }, .mask = linux.sigemptyset(), .flags = linux.SA.RESETHAND, }; const rc = linux.sigaction(linux.SIG.INT, &sigaction, null); switch (errno(rc)) { .SUCCESS => {}, else => |e| { std.log.err("Error while estabilishing interruption handler: {s}", .{@tagName(e)}); return error.SystemError; }, } try server.listen(&running); }