mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 22:16:08 +00:00
Implement redirects + htmx redirect support
This commit is contained in:
parent
9b255eb19a
commit
993caa5d3f
@ -16,8 +16,8 @@ const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
/// Define any custom data fields you want to store here. Assigning to these fields in the `init`
|
||||
/// function allows you to access them in the `beforeRequest` and `afterRequest` functions, where
|
||||
/// they can also be modified.
|
||||
/// function allows you to access them in various middleware callbacks defined below, where they
|
||||
/// can also be modified.
|
||||
my_custom_value: []const u8,
|
||||
|
||||
const Self = @This();
|
||||
@ -29,25 +29,34 @@ pub fn init(request: *jetzig.http.Request) !*Self {
|
||||
return middleware;
|
||||
}
|
||||
|
||||
/// Invoked immediately after the request head has been processed, before relevant view function
|
||||
/// is processed. This gives you access to request headers but not the request body.
|
||||
pub fn beforeRequest(self: *Self, request: *jetzig.http.Request) !void {
|
||||
request.server.logger.debug("[DemoMiddleware] my_custom_value: {s}", .{self.my_custom_value});
|
||||
/// Invoked immediately after the request is received but before it has started processing.
|
||||
/// Any calls to `request.render` or `request.redirect` will prevent further processing of the
|
||||
/// request, including any other middleware in the chain.
|
||||
pub fn afterRequest(self: *Self, request: *jetzig.http.Request) !void {
|
||||
request.server.logger.debug("[DemoMiddleware:afterRequest] my_custom_value: {s}", .{self.my_custom_value});
|
||||
self.my_custom_value = @tagName(request.method);
|
||||
}
|
||||
|
||||
/// Invoked immediately after the request has finished responding. Provides full access to the
|
||||
/// response as well as the request.
|
||||
pub fn afterRequest(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
||||
/// Invoked immediately before the response renders to the client.
|
||||
/// The response can be modified here if needed.
|
||||
pub fn beforeResponse(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
||||
request.server.logger.debug(
|
||||
"[DemoMiddleware] my_custom_value: {s}, response status: {s}",
|
||||
"[DemoMiddleware:beforeResponse] my_custom_value: {s}, response status: {s}",
|
||||
.{ self.my_custom_value, @tagName(response.status_code) },
|
||||
);
|
||||
}
|
||||
|
||||
/// Invoked after `afterRequest` is called, use this function to do any clean-up.
|
||||
/// Invoked immediately after the response has been finalized and sent to the client.
|
||||
/// Response data can be accessed for logging, but any modifications will have no impact.
|
||||
pub fn afterResponse(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
||||
_ = self;
|
||||
_ = response;
|
||||
request.server.logger.debug("[DemoMiddleware:afterResponse] response completed", .{});
|
||||
}
|
||||
|
||||
/// Invoked after `afterResponse` is called. Use this function to do any clean-up.
|
||||
/// Note that `request.allocator` is an arena allocator, so any allocations are automatically
|
||||
/// done before the next request starts processing.
|
||||
/// freed before the next request starts processing.
|
||||
pub fn deinit(self: *Self, request: *jetzig.http.Request) void {
|
||||
request.allocator.destroy(self);
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
_ = data;
|
||||
const params = try request.params();
|
||||
if (params.get("redirect")) |location| {
|
||||
std.debug.print("location: {s}\n", .{try location.toString()});
|
||||
return request.redirect(try location.toString(), .moved_permanently);
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ processed: bool = false,
|
||||
layout: ?[]const u8 = null,
|
||||
layout_disabled: bool = false,
|
||||
rendered: bool = false,
|
||||
redirected: bool = false,
|
||||
rendered_multiple: bool = false,
|
||||
rendered_view: ?jetzig.views.View = null,
|
||||
|
||||
@ -127,7 +128,13 @@ pub fn respond(self: *Self) !void {
|
||||
|
||||
try self.std_http_request.respond(
|
||||
self.response.content,
|
||||
.{ .keep_alive = false, .extra_headers = std_response_headers.items },
|
||||
.{
|
||||
.keep_alive = false,
|
||||
.status = switch (self.response.status_code) {
|
||||
inline else => |tag| @field(std.http.Status, @tagName(tag)),
|
||||
},
|
||||
.extra_headers = std_response_headers.items,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -149,10 +156,15 @@ pub fn render(self: *Self, status_code: jetzig.http.status_codes.StatusCode) jet
|
||||
/// return request.redirect("https://www.example.com/", .found);
|
||||
/// ```
|
||||
/// The second argument must be `moved_permanently` or `found`.
|
||||
pub fn redirect(self: *Self, location: []const u8, redirect_status: enum { moved_permanently, found }) jetzig.views.View {
|
||||
pub fn redirect(
|
||||
self: *Self,
|
||||
location: []const u8,
|
||||
redirect_status: enum { moved_permanently, found },
|
||||
) jetzig.views.View {
|
||||
if (self.rendered) self.rendered_multiple = true;
|
||||
|
||||
self.rendered = true;
|
||||
self.redirected = true;
|
||||
|
||||
const status_code = switch (redirect_status) {
|
||||
.moved_permanently => jetzig.http.status_codes.StatusCode.moved_permanently,
|
||||
|
@ -99,13 +99,16 @@ fn processNextRequest(self: *Self, allocator: std.mem.Allocator, std_http_server
|
||||
|
||||
try request.process();
|
||||
|
||||
var middleware_data = try jetzig.http.middleware.beforeMiddleware(&request);
|
||||
var middleware_data = try jetzig.http.middleware.afterRequest(&request);
|
||||
|
||||
try self.renderResponse(&request);
|
||||
try request.response.headers.append("content-type", response.content_type);
|
||||
|
||||
try jetzig.http.middleware.beforeResponse(&middleware_data, &request);
|
||||
|
||||
try request.respond();
|
||||
|
||||
try jetzig.http.middleware.afterMiddleware(&middleware_data, &request);
|
||||
try jetzig.http.middleware.afterResponse(&middleware_data, &request);
|
||||
jetzig.http.middleware.deinit(&middleware_data, &request);
|
||||
|
||||
const log_message = try self.requestLogMessage(&request);
|
||||
@ -214,8 +217,9 @@ fn renderView(
|
||||
};
|
||||
|
||||
if (request.rendered_multiple) return error.JetzigMultipleRenderError;
|
||||
|
||||
if (request.rendered_view) |rendered_view| {
|
||||
if (request.redirected) return .{ .view = rendered_view, .content = "" };
|
||||
|
||||
if (template) |capture| {
|
||||
return .{
|
||||
.view = rendered_view,
|
||||
|
@ -10,7 +10,7 @@ else
|
||||
|
||||
const MiddlewareData = std.BoundedArray(*anyopaque, middlewares.len);
|
||||
|
||||
pub fn beforeMiddleware(request: *jetzig.http.Request) !MiddlewareData {
|
||||
pub fn afterRequest(request: *jetzig.http.Request) !MiddlewareData {
|
||||
var middleware_data = MiddlewareData.init(0) catch unreachable;
|
||||
|
||||
inline for (middlewares, 0..) |middleware, index| {
|
||||
@ -20,27 +20,6 @@ pub fn beforeMiddleware(request: *jetzig.http.Request) !MiddlewareData {
|
||||
middleware_data.insert(index, data) catch unreachable;
|
||||
}
|
||||
|
||||
inline for (middlewares, 0..) |middleware, index| {
|
||||
if (comptime !@hasDecl(middleware, "beforeRequest")) continue;
|
||||
if (comptime @hasDecl(middleware, "init")) {
|
||||
const data = middleware_data.get(index);
|
||||
try @call(
|
||||
.always_inline,
|
||||
middleware.beforeRequest,
|
||||
.{ @as(*middleware, @ptrCast(@alignCast(data))), request },
|
||||
);
|
||||
} else {
|
||||
try @call(.always_inline, middleware.beforeRequest, .{request});
|
||||
}
|
||||
}
|
||||
|
||||
return middleware_data;
|
||||
}
|
||||
|
||||
pub fn afterMiddleware(
|
||||
middleware_data: *MiddlewareData,
|
||||
request: *jetzig.http.Request,
|
||||
) !void {
|
||||
inline for (middlewares, 0..) |middleware, index| {
|
||||
if (comptime !@hasDecl(middleware, "afterRequest")) continue;
|
||||
if (comptime @hasDecl(middleware, "init")) {
|
||||
@ -48,10 +27,50 @@ pub fn afterMiddleware(
|
||||
try @call(
|
||||
.always_inline,
|
||||
middleware.afterRequest,
|
||||
.{ @as(*middleware, @ptrCast(@alignCast(data))), request },
|
||||
);
|
||||
} else {
|
||||
try @call(.always_inline, middleware.afterRequest, .{request});
|
||||
}
|
||||
}
|
||||
|
||||
return middleware_data;
|
||||
}
|
||||
|
||||
pub fn beforeResponse(
|
||||
middleware_data: *MiddlewareData,
|
||||
request: *jetzig.http.Request,
|
||||
) !void {
|
||||
inline for (middlewares, 0..) |middleware, index| {
|
||||
if (comptime !@hasDecl(middleware, "beforeResponse")) continue;
|
||||
if (comptime @hasDecl(middleware, "init")) {
|
||||
const data = middleware_data.get(index);
|
||||
try @call(
|
||||
.always_inline,
|
||||
middleware.beforeResponse,
|
||||
.{ @as(*middleware, @ptrCast(@alignCast(data))), request, request.response },
|
||||
);
|
||||
} else {
|
||||
try @call(.always_inline, middleware.afterRequest, .{ request, request.response });
|
||||
try @call(.always_inline, middleware.beforeResponse, .{ request, request.response });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn afterResponse(
|
||||
middleware_data: *MiddlewareData,
|
||||
request: *jetzig.http.Request,
|
||||
) !void {
|
||||
inline for (middlewares, 0..) |middleware, index| {
|
||||
if (comptime !@hasDecl(middleware, "afterResponse")) continue;
|
||||
if (comptime @hasDecl(middleware, "init")) {
|
||||
const data = middleware_data.get(index);
|
||||
try @call(
|
||||
.always_inline,
|
||||
middleware.afterResponse,
|
||||
.{ @as(*middleware, @ptrCast(@alignCast(data))), request, request.response },
|
||||
);
|
||||
} else {
|
||||
try @call(.always_inline, middleware.afterResponse, .{ request, request.response });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ pub fn init(request: *jetzig.http.Request) !*Self {
|
||||
/// request. This allows a view to specify a layout that will render the full page when the
|
||||
/// request doesn't come via htmx and, when the request does come from htmx, only return the
|
||||
/// content rendered directly by the view function.
|
||||
pub fn beforeRequest(self: *Self, request: *jetzig.http.Request) !void {
|
||||
pub fn afterRequest(self: *Self, request: *jetzig.http.Request) !void {
|
||||
_ = self;
|
||||
if (request.getHeader("HX-Target")) |target| {
|
||||
request.server.logger.debug(
|
||||
@ -26,9 +26,10 @@ pub fn beforeRequest(self: *Self, 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 afterRequest(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
||||
pub fn beforeResponse(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
||||
_ = self;
|
||||
if (response.status_code != .moved_permanently and response.status_code != .found) return;
|
||||
if (request.headers.getFirstValue("HX-Request") == null) return;
|
||||
|
||||
if (response.headers.getFirstValue("Location")) |location| {
|
||||
response.headers.remove("Location");
|
||||
|
Loading…
x
Reference in New Issue
Block a user