From 9684181c64f4a11ea7099498c1a1682dbba1086b Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Sat, 23 Nov 2024 19:31:37 +0000 Subject: [PATCH] Fix middleware render detection --- src/jetzig/http/Request.zig | 31 +++++++++++++++++------- src/jetzig/http/Server.zig | 9 +++---- src/jetzig/http/middleware.zig | 19 +++++++++++---- src/jetzig/middleware/HtmxMiddleware.zig | 6 ++++- 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/jetzig/http/Request.zig b/src/jetzig/http/Request.zig index b8e74de..41de5c9 100644 --- a/src/jetzig/http/Request.zig +++ b/src/jetzig/http/Request.zig @@ -11,6 +11,16 @@ pub const Method = enum { DELETE, GET, PATCH, POST, HEAD, PUT, CONNECT, OPTIONS, pub const Modifier = enum { edit, new }; pub const Format = enum { HTML, JSON, UNKNOWN }; pub const Protocol = enum { http, https }; +pub const RequestState = enum { + initial, // No processing has taken place + processed, // Request headers have been processed + after_request, // Initial middleware processing + rendered, // Rendered by middleware or view + redirected, // Redirected by middleware or view + failed, // Failed by middleware or view + before_response, // Post middleware processing + finalized, // Response generated +}; allocator: std.mem.Allocator, path: jetzig.http.Path, @@ -30,14 +40,12 @@ parsed_multipart: ?*jetzig.data.Data = null, _cookies: ?*jetzig.http.Cookies = null, _session: ?*jetzig.http.Session = null, body: []const u8 = undefined, -state: enum { initial, rendered, redirected, failed, processed } = .initial, -response_started: bool = false, +state: RequestState = .initial, dynamic_assigned_template: ?[]const u8 = null, layout: ?[]const u8 = null, layout_disabled: bool = false, redirect_state: ?RedirectState = null, middleware_rendered: ?struct { name: []const u8, action: []const u8 } = null, -middleware_rendered_during_response: bool = false, middleware_data: jetzig.http.middleware.MiddlewareData = undefined, rendered_multiple: bool = false, rendered_view: ?jetzig.views.View = null, @@ -172,6 +180,7 @@ pub fn respond(self: *Request) !void { const status = jetzig.http.status_codes.get(self.response.status_code); self.httpz_response.status = try status.getCodeInt(); self.httpz_response.body = self.response.content; + self.state = .finalized; } /// Set the root value for response data. @@ -186,10 +195,9 @@ pub fn data(self: Request, comptime root: @TypeOf(.enum_literal)) !*jetzig.Data. /// Render a response. This function can only be called once per request (repeat calls will /// trigger an error). pub fn render(self: *Request, status_code: jetzig.http.status_codes.StatusCode) jetzig.views.View { - if (self.state != .processed) self.rendered_multiple = true; + if (self.isRendered()) self.rendered_multiple = true; self.state = .rendered; - if (self.response_started) self.middleware_rendered_during_response = true; self.rendered_view = .{ .data = self.response_data, .status_code = status_code }; return self.rendered_view.?; } @@ -197,14 +205,20 @@ pub fn render(self: *Request, status_code: jetzig.http.status_codes.StatusCode) /// Render an error. This function can only be called once per request (repeat calls will /// trigger an error). pub fn fail(self: *Request, status_code: jetzig.http.status_codes.StatusCode) jetzig.views.View { - if (self.state != .processed) self.rendered_multiple = true; + if (self.isRendered()) self.rendered_multiple = true; self.state = .failed; - if (self.response_started) self.middleware_rendered_during_response = true; self.rendered_view = .{ .data = self.response_data, .status_code = status_code }; return self.rendered_view.?; } +pub inline fn isRendered(self: *const Request) bool { + return switch (self.state) { + .initial, .processed, .after_request, .before_response => false, + .rendered, .redirected, .failed, .finalized => true, + }; +} + /// Issue a redirect to a new location. /// ```zig /// return request.redirect("https://www.example.com/", .moved_permanently); @@ -218,10 +232,9 @@ pub fn redirect( location: []const u8, redirect_status: enum { moved_permanently, found }, ) jetzig.views.View { - if (self.state != .processed) self.rendered_multiple = true; + if (self.isRendered()) self.rendered_multiple = true; self.state = .redirected; - if (self.response_started) self.middleware_rendered_during_response = true; const status_code = switch (redirect_status) { .moved_permanently => jetzig.http.status_codes.StatusCode.moved_permanently, diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig index fad3456..ca0c5d4 100644 --- a/src/jetzig/http/Server.zig +++ b/src/jetzig/http/Server.zig @@ -138,7 +138,8 @@ pub fn processNextRequest( var middleware_data = try jetzig.http.middleware.afterRequest(&request); - if (request.middleware_rendered) |_| { // Request processing ends when a middleware renders or redirects. + if (request.middleware_rendered) |_| { + // Request processing ends when a middleware renders or redirects. if (request.redirect_state) |state| { try request.renderRedirect(state); } else if (request.rendered_view) |rendered| { @@ -150,8 +151,8 @@ pub fn processNextRequest( } else { try self.renderResponse(&request); try request.response.headers.append("Content-Type", response.content_type); - try jetzig.http.middleware.beforeResponse(&middleware_data, &request); + try jetzig.http.middleware.beforeResponse(&middleware_data, &request); try request.respond(); try jetzig.http.middleware.afterResponse(&middleware_data, &request); jetzig.http.middleware.deinit(&middleware_data, &request); @@ -371,7 +372,7 @@ fn renderView( }; } } else { - if (request.state != .redirected) { + if (request.state == .processed) { try self.logger.WARN("`request.render` was not invoked. Rendering empty content.", .{}); } request.response_data.reset(); @@ -475,8 +476,6 @@ fn renderInternalServerError( stack_trace: ?*std.builtin.StackTrace, err: anyerror, ) !RenderedView { - // request.response_data.reset(); - try self.logger.logError(stack_trace, err); const status = jetzig.http.StatusCode.internal_server_error; diff --git a/src/jetzig/http/middleware.zig b/src/jetzig/http/middleware.zig index 31dc50c..e049a17 100644 --- a/src/jetzig/http/middleware.zig +++ b/src/jetzig/http/middleware.zig @@ -60,8 +60,11 @@ pub fn afterRequest(request: *jetzig.http.Request) !MiddlewareData { } } + request.state = .after_request; + inline for (middlewares, 0..) |middleware, index| { if (comptime !@hasDecl(middleware, "afterRequest")) continue; + if (comptime @hasDecl(middleware, "init")) { const data = middleware_data.get(index).?; try @call( @@ -72,7 +75,8 @@ pub fn afterRequest(request: *jetzig.http.Request) !MiddlewareData { } else { try @call(.always_inline, middleware.afterRequest, .{request}); } - if (request.state != .initial) { + + if (request.state != .after_request) { request.middleware_rendered = .{ .name = @typeName(middleware), .action = "afterRequest" }; break; } @@ -86,11 +90,11 @@ pub fn beforeResponse( middleware_data: *MiddlewareData, request: *jetzig.http.Request, ) !void { - request.response_started = true; + request.state = .before_response; inline for (middlewares, 0..) |middleware, index| { if (comptime !@hasDecl(middleware, "beforeResponse")) continue; - if (!request.middleware_rendered_during_response) { + if (request.state == .before_response) { if (comptime @hasDecl(middleware, "init")) { const data = middleware_data.get(index).?; try @call( @@ -99,10 +103,15 @@ pub fn beforeResponse( .{ @as(*middleware, @ptrCast(@alignCast(data))), request, request.response }, ); } else { - try @call(.always_inline, middleware.beforeResponse, .{ request, request.response }); + try @call( + .always_inline, + middleware.beforeResponse, + .{ request, request.response }, + ); } } - if (request.middleware_rendered_during_response) { + + if (request.state != .before_response) { request.middleware_rendered = .{ .name = @typeName(middleware), .action = "beforeResponse", diff --git a/src/jetzig/middleware/HtmxMiddleware.zig b/src/jetzig/middleware/HtmxMiddleware.zig index 022d010..a727f0e 100644 --- a/src/jetzig/middleware/HtmxMiddleware.zig +++ b/src/jetzig/middleware/HtmxMiddleware.zig @@ -20,7 +20,11 @@ pub fn afterRequest(request: *jetzig.http.Request) !void { /// If a redirect was issued during request processing, reset any response data, set response /// status to `200 OK` and replace the `Location` header with a `HX-Redirect` header. pub fn beforeResponse(request: *jetzig.http.Request, response: *jetzig.http.Response) !void { - if (response.status_code != .moved_permanently and response.status_code != .found) return; + switch (response.status_code) { + .moved_permanently, .found => {}, + else => return, + } + if (request.headers.get("HX-Request") == null) return; if (response.headers.get("Location")) |location| {