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,
});
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");

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
},

View File

@@ -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);