web: fix compile errors and critical runtime errors

This commit is contained in:
2026-03-08 15:48:20 +01:00
parent 738ba5bd37
commit e09a00a4ba
7 changed files with 105 additions and 61 deletions

View File

@@ -14,6 +14,22 @@ pub fn build(b: *std.Build) void {
.root_module = module, .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 run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run tests"); const test_step = b.step("test", "Run tests");

View File

@@ -9,7 +9,7 @@ pub const FileDescriptor = enum(i32) {
stderr = 2, 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); const rc = linux.memfd_create(name, flags);
return switch (errno(rc)) { return switch (errno(rc)) {
.SUCCESS => @enumFromInt(@as(i32, @intCast(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); const rc = linux.socket(domain, socket_type, protocol);
return switch (errno(rc)) { return switch (errno(rc)) {
.SUCCESS => @enumFromInt(@as(i32, @intCast(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 { 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)) { return switch (errno(rc)) {
.SUCCESS => @enumFromInt(@as(i32, @intCast(rc))), .SUCCESS => @enumFromInt(@as(i32, @intCast(rc))),
.CONNABORTED => return error.ConnectionAborted, .CONNABORTED => return error.ConnectionAborted,

View File

@@ -13,6 +13,7 @@ const errno = linux.E.init;
fd: FileDescriptor, fd: FileDescriptor,
address: std.net.Address, address: std.net.Address,
workers: []Worker, workers: []Worker,
threads: []std.Thread,
request_router: RequestRouter, request_router: RequestRouter,
connection_queue: std.DoublyLinkedList, connection_queue: std.DoublyLinkedList,
@@ -65,21 +66,22 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
var socklen = options.address.getOsSockLen(); var socklen = options.address.getOsSockLen();
try fd.bind(&options.address.any, socklen); try fd.bind(&options.address.any, socklen);
try fd.listen(options.kernel_backlog); try fd.listen(options.max_connections);
var listen_address = options.address; 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); const workers = try allocator.alloc(Worker, worker_count);
errdefer allocator.free(workers); errdefer allocator.free(workers);
// Allocate connection pool
const connection_buffer = try allocator.alloc(Connection, options.max_connections); const connection_buffer = try allocator.alloc(Connection, options.max_connections);
errdefer allocator.free(connection_buffer); errdefer allocator.free(connection_buffer);
const threads = try allocator.alloc(std.Thread, worker_count);
errdefer allocator.free(threads);
// Allocate and remap read buffers // Allocate and remap read buffers
const single_read_buffer_size = @as(usize, options.read_buffer_pages) * huge_page_size; 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); const read_buffer_fd: FileDescriptor = try .memfd_create("read_buffer", 0);
defer read_buffer_fd.close(); 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( const read_buffer_ptr = try errOrPtr(linux.mmap(
null, null,
@@ -114,7 +116,7 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
linux.PROT.READ | linux.PROT.WRITE, linux.PROT.READ | linux.PROT.WRITE,
linux.MAP{ .TYPE = .SHARED, .FIXED = true }, linux.MAP{ .TYPE = .SHARED, .FIXED = true },
@intFromEnum(read_buffer_fd), @intFromEnum(read_buffer_fd),
offset, @intCast(offset),
)); ));
try err(linux.mmap( 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.PROT.READ | linux.PROT.WRITE,
linux.MAP{ .TYPE = .SHARED, .FIXED = true }, linux.MAP{ .TYPE = .SHARED, .FIXED = true },
@intFromEnum(read_buffer_fd), @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 = .{}; var connection_pool: std.DoublyLinkedList = .{};
for (connection_buffer) |*c| { for (connection_buffer) |*c| {
connection_pool.prepend(c.node); connection_pool.prepend(&c.node);
} }
return .{ return .{
.fd = fd, .fd = fd,
.address = listen_address, .address = listen_address,
.workers = workers, .workers = workers,
.threads = threads,
.request_router = options.request_router, .request_router = options.request_router,
.connection_queue = .{}, .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 { pub fn deinit(self: *Server, allocator: std.mem.Allocator) void {
// TODO Deinitialize workers // TODO Deinitialize workers
self.fd.close(); self.fd.close();
allocator.free(self.threads);
allocator.free(self.connection_buffer); allocator.free(self.connection_buffer);
allocator.free(self.workers);
self.* = undefined; self.* = undefined;
} }
@@ -198,13 +203,13 @@ pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void {
defer { defer {
worker_running.store(false, .release); worker_running.store(false, .release);
self.cond_connection_queued.broadcast(); self.cond_connection_queued.broadcast();
for (self.workers[0..spawned]) |*worker| { for (self.threads[0..spawned]) |*thread| {
worker.thread.join(); thread.join();
} }
} }
for (self.workers) |*worker| { for (self.workers, 0..) |*worker, i| {
worker.thread = try std.Thread.spawn(.{}, Worker.worker, .{ worker, self, &worker_running }); self.threads[i] = try std.Thread.spawn(.{}, Worker.worker, .{ worker, self, &worker_running });
spawned += 1; spawned += 1;
} }
@@ -240,7 +245,7 @@ pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void {
fn err(rc: usize) !void { fn err(rc: usize) !void {
const e = errno(rc); const e = errno(rc);
return if (e != .SUCCESS) error.SystemError else rc; return if (e != .SUCCESS) error.SystemError else {};
} }
fn errOrPtr(rc: usize) ![*]u8 { fn errOrPtr(rc: usize) ![*]u8 {

View File

@@ -14,7 +14,7 @@ read_buffer_size: usize,
read_head: usize, read_head: usize,
read_tail: usize, read_tail: usize,
write_buffer: []const u8, write_buffer: []u8,
pub fn worker( pub fn worker(
self: *Worker, self: *Worker,
@@ -31,11 +31,11 @@ pub fn worker(
server.mutex.unlock(); server.mutex.unlock();
defer { defer {
server.mutex.lock(); server.mutex.lock();
server.connection_pool.append(self.connection.node); server.connection_pool.append(&connection.node);
server.cond_connection_freed.signal(); 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}); std.log.err("Error while handling connection: {}", .{err});
}; };
} else { } else {
@@ -53,8 +53,9 @@ fn handleConnection(
defer connection.deinit(); defer connection.deinit();
while (running.load(.acquire)) { 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}); std.log.err("Error while handling request: {}", .{err});
return err;
}; };
if (!res) break; if (!res) break;
@@ -68,6 +69,11 @@ fn handleRequest(
) !bool { ) !bool {
var response: Response = .init(connection, self.write_buffer); var response: Response = .init(connection, self.write_buffer);
var parser: http.Parser = .init(request_router, &response); 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; var leftover_bytes = self.read_tail - self.read_head;
const max_read_tail = self.read_head + self.read_buffer_size; const max_read_tail = self.read_head + self.read_buffer_size;

View File

@@ -299,6 +299,7 @@ pub const Name = union(enum) {
/// Maps **lowercased** header names to enum values. /// Maps **lowercased** header names to enum values.
pub const map: std.StaticStringMap(Known) = blk: { pub const map: std.StaticStringMap(Known) = blk: {
@setEvalBranchQuota(20000);
const fields = @typeInfo(Known).@"enum".fields; const fields = @typeInfo(Known).@"enum".fields;
var kvs_list: [fields.len]struct { []const u8, Known } = undefined; var kvs_list: [fields.len]struct { []const u8, Known } = undefined;

View File

@@ -22,7 +22,7 @@ const vec_len = @typeInfo(Vec).vector.len;
const Pattern = struct { const Pattern = struct {
value: Vec, value: Vec,
mask: Vec, mask: Vec,
len: u6, len: u32,
pub fn init(comptime prefix: []const u8) Pattern { pub fn init(comptime prefix: []const u8) Pattern {
if (prefix.len > vec_len) { if (prefix.len > vec_len) {
@@ -40,6 +40,12 @@ const Pattern = struct {
mask[i] = 0x00; mask[i] = 0x00;
} }
} }
return .{
.value = value,
.mask = mask,
.len = prefix.len,
};
} }
inline fn check(self: Pattern, vec: Vec) bool { inline fn check(self: Pattern, vec: Vec) bool {
@@ -199,21 +205,22 @@ pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult {
}; };
}, },
else => { else => {
if (chars.len - i >= vec_len) { // TODO Fix
const vec_res = try self.consumeVec(chars[i..][0..vec_len]); // if (chars.len - i >= vec_len) {
i += vec_res.consumed; // const vec_res = try self.consumeVec(chars[i..][0..vec_len]);
// i += vec_res.consumed;
if (vec_res.done) { // if (vec_res.done) {
return .{ // return .{
.consumed = i, // .consumed = i,
.done = true, // .done = true,
}; // };
} // }
if (vec_res.consumed > 0) { // if (vec_res.consumed > 0) {
continue; // continue;
} // }
} // }
const char_res = try self.consumeChar(&chars[i]); const char_res = try self.consumeChar(&chars[i]);
i += 1; 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) { switch (self.state) {
.init => { .init => {
inline for (@typeInfo(patterns.methods).@"struct".decls) |decl| { 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]); self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + vec_len]);
return .{
.consumed = vec_len,
.done = false,
};
}, },
.pathname_complete => { .pathname_complete => {
if (patterns.@"version_http/1.1".check(vec)) { 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]); self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + vec_len]);
return .{
.consumed = vec_len,
.done = false,
};
}, },
else => { else => {
// Delegate to `consumeChar`. // 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.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; self.last_handler_error = err;
return error.HandlerError; return error.HandlerError;
}; };
@@ -458,7 +474,10 @@ pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
.headers_end => switch (c) { .headers_end => switch (c) {
'\n' => { '\n' => {
if (self.content_length == 0) { 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; return .done;
} }
self.state = .{ .body = @as([*]const u8, @ptrCast(c_ptr))[1..1] }; 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]; const new_body = body.ptr[0 .. body.len + 1];
self.state = .{ .body = new_body }; self.state = .{ .body = new_body };
if (new_body.len >= self.content_length) { 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; return .done;
} }
}, },

View File

@@ -1,17 +1,9 @@
const std = @import("std"); const std = @import("std");
const http = @import("http.zig"); const web = @import("web");
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 linux = std.os.linux; const linux = std.os.linux;
const errno = linux.E.init; const errno = linux.E.init;
const UUID = web.UUID;
var running: std.atomic.Value(bool) = .init(true); 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 .{ return .{
.ptr = self, .ptr = self,
.vtable = &.{ .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 self: *Router = @ptrCast(@alignCast(ctx));
const handler = try self.allocator.create(Handler); const handler = try self.allocator.create(Handler);
handler.* = .init(self.allocator, route); handler.* = try .init(self.allocator, route);
return handler.interface(); return handler.interface();
} }
}; };
@@ -54,7 +46,7 @@ const Router = struct {
const Handler = struct { const Handler = struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
route: Route, route: web.Route,
uuid: UUID, uuid: UUID,
timer: std.time.Timer, timer: std.time.Timer,
@@ -63,17 +55,17 @@ const Handler = struct {
accept_language: ?[]const u8 = null, accept_language: ?[]const u8 = null,
user_agent: ?[]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 .{ return .{
.allocator = allocator, .allocator = allocator,
.route = route, .route = route,
.uuid = UUID.v7(), .uuid = UUID.v7(),
.timer = .start(), .timer = try .start(),
}; };
} }
fn interface(self: *Handler) RequestHandler { fn interface(self: *Handler) web.RequestHandler {
return .{ return .{
.ptr = self, .ptr = self,
.vtable = &.{ .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)); const self: *Handler = @ptrCast(@alignCast(ctx));
switch (header.name) { switch (header.name) {
@@ -101,6 +93,7 @@ const Handler = struct {
.@"User-Agent" => { .@"User-Agent" => {
self.user_agent = header.value; self.user_agent = header.value;
}, },
else => {},
}, },
.other => {}, .other => {},
} }
@@ -108,12 +101,12 @@ const Handler = struct {
_ = response; _ = 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)); const self: *Handler = @ptrCast(@alignCast(ctx));
try response.sendResponse(.{ try response.sendResponse(.{
.media_type = "application/json", .media_type = "application/json",
.response_body = "{\"ok\":true}", .response_body = "{\"ok\":true}\r\n",
}); });
_ = self; _ = self;
@@ -126,7 +119,7 @@ const Handler = struct {
const time_ns = self.timer.read(); const time_ns = self.timer.read();
const time_us_ceil = (time_ns + std.time.ns_per_us - 1) / std.time.ns_per_us; 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); self.allocator.destroy(self);
} }
@@ -141,8 +134,9 @@ pub fn main() !void {
var router: Router = .init(allocator); var router: Router = .init(allocator);
var server = try Server.init(allocator, .{ var server = try web.Server.init(allocator, .{
.request_router = router.interface(), .request_router = router.interface(),
.address = .initIp4(.{ 127, 0, 0, 1 }, 8000),
}); });
defer server.deinit(allocator); defer server.deinit(allocator);