Update server.zig to use new handlers

This commit is contained in:
jaina heartles 2022-11-24 03:30:49 -08:00
parent 73f7022d36
commit 03338da307
2 changed files with 89 additions and 52 deletions

View File

@ -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 }),
{},
);
}
};

View File

@ -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() });
}