From 458ea252e7bb40326a682fe7a00d6927a41ffe64 Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Tue, 7 May 2024 20:21:40 +0100 Subject: [PATCH] WIP --- build.zig.zon | 8 ++--- src/jetzig.zig | 1 - src/jetzig/HttpzServer.zig | 58 -------------------------------- src/jetzig/http/Request.zig | 29 ++++++++++------ src/jetzig/http/Server.zig | 66 +++++++++++++++++++++++-------------- 5 files changed, 64 insertions(+), 98 deletions(-) delete mode 100644 src/jetzig/HttpzServer.zig diff --git a/build.zig.zon b/build.zig.zon index 7088855..1435df6 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -19,12 +19,12 @@ .hash = "12208a1de366740d11de525db7289345949f5fd46527db3f89eecc7bb49b012c0732", }, .smtp_client = .{ - .url = "https://github.com/karlseguin/smtp_client.zig/archive/e79e411862d4f4d41657bf41efb884efca3d67dd.tar.gz", - .hash = "12209907c69891a38e6923308930ac43bfb40135bc609ea370b5759fc2e1c4f57284", + .url = "https://github.com/karlseguin/smtp_client.zig/archive/964152ad4e19dc1d22f6def6f659c86df60e7832.tar.gz", + .hash = "1220d4f1c2472769b0d689ea878f41f0a66cb07f28569a138aea2c0a648a5c90dd4e", }, .httpz = .{ - .url = "https://github.com/karlseguin/http.zig/archive/bf748c6508090464213d3088a77d51faf9cd0876.tar.gz", - .hash = "12209b7426293ebe5075b930ae6029c009bfb6deb7ff92b9d69e28463abd14ad03da", + .url = "https://github.com/karlseguin/http.zig/archive/0d4a5cd520a54eaf800438e0b9093c77c90dcf11.tar.gz", + .hash = "12209b8216a80f21be12d43e588811150bdbbb53d35eac6a2a61c460f197350e19ad", }, }, diff --git a/src/jetzig.zig b/src/jetzig.zig index 3a72e6b..4d18546 100644 --- a/src/jetzig.zig +++ b/src/jetzig.zig @@ -16,7 +16,6 @@ pub const markdown = @import("jetzig/markdown.zig"); pub const jobs = @import("jetzig/jobs.zig"); pub const mail = @import("jetzig/mail.zig"); pub const kv = @import("jetzig/kv.zig"); -pub const HttpzServer = @import("jetzig/HttpzServer.zig"); /// The primary interface for a Jetzig application. Create an `App` in your application's /// `src/main.zig` and call `start` to launch the application. diff --git a/src/jetzig/HttpzServer.zig b/src/jetzig/HttpzServer.zig deleted file mode 100644 index 40184e3..0000000 --- a/src/jetzig/HttpzServer.zig +++ /dev/null @@ -1,58 +0,0 @@ -const std = @import("std"); - -const httpz = @import("httpz"); - -const jetzig = @import("../jetzig.zig"); - -allocator: std.mem.Allocator, -server: httpz.ServerCtx(void, void), - -const HttpzServer = @This(); - -const DispatcherFn = *const fn (*jetzig.Server, *httpz.Request, *httpz.Response) anyerror!void; - -pub fn init(allocator: std.mem.Allocator, dispatcherFn: DispatcherFn) !HttpzServer { - return .{ - .allocator = allocator, - .dispatcherFn = dispatcherFn, - .server = try httpz.Server().init(allocator, .{ .port = 8080 }), - }; -} - -pub fn deinit(self: *HttpzServer) void { - self.server.deinit(); -} - -pub fn configure(self: *HttpzServer, dispatcherFn: DispatcherFn) void { - // Bypass router. - self.server.notFound(dispatcherFn); -} - -// var server = ; -// -// // set a global dispatch for any routes defined from this point on -// server.dispatcher(mainDispatcher); -// -// // set a dispatcher for this route -// // note the use of "deleteC" the "C" is for Configuration and is used -// // since Zig doesn't have overloading or optional parameters. -// server.router().deleteC("/v1/session", logout, .{.dispatcher = loggedIn}) -// ... -// -// fn mainDispatcher(action: httpz.Action(void), req: *httpz.Request, res: *httpz.Response) !void { -// res.header("cors", "isslow"); -// return action(req, res); -// } -// -// fn loggedIn(action: httpz.Action(void), req: *httpz.Request, res: *httpz.Response) !void { -// if (req.header("authorization")) |_auth| { -// // TODO: make sure "auth" is valid! -// return mainDispatcher(action, req, res); -// } -// res.status = 401; -// res.body = "Not authorized"; -// } -// -// fn logout(req: *httpz.Request, res: *httpz.Response) !void { -// ... -// } diff --git a/src/jetzig/http/Request.zig b/src/jetzig/http/Request.zig index 7ff4428..abd6284 100644 --- a/src/jetzig/http/Request.zig +++ b/src/jetzig/http/Request.zig @@ -169,8 +169,23 @@ pub fn process(self: *Request) !void { self.processed = true; } +pub const CallbackState = struct { + arena: *std.heap.ArenaAllocator, + allocator: std.mem.Allocator, +}; + +pub fn responseCompleteCallback(ptr: *anyopaque) void { + var state: *CallbackState = @ptrCast(@alignCast(ptr)); + state.arena.deinit(); + state.allocator.destroy(state.arena); + state.allocator.destroy(state); +} + /// Set response headers, write response payload, and finalize the response. -pub fn respond(self: *Request) !void { +pub fn respond( + self: *Request, + state: *CallbackState, +) !void { if (!self.processed) unreachable; var cookie_it = self.cookies.headerIterator(); @@ -179,20 +194,14 @@ pub fn respond(self: *Request) !void { try self.response.headers.append("Set-Cookie", header); } - var std_response_headers = try self.response.headers.stdHeaders(); - defer std_response_headers.deinit(self.allocator); - for (self.response.headers.headers.items) |header| { - self.httpz_response.header( - try self.httpz_response.arena.dupe(u8, header.name), - try self.httpz_response.arena.dupe(u8, header.value), - ); + self.httpz_response.header(header.name, header.value); } const status = jetzig.http.status_codes.get(self.response.status_code); self.httpz_response.status = try status.getCodeInt(); - self.httpz_response.body = try self.httpz_response.arena.dupe(u8, self.response.content); - // try self.httpz_response.write(); + self.httpz_response.body = self.response.content; + self.httpz_response.callback(responseCompleteCallback, @ptrCast(state)); } /// Render a response. This function can only be called once per request (repeat calls will diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig index 2f5b236..9ea6dcb 100644 --- a/src/jetzig/http/Server.zig +++ b/src/jetzig/http/Server.zig @@ -60,48 +60,64 @@ pub fn deinit(self: *Server) void { self.allocator.free(self.options.bind); } +const Dispatcher = struct { + server: *Server, + + pub fn handle(self: Dispatcher, request: *httpz.Request, response: *httpz.Response) void { + self.server.processNextRequest(request, response) catch |err| { + self.server.errorHandlerFn(request, response, err); + }; + } +}; + pub fn listen(self: *Server) !void { - var httpz_server = try httpz.ServerCtx(*jetzig.http.Server, *jetzig.http.Server).init( + var httpz_server = try httpz.ServerCtx(Dispatcher, Dispatcher).init( self.allocator, .{ .port = self.options.port, .address = self.options.bind, - .workers = .{ .count = 20 }, - .thread_pool = .{ .count = 1 }, + .thread_pool = .{ .count = @intCast(try std.Thread.getCpuCount()) }, }, - self, + Dispatcher{ .server = self }, ); defer httpz_server.deinit(); - httpz_server.notFound(jetzig.http.Server.dispatcherFn); - // httpz_server.dispatcher(jetzig.http.Server.dispatcherFn); + + try self.logger.INFO("Listening on http://{s}:{} [{s}]", .{ + self.options.bind, + self.options.port, + @tagName(self.options.environment), + }); + + self.initialized = true; return try httpz_server.listen(); - // const address = try std.net.Address.parseIp(self.options.bind, self.options.port); - // self.std_net_server = try address.listen(.{ .reuse_port = true }); - // - // self.initialized = true; - // - // try self.logger.INFO("Listening on http://{s}:{} [{s}]", .{ - // self.options.bind, - // self.options.port, - // @tagName(self.options.environment), - // }); - // try self.processRequests(); } -pub fn dispatcherFn(self: *Server, request: *httpz.Request, response: *httpz.Response) !void { - var arena = std.heap.ArenaAllocator.init(self.allocator); - defer arena.deinit(); - const allocator = arena.allocator(); - try self.processNextRequest(allocator, request, response); +pub fn errorHandlerFn(self: *Server, request: *httpz.Request, response: *httpz.Response, err: anyerror) void { + if (isBadHttpError(err)) return; + + self.logger.ERROR("Encountered error: {s} {s}", .{ @errorName(err), request.url.raw }) catch {}; + response.body = "500 Internal Server Error"; } fn processNextRequest( self: *Server, - allocator: std.mem.Allocator, httpz_request: *httpz.Request, httpz_response: *httpz.Response, ) !void { + const state = try self.allocator.create(jetzig.http.Request.CallbackState); + const arena = try self.allocator.create(std.heap.ArenaAllocator); + arena.* = std.heap.ArenaAllocator.init(self.allocator); + state.* = .{ + .arena = arena, + .allocator = self.allocator, + }; + + // Regular arena deinit occurs in jetzig.http.Request.responseCompletCallback + errdefer state.arena.deinit(); + + const allocator = state.arena.allocator(); + const start_time = std.time.nanoTimestamp(); var response = try jetzig.http.Response.init(allocator); @@ -123,12 +139,12 @@ fn processNextRequest( try jetzig.http.middleware.beforeResponse(&middleware_data, &request); - try request.respond(); + try request.respond(state); try jetzig.http.middleware.afterResponse(&middleware_data, &request); jetzig.http.middleware.deinit(&middleware_data, &request); - // try self.logger.logRequest(&request); + try self.logger.logRequest(&request); } fn renderResponse(self: *Server, request: *jetzig.http.Request) !void {