summaryrefslogtreecommitdiff
path: root/src/zigwebserver.zig
diff options
context:
space:
mode:
authorMartin Ashby <martin@ashbysoft.com>2023-08-06 06:59:44 +0100
committerMartin Ashby <martin@ashbysoft.com>2023-08-06 06:59:44 +0100
commit95699a6ed77e1480e7b9256035225981cb33bfbb (patch)
tree4ac270c06a6e675761731ff65943a39072ff8d52 /src/zigwebserver.zig
parent292e1bf1692091fa218ced45a8b49fc799a2759a (diff)
downloadzigwebserver-95699a6ed77e1480e7b9256035225981cb33bfbb.tar.gz
zigwebserver-95699a6ed77e1480e7b9256035225981cb33bfbb.tar.bz2
zigwebserver-95699a6ed77e1480e7b9256035225981cb33bfbb.tar.xz
zigwebserver-95699a6ed77e1480e7b9256035225981cb33bfbb.zip
Add path parsing, use it
Diffstat (limited to 'src/zigwebserver.zig')
-rw-r--r--src/zigwebserver.zig103
1 files changed, 97 insertions, 6 deletions
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, "<html><body>Server error!</body></html>", .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;
}