Fix middleware render detection

This commit is contained in:
Bob Farrell 2024-11-23 19:31:37 +00:00
parent 1565ae3b73
commit 9684181c64
4 changed files with 45 additions and 20 deletions

View File

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

View File

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

View File

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

View File

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