web: fix compile errors and critical runtime errors
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user