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)));
|
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 {
|
return struct {
|
||||||
values: Rhs,
|
values: Values,
|
||||||
pub fn handle(self: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void {
|
pub fn handle(self: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void {
|
||||||
return next.handle(req, res, addFields(ctx, self.values), {});
|
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 {
|
pub fn NextHandler(comptime First: type, comptime Next: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
first: First,
|
first: First,
|
||||||
|
@ -146,13 +154,34 @@ pub const split_uri = struct {
|
||||||
}
|
}
|
||||||
}{};
|
}{};
|
||||||
|
|
||||||
// helper function for doing route analysis
|
// routes a request to the correct handler based on declared HTTP method and path
|
||||||
fn routeApplies(comptime R: type, req: anytype) bool {
|
pub fn Router(comptime Routes: []const type) type {
|
||||||
if (R.method != req.method) return false;
|
return struct {
|
||||||
|
routes: std.meta.Tuple(Routes),
|
||||||
|
|
||||||
var path_iter = util.PathIter.from(req.path);
|
pub fn handle(self: @This(), req: anytype, res: anytype, ctx: anytype, next: void) !void {
|
||||||
comptime var route_iter = util.PathIter.from(R.path);
|
_ = next;
|
||||||
inline while (comptime route_iter.next()) |route_segment| {
|
|
||||||
|
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;
|
const path_segment = path_iter.next() orelse return false;
|
||||||
if (route_segment.len > 0 and route_segment[0] == ':') {
|
if (route_segment.len > 0 and route_segment[0] == ':') {
|
||||||
// Route Argument
|
// Route Argument
|
||||||
|
@ -164,26 +193,37 @@ fn routeApplies(comptime R: type, req: anytype) bool {
|
||||||
|
|
||||||
return true;
|
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
|
desc: Desc,
|
||||||
pub fn Router(comptime Routes: []const type) type {
|
|
||||||
|
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 {
|
return struct {
|
||||||
routes: std.meta.Tuple(Routes),
|
const route = Route{ .desc = desc };
|
||||||
|
pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void {
|
||||||
pub fn handle(self: @This(), req: anytype, res: anytype, ctx: anytype, next: void) !void {
|
return route.handle(req, res, ctx, next);
|
||||||
_ = 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;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
return struct {
|
||||||
next: Next,
|
pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void {
|
||||||
|
|
||||||
pub fn handler(self: @This(), req: anytype, res: anytype, ctx: anytype) !void {
|
|
||||||
const query = try query_utils.parseQuery(ctx.allocator, QueryParams, ctx.query_string);
|
const query = try query_utils.parseQuery(ctx.allocator, QueryParams, ctx.query_string);
|
||||||
defer ctx.allocator.free(query);
|
defer ctx.allocator.free(query);
|
||||||
|
|
||||||
return self.next.handler(
|
return next.handle(
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
addFields(ctx, .{ .query = query }),
|
addFields(ctx, .{ .query_params = query }),
|
||||||
|
{},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -92,7 +92,6 @@ pub const Server = struct {
|
||||||
pub fn handleLoop(
|
pub fn handleLoop(
|
||||||
self: *Server,
|
self: *Server,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
ctx: anytype,
|
|
||||||
handler: anytype,
|
handler: anytype,
|
||||||
) void {
|
) void {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -109,7 +108,6 @@ pub const Server = struct {
|
||||||
.stream = Stream{ .kind = .tcp, .socket = conn.stream.handle },
|
.stream = Stream{ .kind = .tcp, .socket = conn.stream.handle },
|
||||||
.address = conn.address,
|
.address = conn.address,
|
||||||
},
|
},
|
||||||
ctx,
|
|
||||||
handler,
|
handler,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -118,12 +116,23 @@ pub const Server = struct {
|
||||||
fn serveConn(
|
fn serveConn(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
conn: Connection,
|
conn: Connection,
|
||||||
ctx: anytype,
|
|
||||||
handler: anytype,
|
handler: anytype,
|
||||||
) void {
|
) void {
|
||||||
while (true) {
|
while (true) {
|
||||||
var req = request.parse(allocator, conn.stream.reader()) catch |err| {
|
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{
|
var res = Response{
|
||||||
|
@ -131,7 +140,10 @@ pub const Server = struct {
|
||||||
.stream = conn.stream,
|
.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 (req.headers.get("Connection")) |hdr| {
|
||||||
if (std.ascii.indexOfIgnoreCase(hdr, "close")) |_| return;
|
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