Update server.zig to use new handlers
This commit is contained in:
parent
73f7022d36
commit
03338da307
2 changed files with 89 additions and 52 deletions
|
@ -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 }),
|
||||
{},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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() });
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue