From e09a00a4bae620d0c7e11492b69174a91c0f1520 Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Sun, 8 Mar 2026 15:48:20 +0100 Subject: [PATCH] web: fix compile errors and critical runtime errors --- packages/web/build.zig | 16 ++++++++ packages/web/src/FileDescriptor.zig | 6 +-- packages/web/src/Server.zig | 33 +++++++++------- packages/web/src/Worker.zig | 14 +++++-- packages/web/src/http/Header.zig | 1 + packages/web/src/http/Parser.zig | 58 ++++++++++++++++++++--------- packages/web/src/main.zig | 38 ++++++++----------- 7 files changed, 105 insertions(+), 61 deletions(-) diff --git a/packages/web/build.zig b/packages/web/build.zig index ca12cfd..86971bc 100644 --- a/packages/web/build.zig +++ b/packages/web/build.zig @@ -14,6 +14,22 @@ pub fn build(b: *std.Build) void { .root_module = module, }); + const exe_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "web", .module = module }, + }, + }); + + const exe = b.addExecutable(.{ + .name = "web", + .root_module = exe_module, + }); + + b.installArtifact(exe); + const run_tests = b.addRunArtifact(tests); const test_step = b.step("test", "Run tests"); diff --git a/packages/web/src/FileDescriptor.zig b/packages/web/src/FileDescriptor.zig index 963bdc6..63fc504 100644 --- a/packages/web/src/FileDescriptor.zig +++ b/packages/web/src/FileDescriptor.zig @@ -9,7 +9,7 @@ pub const FileDescriptor = enum(i32) { stderr = 2, _, - pub fn memfd_create(name: [*:0]const u8, flags: u32) FileDescriptor { + pub fn memfd_create(name: [*:0]const u8, flags: u32) !FileDescriptor { const rc = linux.memfd_create(name, flags); return switch (errno(rc)) { .SUCCESS => @enumFromInt(@as(i32, @intCast(rc))), @@ -17,7 +17,7 @@ pub const FileDescriptor = enum(i32) { }; } - pub fn socket(domain: u32, socket_type: u32, protocol: u32) FileDescriptor { + pub fn socket(domain: u32, socket_type: u32, protocol: u32) !FileDescriptor { const rc = linux.socket(domain, socket_type, protocol); return switch (errno(rc)) { .SUCCESS => @enumFromInt(@as(i32, @intCast(rc))), @@ -30,7 +30,7 @@ pub const FileDescriptor = enum(i32) { } pub fn accept(self: FileDescriptor, noalias addr: ?*linux.sockaddr, noalias len: ?*linux.socklen_t) !FileDescriptor { - const rc = linux.accept(@intFromEnum(self), &addr, &len); + const rc = linux.accept(@intFromEnum(self), addr, len); return switch (errno(rc)) { .SUCCESS => @enumFromInt(@as(i32, @intCast(rc))), .CONNABORTED => return error.ConnectionAborted, diff --git a/packages/web/src/Server.zig b/packages/web/src/Server.zig index 625eab5..930ab51 100644 --- a/packages/web/src/Server.zig +++ b/packages/web/src/Server.zig @@ -13,6 +13,7 @@ const errno = linux.E.init; fd: FileDescriptor, address: std.net.Address, workers: []Worker, +threads: []std.Thread, request_router: RequestRouter, connection_queue: std.DoublyLinkedList, @@ -65,21 +66,22 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server { var socklen = options.address.getOsSockLen(); try fd.bind(&options.address.any, socklen); - try fd.listen(options.kernel_backlog); + try fd.listen(options.max_connections); var listen_address = options.address; - try fd.getsockname(fd, &listen_address, &socklen); + try fd.getsockname(&listen_address.any, &socklen); - // Allocate workers + // Allocate arrays const workers = try allocator.alloc(Worker, worker_count); errdefer allocator.free(workers); - // Allocate connection pool - const connection_buffer = try allocator.alloc(Connection, options.max_connections); errdefer allocator.free(connection_buffer); + const threads = try allocator.alloc(std.Thread, worker_count); + errdefer allocator.free(threads); + // Allocate and remap read buffers const single_read_buffer_size = @as(usize, options.read_buffer_pages) * huge_page_size; @@ -91,7 +93,7 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server { const read_buffer_fd: FileDescriptor = try .memfd_create("read_buffer", 0); defer read_buffer_fd.close(); - try read_buffer_fd.ftruncate(all_read_buffers_size); + try read_buffer_fd.ftruncate(@intCast(all_read_buffers_size)); const read_buffer_ptr = try errOrPtr(linux.mmap( null, @@ -114,7 +116,7 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server { linux.PROT.READ | linux.PROT.WRITE, linux.MAP{ .TYPE = .SHARED, .FIXED = true }, @intFromEnum(read_buffer_fd), - offset, + @intCast(offset), )); try err(linux.mmap( @@ -123,7 +125,7 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server { linux.PROT.READ | linux.PROT.WRITE, linux.MAP{ .TYPE = .SHARED, .FIXED = true }, @intFromEnum(read_buffer_fd), - offset, + @intCast(offset), )); } @@ -162,13 +164,14 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server { var connection_pool: std.DoublyLinkedList = .{}; for (connection_buffer) |*c| { - connection_pool.prepend(c.node); + connection_pool.prepend(&c.node); } return .{ .fd = fd, .address = listen_address, .workers = workers, + .threads = threads, .request_router = options.request_router, .connection_queue = .{}, @@ -184,7 +187,9 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server { pub fn deinit(self: *Server, allocator: std.mem.Allocator) void { // TODO Deinitialize workers self.fd.close(); + allocator.free(self.threads); allocator.free(self.connection_buffer); + allocator.free(self.workers); self.* = undefined; } @@ -198,13 +203,13 @@ pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void { defer { worker_running.store(false, .release); self.cond_connection_queued.broadcast(); - for (self.workers[0..spawned]) |*worker| { - worker.thread.join(); + for (self.threads[0..spawned]) |*thread| { + thread.join(); } } - for (self.workers) |*worker| { - worker.thread = try std.Thread.spawn(.{}, Worker.worker, .{ worker, self, &worker_running }); + for (self.workers, 0..) |*worker, i| { + self.threads[i] = try std.Thread.spawn(.{}, Worker.worker, .{ worker, self, &worker_running }); spawned += 1; } @@ -240,7 +245,7 @@ pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void { fn err(rc: usize) !void { const e = errno(rc); - return if (e != .SUCCESS) error.SystemError else rc; + return if (e != .SUCCESS) error.SystemError else {}; } fn errOrPtr(rc: usize) ![*]u8 { diff --git a/packages/web/src/Worker.zig b/packages/web/src/Worker.zig index 6392e21..3bfad8c 100644 --- a/packages/web/src/Worker.zig +++ b/packages/web/src/Worker.zig @@ -14,7 +14,7 @@ read_buffer_size: usize, read_head: usize, read_tail: usize, -write_buffer: []const u8, +write_buffer: []u8, pub fn worker( self: *Worker, @@ -31,11 +31,11 @@ pub fn worker( server.mutex.unlock(); defer { server.mutex.lock(); - server.connection_pool.append(self.connection.node); + server.connection_pool.append(&connection.node); server.cond_connection_freed.signal(); } - self.handleConnection(server, server.request_router, connection, running) catch |err| { + self.handleConnection(server.request_router, connection, running) catch |err| { std.log.err("Error while handling connection: {}", .{err}); }; } else { @@ -53,8 +53,9 @@ fn handleConnection( defer connection.deinit(); while (running.load(.acquire)) { - const res = self.handleRequest(request_router, running) catch |err| { + const res = self.handleRequest(request_router, connection) catch |err| { std.log.err("Error while handling request: {}", .{err}); + return err; }; if (!res) break; @@ -68,6 +69,11 @@ fn handleRequest( ) !bool { var response: Response = .init(connection, self.write_buffer); var parser: http.Parser = .init(request_router, &response); + defer { + if (parser.request_handler) |rh| { + rh.rawFinalize(); + } + } var leftover_bytes = self.read_tail - self.read_head; const max_read_tail = self.read_head + self.read_buffer_size; diff --git a/packages/web/src/http/Header.zig b/packages/web/src/http/Header.zig index fe4fa22..06f39ed 100644 --- a/packages/web/src/http/Header.zig +++ b/packages/web/src/http/Header.zig @@ -299,6 +299,7 @@ pub const Name = union(enum) { /// Maps **lowercased** header names to enum values. pub const map: std.StaticStringMap(Known) = blk: { + @setEvalBranchQuota(20000); const fields = @typeInfo(Known).@"enum".fields; var kvs_list: [fields.len]struct { []const u8, Known } = undefined; diff --git a/packages/web/src/http/Parser.zig b/packages/web/src/http/Parser.zig index 227b13b..54836a7 100644 --- a/packages/web/src/http/Parser.zig +++ b/packages/web/src/http/Parser.zig @@ -22,7 +22,7 @@ const vec_len = @typeInfo(Vec).vector.len; const Pattern = struct { value: Vec, mask: Vec, - len: u6, + len: u32, pub fn init(comptime prefix: []const u8) Pattern { if (prefix.len > vec_len) { @@ -40,6 +40,12 @@ const Pattern = struct { mask[i] = 0x00; } } + + return .{ + .value = value, + .mask = mask, + .len = prefix.len, + }; } inline fn check(self: Pattern, vec: Vec) bool { @@ -199,21 +205,22 @@ pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult { }; }, else => { - if (chars.len - i >= vec_len) { - const vec_res = try self.consumeVec(chars[i..][0..vec_len]); - i += vec_res.consumed; + // TODO Fix + // if (chars.len - i >= vec_len) { + // const vec_res = try self.consumeVec(chars[i..][0..vec_len]); + // i += vec_res.consumed; - if (vec_res.done) { - return .{ - .consumed = i, - .done = true, - }; - } + // if (vec_res.done) { + // return .{ + // .consumed = i, + // .done = true, + // }; + // } - if (vec_res.consumed > 0) { - continue; - } - } + // if (vec_res.consumed > 0) { + // continue; + // } + // } const char_res = try self.consumeChar(&chars[i]); i += 1; @@ -231,7 +238,8 @@ pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult { }; } -pub fn consumeVec(self: *Parser, vec: *const [vec_len]u8) Error!ConsumeResult { +pub fn consumeVec(self: *Parser, vec_ptr: *const [vec_len]u8) Error!ConsumeResult { + const vec: Vec = vec_ptr.*; switch (self.state) { .init => { inline for (@typeInfo(patterns.methods).@"struct".decls) |decl| { @@ -257,6 +265,10 @@ pub fn consumeVec(self: *Parser, vec: *const [vec_len]u8) Error!ConsumeResult { } self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + vec_len]); + return .{ + .consumed = vec_len, + .done = false, + }; }, .pathname_complete => { if (patterns.@"version_http/1.1".check(vec)) { @@ -279,6 +291,10 @@ pub fn consumeVec(self: *Parser, vec: *const [vec_len]u8) Error!ConsumeResult { } self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + vec_len]); + return .{ + .consumed = vec_len, + .done = false, + }; }, else => { // Delegate to `consumeChar`. @@ -444,7 +460,7 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult { self.content_length = std.fmt.parseInt(usize, header.value, 10) catch return error.InvalidContentLength; } - self.request_handler.rawHeader(header) catch |err| { + self.request_handler.?.rawHeader(self.response, header) catch |err| { self.last_handler_error = err; return error.HandlerError; }; @@ -458,7 +474,10 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult { .headers_end => switch (c) { '\n' => { if (self.content_length == 0) { - self.handler.rawBody(self.request, &.{}); + self.request_handler.?.rawBody(self.response, &.{}) catch |err| { + self.last_handler_error = err; + return error.HandlerError; + }; return .done; } self.state = .{ .body = @as([*]const u8, @ptrCast(c_ptr))[1..1] }; @@ -469,7 +488,10 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult { const new_body = body.ptr[0 .. body.len + 1]; self.state = .{ .body = new_body }; if (new_body.len >= self.content_length) { - self.handler.rawBody(self.request, new_body); + self.request_handler.?.rawBody(self.response, new_body) catch |err| { + self.last_handler_error = err; + return error.HandlerError; + }; return .done; } }, diff --git a/packages/web/src/main.zig b/packages/web/src/main.zig index 7fad64e..e639dc9 100644 --- a/packages/web/src/main.zig +++ b/packages/web/src/main.zig @@ -1,17 +1,9 @@ const std = @import("std"); -const http = @import("http.zig"); - -const Connection = @import("Connection.zig"); -const Response = @import("Response.zig"); -const RequestHandler = @import("RequestHandler.zig"); -const RequestRouter = @import("RequestRouter.zig"); -const Route = @import("Route.zig"); -const Server = @import("Server.zig"); -const UUID = @import("UUID.zig"); -const Worker = @import("Worker.zig"); +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); @@ -33,7 +25,7 @@ const Router = struct { }; } - fn interface(self: *Router) RequestRouter { + fn interface(self: *Router) web.RequestRouter { return .{ .ptr = self, .vtable = &.{ @@ -42,11 +34,11 @@ const Router = struct { }; } - fn onRoute(ctx: *anyopaque, route: Route) !RequestHandler { + fn onRoute(ctx: *anyopaque, route: web.Route) !web.RequestHandler { const self: *Router = @ptrCast(@alignCast(ctx)); const handler = try self.allocator.create(Handler); - handler.* = .init(self.allocator, route); + handler.* = try .init(self.allocator, route); return handler.interface(); } }; @@ -54,7 +46,7 @@ const Router = struct { const Handler = struct { allocator: std.mem.Allocator, - route: Route, + route: web.Route, uuid: UUID, timer: std.time.Timer, @@ -63,17 +55,17 @@ const Handler = struct { accept_language: ?[]const u8 = null, user_agent: ?[]const u8 = null, - fn init(allocator: std.mem.Allocator, route: Route) Handler { + fn init(allocator: std.mem.Allocator, route: web.Route) !Handler { return .{ .allocator = allocator, .route = route, .uuid = UUID.v7(), - .timer = .start(), + .timer = try .start(), }; } - fn interface(self: *Handler) RequestHandler { + fn interface(self: *Handler) web.RequestHandler { return .{ .ptr = self, .vtable = &.{ @@ -84,7 +76,7 @@ const Handler = struct { }; } - fn onHeader(ctx: *anyopaque, response: *Response, header: http.Header) void { + fn onHeader(ctx: *anyopaque, response: *web.Response, header: web.http.Header) !void { const self: *Handler = @ptrCast(@alignCast(ctx)); switch (header.name) { @@ -101,6 +93,7 @@ const Handler = struct { .@"User-Agent" => { self.user_agent = header.value; }, + else => {}, }, .other => {}, } @@ -108,12 +101,12 @@ const Handler = struct { _ = response; } - fn onBody(ctx: *anyopaque, response: *Response, body: []const u8) !void { + 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}", + .response_body = "{\"ok\":true}\r\n", }); _ = self; @@ -126,7 +119,7 @@ const Handler = struct { 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 }); + std.log.info("{s} {s} ({} µs)", .{ @tagName(self.route.method), self.route.pathname, time_us_ceil }); self.allocator.destroy(self); } @@ -141,8 +134,9 @@ pub fn main() !void { var router: Router = .init(allocator); - var server = try Server.init(allocator, .{ + var server = try web.Server.init(allocator, .{ .request_router = router.interface(), + .address = .initIp4(.{ 127, 0, 0, 1 }, 8000), }); defer server.deinit(allocator);