diff options
| -rw-r--r-- | src/http/Response.zig | 35 | ||||
| -rw-r--r-- | src/http/Route.zig | 4 | ||||
| -rw-r--r-- | src/http/RouteSet.zig | 2 | ||||
| -rw-r--r-- | src/http/Server.zig | 22 | ||||
| -rw-r--r-- | src/http/handler/Interface.zig | 44 | ||||
| -rw-r--r-- | src/http/handler/context.zig (renamed from src/http/handler.zig) | 87 | ||||
| -rw-r--r-- | src/http/handler/root.zig | 35 | ||||
| -rw-r--r-- | src/http/handler/static.zig | 13 | ||||
| -rw-r--r-- | src/http/root.zig | 2 | ||||
| -rw-r--r-- | src/main.zig | 14 |
10 files changed, 163 insertions, 95 deletions
diff --git a/src/http/Response.zig b/src/http/Response.zig index a5f7727..8d10992 100644 --- a/src/http/Response.zig +++ b/src/http/Response.zig @@ -3,14 +3,14 @@ const std = @import("std"); pub const Message = union(enum) { void: void, static: []const u8, - file_content: std.Io.File, + streaming: std.Io.File, pub fn string(content: []const u8) Message { return .{ .static = content }; } pub fn file(io: std.Io, path: []const u8) !Message { - return .{ .file_content = try std.Io.Dir.cwd().openFile(io, path, .{ + return .{ .streaming = try std.Io.Dir.cwd().openFile(io, path, .{ .allow_directory = false, }) }; } @@ -26,3 +26,34 @@ pub fn with(status: std.http.Status, message: Message) @This() { pub fn ok(message: Message) @This() { return with(.ok, message); } + +pub fn send( + self: *const @This(), + io: std.Io, + request: *std.http.Server.Request, +) !void { + switch (self.message) { + .void => { + try request.respond("", .{ .status = self.status }); + }, + .static => |content| { + try request.respond(content, .{ .status = self.status }); + }, + .streaming => |file| { + var write_buffer: [1024]u8 = undefined; + var read_buffer: [1024]u8 = undefined; + + var body_writer = try request.respondStreaming(&write_buffer, .{ + .respond_options = .{ + .status = self.status, + .transfer_encoding = .chunked, + }, + }); + + var file_reader = file.reader(io, &read_buffer); + + _ = try file_reader.interface.streamRemaining(&body_writer.writer); + try body_writer.end(); + }, + } +} diff --git a/src/http/Route.zig b/src/http/Route.zig index f979d04..7dd8ec0 100644 --- a/src/http/Route.zig +++ b/src/http/Route.zig @@ -19,6 +19,10 @@ pub const index: @This() = .{ .segments = &.{} }; pub fn fromAny(comptime route: anytype) @This() { var segments: []const Segment = &.{}; + if (route == .index) { + return index; + } + inline for (route) |r| { const segment: []const Segment = &.{switch (@TypeOf(r)) { @EnumLiteral() => .{ .identifier = @tagName(r) }, diff --git a/src/http/RouteSet.zig b/src/http/RouteSet.zig index 623a2a9..de8b3c2 100644 --- a/src/http/RouteSet.zig +++ b/src/http/RouteSet.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Route = @import("Route.zig"); -const handler = @import("handler.zig"); +const handler = @import("handler/root.zig"); const Interface = handler.Interface; const Context = handler.Context; diff --git a/src/http/Server.zig b/src/http/Server.zig index 71037bb..955b8cc 100644 --- a/src/http/Server.zig +++ b/src/http/Server.zig @@ -1,23 +1,28 @@ const std = @import("std"); +const RouteSet = @import("RouteSet.zig"); + const Self = @This(); server: std.Io.net.Server, threaded: std.Io.Threaded, +routes: RouteSet, pub fn init( gpa: std.mem.Allocator, address: std.Io.net.IpAddress, + comptime routes: RouteSet, ) !Self { var threaded: std.Io.Threaded = .init(gpa, .{}); return .{ .threaded = threaded, .server = try address.listen(threaded.io(), .{ .reuse_address = true }), + .routes = routes, }; } -pub fn serve(self: *Self, content: []const u8) !void { +pub fn serve(self: *Self, gpa: std.mem.Allocator) !void { var read_buffer: [1024 * 8]u8 = undefined; var write_buffer: [1024 * 8]u8 = undefined; @@ -36,15 +41,14 @@ pub fn serve(self: *Self, content: []const u8) !void { std.debug.print("{s} {s}\n", .{ @tagName(request.head.method), request.head.target }); - if (std.mem.eql(u8, request.head.target, "/")) { - request.respond(content, .{ - .status = .ok, - .extra_headers = &.{ - .{ .name = "Content-Type", .value = "text/html" }, - }, - }) catch continue; + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + + if (self.routes.getInterface(request.head.target)) |interface| { + const message = interface.handle(gpa, arena.allocator(), self.threaded.io(), &request); + message.send(self.threaded.io(), &request) catch continue; } else { - request.respond("Not Found", .{ .status = .not_found }) catch continue; + request.respond("", .{ .status = .not_found }) catch continue; } } } 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.zig b/src/http/handler/context.zig index 2220fd0..4e55446 100644 --- a/src/http/handler.zig +++ b/src/http/handler/context.zig @@ -1,15 +1,15 @@ const std = @import("std"); - -const Response = @import("Response.zig"); -const Route = @import("Route.zig"); -const Parameter = @import("Parameter.zig"); +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 { - const route = Route.fromAny(r); - const Params = Parameter.Struct(route); + pub const route = Route.fromAny(r); + pub const Params = Parameter.Struct(route); - const Request = struct { + pub const Request = struct { base_request: *std.http.Server.Request, params: Params, arena: std.mem.Allocator, @@ -74,76 +74,3 @@ pub fn Context(comptime r: anytype) type { } }; } - -pub const Interface = struct { - 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); - } -}; - -test "interface" { - const M = struct { - 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/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 }; +} diff --git a/src/http/root.zig b/src/http/root.zig index 2fa4042..88d1420 100644 --- a/src/http/root.zig +++ b/src/http/root.zig @@ -3,7 +3,7 @@ const std = @import("std"); pub const Server = @import("Server.zig"); -pub const handler = @import("handler.zig"); +pub const handler = @import("handler/root.zig"); pub const Response = @import("Response.zig"); pub const Route = @import("Route.zig"); pub const RouteSet = @import("RouteSet.zig"); diff --git a/src/main.zig b/src/main.zig index 4b2b025..6d2b9ce 100644 --- a/src/main.zig +++ b/src/main.zig @@ -8,6 +8,8 @@ const http = @import("http"); const html = @import("html"); const z = @import("z"); +const Static = http.handler.Static; + const components = @import("web/component/root.zig").components; const document: html.Element = .native(.html, .{}, .children(&.{ @@ -26,9 +28,17 @@ const document: html.Element = .native(.html, .{}, .children(&.{ })), })); +const routes: http.RouteSet = .init(&.{ + Static(.index, document.toDocument()).interface(), +}); + pub fn main(init: std.process.Init) !void { - var server: http.Server = try .init(init.gpa, try .parseLiteral("0.0.0.0:8080")); - try server.serve(comptime document.toDocument()); + var server: http.Server = try .init( + init.gpa, + try .parseLiteral("0.0.0.0:8080"), + routes, + ); + try server.serve(init.gpa); } test { |