diff --git a/demo/src/DemoMiddleware.zig b/demo/src/DemoMiddleware.zig index b1d8f5c..2209795 100644 --- a/demo/src/DemoMiddleware.zig +++ b/demo/src/DemoMiddleware.zig @@ -12,13 +12,13 @@ pub fn init(request: *jetzig.http.Request) !*Self { } pub fn beforeRequest(self: *Self, request: *jetzig.http.Request) !void { - request.server.logger.debug("[middleware] Before request, custom data: {d}", .{self.my_data}); + request.server.logger.debug("[DemoMiddleware] Before request, custom data: {d}", .{self.my_data}); self.my_data = 43; } pub fn afterRequest(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void { - request.server.logger.debug("[middleware] After request, custom data: {d}", .{self.my_data}); - request.server.logger.debug("[middleware] content-type: {s}", .{response.content_type}); + request.server.logger.debug("[DemoMiddleware] After request, custom data: {d}", .{self.my_data}); + request.server.logger.debug("[DemoMiddleware] content-type: {s}", .{response.content_type}); } pub fn deinit(self: *Self, request: *jetzig.http.Request) void { diff --git a/demo/src/app/views/quotes.zig b/demo/src/app/views/quotes.zig index d682c3c..76eac25 100644 --- a/demo/src/app/views/quotes.zig +++ b/demo/src/app/views/quotes.zig @@ -18,8 +18,10 @@ pub fn get(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig } pub fn post(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { - _ = data; + var root = try data.object(); const params = try request.params(); + try root.put("param", params.get("foo").?); + std.debug.print("{}\n", .{params}); return request.render(.ok); } diff --git a/src/jetzig.zig b/src/jetzig.zig index 7fec6e6..48d8b94 100644 --- a/src/jetzig.zig +++ b/src/jetzig.zig @@ -33,6 +33,7 @@ pub const View = views.View; pub const config = struct { pub const max_bytes_request_body: usize = std.math.pow(usize, 2, 16); pub const max_bytes_static_content: usize = std.math.pow(usize, 2, 16); + pub const http_buffer_size: usize = std.math.pow(usize, 2, 16); pub const public_content = .{ .path = "public" }; }; diff --git a/src/jetzig/App.zig b/src/jetzig/App.zig index 9c476c5..8c2c645 100644 --- a/src/jetzig/App.zig +++ b/src/jetzig/App.zig @@ -18,30 +18,46 @@ pub fn deinit(self: Self) void { /// Starts an application. `routes` should be `@import("routes").routes`, a generated file /// automatically created at build time. `templates` should be /// `@import("src/app/views/zmpl.manifest.zig").templates`, created by Zmpl at compile time. -pub fn start(self: Self, routes: []jetzig.views.Route, templates: []jetzig.TemplateFn) !void { +pub fn start(self: Self, comptime_routes: []jetzig.views.Route, templates: []jetzig.TemplateFn) !void { var mime_map = jetzig.http.mime.MimeMap.init(self.allocator); defer mime_map.deinit(); try mime_map.build(); + var routes = std.ArrayList(*jetzig.views.Route).init(self.allocator); + + for (comptime_routes) |*comptime_route| { + var route = try self.allocator.create(jetzig.views.Route); + route.* = jetzig.views.Route{ + .name = comptime_route.name, + .action = comptime_route.action, + .uri_path = comptime_route.uri_path, + .view = comptime_route.view, + .static_view = comptime_route.static_view, + .static = comptime_route.static, + .render = comptime_route.render, + .renderStatic = comptime_route.renderStatic, + .template = comptime_route.template, + .json_params = comptime_route.json_params, + }; + try route.initParams(self.allocator); + try routes.append(route); + } + defer routes.deinit(); + defer for (routes.items) |route| { + route.deinitParams(); + self.allocator.destroy(route); + }; + var server = jetzig.http.Server.init( self.allocator, self.host, self.port, self.server_options, - routes, + routes.items, templates, &mime_map, ); - for (routes) |*route| { - var mutable = @constCast(route); // FIXME - try mutable.initParams(self.allocator); - } - defer for (routes) |*route| { - var mutable = @constCast(route); // FIXME - mutable.deinitParams(); - }; - defer server.deinit(); defer self.allocator.free(self.root_path); defer self.allocator.free(self.host); diff --git a/src/jetzig/http/Headers.zig b/src/jetzig/http/Headers.zig index 98167e3..a457fb7 100644 --- a/src/jetzig/http/Headers.zig +++ b/src/jetzig/http/Headers.zig @@ -1,35 +1,44 @@ const std = @import("std"); allocator: std.mem.Allocator, -std_headers: std.http.Headers, +headers: HeadersArray, const Self = @This(); +pub const max_headers = 25; +const HeadersArray = std.ArrayListUnmanaged(std.http.Header); -pub fn init(allocator: std.mem.Allocator, headers: std.http.Headers) Self { - return .{ .allocator = allocator, .std_headers = headers }; +pub fn init(allocator: std.mem.Allocator) Self { + return .{ + .allocator = allocator, + .headers = HeadersArray.initCapacity(allocator, max_headers) catch @panic("OOM"), + }; } pub fn deinit(self: *Self) void { - self.std_headers.deinit(); + self.headers.deinit(); } // Gets the first value for a given header identified by `name`. pub fn getFirstValue(self: *Self, name: []const u8) ?[]const u8 { - return self.std_headers.getFirstValue(name); + for (self.headers.items) |header| { + if (std.mem.eql(u8, header.name, name)) return header.value; + } + return null; } /// Appends `name` and `value` to headers. pub fn append(self: *Self, name: []const u8, value: []const u8) !void { - try self.std_headers.append(name, value); + self.headers.appendAssumeCapacity(.{ .name = name, .value = value }); } /// Returns an iterator which implements `next()` returning each name/value of the stored headers. pub fn iterator(self: *Self) Iterator { - return Iterator{ .std_headers = self.std_headers }; + return Iterator{ .headers = self.headers }; } +/// Iterates through stored headers yielidng a `Header` on each call to `next()` const Iterator = struct { - std_headers: std.http.Headers, + headers: HeadersArray, index: usize = 0, const Header = struct { @@ -39,8 +48,8 @@ const Iterator = struct { /// Returns the next item in the current iteration of headers. pub fn next(self: *Iterator) ?Header { - if (self.std_headers.list.items.len > self.index) { - const std_header = self.std_headers.list.items[self.index]; + if (self.headers.items.len > self.index) { + const std_header = self.headers.items[self.index]; self.index += 1; return .{ .name = std_header.name, .value = std_header.value }; } else { diff --git a/src/jetzig/http/Request.zig b/src/jetzig/http/Request.zig index 2346595..69ea24a 100644 --- a/src/jetzig/http/Request.zig +++ b/src/jetzig/http/Request.zig @@ -15,22 +15,24 @@ method: Method, headers: jetzig.http.Headers, segments: std.ArrayList([]const u8), server: *jetzig.http.Server, -session: *jetzig.http.Session, +std_http_request: std.http.Server.Request, response: *jetzig.http.Response, status_code: jetzig.http.status_codes.StatusCode = undefined, response_data: *jetzig.data.Data, query_data: *jetzig.data.Data, query: *jetzig.http.Query, -cookies: *jetzig.http.Cookies, -body: []const u8, +cookies: *jetzig.http.Cookies = undefined, +session: *jetzig.http.Session = undefined, +body: []const u8 = undefined, +processed: bool = false, pub fn init( allocator: std.mem.Allocator, server: *jetzig.http.Server, + std_http_request: std.http.Server.Request, response: *jetzig.http.Response, - body: []const u8, ) !Self { - const method = switch (response.std_response.request.method) { + const method = switch (std_http_request.head.method) { .DELETE => Method.DELETE, .GET => Method.GET, .PATCH => Method.PATCH, @@ -50,7 +52,7 @@ pub fn init( // * Extension: "/foo/bar/baz/1.json" => ".json" // * Query params: "/foo/bar/baz?foo=bar&baz=qux" => .{ .foo = "bar", .baz => "qux" } // * Anything else ? - var it = std.mem.splitScalar(u8, response.std_response.request.target, '/'); + var it = std.mem.splitScalar(u8, std_http_request.head.target, '/'); var segments = std.ArrayList([]const u8).init(allocator); while (it.next()) |segment| { if (std.mem.indexOfScalar(u8, segment, '?')) |query_index| { @@ -60,25 +62,6 @@ pub fn init( } } - var cookies = try allocator.create(jetzig.http.Cookies); - cookies.* = jetzig.http.Cookies.init( - allocator, - response.std_response.request.headers.getFirstValue("Cookie") orelse "", - ); - try cookies.parse(); - - var session = try allocator.create(jetzig.http.Session); - session.* = jetzig.http.Session.init(allocator, cookies, server.options.secret); - session.parse() catch |err| { - switch (err) { - error.JetzigInvalidSessionCookie => { - server.logger.debug("Invalid session cookie detected. Resetting session.", .{}); - try session.reset(); - }, - else => return err, - } - }; - const response_data = try allocator.create(jetzig.data.Data); response_data.* = jetzig.data.Data.init(allocator); @@ -89,26 +72,82 @@ pub fn init( return .{ .allocator = allocator, - .path = response.std_response.request.target, + .path = std_http_request.head.target, .method = method, - .headers = jetzig.http.Headers.init(allocator, response.std_response.request.headers), + .headers = jetzig.http.Headers.init(allocator), .server = server, .segments = segments, - .cookies = cookies, - .session = session, + .response = response, .response_data = response_data, .query_data = query_data, .query = query, - .body = body, - .response = response, + .std_http_request = std_http_request, }; } pub fn deinit(self: *Self) void { - self.session.deinit(); + // self.session.deinit(); self.segments.deinit(); self.allocator.destroy(self.cookies); self.allocator.destroy(self.session); + if (self.processed) self.allocator.free(self.body); +} + +/// Process request, read body if present, parse headers (TODO) +pub fn process(self: *Self) !void { + var headers_it = self.std_http_request.iterateHeaders(); + var cookie: ?[]const u8 = null; + + while (headers_it.next()) |header| { + try self.headers.append(header.name, header.value); + if (std.mem.eql(u8, header.name, "Cookie")) cookie = header.value; + } + + self.cookies = try self.allocator.create(jetzig.http.Cookies); + self.cookies.* = jetzig.http.Cookies.init( + self.allocator, + cookie orelse "", + ); + try self.cookies.parse(); + + self.session = try self.allocator.create(jetzig.http.Session); + self.session.* = jetzig.http.Session.init(self.allocator, self.cookies, self.server.options.secret); + self.session.parse() catch |err| { + switch (err) { + error.JetzigInvalidSessionCookie => { + self.server.logger.debug("Invalid session cookie detected. Resetting session.", .{}); + try self.session.reset(); + }, + else => return err, + } + }; + + const reader = try self.std_http_request.reader(); + self.body = try reader.readAllAlloc(self.allocator, jetzig.config.max_bytes_request_body); + self.processed = true; +} + +/// Set response headers, write response payload, and finalize the response. +pub fn respond(self: *Self) !void { + if (!self.processed) unreachable; + + var cookie_it = self.cookies.headerIterator(); + while (try cookie_it.next()) |header| { + // FIXME: Skip setting cookies that are already present ? + try self.response.headers.append("Set-Cookie", header); + } + + // TODO: Move to jetzig.http.Response.stdHeaders() + var std_response_headers = std.ArrayList(std.http.Header).init(self.allocator); + var headers_it = self.response.headers.iterator(); + while (headers_it.next()) |header| try std_response_headers.append( + .{ .name = header.name, .value = header.value }, + ); + + try self.std_http_request.respond( + self.response.content, + .{ .keep_alive = false, .extra_headers = std_response_headers.items }, + ); } pub fn render(self: *Self, status_code: jetzig.http.status_codes.StatusCode) jetzig.views.View { @@ -129,6 +168,8 @@ pub fn getHeader(self: *Self, key: []const u8) ?[]const u8 { /// otherwise the parsed JSON request body will take precedence and query parameters will be /// ignored. pub fn params(self: *Self) !*jetzig.data.Value { + if (!self.processed) unreachable; + switch (self.requestFormat()) { .JSON => { if (self.body.len == 0) return self.queryParams(); @@ -137,7 +178,7 @@ pub fn params(self: *Self) !*jetzig.data.Value { data.* = jetzig.data.Data.init(self.allocator); data.fromJson(self.body) catch |err| { switch (err) { - error.UnexpectedEndOfInput => return error.JetzigBodyParseError, + error.SyntaxError, error.UnexpectedEndOfInput => return error.JetzigBodyParseError, else => return err, } }; @@ -253,22 +294,30 @@ pub fn resourceId(self: *Self) []const u8 { // Determine if a given route matches the current request. pub fn match(self: *Self, route: jetzig.views.Route) !bool { - switch (self.method) { - .GET => { - return switch (route.action) { - .index => self.isMatch(.exact, route), - .get => self.isMatch(.resource_id, route), - else => false, - }; + return switch (self.method) { + .GET => switch (route.action) { + .index => self.isMatch(.exact, route), + .get => self.isMatch(.resource_id, route), + else => false, }, - .POST => return self.isMatch(.exact, route), - .PUT => return self.isMatch(.resource_id, route), - .PATCH => return self.isMatch(.resource_id, route), - .DELETE => return self.isMatch(.resource_id, route), - else => return false, - } - - return false; + .POST => switch (route.action) { + .post => self.isMatch(.exact, route), + else => false, + }, + .PUT => switch (route.action) { + .put => self.isMatch(.resource_id, route), + else => false, + }, + .PATCH => switch (route.action) { + .patch => self.isMatch(.resource_id, route), + else => false, + }, + .DELETE => switch (route.action) { + .delete => self.isMatch(.resource_id, route), + else => false, + }, + .HEAD, .CONNECT, .OPTIONS, .TRACE => false, + }; } fn isMatch(self: *Self, match_type: enum { exact, resource_id }, route: jetzig.views.Route) bool { diff --git a/src/jetzig/http/Response.zig b/src/jetzig/http/Response.zig index a40bf1d..3f2bb0b 100644 --- a/src/jetzig/http/Response.zig +++ b/src/jetzig/http/Response.zig @@ -5,7 +5,6 @@ const http = @import("../http.zig"); const Self = @This(); allocator: std.mem.Allocator, -std_response: *std.http.Server.Response, headers: *jetzig.http.Headers, content: []const u8, status_code: http.status_codes.StatusCode, @@ -13,14 +12,12 @@ content_type: []const u8, pub fn init( allocator: std.mem.Allocator, - std_response: *std.http.Server.Response, ) !Self { const headers = try allocator.create(jetzig.http.Headers); - headers.* = jetzig.http.Headers.init(allocator, std_response.headers); + headers.* = jetzig.http.Headers.init(allocator); return .{ .allocator = allocator, - .std_response = std_response, .status_code = .no_content, .content_type = "application/octet-stream", .content = "", @@ -33,53 +30,3 @@ pub fn deinit(self: *const Self) void { self.allocator.destroy(self.headers); self.std_response.deinit(); } - -const ResetState = enum { reset, closing }; - -/// Resets the current connection. -pub fn reset(self: *const Self) ResetState { - return switch (self.std_response.reset()) { - .reset => .reset, - .closing => .closing, - }; -} - -/// Waits for the current request to finish sending. -pub fn wait(self: *const Self) !void { - try self.std_response.wait(); -} - -/// Finalizes a request. Appends any stored headers, sets the response status code, and writes -/// the response body. -pub fn finish(self: *const Self) !void { - self.std_response.status = switch (self.status_code) { - inline else => |status_code| @field(std.http.Status, @tagName(status_code)), - }; - - var it = self.headers.iterator(); - while (it.next()) |header| { - try self.std_response.headers.append(header.name, header.value); - } - - try self.std_response.send(); - try self.std_response.writeAll(self.content); - try self.std_response.finish(); -} - -/// Reads the current request body. Caller owns memory. -pub fn read(self: *const Self) ![]const u8 { - return try self.std_response.reader().readAllAlloc(self.allocator, jetzig.config.max_bytes_request_body); -} - -const TransferEncodingOptions = struct { - content_length: usize, -}; - -/// Sets the transfer encoding for the current response (content length/chunked encoding). -/// ``` -/// setTransferEncoding(.{ .content_length = 1000 }); -/// ``` -pub fn setTransferEncoding(self: *const Self, transfer_encoding: TransferEncodingOptions) void { - // TODO: Chunked encoding - self.std_response.transfer_encoding = .{ .content_length = transfer_encoding.content_length }; -} diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig index 0f28780..098a87d 100644 --- a/src/jetzig/http/Server.zig +++ b/src/jetzig/http/Server.zig @@ -16,7 +16,6 @@ pub const ServerOptions = struct { secret: []const u8, }; -server: std.http.Server, allocator: std.mem.Allocator, port: u16, host: []const u8, @@ -24,9 +23,10 @@ cache: jetzig.caches.Cache, logger: jetzig.loggers.Logger, options: ServerOptions, start_time: i128 = undefined, -routes: []jetzig.views.Route, +routes: []*jetzig.views.Route, templates: []jetzig.TemplateFn, mime_map: *jetzig.http.mime.MimeMap, +std_net_server: std.net.Server = undefined, const Self = @This(); @@ -35,14 +35,11 @@ pub fn init( host: []const u8, port: u16, options: ServerOptions, - routes: []jetzig.views.Route, + routes: []*jetzig.views.Route, templates: []jetzig.TemplateFn, mime_map: *jetzig.http.mime.MimeMap, ) Self { - const server = std.http.Server.init(.{ .reuse_address = true }); - return .{ - .server = server, .allocator = allocator, .host = host, .port = port, @@ -56,109 +53,93 @@ pub fn init( } pub fn deinit(self: *Self) void { - self.server.deinit(); + self.std_net_server.deinit(); } pub fn listen(self: *Self) !void { - const address = std.net.Address.parseIp(self.host, self.port) catch unreachable; + const address = try std.net.Address.parseIp("127.0.0.1", 8080); + self.std_net_server = try address.listen(.{ .reuse_port = true }); - try self.server.listen(address); const cache_status = if (self.options.cache == .null_cache) "disabled" else "enabled"; self.logger.debug("Listening on http://{s}:{} [cache:{s}]", .{ self.host, self.port, cache_status }); try self.processRequests(); } fn processRequests(self: *Self) !void { + // TODO: Keepalive while (true) { var arena = std.heap.ArenaAllocator.init(self.allocator); + errdefer arena.deinit(); const allocator = arena.allocator(); - var std_response = try self.server.accept(.{ .allocator = allocator }); + const connection = try self.std_net_server.accept(); - var response = try jetzig.http.Response.init( - allocator, - &std_response, - ); - errdefer response.deinit(); - errdefer arena.deinit(); + var buf: [jetzig.config.http_buffer_size]u8 = undefined; + var std_http_server = std.http.Server.init(connection, &buf); + errdefer std_http_server.connection.stream.close(); - try response.headers.append("Connection", "close"); + self.processNextRequest(allocator, &std_http_server) catch |err| { + if (isBadHttpError(err)) { + std.debug.print("Encountered HTTP error: {s}\n", .{@errorName(err)}); + std_http_server.connection.stream.close(); + continue; + } else return err; + }; - while (response.reset() != .closing) { - self.processNextRequest(allocator, &response) catch |err| { - switch (err) { - error.EndOfStream, error.ConnectionResetByPeer => continue, - error.UnknownHttpMethod => continue, // TODO: Render 400 Bad Request here ? - else => return err, - } - }; - } - - response.deinit(); + std_http_server.connection.stream.close(); arena.deinit(); } } -fn processNextRequest(self: *Self, allocator: std.mem.Allocator, response: *jetzig.http.Response) !void { - try response.wait(); - +fn processNextRequest(self: *Self, allocator: std.mem.Allocator, std_http_server: *std.http.Server) !void { self.start_time = std.time.nanoTimestamp(); - const body = try response.read(); - defer self.allocator.free(body); + const std_http_request = try std_http_server.receiveHead(); + if (std_http_server.state == .receiving_head) return error.JetzigParseHeadError; - var request = try jetzig.http.Request.init(allocator, self, response, body); - defer request.deinit(); + var response = try jetzig.http.Response.init(allocator); + var request = try jetzig.http.Request.init(allocator, self, std_http_request, &response); + + try request.process(); var middleware_data = try jetzig.http.middleware.beforeMiddleware(&request); - try self.renderResponse(&request, response); + try self.renderResponse(&request); + try request.response.headers.append("content-type", response.content_type); + try request.respond(); - try jetzig.http.middleware.afterMiddleware(&middleware_data, &request, response); + try jetzig.http.middleware.afterMiddleware(&middleware_data, &request); + jetzig.http.middleware.deinit(&middleware_data, &request); - response.setTransferEncoding(.{ .content_length = response.content.len }); - - var cookie_it = request.cookies.headerIterator(); - while (try cookie_it.next()) |header| { - // FIXME: Skip setting cookies that are already present ? - try response.headers.append("Set-Cookie", header); - } - - try response.headers.append("Content-Type", response.content_type); - - try response.finish(); - - const log_message = try self.requestLogMessage(&request, response); + const log_message = try self.requestLogMessage(&request); defer self.allocator.free(log_message); self.logger.debug("{s}", .{log_message}); - - jetzig.http.middleware.deinit(&middleware_data, &request); } -fn renderResponse(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void { - const static = self.matchStaticResource(request) catch |err| { +fn renderResponse(self: *Self, request: *jetzig.http.Request) !void { + const static_resource = self.matchStaticResource(request) catch |err| { if (isUnhandledError(err)) return err; const rendered = try self.renderInternalServerError(request, err); - response.content = rendered.content; - response.status_code = .internal_server_error; - response.content_type = "text/html"; + request.response.content = rendered.content; + request.response.status_code = .internal_server_error; + request.response.content_type = "text/html"; return; }; - if (static) |resource| { - try renderStatic(resource, response); + if (static_resource) |resource| { + try renderStatic(resource, request.response); return; } const route = try self.matchRoute(request, false); switch (request.requestFormat()) { - .HTML => try self.renderHTML(request, response, route), - .JSON => try self.renderJSON(request, response, route), - .UNKNOWN => try self.renderHTML(request, response, route), + .HTML => try self.renderHTML(request, route), + .JSON => try self.renderJSON(request, route), + .UNKNOWN => try self.renderHTML(request, route), } } @@ -171,32 +152,30 @@ fn renderStatic(resource: StaticResource, response: *jetzig.http.Response) !void fn renderHTML( self: *Self, request: *jetzig.http.Request, - response: *jetzig.http.Response, - route: ?jetzig.views.Route, + route: ?*jetzig.views.Route, ) !void { if (route) |matched_route| { for (self.templates) |template| { // TODO: Use a hashmap to avoid O(n) if (std.mem.eql(u8, matched_route.template, template.name)) { const rendered = try self.renderView(matched_route, request, template); - response.content = rendered.content; - response.status_code = rendered.view.status_code; - response.content_type = "text/html"; + request.response.content = rendered.content; + request.response.status_code = rendered.view.status_code; + request.response.content_type = "text/html"; return; } } } - response.content = ""; - response.status_code = .not_found; - response.content_type = "text/html"; + request.response.content = ""; + request.response.status_code = .not_found; + request.response.content_type = "text/html"; } fn renderJSON( self: *Self, request: *jetzig.http.Request, - response: *jetzig.http.Response, - route: ?jetzig.views.Route, + route: ?*jetzig.views.Route, ) !void { if (route) |matched_route| { const rendered = try self.renderView(matched_route, request, null); @@ -205,13 +184,13 @@ fn renderJSON( if (data.value) |_| {} else _ = try data.object(); try request.headers.append("Content-Type", "application/json"); - response.content = try data.toJson(); - response.status_code = rendered.view.status_code; - response.content_type = "application/json"; + request.response.content = try data.toJson(); + request.response.status_code = rendered.view.status_code; + request.response.content_type = "application/json"; } else { - response.content = ""; - response.status_code = .not_found; - response.content_type = "application/json"; + request.response.content = ""; + request.response.status_code = .not_found; + request.response.content_type = "application/json"; } } @@ -219,11 +198,11 @@ const RenderedView = struct { view: jetzig.views.View, content: []const u8 }; fn renderView( self: *Self, - route: jetzig.views.Route, + route: *jetzig.views.Route, request: *jetzig.http.Request, template: ?jetzig.TemplateFn, ) !RenderedView { - const view = route.render(route, request) catch |err| { + const view = route.render(route.*, request) catch |err| { self.logger.debug("Encountered error: {s}", .{@errorName(err)}); if (isUnhandledError(err)) return err; if (isBadRequest(err)) return try self.renderBadRequest(request); @@ -248,6 +227,24 @@ fn isUnhandledError(err: anyerror) bool { }; } +fn isBadHttpError(err: anyerror) bool { + return switch (err) { + error.JetzigParseHeadError, + error.UnknownHttpMethod, + error.HttpHeadersInvalid, + error.HttpHeaderContinuationsUnsupported, + error.HttpTransferEncodingUnsupported, + error.HttpConnectionHeaderUnsupported, + error.InvalidContentLength, + error.CompressionUnsupported, + error.MissingFinalNewline, + error.HttpConnectionClosing, + error.ConnectionResetByPeer, + => true, + else => false, + }; +} + fn renderInternalServerError(self: *Self, request: *jetzig.http.Request, err: anyerror) !RenderedView { request.response_data.reset(); @@ -292,8 +289,8 @@ fn logStackTrace( try object.put("backtrace", request.response_data.string(array.items)); } -fn requestLogMessage(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) ![]const u8 { - const status: jetzig.http.status_codes.TaggedStatusCode = switch (response.status_code) { +fn requestLogMessage(self: *Self, request: *jetzig.http.Request) ![]const u8 { + const status: jetzig.http.status_codes.TaggedStatusCode = switch (request.response.status_code) { inline else => |status_code| @unionInit( jetzig.http.status_codes.TaggedStatusCode, @tagName(status_code), @@ -316,14 +313,14 @@ fn duration(self: *Self) i64 { return @intCast(std.time.nanoTimestamp() - self.start_time); } -fn matchRoute(self: *Self, request: *jetzig.http.Request, static: bool) !?jetzig.views.Route { +fn matchRoute(self: *Self, request: *jetzig.http.Request, static: bool) !?*jetzig.views.Route { for (self.routes) |route| { // .index routes always take precedence. - if (route.static == static and route.action == .index and try request.match(route)) return route; + if (route.static == static and route.action == .index and try request.match(route.*)) return route; } for (self.routes) |route| { - if (route.static == static and try request.match(route)) return route; + if (route.static == static and try request.match(route.*)) return route; } return null; @@ -398,7 +395,7 @@ fn matchStaticContent(self: *Self, request: *jetzig.http.Request) !?[]const u8 { const matched_route = try self.matchRoute(request, true); if (matched_route) |route| { - const static_path = try staticPath(request, route); + const static_path = try staticPath(request, route.*); if (static_path) |capture| { return static_dir.readFileAlloc( diff --git a/src/jetzig/http/middleware.zig b/src/jetzig/http/middleware.zig index 8729fa0..2817f1b 100644 --- a/src/jetzig/http/middleware.zig +++ b/src/jetzig/http/middleware.zig @@ -40,7 +40,6 @@ pub fn beforeMiddleware(request: *jetzig.http.Request) !MiddlewareData { pub fn afterMiddleware( middleware_data: *MiddlewareData, request: *jetzig.http.Request, - response: *jetzig.http.Response, ) !void { inline for (middlewares, 0..) |middleware, index| { if (comptime !@hasDecl(middleware, "afterRequest")) continue; @@ -49,10 +48,10 @@ pub fn afterMiddleware( try @call( .always_inline, middleware.afterRequest, - .{ @as(*middleware, @ptrCast(@alignCast(data))), request, response }, + .{ @as(*middleware, @ptrCast(@alignCast(data))), request, request.response }, ); } else { - try @call(.always_inline, middleware.afterRequest, .{ request, response }); + try @call(.always_inline, middleware.afterRequest, .{ request, request.response }); } } } diff --git a/src/jetzig/views/Route.zig b/src/jetzig/views/Route.zig index 75061e5..0d29b3a 100644 --- a/src/jetzig/views/Route.zig +++ b/src/jetzig/views/Route.zig @@ -61,7 +61,10 @@ pub fn initParams(self: *Self, allocator: std.mem.Allocator) !void { } pub fn deinitParams(self: *const Self) void { - for (self.params.items) |data| data.deinit(); + for (self.params.items) |data| { + data.deinit(); + data._allocator.destroy(data); + } self.params.deinit(); }