Skip to content

Commit

Permalink
feat(router): add serve_not_found fn to router
Browse files Browse the repository at this point in the history
  • Loading branch information
mookums committed Nov 12, 2024
1 parent fd97979 commit 026c333
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 66 deletions.
12 changes: 11 additions & 1 deletion examples/basic/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub fn main() !void {
const num: i8 = 12;

try router.serve_route("/", Route.init().get(&num, struct {
pub fn handler_fn(ctx: *Context, id: *const i8) !void {
fn handler_fn(ctx: *Context, id: *const i8) !void {
const body_fmt =
\\ <!DOCTYPE html>
\\ <html>
Expand All @@ -59,6 +59,16 @@ pub fn main() !void {
}
}.handler_fn));

router.serve_not_found(Route.init().get({}, struct {
fn handler_fn(ctx: *Context, _: void) !void {
try ctx.respond(.{
.status = .@"Not Found",
.mime = http.Mime.HTML,
.body = "Not Found Handler!",
});
}
}.handler_fn));

// This provides the entry function into the Tardy runtime. This will run
// exactly once inside of each runtime (each thread gets a single runtime).
try t.entry(
Expand Down
2 changes: 1 addition & 1 deletion src/http/route.zig
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub fn Route(comptime Server: type) type {
// You can either give a void (if you don't want to pass data through) or a pointer.
comptime assert(@typeInfo(@TypeOf(data)) == .Pointer or @typeInfo(@TypeOf(data)) == .Void);
const inner_data = switch (comptime @typeInfo(@TypeOf(data))) {
.Void => @intFromPtr(&data),
.Void => undefined,
.Pointer => @intFromPtr(data),
else => unreachable,
};
Expand Down
26 changes: 23 additions & 3 deletions src/http/router.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub fn Router(comptime Server: type) type {
allocator: std.mem.Allocator,
arena: std.heap.ArenaAllocator,
routes: RoutingTrie,
not_found_route: ?Route = null,
/// This makes the router immutable, also making it
/// thread-safe when shared.
locked: bool = false,
Expand Down Expand Up @@ -78,6 +79,7 @@ pub fn Router(comptime Server: type) type {
//
// If we have a matching etag, we can just respond with Not Modified.
// If we don't then we continue doing what we normally do.

try rt.fs.read(
provision,
read_file_task,
Expand Down Expand Up @@ -105,7 +107,6 @@ pub fn Router(comptime Server: type) type {
}

const length: usize = @intCast(result);

try provision.list.appendSlice(provision.buffer[0..length]);

// TODO: This needs to be a setting you pass in to the router.
Expand Down Expand Up @@ -274,13 +275,32 @@ pub fn Router(comptime Server: type) type {
try self.serve_route(path, route);
}

pub fn serve_not_found(self: *Self, route: Route) void {
self.not_found_route = route;
}

pub fn serve_route(self: *Self, path: []const u8, route: Route) !void {
assert(!self.locked);
try self.routes.add_route(path, route);
}

pub fn get_route_from_host(self: Self, host: []const u8, captures: []Capture, queries: *QueryMap) ?FoundRoute {
return self.routes.get_route(host, captures, queries);
fn not_found_handler(ctx: *Context, _: void) !void {
try ctx.respond(.{
.status = .@"Not Found",
.mime = Mime.HTML,
.body = "",
});
}

pub fn get_route_from_host(self: Self, path: []const u8, captures: []Capture, queries: *QueryMap) FoundRoute {
const base_404_route = comptime Route.init().get({}, not_found_handler);

return self.routes.get_route(path, captures, queries) orelse {
if (self.not_found_route) |not_found| {
queries.clearRetainingCapacity();
return FoundRoute{ .route = not_found, .captures = captures[0..0], .queries = queries };
} else return FoundRoute{ .route = base_404_route, .captures = captures[0..], .queries = queries };
};
}
};
}
112 changes: 51 additions & 61 deletions src/http/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -745,78 +745,68 @@ pub fn Server(comptime security: Security) type {
fn route_and_respond(runtime: *Runtime, p: *Provision, router: *const Router) !RecvStatus {
route: {
const found = router.get_route_from_host(p.request.uri, p.captures, &p.queries);
if (found) |f| {
const handler = f.route.get_handler(p.request.method);

if (handler) |h_with_data| {
const context: *Context = try p.arena.allocator().create(Context);
context.* = .{
.allocator = p.arena.allocator(),
.runtime = runtime,
.request = &p.request,
.response = &p.response,
.path = p.request.uri,
.captures = f.captures,
.queries = f.queries,
.provision = p,
};

@call(.auto, h_with_data.handler, .{
context,
@as(*anyopaque, @ptrFromInt(h_with_data.data)),
}) catch |e| {
log.err("\"{s}\" handler failed with error: {}", .{ p.request.uri, e });
p.response.set(.{
.status = .@"Internal Server Error",
.mime = Mime.HTML,
.body = "",
});

return try raw_respond(p);
};
const handler = found.route.get_handler(p.request.method);

if (handler) |h_with_data| {
const context: *Context = try p.arena.allocator().create(Context);
context.* = .{
.allocator = p.arena.allocator(),
.runtime = runtime,
.request = &p.request,
.response = &p.response,
.path = p.request.uri,
.captures = found.captures,
.queries = found.queries,
.provision = p,
};

return .spawned;
} else {
// If we match the route but not the method.
@call(.auto, h_with_data.handler, .{
context,
@as(*anyopaque, @ptrFromInt(h_with_data.data)),
}) catch |e| {
log.err("\"{s}\" handler failed with error: {}", .{ p.request.uri, e });
p.response.set(.{
.status = .@"Method Not Allowed",
.status = .@"Internal Server Error",
.mime = Mime.HTML,
.body = "405 Method Not Allowed",
.body = "",
});

// We also need to add to Allow header.
// This uses the connection's arena to allocate 64 bytes.
const allowed = f.route.get_allowed(p.arena.allocator()) catch {
p.response.set(.{
.status = .@"Internal Server Error",
.mime = Mime.HTML,
.body = "",
});
return try raw_respond(p);
};

break :route;
};
return .spawned;
} else {
// If we match the route but not the method.
p.response.set(.{
.status = .@"Method Not Allowed",
.mime = Mime.HTML,
.body = "405 Method Not Allowed",
});

p.response.headers.add("Allow", allowed) catch {
p.response.set(.{
.status = .@"Internal Server Error",
.mime = Mime.HTML,
.body = "",
});
// We also need to add to Allow header.
// This uses the connection's arena to allocate 64 bytes.
const allowed = found.route.get_allowed(p.arena.allocator()) catch {
p.response.set(.{
.status = .@"Internal Server Error",
.mime = Mime.HTML,
.body = "",
});

break :route;
};
break :route;
};

p.response.headers.add("Allow", allowed) catch {
p.response.set(.{
.status = .@"Internal Server Error",
.mime = Mime.HTML,
.body = "",
});

break :route;
}
}
};

// Didn't match any route.
p.response.set(.{
.status = .@"Not Found",
.mime = Mime.HTML,
.body = "404 Not Found",
});
break :route;
break :route;
}
}

if (p.response.status == .Kill) {
Expand Down

0 comments on commit 026c333

Please sign in to comment.