From 03338da307711d67fef6eda79922d7801d6e9279 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Thu, 24 Nov 2022 03:30:49 -0800 Subject: [PATCH] Update server.zig to use new handlers --- src/http/middleware.zig | 105 +++++++++++++++++++++++++++------------- src/http/server.zig | 36 +++++++------- 2 files changed, 89 insertions(+), 52 deletions(-) diff --git a/src/http/middleware.zig b/src/http/middleware.zig index 428d52f..9e8ce1b 100644 --- a/src/http/middleware.zig +++ b/src/http/middleware.zig @@ -55,19 +55,27 @@ fn applyInternal(middlewares: anytype, comptime fields: []const std.builtin.Type }; } -pub fn apply(middlewares: anytype) ApplyInternal(std.meta.fields(@TypeOf(middlewares))) { +pub fn apply(middlewares: anytype) Apply(@TypeOf(middlewares)) { return applyInternal(middlewares, std.meta.fields(@TypeOf(middlewares))); } -pub fn AddContext(comptime Rhs: type) type { +pub fn Apply(comptime Middlewares: type) type { + return ApplyInternal(std.meta.fields(Middlewares)); +} + +pub fn InjectContext(comptime Values: type) type { return struct { - values: Rhs, + values: Values, pub fn handle(self: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void { return next.handle(req, res, addFields(ctx, self.values), {}); } }; } +pub fn injectContext(values: anytype) InjectContext(@TypeOf(values)) { + return .{ .values = values }; +} + pub fn NextHandler(comptime First: type, comptime Next: type) type { return struct { first: First, @@ -146,13 +154,34 @@ pub const split_uri = struct { } }{}; -// helper function for doing route analysis -fn routeApplies(comptime R: type, req: anytype) bool { - if (R.method != req.method) return false; +// routes a request to the correct handler based on declared HTTP method and path +pub fn Router(comptime Routes: []const type) type { + return struct { + routes: std.meta.Tuple(Routes), - var path_iter = util.PathIter.from(req.path); - comptime var route_iter = util.PathIter.from(R.path); - inline while (comptime route_iter.next()) |route_segment| { + pub fn handle(self: @This(), req: anytype, res: anytype, ctx: anytype, next: void) !void { + _ = next; + + inline for (self.routes) |r| { + if (r.handle(req, res, ctx, {})) |_| + // success + return + else |err| switch (err) { + error.RouteMismatch => {}, + else => return err, + } + } + + return error.RouteMismatch; + } + }; +} + +// helper function for doing route analysis +fn pathMatches(route: []const u8, path: []const u8) bool { + var path_iter = util.PathIter.from(path); + var route_iter = util.PathIter.from(route); + while (route_iter.next()) |route_segment| { const path_segment = path_iter.next() orelse return false; if (route_segment.len > 0 and route_segment[0] == ':') { // Route Argument @@ -164,26 +193,37 @@ fn routeApplies(comptime R: type, req: anytype) bool { return true; } +pub const Route = struct { + pub const Desc = struct { + path: []const u8, + method: http.Method, + }; -// routes a request to the correct handler based on declared HTTP method and path -pub fn Router(comptime Routes: []const type) type { + desc: Desc, + + fn applies(self: @This(), req: anytype, ctx: anytype) bool { + if (self.desc.method != req.method) return false; + + const eff_path = if (@hasDecl(ctx, "path")) + ctx.path + else + std.mem.sliceTo(req.uri, '?'); + + return pathMatches(self.desc.path, eff_path); + } + + pub fn handle(self: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void { + return if (self.applies(req, ctx)) + next.handle(req, res, ctx, {}) + else + error.RouteMismatch; + } +}; +pub fn ComptimeRoute(comptime desc: Route.Desc) type { return struct { - routes: std.meta.Tuple(Routes), - - pub fn handle(self: @This(), req: anytype, res: anytype, ctx: anytype, next: void) !void { - _ = next; - - inline for (self.routes) |r| if (routeApplies(@TypeOf(r), req, ctx)) { - if (r.handle(req, res, ctx, {})) |_| { - // success! - return; - } else |err| switch (err) { - error.RouteMismatch => {}, - else => return err, - } - }; - - return error.RouteMismatch; + const route = Route{ .desc = desc }; + pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void { + return route.handle(req, res, ctx, next); } }; } @@ -327,18 +367,17 @@ pub fn ParseBody(comptime Body: type) type { }; } -pub fn ParseQueryParams(comptime Next: type, comptime QueryParams: type) type { +pub fn ParseQueryParams(comptime QueryParams: type) type { return struct { - next: Next, - - pub fn handler(self: @This(), req: anytype, res: anytype, ctx: anytype) !void { + pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void { const query = try query_utils.parseQuery(ctx.allocator, QueryParams, ctx.query_string); defer ctx.allocator.free(query); - return self.next.handler( + return next.handle( req, res, - addFields(ctx, .{ .query = query }), + addFields(ctx, .{ .query_params = query }), + {}, ); } }; diff --git a/src/http/server.zig b/src/http/server.zig index b24ae75..bfb97a9 100644 --- a/src/http/server.zig +++ b/src/http/server.zig @@ -92,7 +92,6 @@ pub const Server = struct { pub fn handleLoop( self: *Server, allocator: std.mem.Allocator, - ctx: anytype, handler: anytype, ) void { while (true) { @@ -109,7 +108,6 @@ pub const Server = struct { .stream = Stream{ .kind = .tcp, .socket = conn.stream.handle }, .address = conn.address, }, - ctx, handler, ); } @@ -118,12 +116,23 @@ pub const Server = struct { fn serveConn( allocator: std.mem.Allocator, conn: Connection, - ctx: anytype, handler: anytype, ) void { while (true) { var req = request.parse(allocator, conn.stream.reader()) catch |err| { - return handleError(conn.stream.writer(), err) catch {}; + const status: http.Status = switch (err) { + error.EndOfStream => return, // Do nothing, the client closed the connection + error.BadRequest => .bad_request, + error.UnsupportedMediaType => .unsupported_media_type, + error.HttpVersionNotSupported => .http_version_not_supported, + + else => blk: { + std.log.err("Unknown error parsing request: {}\n{?s}", .{ err, @errorReturnTrace() }); + break :blk .internal_server_error; + }, + }; + + try conn.stream.writer().print("HTTP/1.1 {} {?s}\r\nConnection: close\r\n\r\n", .{ @enumToInt(status), status.phrase() }); }; var res = Response{ @@ -131,7 +140,10 @@ pub const Server = struct { .stream = conn.stream, }; - handler(ctx, &req, &res); + handler.handle(&req, &res, .{}, {}) catch |err| { + std.log.err("Unhandled error serving request: {}\n{?s}", .{ err, @errorReturnTrace() }); + return; + }; if (req.headers.get("Connection")) |hdr| { if (std.ascii.indexOfIgnoreCase(hdr, "close")) |_| return; @@ -143,17 +155,3 @@ pub const Server = struct { } } }; - -/// Writes an error response message and requests closure of the connection -fn handleError(writer: anytype, err: anyerror) !void { - const status: http.Status = switch (err) { - error.EndOfStream => return, // Do nothing, the client closed the connection - error.BadRequest => .bad_request, - error.UnsupportedMediaType => .unsupported_media_type, - error.HttpVersionNotSupported => .http_version_not_supported, - - else => .internal_server_error, - }; - - try writer.print("HTTP/1.1 {} {?s}\r\nConnection: close\r\n\r\n", .{ @enumToInt(status), status.phrase() }); -}