mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 22:16:08 +00:00
Merge branch 'http.zig' into tmp-merge
This commit is contained in:
commit
63b4265131
@ -57,6 +57,7 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
const jetkv_dep = b.dependency("jetkv", .{ .target = target, .optimize = optimize });
|
||||
const zmd_dep = b.dependency("zmd", .{ .target = target, .optimize = optimize });
|
||||
const httpz_dep = b.dependency("httpz", .{ .target = target, .optimize = optimize });
|
||||
|
||||
// This is the way to make it look nice in the zig build script
|
||||
// If we would do it the other way around, we would have to do
|
||||
@ -75,6 +76,7 @@ pub fn build(b: *std.Build) !void {
|
||||
jetzig_module.addImport("zmd", zmd_dep.module("zmd"));
|
||||
jetzig_module.addImport("jetkv", jetkv_dep.module("jetkv"));
|
||||
jetzig_module.addImport("smtp", smtp_client_dep.module("smtp_client"));
|
||||
jetzig_module.addImport("httpz", httpz_dep.module("httpz"));
|
||||
|
||||
const main_tests = b.addTest(.{
|
||||
.root_source_file = .{ .path = "src/tests.zig" },
|
||||
|
@ -19,8 +19,12 @@
|
||||
.hash = "12208a1de366740d11de525db7289345949f5fd46527db3f89eecc7bb49b012c0732",
|
||||
},
|
||||
.smtp_client = .{
|
||||
.url = "https://github.com/bobf/smtp_client.zig/archive/86315adf5527e6add304fea3f1ff110613126283.tar.gz",
|
||||
.hash = "12203ccdd3f7145f305c6f88b18ecd407d27b36051a523f8f579f0099db6a17cd757",
|
||||
.url = "https://github.com/karlseguin/smtp_client.zig/archive/964152ad4e19dc1d22f6def6f659c86df60e7832.tar.gz",
|
||||
.hash = "1220d4f1c2472769b0d689ea878f41f0a66cb07f28569a138aea2c0a648a5c90dd4e",
|
||||
},
|
||||
.httpz = .{
|
||||
.url = "https://github.com/karlseguin/http.zig/archive/206a34c0ee35a07b89d000f630b2f1e0f7c98119.tar.gz",
|
||||
.hash = "1220768b5925b4e13f73c036f1ca18b4a7d987ffaf5e825af6443d5d4ed8e37e7dfd",
|
||||
},
|
||||
},
|
||||
|
||||
|
7
demo/src/app/views/basic.zig
Normal file
7
demo/src/app/views/basic.zig
Normal file
@ -0,0 +1,7 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
_ = data;
|
||||
return request.render(.ok);
|
||||
}
|
3
demo/src/app/views/basic/index.zmpl
Normal file
3
demo/src/app/views/basic/index.zmpl
Normal file
@ -0,0 +1,3 @@
|
||||
<div>
|
||||
<span>Content goes here</span>
|
||||
</div>
|
@ -15,7 +15,7 @@ pub const jetzig_options = struct {
|
||||
jetzig.middleware.HtmxMiddleware,
|
||||
// Demo middleware included with new projects. Remove once you are familiar with Jetzig's
|
||||
// middleware system.
|
||||
@import("app/middleware/DemoMiddleware.zig"),
|
||||
// @import("app/middleware/DemoMiddleware.zig"),
|
||||
};
|
||||
|
||||
// Maximum bytes to allow in request body.
|
||||
|
@ -115,7 +115,6 @@ pub fn start(self: App, routes_module: type, options: AppOptions) !void {
|
||||
&job_queue,
|
||||
&cache,
|
||||
);
|
||||
defer server.deinit();
|
||||
|
||||
var mutex = std.Thread.Mutex{};
|
||||
var worker_pool = jetzig.jobs.Pool.init(
|
||||
|
@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub const Server = @import("http/Server.zig");
|
||||
pub const Request = @import("http/Request.zig");
|
||||
|
@ -1,5 +1,7 @@
|
||||
const std = @import("std");
|
||||
|
||||
const httpz = @import("httpz");
|
||||
|
||||
const jetzig = @import("../../jetzig.zig");
|
||||
|
||||
const Request = @This();
|
||||
@ -14,7 +16,8 @@ path: jetzig.http.Path,
|
||||
method: Method,
|
||||
headers: jetzig.http.Headers,
|
||||
server: *jetzig.http.Server,
|
||||
std_http_request: std.http.Server.Request,
|
||||
httpz_request: *httpz.Request,
|
||||
httpz_response: *httpz.Response,
|
||||
response: *jetzig.http.Response,
|
||||
status_code: jetzig.http.status_codes.StatusCode = .not_found,
|
||||
response_data: *jetzig.data.Data,
|
||||
@ -90,20 +93,18 @@ pub fn init(
|
||||
allocator: std.mem.Allocator,
|
||||
server: *jetzig.http.Server,
|
||||
start_time: i128,
|
||||
std_http_request: std.http.Server.Request,
|
||||
httpz_request: *httpz.Request,
|
||||
httpz_response: *httpz.Response,
|
||||
response: *jetzig.http.Response,
|
||||
) !Request {
|
||||
const method = switch (std_http_request.head.method) {
|
||||
const method = switch (httpz_request.method) {
|
||||
.DELETE => Method.DELETE,
|
||||
.GET => Method.GET,
|
||||
.PATCH => Method.PATCH,
|
||||
.POST => Method.POST,
|
||||
.HEAD => Method.HEAD,
|
||||
.PUT => Method.PUT,
|
||||
.CONNECT => Method.CONNECT,
|
||||
.OPTIONS => Method.OPTIONS,
|
||||
.TRACE => Method.TRACE,
|
||||
_ => return error.JetzigUnsupportedHttpMethod,
|
||||
};
|
||||
|
||||
const response_data = try allocator.create(jetzig.data.Data);
|
||||
@ -111,13 +112,14 @@ pub fn init(
|
||||
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.path = jetzig.http.Path.init(std_http_request.head.target),
|
||||
.path = jetzig.http.Path.init(httpz_request.url.raw),
|
||||
.method = method,
|
||||
.headers = jetzig.http.Headers.init(allocator),
|
||||
.server = server,
|
||||
.response = response,
|
||||
.response_data = response_data,
|
||||
.std_http_request = std_http_request,
|
||||
.httpz_request = httpz_request,
|
||||
.httpz_response = httpz_response,
|
||||
.start_time = start_time,
|
||||
.store = .{ .store = server.store, .allocator = allocator },
|
||||
.cache = .{ .store = server.cache, .allocator = allocator },
|
||||
@ -134,12 +136,14 @@ pub fn deinit(self: *Request) void {
|
||||
|
||||
/// Process request, read body if present, parse headers (TODO)
|
||||
pub fn process(self: *Request) !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;
|
||||
var header_index: usize = 0;
|
||||
while (header_index < self.httpz_request.headers.len) : (header_index += 1) {
|
||||
const name = self.httpz_request.headers.keys[header_index];
|
||||
const value = self.httpz_request.headers.values[header_index];
|
||||
try self.headers.append(name, value);
|
||||
if (std.mem.eql(u8, name, "Cookie")) cookie = value;
|
||||
}
|
||||
|
||||
self.cookies = try self.allocator.create(jetzig.http.Cookies);
|
||||
@ -161,13 +165,27 @@ pub fn process(self: *Request) !void {
|
||||
}
|
||||
};
|
||||
|
||||
const reader = try self.std_http_request.reader();
|
||||
self.body = try reader.readAllAlloc(self.allocator, jetzig.config.get(usize, "max_bytes_request_body"));
|
||||
self.body = self.httpz_request.body() orelse "";
|
||||
self.processed = true;
|
||||
}
|
||||
|
||||
pub const CallbackState = struct {
|
||||
arena: *std.heap.ArenaAllocator,
|
||||
allocator: std.mem.Allocator,
|
||||
};
|
||||
|
||||
pub fn responseCompleteCallback(ptr: *anyopaque) void {
|
||||
var state: *CallbackState = @ptrCast(@alignCast(ptr));
|
||||
state.arena.deinit();
|
||||
state.allocator.destroy(state.arena);
|
||||
state.allocator.destroy(state);
|
||||
}
|
||||
|
||||
/// Set response headers, write response payload, and finalize the response.
|
||||
pub fn respond(self: *Request) !void {
|
||||
pub fn respond(
|
||||
self: *Request,
|
||||
state: *CallbackState,
|
||||
) !void {
|
||||
if (!self.processed) unreachable;
|
||||
|
||||
var cookie_it = self.cookies.headerIterator();
|
||||
@ -176,19 +194,14 @@ pub fn respond(self: *Request) !void {
|
||||
try self.response.headers.append("Set-Cookie", header);
|
||||
}
|
||||
|
||||
var std_response_headers = try self.response.headers.stdHeaders();
|
||||
defer std_response_headers.deinit(self.allocator);
|
||||
for (self.response.headers.headers.items) |header| {
|
||||
self.httpz_response.header(header.name, header.value);
|
||||
}
|
||||
|
||||
try self.std_http_request.respond(
|
||||
self.response.content,
|
||||
.{
|
||||
.keep_alive = false,
|
||||
.status = switch (self.response.status_code) {
|
||||
inline else => |tag| @field(std.http.Status, @tagName(tag)),
|
||||
},
|
||||
.extra_headers = std_response_headers.items,
|
||||
},
|
||||
);
|
||||
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.httpz_response.callback(responseCompleteCallback, @ptrCast(state));
|
||||
}
|
||||
|
||||
/// Render a response. This function can only be called once per request (repeat calls will
|
||||
|
@ -1,8 +1,10 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const jetzig = @import("../../jetzig.zig");
|
||||
const zmpl = @import("zmpl");
|
||||
const zmd = @import("zmd");
|
||||
const httpz = @import("httpz");
|
||||
|
||||
pub const ServerOptions = struct {
|
||||
logger: jetzig.loggers.Logger,
|
||||
@ -59,53 +61,75 @@ pub fn deinit(self: *Server) void {
|
||||
self.allocator.free(self.options.bind);
|
||||
}
|
||||
|
||||
pub fn listen(self: *Server) !void {
|
||||
const address = try std.net.Address.parseIp(self.options.bind, self.options.port);
|
||||
self.std_net_server = try address.listen(.{ .reuse_port = true });
|
||||
const Dispatcher = struct {
|
||||
server: *Server,
|
||||
|
||||
self.initialized = true;
|
||||
pub fn handle(self: Dispatcher, request: *httpz.Request, response: *httpz.Response) void {
|
||||
self.server.processNextRequest(request, response) catch |err| {
|
||||
self.server.errorHandlerFn(request, response, err);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn listen(self: *Server) !void {
|
||||
var httpz_server = try httpz.ServerCtx(Dispatcher, Dispatcher).init(
|
||||
self.allocator,
|
||||
.{
|
||||
.port = self.options.port,
|
||||
.address = self.options.bind,
|
||||
.thread_pool = .{ .count = @intCast(try std.Thread.getCpuCount()) },
|
||||
},
|
||||
Dispatcher{ .server = self },
|
||||
);
|
||||
defer httpz_server.deinit();
|
||||
|
||||
try self.logger.INFO("Listening on http://{s}:{} [{s}]", .{
|
||||
self.options.bind,
|
||||
self.options.port,
|
||||
@tagName(self.options.environment),
|
||||
});
|
||||
try self.processRequests();
|
||||
|
||||
self.initialized = true;
|
||||
|
||||
return try httpz_server.listen();
|
||||
}
|
||||
|
||||
fn processRequests(self: *Server) !void {
|
||||
// TODO: Keepalive
|
||||
while (true) {
|
||||
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
||||
errdefer arena.deinit();
|
||||
const allocator = arena.allocator();
|
||||
pub fn errorHandlerFn(self: *Server, request: *httpz.Request, response: *httpz.Response, err: anyerror) void {
|
||||
if (isBadHttpError(err)) return;
|
||||
|
||||
const connection = try self.std_net_server.accept();
|
||||
|
||||
var buf: [jetzig.config.get(usize, "http_buffer_size")]u8 = undefined;
|
||||
var std_http_server = std.http.Server.init(connection, &buf);
|
||||
errdefer std_http_server.connection.stream.close();
|
||||
|
||||
self.processNextRequest(allocator, &std_http_server) catch |err| {
|
||||
if (isBadHttpError(err)) {
|
||||
std_http_server.connection.stream.close();
|
||||
continue;
|
||||
} else return err;
|
||||
};
|
||||
|
||||
std_http_server.connection.stream.close();
|
||||
arena.deinit();
|
||||
}
|
||||
self.logger.ERROR("Encountered error: {s} {s}", .{ @errorName(err), request.url.raw }) catch {};
|
||||
response.body = "500 Internal Server Error";
|
||||
}
|
||||
|
||||
fn processNextRequest(self: *Server, allocator: std.mem.Allocator, std_http_server: *std.http.Server) !void {
|
||||
fn processNextRequest(
|
||||
self: *Server,
|
||||
httpz_request: *httpz.Request,
|
||||
httpz_response: *httpz.Response,
|
||||
) !void {
|
||||
const state = try self.allocator.create(jetzig.http.Request.CallbackState);
|
||||
const arena = try self.allocator.create(std.heap.ArenaAllocator);
|
||||
arena.* = std.heap.ArenaAllocator.init(self.allocator);
|
||||
state.* = .{
|
||||
.arena = arena,
|
||||
.allocator = self.allocator,
|
||||
};
|
||||
|
||||
// Regular arena deinit occurs in jetzig.http.Request.responseCompletCallback
|
||||
errdefer state.arena.deinit();
|
||||
|
||||
const allocator = state.arena.allocator();
|
||||
|
||||
const start_time = std.time.nanoTimestamp();
|
||||
|
||||
const std_http_request = try std_http_server.receiveHead();
|
||||
if (std_http_server.state == .receiving_head) return error.JetzigParseHeadError;
|
||||
|
||||
var response = try jetzig.http.Response.init(allocator);
|
||||
var request = try jetzig.http.Request.init(allocator, self, start_time, std_http_request, &response);
|
||||
var request = try jetzig.http.Request.init(
|
||||
allocator,
|
||||
self,
|
||||
start_time,
|
||||
httpz_request,
|
||||
httpz_response,
|
||||
&response,
|
||||
);
|
||||
|
||||
try request.process();
|
||||
|
||||
@ -116,7 +140,7 @@ fn processNextRequest(self: *Server, allocator: std.mem.Allocator, std_http_serv
|
||||
|
||||
try jetzig.http.middleware.beforeResponse(&middleware_data, &request);
|
||||
|
||||
try request.respond();
|
||||
try request.respond(state);
|
||||
|
||||
try jetzig.http.middleware.afterResponse(&middleware_data, &request);
|
||||
jetzig.http.middleware.deinit(&middleware_data, &request);
|
||||
|
@ -178,6 +178,12 @@ pub const TaggedStatusCode = union(StatusCode) {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getCodeInt(self: Self) !u16 {
|
||||
return switch (self) {
|
||||
inline else => |capture| try std.fmt.parseInt(u16, capture.code, 10),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getMessage(self: Self) []const u8 {
|
||||
return switch (self) {
|
||||
inline else => |capture| capture.message,
|
||||
|
@ -41,7 +41,6 @@ pub const Writer = struct {
|
||||
|
||||
pub fn print(self: *Writer, comptime message: []const u8, args: anytype) !void {
|
||||
const output = try std.fmt.allocPrint(self.queue.allocator, message, args);
|
||||
defer self.queue.allocator.free(output);
|
||||
try self.queue.append(output);
|
||||
}
|
||||
};
|
||||
@ -73,7 +72,7 @@ pub fn append(self: *LogQueue, message: []const u8) !void {
|
||||
defer self.read_write_mutex.unlock();
|
||||
|
||||
const node = try self.allocator.create(List.Node);
|
||||
node.* = .{ .data = try self.allocator.dupe(u8, message) };
|
||||
node.* = .{ .data = message };
|
||||
self.list.append(node);
|
||||
self.condition.signal();
|
||||
}
|
||||
|
67
src/jetzig/windows.zig
Normal file
67
src/jetzig/windows.zig
Normal file
@ -0,0 +1,67 @@
|
||||
const std = @import("std");
|
||||
const Server = @import("http/Server.zig");
|
||||
const jetzig = @import("../jetzig.zig");
|
||||
|
||||
pub fn listen(self: *Server) !void {
|
||||
const address = try std.net.Address.parseIp(self.options.bind, self.options.port);
|
||||
self.std_net_server = try address.listen(.{ .reuse_port = true });
|
||||
|
||||
self.initialized = true;
|
||||
|
||||
try self.logger.INFO("Listening on http://{s}:{} [{s}]", .{
|
||||
self.options.bind,
|
||||
self.options.port,
|
||||
@tagName(self.options.environment),
|
||||
});
|
||||
try processRequests(self);
|
||||
}
|
||||
|
||||
fn processRequests(self: *Server) !void {
|
||||
while (true) {
|
||||
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
||||
errdefer arena.deinit();
|
||||
const allocator = arena.allocator();
|
||||
|
||||
const connection = try self.std_net_server.accept();
|
||||
|
||||
var buf: [jetzig.config.get(usize, "http_buffer_size")]u8 = undefined;
|
||||
var std_http_server = std.http.Server.init(connection, &buf);
|
||||
errdefer std_http_server.connection.stream.close();
|
||||
|
||||
processNextRequest(self, allocator, &std_http_server) catch |err| {
|
||||
if (Server.isBadHttpError(err)) {
|
||||
std_http_server.connection.stream.close();
|
||||
continue;
|
||||
} else return err;
|
||||
};
|
||||
|
||||
std_http_server.connection.stream.close();
|
||||
arena.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
fn processNextRequest(self: *Server, allocator: std.mem.Allocator, std_http_server: *std.http.Server) !void {
|
||||
const start_time = std.time.nanoTimestamp();
|
||||
|
||||
const std_http_request = try std_http_server.receiveHead();
|
||||
if (std_http_server.state == .receiving_head) return error.JetzigParseHeadError;
|
||||
|
||||
var response = try jetzig.http.Response.init(allocator);
|
||||
var request = try jetzig.http.Request.init(allocator, self, start_time, std_http_request, &response);
|
||||
|
||||
try request.process();
|
||||
|
||||
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.afterResponse(&middleware_data, &request);
|
||||
jetzig.http.middleware.deinit(&middleware_data, &request);
|
||||
|
||||
try self.logger.logRequest(&request);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user