diff --git a/build.zig b/build.zig
index 19456fa..d1a7486 100644
--- a/build.zig
+++ b/build.zig
@@ -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" },
diff --git a/build.zig.zon b/build.zig.zon
index 8917be1..7088855 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -22,6 +22,10 @@
.url = "https://github.com/karlseguin/smtp_client.zig/archive/e79e411862d4f4d41657bf41efb884efca3d67dd.tar.gz",
.hash = "12209907c69891a38e6923308930ac43bfb40135bc609ea370b5759fc2e1c4f57284",
},
+ .httpz = .{
+ .url = "https://github.com/karlseguin/http.zig/archive/bf748c6508090464213d3088a77d51faf9cd0876.tar.gz",
+ .hash = "12209b7426293ebe5075b930ae6029c009bfb6deb7ff92b9d69e28463abd14ad03da",
+ },
},
.paths = .{
diff --git a/demo/src/app/views/basic.zig b/demo/src/app/views/basic.zig
new file mode 100644
index 0000000..4fac587
--- /dev/null
+++ b/demo/src/app/views/basic.zig
@@ -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);
+}
diff --git a/demo/src/app/views/basic/index.zmpl b/demo/src/app/views/basic/index.zmpl
new file mode 100644
index 0000000..76457d0
--- /dev/null
+++ b/demo/src/app/views/basic/index.zmpl
@@ -0,0 +1,3 @@
+
+ Content goes here
+
diff --git a/demo/src/main.zig b/demo/src/main.zig
index 08213d1..691477e 100644
--- a/demo/src/main.zig
+++ b/demo/src/main.zig
@@ -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.
diff --git a/src/jetzig.zig b/src/jetzig.zig
index 4d18546..3a72e6b 100644
--- a/src/jetzig.zig
+++ b/src/jetzig.zig
@@ -16,6 +16,7 @@ pub const markdown = @import("jetzig/markdown.zig");
pub const jobs = @import("jetzig/jobs.zig");
pub const mail = @import("jetzig/mail.zig");
pub const kv = @import("jetzig/kv.zig");
+pub const HttpzServer = @import("jetzig/HttpzServer.zig");
/// The primary interface for a Jetzig application. Create an `App` in your application's
/// `src/main.zig` and call `start` to launch the application.
diff --git a/src/jetzig/App.zig b/src/jetzig/App.zig
index 59af1df..ed0c9a0 100644
--- a/src/jetzig/App.zig
+++ b/src/jetzig/App.zig
@@ -94,6 +94,9 @@ pub fn start(self: App, routes_module: type, options: AppOptions) !void {
std.process.exit(0);
}
+ // var httpz_server = try jetzig.HttpzServer.init(self.allocator);
+ // defer httpz_server.deinit();
+
var server = jetzig.http.Server.init(
self.allocator,
server_options,
@@ -105,7 +108,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(
diff --git a/src/jetzig/HttpzServer.zig b/src/jetzig/HttpzServer.zig
new file mode 100644
index 0000000..40184e3
--- /dev/null
+++ b/src/jetzig/HttpzServer.zig
@@ -0,0 +1,58 @@
+const std = @import("std");
+
+const httpz = @import("httpz");
+
+const jetzig = @import("../jetzig.zig");
+
+allocator: std.mem.Allocator,
+server: httpz.ServerCtx(void, void),
+
+const HttpzServer = @This();
+
+const DispatcherFn = *const fn (*jetzig.Server, *httpz.Request, *httpz.Response) anyerror!void;
+
+pub fn init(allocator: std.mem.Allocator, dispatcherFn: DispatcherFn) !HttpzServer {
+ return .{
+ .allocator = allocator,
+ .dispatcherFn = dispatcherFn,
+ .server = try httpz.Server().init(allocator, .{ .port = 8080 }),
+ };
+}
+
+pub fn deinit(self: *HttpzServer) void {
+ self.server.deinit();
+}
+
+pub fn configure(self: *HttpzServer, dispatcherFn: DispatcherFn) void {
+ // Bypass router.
+ self.server.notFound(dispatcherFn);
+}
+
+// var server = ;
+//
+// // set a global dispatch for any routes defined from this point on
+// server.dispatcher(mainDispatcher);
+//
+// // set a dispatcher for this route
+// // note the use of "deleteC" the "C" is for Configuration and is used
+// // since Zig doesn't have overloading or optional parameters.
+// server.router().deleteC("/v1/session", logout, .{.dispatcher = loggedIn})
+// ...
+//
+// fn mainDispatcher(action: httpz.Action(void), req: *httpz.Request, res: *httpz.Response) !void {
+// res.header("cors", "isslow");
+// return action(req, res);
+// }
+//
+// fn loggedIn(action: httpz.Action(void), req: *httpz.Request, res: *httpz.Response) !void {
+// if (req.header("authorization")) |_auth| {
+// // TODO: make sure "auth" is valid!
+// return mainDispatcher(action, req, res);
+// }
+// res.status = 401;
+// res.body = "Not authorized";
+// }
+//
+// fn logout(req: *httpz.Request, res: *httpz.Response) !void {
+// ...
+// }
diff --git a/src/jetzig/http/Request.zig b/src/jetzig/http/Request.zig
index e260a06..b18c5a5 100644
--- a/src/jetzig/http/Request.zig
+++ b/src/jetzig/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,8 +165,7 @@ 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;
}
@@ -179,16 +182,25 @@ pub fn respond(self: *Request) !void {
var std_response_headers = try self.response.headers.stdHeaders();
defer std_response_headers.deinit(self.allocator);
- 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,
- },
- );
+ for (self.response.headers.headers.items) |header| {
+ self.httpz_response.header(header.name, header.value);
+ }
+
+ 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;
+ try self.httpz_response.write();
+
+ // try self.httpz_response.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,
+ // },
+ // );
}
/// Render a response. This function can only be called once per request (repeat calls will
diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig
index 9434bd6..325796b 100644
--- a/src/jetzig/http/Server.zig
+++ b/src/jetzig/http/Server.zig
@@ -3,6 +3,7 @@ const std = @import("std");
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,
@@ -60,52 +61,58 @@ pub fn deinit(self: *Server) void {
}
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 });
+ var httpz_server = try httpz.ServerCtx(*jetzig.http.Server, *jetzig.http.Server).init(
+ self.allocator,
+ .{
+ .port = self.options.port,
+ .address = self.options.bind,
+ .workers = .{ .count = 8 },
+ .thread_pool = .{ .count = 8 },
+ },
+ self,
+ );
+ defer httpz_server.deinit();
+ httpz_server.notFound(jetzig.http.Server.dispatcherFn);
+ // httpz_server.dispatcher(jetzig.http.Server.dispatcherFn);
- self.initialized = true;
-
- try self.logger.INFO("Listening on http://{s}:{} [{s}]", .{
- self.options.bind,
- self.options.port,
- @tagName(self.options.environment),
- });
- try self.processRequests();
+ return try httpz_server.listen();
+ // 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 self.processRequests();
}
-fn processRequests(self: *Server) !void {
- // TODO: Keepalive
- 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();
-
- 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();
- }
+pub fn dispatcherFn(self: *Server, request: *httpz.Request, response: *httpz.Response) !void {
+ var arena = std.heap.ArenaAllocator.init(self.allocator);
+ defer arena.deinit();
+ const allocator = arena.allocator();
+ try self.processNextRequest(allocator, request, response);
}
-fn processNextRequest(self: *Server, allocator: std.mem.Allocator, std_http_server: *std.http.Server) !void {
+fn processNextRequest(
+ self: *Server,
+ allocator: std.mem.Allocator,
+ httpz_request: *httpz.Request,
+ httpz_response: *httpz.Response,
+) !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);
+ var request = try jetzig.http.Request.init(
+ allocator,
+ self,
+ start_time,
+ httpz_request,
+ httpz_response,
+ &response,
+ );
try request.process();
@@ -121,9 +128,35 @@ fn processNextRequest(self: *Server, allocator: std.mem.Allocator, std_http_serv
try jetzig.http.middleware.afterResponse(&middleware_data, &request);
jetzig.http.middleware.deinit(&middleware_data, &request);
- try self.logger.logRequest(&request);
+ // try self.logger.logRequest(&request);
}
+// 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);
+// }
+//
fn renderResponse(self: *Server, request: *jetzig.http.Request) !void {
const static_resource = self.matchStaticResource(request) catch |err| {
if (isUnhandledError(err)) return err;
diff --git a/src/jetzig/http/status_codes.zig b/src/jetzig/http/status_codes.zig
index 611cc08..2987a8d 100644
--- a/src/jetzig/http/status_codes.zig
+++ b/src/jetzig/http/status_codes.zig
@@ -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,