aboutsummaryrefslogtreecommitdiff
path: root/src/http/handler
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2026-06-06 07:57:50 +0200
committerNathan Reiner <nathan@nathanreiner.xyz>2026-06-06 07:57:50 +0200
commit51307d973255e32ec1440083dee383e8b2cd8878 (patch)
treedac0309c7abd40d2a3822fca3f7df0fd485e04f0 /src/http/handler
parent17561c6be1e4fba11e243f2ac842ebe26fae7e60 (diff)
Implement static handler and synchronous server handling
Diffstat (limited to 'src/http/handler')
-rw-r--r--src/http/handler/Interface.zig44
-rw-r--r--src/http/handler/context.zig76
-rw-r--r--src/http/handler/root.zig35
-rw-r--r--src/http/handler/static.zig13
4 files changed, 168 insertions, 0 deletions
diff --git a/src/http/handler/Interface.zig b/src/http/handler/Interface.zig
new file mode 100644
index 0000000..2238424
--- /dev/null
+++ b/src/http/handler/Interface.zig
@@ -0,0 +1,44 @@
+const std = @import("std");
+const Response = @import("../Response.zig");
+const Route = @import("../Route.zig");
+
+route: Route,
+base: *const anyopaque,
+handler: *const fn (
+ *const anyopaque,
+ std.mem.Allocator,
+ std.mem.Allocator,
+ std.Io,
+ *std.http.Server.Request,
+) Response,
+
+pub fn from(instance: anytype, route: Route) @This() {
+ const PtrT = @TypeOf(instance);
+ const func = struct {
+ pub fn handle(
+ base: *const anyopaque,
+ gpa: std.mem.Allocator,
+ arena: std.mem.Allocator,
+ io: std.Io,
+ request: *std.http.Server.Request,
+ ) Response {
+ return @as(PtrT, @ptrCast(@alignCast(base))).handle(gpa, arena, io, request);
+ }
+ }.handle;
+
+ return .{
+ .base = @ptrCast(instance),
+ .handler = func,
+ .route = route,
+ };
+}
+
+pub fn handle(
+ self: *const @This(),
+ gpa: std.mem.Allocator,
+ arena: std.mem.Allocator,
+ io: std.Io,
+ request: *std.http.Server.Request,
+) Response {
+ return self.handler(self.base, gpa, arena, io, request);
+}
diff --git a/src/http/handler/context.zig b/src/http/handler/context.zig
new file mode 100644
index 0000000..4e55446
--- /dev/null
+++ b/src/http/handler/context.zig
@@ -0,0 +1,76 @@
+const std = @import("std");
+const Response = @import("../Response.zig");
+const Route = @import("../Route.zig");
+const Parameter = @import("../Parameter.zig");
+const Interface = @import("Interface.zig");
+
+pub fn Context(comptime r: anytype) type {
+ return struct {
+ pub const route = Route.fromAny(r);
+ pub const Params = Parameter.Struct(route);
+
+ pub const Request = struct {
+ base_request: *std.http.Server.Request,
+ params: Params,
+ arena: std.mem.Allocator,
+ gpa: std.mem.Allocator,
+ io: std.Io,
+
+ /// Parses the body of the request and returns type `T`.
+ /// The arena allocator will be used to do the parsing.
+ pub fn json(self: @This(), T: type) !T {
+ var buffer: [1024]u8 = undefined;
+ var reader = std.json.Reader.init(self.arena, self.base_request.readerExpectNone(&buffer));
+ return std.json.parseFromTokenSourceLeaky(T, self.arena, &reader, .{});
+ }
+ };
+
+ const badRequest = struct {
+ pub fn badRequest(_: Request) Response {
+ return .with(.bad_request, .string("method does not exist"));
+ }
+ }.badRequest;
+
+ get: *const fn (Request) Response = badRequest,
+ post: *const fn (Request) Response = badRequest,
+ delete: *const fn (Request) Response = badRequest,
+ head: *const fn (Request) Response = badRequest,
+ put: *const fn (Request) Response = badRequest,
+ connect: *const fn (Request) Response = badRequest,
+ options: *const fn (Request) Response = badRequest,
+ trace: *const fn (Request) Response = badRequest,
+ patch: *const fn (Request) Response = badRequest,
+
+ pub fn handle(
+ self: *const @This(),
+ gpa: std.mem.Allocator,
+ arena: std.mem.Allocator,
+ io: std.Io,
+ request: *std.http.Server.Request,
+ ) Response {
+ const req: Request = .{
+ .base_request = request,
+ .arena = arena,
+ .gpa = gpa,
+ .io = io,
+ .params = Parameter.parse(Params, &route, request.head.target),
+ };
+
+ return (switch (request.head.method) {
+ .GET => self.get,
+ .POST => self.post,
+ .DELETE => self.delete,
+ .HEAD => self.head,
+ .PUT => self.put,
+ .CONNECT => self.connect,
+ .OPTIONS => self.options,
+ .TRACE => self.trace,
+ .PATCH => self.patch,
+ })(req);
+ }
+
+ pub fn interface(self: *const @This()) Interface {
+ return .from(self, route);
+ }
+ };
+}
diff --git a/src/http/handler/root.zig b/src/http/handler/root.zig
new file mode 100644
index 0000000..33fdb84
--- /dev/null
+++ b/src/http/handler/root.zig
@@ -0,0 +1,35 @@
+pub const Context = @import("context.zig").Context;
+pub const Interface = @import("Interface.zig");
+pub const Static = @import("static.zig").Static;
+
+test "interface" {
+ const M = struct {
+ const Response = @import("../Response.zig");
+
+ const C = Context(.{ "api", "images", .id, "metadata" });
+ const context: C = .{
+ .post = post,
+ .get = get,
+ };
+
+ const Query = struct {
+ condition: []const u8,
+ start: usize,
+ length: usize,
+ };
+
+ fn post(request: C.Request) Response {
+ const query = request.json(Query) catch return .with(.bad_request, .void);
+ _ = query;
+ _ = request.params.id;
+
+ return .ok(.string(request.params.id.as([]const u8)));
+ }
+
+ fn get(request: C.Request) Response {
+ return .ok(Response.Message.file(request.io, "./some/path.txt") catch return .with(.not_found, .void));
+ }
+ };
+
+ _ = M.context.interface();
+}
diff --git a/src/http/handler/static.zig b/src/http/handler/static.zig
new file mode 100644
index 0000000..d53f213
--- /dev/null
+++ b/src/http/handler/static.zig
@@ -0,0 +1,13 @@
+const Interface = @import("Interface.zig");
+const Context = @import("context.zig").Context;
+const Response = @import("../Response.zig");
+
+pub fn Static(route: anytype, content: []const u8) Context(route) {
+ const get = struct {
+ fn get(_: Context(route).Request) Response {
+ return .ok(.string(content));
+ }
+ }.get;
+
+ return .{ .get = get };
+}