Create router.zig
This commit is contained in:
parent
40cad8287a
commit
6a37685643
3 changed files with 172 additions and 164 deletions
165
src/http.zig
165
src/http.zig
|
@ -4,6 +4,7 @@ const root = @import("root");
|
|||
const ciutf8 = root.ciutf8;
|
||||
const Reader = std.net.Stream.Reader;
|
||||
const Writer = std.net.Stream.Writer;
|
||||
const Route = root.router.Route;
|
||||
|
||||
const HeaderMap = std.HashMap([]const u8, []const u8, struct {
|
||||
pub fn eql(_: @This(), a: []const u8, b: []const u8) bool {
|
||||
|
@ -186,7 +187,7 @@ fn handleHttpRequest(reader: Reader, writer: Writer) anyerror!void {
|
|||
.allocator = allocator,
|
||||
};
|
||||
|
||||
try routeRequest(&context);
|
||||
try root.router.routeRequest(&context);
|
||||
}
|
||||
|
||||
pub const Context = struct {
|
||||
|
@ -263,165 +264,3 @@ pub const Context = struct {
|
|||
response: Response,
|
||||
allocator: std.mem.Allocator,
|
||||
};
|
||||
|
||||
pub const Route = struct {
|
||||
const Segment = union(enum) {
|
||||
param: []const u8,
|
||||
literal: []const u8,
|
||||
};
|
||||
|
||||
pub const Handler = fn (*Context) callconv(.Async) anyerror!void;
|
||||
|
||||
fn normalize(comptime path: []const u8) []const u8 {
|
||||
var arr: [path.len]u8 = undefined;
|
||||
|
||||
var i = 0;
|
||||
for (path) |ch| {
|
||||
if (i == 0 and ch == '/') continue;
|
||||
if (i > 0 and ch == '/' and arr[i - 1] == '/') continue;
|
||||
|
||||
arr[i] = ch;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i > 0 and arr[i - 1] == '/') {
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
return arr[0..i];
|
||||
}
|
||||
|
||||
fn parseSegments(comptime path: []const u8) []const Segment {
|
||||
var count = 1;
|
||||
for (path) |ch| {
|
||||
if (ch == '/') count += 1;
|
||||
}
|
||||
|
||||
var segment_array: [count]Segment = undefined;
|
||||
|
||||
var segment_start = 0;
|
||||
for (segment_array) |*seg| {
|
||||
var index = segment_start;
|
||||
while (index < path.len) : (index += 1) {
|
||||
if (path[index] == '/') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const slice = path[segment_start..index];
|
||||
if (slice.len > 0 and slice[0] == ':') {
|
||||
// doing this kinda jankily to get around segfaults in compiler
|
||||
const param = path[segment_start + 1 .. index];
|
||||
seg.* = .{ .param = param };
|
||||
} else {
|
||||
seg.* = .{ .literal = slice };
|
||||
}
|
||||
|
||||
segment_start = index + 1;
|
||||
}
|
||||
|
||||
return &segment_array;
|
||||
}
|
||||
|
||||
pub fn from(method: Method, comptime path: []const u8, handler: Handler) Route {
|
||||
const segments = parseSegments(normalize(path));
|
||||
return Route{ .method = method, .path = segments, .handler = handler };
|
||||
}
|
||||
|
||||
fn nextSegment(path: []const u8) ?[]const u8 {
|
||||
var start: usize = 0;
|
||||
var end: usize = start;
|
||||
while (end < path.len) : (end += 1) {
|
||||
// skip leading slash
|
||||
if (end == start and path[start] == '/') {
|
||||
start += 1;
|
||||
continue;
|
||||
} else if (path[end] == '/') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (start == end) return null;
|
||||
|
||||
return path[start..end];
|
||||
}
|
||||
|
||||
pub fn matches(self: Route, path: []const u8) bool {
|
||||
var segment_start: usize = 0;
|
||||
for (self.path) |seg| {
|
||||
var index = segment_start;
|
||||
while (index < path.len) : (index += 1) {
|
||||
// skip leading slash
|
||||
if (index == segment_start and path[index] == '/') {
|
||||
segment_start += 1;
|
||||
continue;
|
||||
} else if (path[index] == '/') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const slice = path[segment_start..index];
|
||||
const match = switch (seg) {
|
||||
.literal => |str| ciutf8.eql(slice, str),
|
||||
.param => true,
|
||||
};
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
segment_start = index + 1;
|
||||
}
|
||||
|
||||
// check for trailing path
|
||||
while (segment_start < path.len) : (segment_start += 1) {
|
||||
if (path[segment_start] != '/') return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn arg(self: Route, name: []const u8, path: []const u8) []const u8 {
|
||||
var index: usize = 0;
|
||||
for (self.path) |seg| {
|
||||
const slice = nextSegment(path[index..]);
|
||||
if (slice == null) return "";
|
||||
|
||||
index = @ptrToInt(slice.?.ptr) - @ptrToInt(path.ptr) + slice.?.len + 1;
|
||||
|
||||
switch (seg) {
|
||||
.param => |param| {
|
||||
if (std.mem.eql(u8, param, name)) {
|
||||
return slice.?;
|
||||
}
|
||||
},
|
||||
.literal => continue,
|
||||
}
|
||||
}
|
||||
|
||||
std.log.err("unknown parameter {s}", .{name});
|
||||
return "";
|
||||
}
|
||||
|
||||
method: Method,
|
||||
path: []const Segment,
|
||||
handler: Handler,
|
||||
};
|
||||
|
||||
fn handleNotFound(ctx: *Context) !void {
|
||||
try ctx.response.writer.writeAll("HTTP/1.1 404 Not Found\r\n\r\n");
|
||||
}
|
||||
|
||||
fn routeRequest(ctx: *Context) !void {
|
||||
for (root.routes) |*route| {
|
||||
if (route.method == ctx.request.method and route.matches(ctx.request.path)) {
|
||||
std.log.info("{s} {s}", .{ @tagName(ctx.request.method), ctx.request.path });
|
||||
ctx.request.route = route;
|
||||
|
||||
var buf = try ctx.allocator.allocWithOptions(u8, @frameSize(route.handler), 8, null);
|
||||
defer ctx.allocator.free(buf);
|
||||
return await @asyncCall(buf, {}, route.handler, .{ctx});
|
||||
}
|
||||
}
|
||||
|
||||
std.log.info("404 {s} {s}", .{ @tagName(ctx.request.method), ctx.request.path });
|
||||
try handleNotFound(ctx);
|
||||
}
|
||||
|
|
|
@ -3,13 +3,14 @@ const std = @import("std");
|
|||
pub const db = @import("./db.zig");
|
||||
pub const util = @import("./util.zig");
|
||||
pub const http = @import("./http.zig");
|
||||
pub const router = @import("./router.zig");
|
||||
|
||||
pub const Uuid = util.Uuid;
|
||||
pub const ciutf8 = util.ciutf8;
|
||||
|
||||
pub const io_mode = .evented;
|
||||
|
||||
const Route = http.Route;
|
||||
const Route = router.Route;
|
||||
|
||||
pub const routes = [_]Route{
|
||||
Route.from(.GET, "/", staticString("Index Page")),
|
||||
|
|
168
src/router.zig
Normal file
168
src/router.zig
Normal file
|
@ -0,0 +1,168 @@
|
|||
const std = @import("std");
|
||||
const root = @import("root");
|
||||
|
||||
const Context = root.http.Context;
|
||||
const Method = root.http.Method;
|
||||
const ciutf8 = root.ciutf8;
|
||||
|
||||
pub const Route = struct {
|
||||
const Segment = union(enum) {
|
||||
param: []const u8,
|
||||
literal: []const u8,
|
||||
};
|
||||
|
||||
pub const Handler = fn (*Context) callconv(.Async) anyerror!void;
|
||||
|
||||
fn normalize(comptime path: []const u8) []const u8 {
|
||||
var arr: [path.len]u8 = undefined;
|
||||
|
||||
var i = 0;
|
||||
for (path) |ch| {
|
||||
if (i == 0 and ch == '/') continue;
|
||||
if (i > 0 and ch == '/' and arr[i - 1] == '/') continue;
|
||||
|
||||
arr[i] = ch;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i > 0 and arr[i - 1] == '/') {
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
return arr[0..i];
|
||||
}
|
||||
|
||||
fn parseSegments(comptime path: []const u8) []const Segment {
|
||||
var count = 1;
|
||||
for (path) |ch| {
|
||||
if (ch == '/') count += 1;
|
||||
}
|
||||
|
||||
var segment_array: [count]Segment = undefined;
|
||||
|
||||
var segment_start = 0;
|
||||
for (segment_array) |*seg| {
|
||||
var index = segment_start;
|
||||
while (index < path.len) : (index += 1) {
|
||||
if (path[index] == '/') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const slice = path[segment_start..index];
|
||||
if (slice.len > 0 and slice[0] == ':') {
|
||||
// doing this kinda jankily to get around segfaults in compiler
|
||||
const param = path[segment_start + 1 .. index];
|
||||
seg.* = .{ .param = param };
|
||||
} else {
|
||||
seg.* = .{ .literal = slice };
|
||||
}
|
||||
|
||||
segment_start = index + 1;
|
||||
}
|
||||
|
||||
return &segment_array;
|
||||
}
|
||||
|
||||
pub fn from(method: Method, comptime path: []const u8, handler: Handler) Route {
|
||||
const segments = parseSegments(normalize(path));
|
||||
return Route{ .method = method, .path = segments, .handler = handler };
|
||||
}
|
||||
|
||||
fn nextSegment(path: []const u8) ?[]const u8 {
|
||||
var start: usize = 0;
|
||||
var end: usize = start;
|
||||
while (end < path.len) : (end += 1) {
|
||||
// skip leading slash
|
||||
if (end == start and path[start] == '/') {
|
||||
start += 1;
|
||||
continue;
|
||||
} else if (path[end] == '/') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (start == end) return null;
|
||||
|
||||
return path[start..end];
|
||||
}
|
||||
|
||||
pub fn matches(self: Route, path: []const u8) bool {
|
||||
var segment_start: usize = 0;
|
||||
for (self.path) |seg| {
|
||||
var index = segment_start;
|
||||
while (index < path.len) : (index += 1) {
|
||||
// skip leading slash
|
||||
if (index == segment_start and path[index] == '/') {
|
||||
segment_start += 1;
|
||||
continue;
|
||||
} else if (path[index] == '/') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const slice = path[segment_start..index];
|
||||
const match = switch (seg) {
|
||||
.literal => |str| ciutf8.eql(slice, str),
|
||||
.param => true,
|
||||
};
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
segment_start = index + 1;
|
||||
}
|
||||
|
||||
// check for trailing path
|
||||
while (segment_start < path.len) : (segment_start += 1) {
|
||||
if (path[segment_start] != '/') return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn arg(self: Route, name: []const u8, path: []const u8) []const u8 {
|
||||
var index: usize = 0;
|
||||
for (self.path) |seg| {
|
||||
const slice = nextSegment(path[index..]);
|
||||
if (slice == null) return "";
|
||||
|
||||
index = @ptrToInt(slice.?.ptr) - @ptrToInt(path.ptr) + slice.?.len + 1;
|
||||
|
||||
switch (seg) {
|
||||
.param => |param| {
|
||||
if (std.mem.eql(u8, param, name)) {
|
||||
return slice.?;
|
||||
}
|
||||
},
|
||||
.literal => continue,
|
||||
}
|
||||
}
|
||||
|
||||
std.log.err("unknown parameter {s}", .{name});
|
||||
return "";
|
||||
}
|
||||
|
||||
method: Method,
|
||||
path: []const Segment,
|
||||
handler: Handler,
|
||||
};
|
||||
|
||||
fn handleNotFound(ctx: *Context) !void {
|
||||
try ctx.response.writer.writeAll("HTTP/1.1 404 Not Found\r\n\r\n");
|
||||
}
|
||||
|
||||
pub fn routeRequest(ctx: *Context) !void {
|
||||
for (root.routes) |*route| {
|
||||
if (route.method == ctx.request.method and route.matches(ctx.request.path)) {
|
||||
std.log.info("{s} {s}", .{ @tagName(ctx.request.method), ctx.request.path });
|
||||
ctx.request.route = route;
|
||||
|
||||
var buf = try ctx.allocator.allocWithOptions(u8, @frameSize(route.handler), 8, null);
|
||||
defer ctx.allocator.free(buf);
|
||||
return await @asyncCall(buf, {}, route.handler, .{ctx});
|
||||
}
|
||||
}
|
||||
|
||||
std.log.info("404 {s} {s}", .{ @tagName(ctx.request.method), ctx.request.path });
|
||||
try handleNotFound(ctx);
|
||||
}
|
Loading…
Reference in a new issue