Merge pull request #10 from jetzig-framework/zig-std-http-overhaul

std.http overhaul
This commit is contained in:
bobf 2024-03-02 11:32:30 +00:00 committed by GitHub
commit cc39608d46
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 240 additions and 217 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}