From 95699a6ed77e1480e7b9256035225981cb33bfbb Mon Sep 17 00:00:00 2001 From: Martin Ashby Date: Sun, 6 Aug 2023 06:59:44 +0100 Subject: Add path parsing, use it --- src/zigwebserver.zig | 103 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 6 deletions(-) (limited to 'src/zigwebserver.zig') diff --git a/src/zigwebserver.zig b/src/zigwebserver.zig index 6ce4439..37a43a4 100644 --- a/src/zigwebserver.zig +++ b/src/zigwebserver.zig @@ -37,9 +37,9 @@ pub fn Server(comptime Context: type, comptime Handler: type) type { defer res.deinit(); if (res.wait()) { if (self.handler.handle(res, ctx)) { - std.log.info("Success handling request for {}", .{res.address}); + std.log.info("Success handling request [{s} {s} {s}] status {d} client {}", .{ @tagName(res.request.method), res.request.target, @tagName(res.request.version), @intFromEnum(res.status), res.address }); } else |err| { - std.log.err("Error handling request for {} : {}", .{ res.address, err }); + std.log.info("Error handling request [{s} {s} {s}] client {} error {}", .{ @tagName(res.request.method), res.request.target, @tagName(res.request.version), res.address, err }); if (handle_simple_response(res, "Server error!", .internal_server_error)) {} else |err2| { std.log.err("Error sending error page for {} : {}", .{ res.address, err2 }); } @@ -97,6 +97,9 @@ pub fn Router(comptime Context: type, comptime ErrorType: type) type { // It is a programmer error to call this without calling .wait first. if (res.state != .waited) unreachable; + const p = try Path.parse(res.request.target); + const path = p.path; + handler_loop: for (self.handlers) |handler| { if (handler.method != res.request.method) { continue :handler_loop; @@ -106,7 +109,7 @@ pub fn Router(comptime Context: type, comptime ErrorType: type) type { defer path_params.deinit(); var handle_split = std.mem.splitScalar(u8, handler.pattern, '/'); - var req_split = std.mem.splitScalar(u8, res.request.target, '/'); + var req_split = std.mem.splitScalar(u8, path, '/'); while (true) { const maybe_handle_seg = handle_split.next(); @@ -145,9 +148,9 @@ pub fn Router(comptime Context: type, comptime ErrorType: type) type { }; } -const T = struct { +const RouterTest = struct { const TestCtx = struct {}; - const TestErr = error{ TestError, OutOfMemory }; + const TestErr = error{ TestError, OutOfMemory } || Path.ParseError; const TestRouter = Router(TestCtx, TestErr); var notfoundinvoked = false; @@ -311,6 +314,12 @@ const T = struct { .route1expected = true, // .route1paramsexpected = m1, }, + .{ + .target = "/baz/bam/boo?somequery=foo", + .route1 = "/baz/{var}/boo", + .route1expected = true, + // .route1paramsexpected = m1, + }, // .{ // .target = "/baz/bam/bar", // .route1 = "/baz/{var}/boo", @@ -337,6 +346,88 @@ const T = struct { } }; +/// HTTP path parsing +/// which is a subset of URI parsing :) +pub const Path = struct { + path: []const u8, + query: []const u8, + fragment: []const u8, // technically I think the fragment is never received on the server anyway + + pub const ParseError = error{Malformatted}; + + pub fn parse(path: []const u8) ParseError!Path { + var p = Path{ + .path = path, + .query = "", + .fragment = "", + }; + const q_ix = std.mem.indexOfScalar(u8, path, '?'); + const f_ix = std.mem.indexOfScalar(u8, path, '#'); + if (q_ix) |q| { + p.path = path[0..q]; + if (f_ix) |f| { + if (f < q) { + return ParseError.Malformatted; + } + p.query = path[(q + 1)..f]; + p.fragment = path[(f + 1)..]; + } else { + p.query = path[(q + 1)..]; + } + } else if (f_ix) |f| { + p.path = path[0..f]; + p.fragment = path[(f + 1)..]; + } + return p; + } + + pub fn get_query_param(self: Path, key: []const u8) ?[]const u8 { + var it1 = std.mem.splitScalar(u8, self.query, '&'); + var t: ?[]const u8 = it1.first(); + while (t != null) : (t = it1.next()) { + var it2 = std.mem.splitScalar(u8, t.?, '='); + const k = it2.first(); + const v = it2.next(); + if (std.mem.eql(u8, key, k)) { + return v; + } + } + return null; + } +}; + +const PathTest = struct { + test "path" { + const p = try Path.parse("/"); + try std.testing.expectEqualDeep(Path{ .path = "/", .query = "", .fragment = "" }, p); + } + + test "query" { + const p = try Path.parse("/foo?bar=baz"); + try std.testing.expectEqualDeep(Path{ .path = "/foo", .query = "bar=baz", .fragment = "" }, p); + } + + test "query and fragment" { + const p = try Path.parse("/foo?bar=baz#frag"); + try std.testing.expectEqualDeep(Path{ .path = "/foo", .query = "bar=baz", .fragment = "frag" }, p); + } + + test "fragment" { + const p = try Path.parse("/foo#frag"); + try std.testing.expectEqualDeep(Path{ .path = "/foo", .query = "", .fragment = "frag" }, p); + } + + test "query param" { + const p = try Path.parse("/foo?bar=baz#frag"); + const v1 = p.get_query_param("bar"); + try std.testing.expect(v1 != null); + try std.testing.expectEqualSlices(u8, "baz", v1.?); + const v2 = p.get_query_param("bam"); + try std.testing.expect(v2 == null); + } +}; + test { - _ = T; + _ = RouterTest; + _ = PathTest; } -- cgit v1.2.3-ZIG