159 lines
4.1 KiB
Zig
159 lines
4.1 KiB
Zig
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);
|
|
}
|