srctree

Gregory Mullen parent 0f58fd04 1b44734f
provide better routing defaults

src/errors.zig added: 85, removed: 117, total 0
@@ -1,8 +1,6 @@
pub const ServerError = error{
AndExit,
OutOfMemory,
NoSpaceLeft,
ReqResInvalid,
NotImplemented,
Unknown,
};
 
src/http.zig added: 85, removed: 117, total 0
@@ -57,45 +57,10 @@ pub fn serve(http: *HTTP) !void {
try req.addHeader("REMOTE_PORT", ipport[i + 1 ..]);
} else unreachable;
 
var ctx = try buildVerse(a, &req);
var verse = try buildVerse(a, &req);
 
const callable = try http.router.routefn(&ctx);
http.router.buildfn(&ctx, callable) catch |err| {
switch (err) {
error.NetworkCrash => log.warn("client disconnect", .{}),
error.Unrouteable => {
log.err("Unrouteable", .{});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
},
error.NotImplemented,
error.Unknown,
error.ReqResInvalid,
error.AndExit,
error.NoSpaceLeft,
=> {
log.err("Unexpected error '{}'\n", .{err});
return err;
},
error.InvalidURI => unreachable,
error.OutOfMemory => {
log.err("Out of memory at '{}'\n", .{arena.queryCapacity()});
return err;
},
error.Abusive,
error.Unauthenticated,
error.BadData,
error.DataMissing,
=> {
log.err("Abusive {} because {}\n", .{ ctx.request, err });
var itr = ctx.request.raw.http.iterateHeaders();
while (itr.next()) |vars| {
log.err("Abusive var '{s}' => '''{s}'''\n", .{ vars.name, vars.value });
}
},
}
};
const callable = http.router.routerfn(&verse, http.router.routefn);
http.router.builderfn(&verse, callable);
}
unreachable;
}
 
src/router.zig added: 85, removed: 117, total 0
@@ -12,15 +12,29 @@ const StaticFile = @import("static-file.zig");
pub const Errors = @import("errors.zig");
pub const Error = Errors.ServerError || Errors.ClientError || Errors.NetworkError;
 
pub const RouteFn = *const fn (*Verse) Error!BuildFn;
/// The default page generator, this is the function that will be called, and
/// expected to write the page data back to the client.
pub const BuildFn = *const fn (*Verse) Error!void;
pub const PrepareFn = *const fn (*Verse, BuildFn) Error!void;
/// Similar to RouteFn and RouterFn above, Verse requires all page build steps
/// to finish cleanly. While a default is provided. It's strongly recommended
/// that a custom builder function be provided when custom error handling is
/// desired.
pub const BuilderFn = *const fn (*Verse, BuildFn) void;
 
/// Route Functions are allowed to return errors for select cases where
/// backtracking through the routing system might be useful. This in an
/// exercise left to the caller, as eventually a sever default server error page
/// will need to be returned.
pub const RouteFn = *const fn (*Verse) Error!BuildFn;
/// The provided RouteFn will be wrapped with a default error provider that will
/// return a default BuildFn.
pub const RouterFn = *const fn (*Verse, RouteFn) BuildFn;
 
pub const Router = @This();
 
builderfn: BuilderFn = defaultBuilder,
routefn: RouteFn,
// TODO better naming
buildfn: PrepareFn = basePrepare,
routerfn: RouterFn = defaultRouter,
 
/// Methods is a struct so bitwise or will work as expected
pub const Methods = packed struct {
@@ -47,11 +61,6 @@ pub const Methods = packed struct {
}
};
 
pub const Endpoint = struct {
builder: BuildFn,
methods: Methods = .{ .GET = true },
};
 
pub const Match = struct {
name: []const u8,
match: union(enum) {
@@ -137,10 +146,11 @@ fn default(vrs: *Verse) Error!void {
return vrs.sendRawSlice(index);
}
 
pub fn router(vrs: *Verse, comptime routes: []const Match) BuildFn {
pub fn router(vrs: *Verse, comptime routes: []const Match) Error!BuildFn {
const search = vrs.uri.peek() orelse {
// Calling router without a next URI is unsupported.
log.warn("No endpoint found: URI is empty.", .{});
return notFound;
return error.Unrouteable;
};
inline for (routes) |ep| {
if (eql(u8, search, ep.name)) {
@@ -166,38 +176,70 @@ pub fn router(vrs: *Verse, comptime routes: []const Match) BuildFn {
}
}
}
return notFound;
return error.Unrouteable;
}
 
pub fn defaultBuilder(vrs: *Verse, build: BuildFn) void {
build(vrs) catch |err| {
switch (err) {
error.NoSpaceLeft,
error.OutOfMemory,
=> @panic("OOM"),
error.NetworkCrash => log.warn("client disconnect", .{}),
error.Unrouteable => {
// Reaching an Unrouteable error here should be impossible as
// the router has decided the target endpoint is correct.
// However it's a vaild error in somecases. A non-default buildfn
// could provide a replacement default. But this does not.
log.err("Unrouteable", .{});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
@panic("Unroutable");
},
error.NotImplemented,
error.Unknown,
=> unreachable,
error.InvalidURI,
=> log.err("Unexpected error '{}'\n", .{err}),
error.Abusive,
error.Unauthenticated,
error.BadData,
error.DataMissing,
=> {
// BadData and DataMissing aren't likely to be abusive, but
// dumping the information is likely to help with debugging the
// error.
log.err("Abusive {} because {}\n", .{ vrs.request, err });
var itr = vrs.request.raw.http.iterateHeaders();
while (itr.next()) |vars| {
log.err("Abusive var '{s}' => '''{s}'''\n", .{ vars.name, vars.value });
}
},
}
};
}
 
const root = [_]Match{
ROUTE("", default),
};
 
pub fn basePrepare(vrs: *Verse, build: BuildFn) Error!void {
return build(vrs);
}
 
pub fn baseRouter(vrs: *Verse) Error!void {
log.debug("baserouter {s}", .{vrs.uri.peek().?});
pub fn defaultRouter(vrs: *Verse, routefn: RouteFn) BuildFn {
if (vrs.uri.peek()) |first| {
if (first.len > 0) {
const route: BuildFn = router(vrs, &root);
return route(vrs);
}
if (first.len > 0)
return routefn(vrs) catch router(vrs, &root) catch default;
}
return default(vrs);
return internalServerError;
}
 
const root_with_static = root ++
[_]Match{.{ .name = "static", .match = .{ .call = StaticFile.file } }};
const root_with_static = root ++ [_]Match{
ROUTE("static", StaticFile.file),
};
 
pub fn baseRouterHtml(vrs: *Verse) Error!void {
log.debug("baserouter {s}\n", .{vrs.uri.peek().?});
pub fn defaultRouterHtml(vrs: *Verse, routefn: RouteFn) Error!void {
if (vrs.uri.peek()) |first| {
if (first.len > 0) {
const route: BuildFn = router(vrs, &root_with_static);
return route(vrs);
}
if (first.len > 0)
return routefn(vrs) catch router(vrs, &root_with_static) catch default;
}
return default(vrs);
return internalServerError;
}
 
src/zwsgi.zig added: 85, removed: 117, total 0
@@ -56,10 +56,10 @@ pub fn serve(z: *zWSGI) !void {
 
var zreq = try readHeader(a, &acpt);
var request = try Request.init(a, &zreq);
var ctx = try buildVerse(a, &request);
var verse = try buildVerse(a, &request);
 
defer {
const vars = ctx.request.raw.zwsgi.vars;
const vars = verse.request.raw.zwsgi.vars;
log.err("zWSGI: [{d:.3}] {s} - {s}: {s} -- \"{s}\"", .{
@as(f64, @floatFromInt(timer.lap())) / 1000000.0,
findOr(vars, "REMOTE_ADDR"),
@@ -69,45 +69,8 @@ pub fn serve(z: *zWSGI) !void {
});
}
 
const callable = try z.router.routefn(&ctx);
z.router.buildfn(&ctx, callable) catch |err| {
switch (err) {
error.NetworkCrash => log.err("client disconnect", .{}),
error.Unrouteable => {
log.err("Unrouteable", .{});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
},
error.NotImplemented,
error.Unknown,
error.ReqResInvalid,
error.AndExit,
error.NoSpaceLeft,
=> {
log.err("Unexpected error '{}'", .{err});
return err;
},
error.InvalidURI => unreachable,
error.OutOfMemory => {
log.err("Out of memory at '{}'", .{arena.queryCapacity()});
return err;
},
error.Abusive,
error.Unauthenticated,
error.BadData,
error.DataMissing,
=> {
log.err("Abusive {} because {}", .{ ctx.request, err });
for (ctx.request.raw.zwsgi.vars) |vars| {
log.err("Abusive var '{s}' => '''{s}'''", .{ vars.key, vars.val });
}
if (ctx.reqdata.post) |post_data| {
log.err("post data => '''{s}'''", .{post_data.rawpost});
}
},
}
};
const callable = z.router.routerfn(&verse, z.router.routefn);
z.router.builderfn(&verse, callable);
}
}