diff --git a/README.md b/README.md index f4ea32f..5f775d6 100644 --- a/README.md +++ b/README.md @@ -113,9 +113,9 @@ port and docs dir: `zig build docserver && zig-out/bin/docserver --port=8989 less type-safe than `zap.Middleware`'s use of contexts. - [**Per Request Contexts**](./src/zap.zig#L102) : With the introduction of `setUserContext()` and `getUserContext()`, you can, of course use those two in - projects that don't use `zap.SimpleEndpoint` or `zap.Middleware`, too, if you + projects that don't use `zap.Endpoint` or `zap.Middleware`, too, if you really, really, absolutely don't find another way to solve your context - problem. **We recommend using a `zap.SimpleEndpoint`** inside of a struct that + problem. **We recommend using a `zap.Endpoint`** inside of a struct that can provide all the context you need **instead**. You get access to your struct in the callbacks via the `@fieldParentPtr()` trick that is used extensively in Zap's examples, like the [endpoint @@ -376,7 +376,7 @@ $ zig build run-routes const std = @import("std"); const zap = @import("zap"); -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { if (r.path) |the_path| { std.debug.print("PATH: {s}\n", .{the_path}); } @@ -388,7 +388,7 @@ fn on_request(r: zap.SimpleRequest) void { } pub fn main() !void { - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = on_request, .log = true, diff --git a/doc/authentication.md b/doc/authentication.md index 931b7e5..313a827 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -12,7 +12,7 @@ For convenience, Authenticator types exist that can authenticate requests. Zap also provides an `AuthenticatingEndpoint` endpoint-wrapper. Have a look at the [example](../examples/endpoint_auth) and the [tests](../src/tests/test_auth.zig). The following describes the Authenticator types. All of them provide the -`authenticateRequest()` function, which takes a `zap.SimpleRequest` and returns +`authenticateRequest()` function, which takes a `zap.Request` and returns a bool value whether it could be authenticated or not. Further down, we show how to use the Authenticators, and also the @@ -60,7 +60,7 @@ var auth = try zap.BearerAuthSingle.init(allocator, token, null); defer auth.deinit(); -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { if(authenticator.authenticateRequest(r)) { r.sendBody( \\ @@ -94,7 +94,7 @@ var auth = try zap.BearerAuthMulti(Set).init(allocator, &set, null); defer auth.deinit(); -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { if(authenticator.authenticateRequest(r)) { r.sendBody( \\ @@ -132,7 +132,7 @@ var auth = try Authenticator.init(a, &map, null); defer auth.deinit(); -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { if(authenticator.authenticateRequest(r)) { r.sendBody( \\ @@ -168,7 +168,7 @@ var auth = try Authenticator.init(allocator, &set, null); defer auth.deinit(); -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { if(authenticator.authenticateRequest(r)) { r.sendBody( \\ @@ -206,13 +206,13 @@ const HTTP_RESPONSE: []const u8 = ; // authenticated requests go here -fn endpoint_http_get(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { +fn endpoint_http_get(e: *zap.Endpoint, r: zap.Request) void { _ = e; r.sendBody(HTTP_RESPONSE) catch return; } // just for fun, we also catch the unauthorized callback -fn endpoint_http_unauthorized(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { +fn endpoint_http_unauthorized(e: *zap.Endpoint, r: zap.Request) void { _ = e; r.setStatus(.unauthorized); r.sendBody("UNAUTHORIZED ACCESS") catch return; @@ -220,7 +220,7 @@ fn endpoint_http_unauthorized(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void pub fn main() !void { // setup listener - var listener = zap.SimpleEndpointListener.init( + var listener = zap.EndpointListener.init( a, .{ .port = 3000, @@ -233,7 +233,7 @@ pub fn main() !void { defer listener.deinit(); // create mini endpoint - var ep = zap.SimpleEndpoint.init(.{ + var ep = zap.Endpoint.init(.{ .path = "/test", .get = endpoint_http_get, .unauthorized = endpoint_http_unauthorized, @@ -248,7 +248,7 @@ pub fn main() !void { const BearerAuthEndpoint = zap.AuthenticatingEndpoint(Authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); - try listener.addEndpoint(auth_ep.getEndpoint()); + try listener.register(auth_ep.endpoint()); listener.listen() catch {}; std.debug.print( diff --git a/doc/why-no-errors.md b/doc/why-no-errors.md index db80377..17fffd4 100644 --- a/doc/why-no-errors.md +++ b/doc/why-no-errors.md @@ -58,14 +58,14 @@ Now that I think I've made my point 😊, you can, of course always do the following: ```zig -fn on_request_with_errors(r: zap.SimpleHttpRequest) !void { +fn on_request_with_errors(r: zap.HttpRequest) !void { // do all the try stuff here } ``` ```zig // THIS IS WHAT YOU PASS TO THE LISTENER / ENDPONT / ... -fn on_request(r: zap.SimpleHttpRequest) void { +fn on_request(r: zap.HttpRequest) void { on_request_with_errors(r) catch |err| { // log the error or use: return r.returnWithErrorStackTrace(err); diff --git a/doc/zig-ception.md b/doc/zig-ception.md index 4d70551..cdea9d8 100644 --- a/doc/zig-ception.md +++ b/doc/zig-ception.md @@ -10,29 +10,12 @@ Here is how it is used in user-code: ```zig // create a combined context struct -const Context = zap.Middleware.MixContexts(.{ - .{ .name = "?user", .type = UserMiddleWare.User }, - .{ .name = "?session", .type = SessionMiddleWare.Session }, -}); +const Context = struct { + user: ?UserMiddleWare.User = null, + session: ?SessionMiddleWare.Session = null, +}; ``` -The result of this function call is a struct that has a `user` field of type -`?UserMiddleWare.User`, which is the `User` struct inside of its containing -struct - and a `session` field of type `?SessionMiddleWare.Session`. - -So `MixContexts` accepts a **tuple** of structs that each contain a -`name` field and a `type` field. As a hack, we support the `?` in the name to -indicate we want the resulting struct field to be an optional. - -A **tuple** means that we can "mix" as many structs as we like. Not just two -like in the example above. - -`MixContexts` inspects the passed-in `type` fields and **composes a new struct -type at comptime**! Have a look at its [source code](../src/middleware.zig). -You'll be blown away if this kind of metaprogramming stuff isn't what you do -everyday. I was totally blown away by trying it out and seeing it that it -_actually_ worked. - Why do we create combined structs? Because all our Middleware handler functions need to receive a per-request context. But each wants their own data: the User middleware might want to access a User struct, the Session middleware might want @@ -62,10 +45,10 @@ Have a look at an excerpt of the example: ```zig // create a combined context struct -const Context = zap.Middleware.MixContexts(.{ - .{ .name = "?user", .type = UserMiddleWare.User }, - .{ .name = "?session", .type = SessionMiddleWare.Session }, -}); +const Context = struct { + user: ?UserMiddleWare.User = null, + session: ?SessionMiddleWare.Session = null, +}; // we create a Handler type based on our Context const Handler = zap.Middleware.Handler(Context); diff --git a/examples/bindataformpost/bindataformpost.zig b/examples/bindataformpost/bindataformpost.zig index 1facccf..0406c58 100644 --- a/examples/bindataformpost/bindataformpost.zig +++ b/examples/bindataformpost/bindataformpost.zig @@ -4,7 +4,7 @@ const zap = @import("zap"); const Handler = struct { var alloc: std.mem.Allocator = undefined; - pub fn on_request(r: zap.SimpleRequest) void { + pub fn on_request(r: zap.Request) void { // check for FORM parameters r.parseBody() catch |err| { std.log.err("Parse Body error: {any}. Expected if body is empty", .{err}); @@ -56,7 +56,7 @@ const Handler = struct { else => { // might be a string param, we don't care // let's just get it as string - if (r.getParamStr(kv.key.str, Handler.alloc, false)) |maybe_str| { + if (r.getParamStr(Handler.alloc, kv.key.str, false)) |maybe_str| { const value: []const u8 = if (maybe_str) |s| s.str else "(no value)"; std.log.debug(" {s} = {s}", .{ kv.key.str, value }); } else |err| { @@ -68,12 +68,12 @@ const Handler = struct { } // check if we received a terminate=true parameter - if (r.getParamStr("terminate", Handler.alloc, false)) |maybe_str| { + if (r.getParamStr(Handler.alloc, "terminate", false)) |maybe_str| { if (maybe_str) |*s| { defer s.deinit(); std.log.info("?terminate={s}\n", .{s.str}); if (std.mem.eql(u8, s.str, "true")) { - zap.fio_stop(); + zap.stop(); } } } else |err| { @@ -92,7 +92,7 @@ pub fn main() !void { Handler.alloc = allocator; // setup listener - var listener = zap.SimpleHttpListener.init( + var listener = zap.HttpListener.init( .{ .port = 3000, .on_request = Handler.on_request, diff --git a/examples/cookies/cookies.zig b/examples/cookies/cookies.zig index 55ecf22..d80cafc 100644 --- a/examples/cookies/cookies.zig +++ b/examples/cookies/cookies.zig @@ -33,7 +33,7 @@ pub fn main() !void { const Handler = struct { var alloc: std.mem.Allocator = undefined; - pub fn on_request(r: zap.SimpleRequest) void { + pub fn on_request(r: zap.Request) void { std.debug.print("\n=====================================================\n", .{}); defer std.debug.print("=====================================================\n\n", .{}); @@ -61,7 +61,7 @@ pub fn main() !void { // let's get cookie "ZIG_ZAP" by name std.debug.print("\n", .{}); - if (r.getCookieStr("ZIG_ZAP", alloc, false)) |maybe_str| { + if (r.getCookieStr(alloc, "ZIG_ZAP", false)) |maybe_str| { if (maybe_str) |*s| { defer s.deinit(); @@ -98,7 +98,7 @@ pub fn main() !void { Handler.alloc = allocator; // setup listener - var listener = zap.SimpleHttpListener.init( + var listener = zap.HttpListener.init( .{ .port = 3000, .on_request = Handler.on_request, diff --git a/examples/endpoint/main.zig b/examples/endpoint/main.zig index f8c1889..fcc63c3 100644 --- a/examples/endpoint/main.zig +++ b/examples/endpoint/main.zig @@ -1,10 +1,10 @@ const std = @import("std"); const zap = @import("zap"); -const Endpoint = @import("endpoint.zig"); +const UserWeb = @import("userweb.zig"); const StopEndpoint = @import("stopendpoint.zig"); // this is just to demo that we can catch arbitrary slugs -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { if (r.path) |the_path| { std.debug.print("REQUESTED PATH: {s}\n", .{the_path}); } @@ -21,7 +21,7 @@ pub fn main() !void { // we scope everything that can allocate within this block for leak detection { // setup listener - var listener = zap.SimpleEndpointListener.init( + var listener = zap.EndpointListener.init( allocator, .{ .port = 3000, @@ -34,19 +34,20 @@ pub fn main() !void { ); defer listener.deinit(); - var endpoint = Endpoint.init(allocator, "/users"); - defer endpoint.deinit(); + // /users endpoint + var userWeb = UserWeb.init(allocator, "/users"); + defer userWeb.deinit(); var stopEp = StopEndpoint.init("/stop"); - // add endpoint - try listener.addEndpoint(endpoint.getUserEndpoint()); - try listener.addEndpoint(stopEp.getEndpoint()); + // register endpoints with the listener + try listener.register(userWeb.endpoint()); + try listener.register(stopEp.endpoint()); // fake some users var uid: usize = undefined; - uid = try endpoint.getUsers().addByName("renerocksai", null); - uid = try endpoint.getUsers().addByName("renerocksai", "your mom"); + uid = try userWeb.users().addByName("renerocksai", null); + uid = try userWeb.users().addByName("renerocksai", "your mom"); // listen try listener.listen(); diff --git a/examples/endpoint/stopendpoint.zig b/examples/endpoint/stopendpoint.zig index 2735074..9411d49 100644 --- a/examples/endpoint/stopendpoint.zig +++ b/examples/endpoint/stopendpoint.zig @@ -5,24 +5,24 @@ const zap = @import("zap"); /// the main thread usually continues at the instructions after the call to zap.start(). pub const Self = @This(); -endpoint: zap.SimpleEndpoint = undefined, +ep: zap.Endpoint = undefined, pub fn init( path: []const u8, ) Self { return .{ - .endpoint = zap.SimpleEndpoint.init(.{ + .ep = zap.Endpoint.init(.{ .path = path, .get = get, }), }; } -pub fn getEndpoint(self: *Self) *zap.SimpleEndpoint { - return &self.endpoint; +pub fn endpoint(self: *Self) *zap.Endpoint { + return &self.ep; } -fn get(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { +fn get(e: *zap.Endpoint, r: zap.Request) void { _ = e; _ = r; zap.stop(); diff --git a/examples/endpoint/endpoint.zig b/examples/endpoint/userweb.zig similarity index 71% rename from examples/endpoint/endpoint.zig rename to examples/endpoint/userweb.zig index d1193bf..be6c06b 100644 --- a/examples/endpoint/endpoint.zig +++ b/examples/endpoint/userweb.zig @@ -8,8 +8,8 @@ const User = Users.User; pub const Self = @This(); alloc: std.mem.Allocator = undefined, -endpoint: zap.SimpleEndpoint = undefined, -users: Users = undefined, +ep: zap.Endpoint = undefined, +_users: Users = undefined, pub fn init( a: std.mem.Allocator, @@ -17,8 +17,8 @@ pub fn init( ) Self { return .{ .alloc = a, - .users = Users.init(a), - .endpoint = zap.SimpleEndpoint.init(.{ + ._users = Users.init(a), + .ep = zap.Endpoint.init(.{ .path = user_path, .get = getUser, .post = postUser, @@ -31,30 +31,30 @@ pub fn init( } pub fn deinit(self: *Self) void { - self.users.deinit(); + self._users.deinit(); } -pub fn getUsers(self: *Self) *Users { - return &self.users; +pub fn users(self: *Self) *Users { + return &self._users; } -pub fn getUserEndpoint(self: *Self) *zap.SimpleEndpoint { - return &self.endpoint; +pub fn endpoint(self: *Self) *zap.Endpoint { + return &self.ep; } fn userIdFromPath(self: *Self, path: []const u8) ?usize { - if (path.len >= self.endpoint.settings.path.len + 2) { - if (path[self.endpoint.settings.path.len] != '/') { + if (path.len >= self.ep.settings.path.len + 2) { + if (path[self.ep.settings.path.len] != '/') { return null; } - const idstr = path[self.endpoint.settings.path.len + 1 ..]; + const idstr = path[self.ep.settings.path.len + 1 ..]; return std.fmt.parseUnsigned(usize, idstr, 10) catch null; } return null; } -fn getUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { - const self = @fieldParentPtr(Self, "endpoint", e); +fn getUser(e: *zap.Endpoint, r: zap.Request) void { + const self = @fieldParentPtr(Self, "ep", e); if (r.path) |path| { // /users if (path.len == e.settings.path.len) { @@ -62,7 +62,7 @@ fn getUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { } var jsonbuf: [256]u8 = undefined; if (self.userIdFromPath(path)) |id| { - if (self.users.get(id)) |user| { + if (self._users.get(id)) |user| { if (zap.stringifyBuf(&jsonbuf, user, .{})) |json| { r.sendJson(json) catch return; } @@ -71,8 +71,8 @@ fn getUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { } } -fn listUsers(self: *Self, r: zap.SimpleRequest) void { - if (self.users.toJSON()) |json| { +fn listUsers(self: *Self, r: zap.Request) void { + if (self._users.toJSON()) |json| { defer self.alloc.free(json); r.sendJson(json) catch return; } else |err| { @@ -80,13 +80,13 @@ fn listUsers(self: *Self, r: zap.SimpleRequest) void { } } -fn postUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { - const self = @fieldParentPtr(Self, "endpoint", e); +fn postUser(e: *zap.Endpoint, r: zap.Request) void { + const self = @fieldParentPtr(Self, "ep", e); if (r.body) |body| { const maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null; if (maybe_user) |u| { defer u.deinit(); - if (self.users.addByName(u.value.first_name, u.value.last_name)) |id| { + if (self._users.addByName(u.value.first_name, u.value.last_name)) |id| { var jsonbuf: [128]u8 = undefined; if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| { r.sendJson(json) catch return; @@ -99,17 +99,17 @@ fn postUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { } } -fn putUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { - const self = @fieldParentPtr(Self, "endpoint", e); +fn putUser(e: *zap.Endpoint, r: zap.Request) void { + const self = @fieldParentPtr(Self, "ep", e); if (r.path) |path| { if (self.userIdFromPath(path)) |id| { - if (self.users.get(id)) |_| { + if (self._users.get(id)) |_| { if (r.body) |body| { const maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null; if (maybe_user) |u| { defer u.deinit(); var jsonbuf: [128]u8 = undefined; - if (self.users.update(id, u.value.first_name, u.value.last_name)) { + if (self._users.update(id, u.value.first_name, u.value.last_name)) { if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| { r.sendJson(json) catch return; } @@ -125,12 +125,12 @@ fn putUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { } } -fn deleteUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { - const self = @fieldParentPtr(Self, "endpoint", e); +fn deleteUser(e: *zap.Endpoint, r: zap.Request) void { + const self = @fieldParentPtr(Self, "ep", e); if (r.path) |path| { if (self.userIdFromPath(path)) |id| { var jsonbuf: [128]u8 = undefined; - if (self.users.delete(id)) { + if (self._users.delete(id)) { if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| { r.sendJson(json) catch return; } @@ -143,7 +143,7 @@ fn deleteUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { } } -fn optionsUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { +fn optionsUser(e: *zap.Endpoint, r: zap.Request) void { _ = e; r.setHeader("Access-Control-Allow-Origin", "*") catch return; r.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") catch return; diff --git a/examples/endpoint_auth/endpoint_auth.zig b/examples/endpoint_auth/endpoint_auth.zig index b6056c1..3c0f19e 100644 --- a/examples/endpoint_auth/endpoint_auth.zig +++ b/examples/endpoint_auth/endpoint_auth.zig @@ -11,13 +11,13 @@ const HTTP_RESPONSE: []const u8 = ; // authenticated requests go here -fn endpoint_http_get(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { +fn endpoint_http_get(e: *zap.Endpoint, r: zap.Request) void { _ = e; r.sendBody(HTTP_RESPONSE) catch return; } // just for fun, we also catch the unauthorized callback -fn endpoint_http_unauthorized(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { +fn endpoint_http_unauthorized(e: *zap.Endpoint, r: zap.Request) void { _ = e; r.setStatus(.unauthorized); r.sendBody("UNAUTHORIZED ACCESS") catch return; @@ -25,7 +25,7 @@ fn endpoint_http_unauthorized(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void pub fn main() !void { // setup listener - var listener = zap.SimpleEndpointListener.init( + var listener = zap.EndpointListener.init( a, .{ .port = 3000, @@ -38,7 +38,7 @@ pub fn main() !void { defer listener.deinit(); // create mini endpoint - var ep = zap.SimpleEndpoint.init(.{ + var ep = zap.Endpoint.init(.{ .path = "/test", .get = endpoint_http_get, .unauthorized = endpoint_http_unauthorized, @@ -53,7 +53,7 @@ pub fn main() !void { const BearerAuthEndpoint = zap.AuthenticatingEndpoint(Authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); - try listener.addEndpoint(auth_ep.getEndpoint()); + try listener.register(auth_ep.endpoint()); listener.listen() catch {}; std.debug.print( diff --git a/examples/hello/hello.zig b/examples/hello/hello.zig index 738e345..8b0f760 100644 --- a/examples/hello/hello.zig +++ b/examples/hello/hello.zig @@ -1,7 +1,7 @@ const std = @import("std"); const zap = @import("zap"); -fn on_request_verbose(r: zap.SimpleRequest) void { +fn on_request_verbose(r: zap.Request) void { if (r.path) |the_path| { std.debug.print("PATH: {s}\n", .{the_path}); } @@ -12,12 +12,12 @@ fn on_request_verbose(r: zap.SimpleRequest) void { r.sendBody("

Hello from ZAP!!!

") catch return; } -fn on_request_minimal(r: zap.SimpleRequest) void { +fn on_request_minimal(r: zap.Request) void { r.sendBody("

Hello from ZAP!!!

") catch return; } pub fn main() !void { - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = on_request_verbose, .log = true, diff --git a/examples/hello2/hello2.zig b/examples/hello2/hello2.zig index 75afaf0..5e7fa9a 100644 --- a/examples/hello2/hello2.zig +++ b/examples/hello2/hello2.zig @@ -1,7 +1,7 @@ const std = @import("std"); const zap = @import("zap"); -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { const m = r.method orelse ""; const p = r.path orelse "/"; const qm = if (r.query) |_| "?" else ""; @@ -35,7 +35,7 @@ fn on_request(r: zap.SimpleRequest) void { } pub fn main() !void { - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = on_request, .log = false, diff --git a/examples/hello_json/hello_json.zig b/examples/hello_json/hello_json.zig index 6de3aba..8974636 100644 --- a/examples/hello_json/hello_json.zig +++ b/examples/hello_json/hello_json.zig @@ -6,7 +6,7 @@ const User = struct { last_name: ?[]const u8 = null, }; -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { if (!std.mem.eql(u8, r.method.?, "GET")) return; @@ -44,7 +44,7 @@ fn setupUserData(a: std.mem.Allocator) !void { pub fn main() !void { const a = std.heap.page_allocator; try setupUserData(a); - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = on_request, .log = false, diff --git a/examples/http_params/http_params.zig b/examples/http_params/http_params.zig index 4095b98..68b3b65 100644 --- a/examples/http_params/http_params.zig +++ b/examples/http_params/http_params.zig @@ -32,7 +32,7 @@ pub fn main() !void { const Handler = struct { var alloc: std.mem.Allocator = undefined; - pub fn on_request(r: zap.SimpleRequest) void { + pub fn on_request(r: zap.Request) void { std.debug.print("\n=====================================================\n", .{}); defer std.debug.print("=====================================================\n\n", .{}); @@ -66,7 +66,7 @@ pub fn main() !void { // let's get param "one" by name std.debug.print("\n", .{}); - if (r.getParamStr("one", alloc, false)) |maybe_str| { + if (r.getParamStr(alloc, "one", false)) |maybe_str| { if (maybe_str) |*s| { defer s.deinit(); @@ -82,11 +82,11 @@ pub fn main() !void { } // check if we received a terminate=true parameter - if (r.getParamStr("terminate", alloc, false)) |maybe_str| { + if (r.getParamStr(alloc, "terminate", false)) |maybe_str| { if (maybe_str) |*s| { defer s.deinit(); if (std.mem.eql(u8, s.str, "true")) { - zap.fio_stop(); + zap.stop(); } } } else |err| { @@ -98,7 +98,7 @@ pub fn main() !void { Handler.alloc = allocator; // setup listener - var listener = zap.SimpleHttpListener.init( + var listener = zap.HttpListener.init( .{ .port = 3000, .on_request = Handler.on_request, diff --git a/examples/https/https.zig b/examples/https/https.zig index d6e4000..88c53a7 100644 --- a/examples/https/https.zig +++ b/examples/https/https.zig @@ -1,7 +1,7 @@ const std = @import("std"); const zap = @import("zap"); -fn on_request_verbose(r: zap.SimpleRequest) void { +fn on_request_verbose(r: zap.Request) void { if (r.path) |the_path| { std.debug.print("PATH: {s}\n", .{the_path}); } @@ -12,7 +12,7 @@ fn on_request_verbose(r: zap.SimpleRequest) void { r.sendBody("

Hello from ZAP!!!

") catch return; } -fn on_request_minimal(r: zap.SimpleRequest) void { +fn on_request_minimal(r: zap.Request) void { r.sendBody("

Hello from ZAP!!!

") catch return; } @@ -51,7 +51,7 @@ pub fn main() !void { }); defer tls.deinit(); - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = 4443, .on_request = on_request_verbose, .log = true, diff --git a/examples/middleware/middleware.zig b/examples/middleware/middleware.zig index 4152c0f..189e6f2 100644 --- a/examples/middleware/middleware.zig +++ b/examples/middleware/middleware.zig @@ -69,7 +69,7 @@ const UserMiddleWare = struct { } // note that the first parameter is of type *Handler, not *Self !!! - pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool { + pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { // this is how we would get our self pointer const self = @fieldParentPtr(Self, "handler", handler); @@ -113,7 +113,7 @@ const SessionMiddleWare = struct { } // note that the first parameter is of type *Handler, not *Self !!! - pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool { + pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { // this is how we would get our self pointer const self = @fieldParentPtr(Self, "handler", handler); _ = self; @@ -148,7 +148,7 @@ const HtmlMiddleWare = struct { } // note that the first parameter is of type *Handler, not *Self !!! - pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool { + pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { // this is how we would get our self pointer const self = @fieldParentPtr(Self, "handler", handler); diff --git a/examples/middleware_with_endpoint/middleware_with_endpoint.zig b/examples/middleware_with_endpoint/middleware_with_endpoint.zig index b97fdf9..60b48e5 100644 --- a/examples/middleware_with_endpoint/middleware_with_endpoint.zig +++ b/examples/middleware_with_endpoint/middleware_with_endpoint.zig @@ -20,10 +20,11 @@ const SharedAllocator = struct { }; // create a combined context struct -const Context = zap.Middleware.MixContexts(.{ - .{ .name = "?user", .type = UserMiddleWare.User }, - .{ .name = "?session", .type = SessionMiddleWare.Session }, -}); +// NOTE: context struct members need to be optionals which default to null!!! +const Context = struct { + user: ?UserMiddleWare.User = null, + session: ?SessionMiddleWare.Session = null, +}; // we create a Handler type based on our Context const Handler = zap.Middleware.Handler(Context); @@ -56,7 +57,7 @@ const UserMiddleWare = struct { } // note that the first parameter is of type *Handler, not *Self !!! - pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool { + pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { // this is how we would get our self pointer const self = @fieldParentPtr(Self, "handler", handler); @@ -102,7 +103,7 @@ const SessionMiddleWare = struct { } // note that the first parameter is of type *Handler, not *Self !!! - pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool { + pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool { // this is how we would get our self pointer const self = @fieldParentPtr(Self, "handler", handler); _ = self; @@ -137,24 +138,24 @@ const SessionMiddleWare = struct { // parameter. // const HtmlEndpoint = struct { - endpoint: zap.SimpleEndpoint = undefined, + ep: zap.Endpoint = undefined, const Self = @This(); pub fn init() Self { return .{ - .endpoint = zap.SimpleEndpoint.init(.{ - .path = "/doesn'tmatter", + .ep = zap.Endpoint.init(.{ + .path = "/doesn't+matter", .get = get, }), }; } - pub fn getEndpoint(self: *Self) *zap.SimpleEndpoint { - return &self.endpoint; + pub fn endpoint(self: *Self) *zap.Endpoint { + return &self.ep; } - pub fn get(ep: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { - const self = @fieldParentPtr(Self, "endpoint", ep); + pub fn get(ep: *zap.Endpoint, r: zap.Request) void { + const self = @fieldParentPtr(Self, "ep", ep); _ = self; var buf: [1024]u8 = undefined; @@ -207,7 +208,7 @@ pub fn main() !void { // we wrap the endpoint with a middleware handler var htmlHandler = zap.Middleware.EndpointHandler(Handler, Context).init( - htmlEndpoint.getEndpoint(), // the endpoint + htmlEndpoint.endpoint(), // the endpoint null, // no other handler (we are the last in the chain) true, // break on finish. See EndpointHandler for this. Not applicable here. ); diff --git a/examples/mustache/mustache.zig b/examples/mustache/mustache.zig index a254c35..26dd6d9 100644 --- a/examples/mustache/mustache.zig +++ b/examples/mustache/mustache.zig @@ -2,7 +2,7 @@ const std = @import("std"); const zap = @import("zap"); const Mustache = @import("zap").Mustache; -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { const template = \\ {{=<< >>=}} \\ * Users: @@ -49,7 +49,7 @@ fn on_request(r: zap.SimpleRequest) void { } pub fn main() !void { - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = on_request, .log = true, diff --git a/examples/routes/routes.zig b/examples/routes/routes.zig index 5c4e7bd..43222d1 100644 --- a/examples/routes/routes.zig +++ b/examples/routes/routes.zig @@ -1,7 +1,9 @@ const std = @import("std"); const zap = @import("zap"); -fn dispatch_routes(r: zap.SimpleRequest) void { +// NOTE: this is a super simplified example, just using a hashmap to map +// from HTTP path to request function. +fn dispatch_routes(r: zap.Request) void { // dispatch if (r.path) |the_path| { if (routes.get(the_path)) |foo| { @@ -20,12 +22,12 @@ fn dispatch_routes(r: zap.SimpleRequest) void { ) catch return; } -fn static_site(r: zap.SimpleRequest) void { +fn static_site(r: zap.Request) void { r.sendBody("

Hello from STATIC ZAP!

") catch return; } var dynamic_counter: i32 = 0; -fn dynamic_site(r: zap.SimpleRequest) void { +fn dynamic_site(r: zap.Request) void { dynamic_counter += 1; var buf: [128]u8 = undefined; const filled_buf = std.fmt.bufPrintZ( @@ -37,16 +39,16 @@ fn dynamic_site(r: zap.SimpleRequest) void { } fn setup_routes(a: std.mem.Allocator) !void { - routes = std.StringHashMap(zap.SimpleHttpRequestFn).init(a); + routes = std.StringHashMap(zap.HttpRequestFn).init(a); try routes.put("/static", static_site); try routes.put("/dynamic", dynamic_site); } -var routes: std.StringHashMap(zap.SimpleHttpRequestFn) = undefined; +var routes: std.StringHashMap(zap.HttpRequestFn) = undefined; pub fn main() !void { try setup_routes(std.heap.page_allocator); - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = dispatch_routes, .log = true, diff --git a/examples/senderror/senderror.zig b/examples/senderror/senderror.zig index 32addd9..9a2fe73 100644 --- a/examples/senderror/senderror.zig +++ b/examples/senderror/senderror.zig @@ -5,14 +5,14 @@ fn MAKE_MEGA_ERROR() !void { return error.MEGA_ERROR; } -fn MY_REQUEST_HANDLER(r: zap.SimpleRequest) void { +fn MY_REQUEST_HANDLER(r: zap.Request) void { MAKE_MEGA_ERROR() catch |err| { r.sendError(err, 505); }; } pub fn main() !void { - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = MY_REQUEST_HANDLER, .log = true, diff --git a/examples/sendfile/sendfile.zig b/examples/sendfile/sendfile.zig index b7099e7..4c97309 100644 --- a/examples/sendfile/sendfile.zig +++ b/examples/sendfile/sendfile.zig @@ -6,7 +6,7 @@ var read_len: ?usize = null; const testfile = @embedFile("testfile.txt"); -pub fn on_request(r: zap.SimpleRequest) void { +pub fn on_request(r: zap.Request) void { // Sends a file if present in the filesystem orelse returns an error. // // - efficiently sends a file using gzip compression @@ -27,7 +27,7 @@ pub fn on_request(r: zap.SimpleRequest) void { pub fn main() !void { // setup listener - var listener = zap.SimpleHttpListener.init( + var listener = zap.HttpListener.init( .{ .port = 3000, .on_request = on_request, diff --git a/examples/serve/serve.zig b/examples/serve/serve.zig index 117e553..d9f9d65 100644 --- a/examples/serve/serve.zig +++ b/examples/serve/serve.zig @@ -1,13 +1,13 @@ const std = @import("std"); const zap = @import("zap"); -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { r.setStatus(.not_found); r.sendBody("

404 - File not found

") catch return; } pub fn main() !void { - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = on_request, .public_folder = "examples/serve", diff --git a/examples/userpass_session_auth/userpass_session_auth.zig b/examples/userpass_session_auth/userpass_session_auth.zig index 72645b3..0880eb2 100644 --- a/examples/userpass_session_auth/userpass_session_auth.zig +++ b/examples/userpass_session_auth/userpass_session_auth.zig @@ -20,17 +20,17 @@ const loginpage = @embedFile("html/login.html"); const img = @embedFile("./html/Ziggy_the_Ziguana.svg.png"); // global vars yeah! -// in bigger projects, we'd probably make use of zap.SimpleEndpoint or +// in bigger projects, we'd probably make use of zap.Endpoint or // zap.Middleware and "hide" stuff like authenticators in there var authenticator: Authenticator = undefined; // the login page (embedded) -fn on_login(r: zap.SimpleRequest) void { +fn on_login(r: zap.Request) void { r.sendBody(loginpage) catch return; } // the "normal page" -fn on_normal_page(r: zap.SimpleRequest) void { +fn on_normal_page(r: zap.Request) void { zap.debug("on_normal_page()\n", .{}); r.sendBody( \\ @@ -42,7 +42,7 @@ fn on_normal_page(r: zap.SimpleRequest) void { } // the logged-out page -fn on_logout(r: zap.SimpleRequest) void { +fn on_logout(r: zap.Request) void { zap.debug("on_logout()\n", .{}); authenticator.logout(&r); // note, the link below doesn't matter as the authenticator will send us @@ -56,7 +56,7 @@ fn on_logout(r: zap.SimpleRequest) void { ) catch return; } -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { switch (authenticator.authenticateRequest(&r)) { .Handled => { // the authenticator handled the entire request for us. @@ -124,7 +124,7 @@ pub fn main() !void { // to detect leaks { const allocator = gpa.allocator(); - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = on_request, .log = true, diff --git a/examples/websockets/websockets.zig b/examples/websockets/websockets.zig index 2799b96..e347242 100644 --- a/examples/websockets/websockets.zig +++ b/examples/websockets/websockets.zig @@ -114,6 +114,7 @@ fn on_close_websocket(context: ?*Context, uuid: isize) void { std.log.info("websocket closed: {s}", .{message}); } } + fn handle_websocket_message( context: ?*Context, handle: WebSockets.WsHandle, @@ -162,7 +163,7 @@ fn handle_websocket_message( // // HTTP stuff // -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { r.setHeader("Server", "zap.example") catch unreachable; r.sendBody( \\ @@ -171,7 +172,7 @@ fn on_request(r: zap.SimpleRequest) void { ) catch return; } -fn on_upgrade(r: zap.SimpleRequest, target_protocol: []const u8) void { +fn on_upgrade(r: zap.Request, target_protocol: []const u8) void { // make sure we're talking the right protocol if (!std.mem.eql(u8, target_protocol, "websocket")) { std.log.warn("received illegal protocol: {s}", .{target_protocol}); @@ -207,7 +208,7 @@ pub fn main() !void { defer GlobalContextManager.deinit(); // setup listener - var listener = zap.SimpleHttpListener.init( + var listener = zap.HttpListener.init( .{ .port = 3010, .on_request = on_request, diff --git a/src/endpoint.zig b/src/endpoint.zig index a04f7f6..e61c591 100644 --- a/src/endpoint.zig +++ b/src/endpoint.zig @@ -2,29 +2,83 @@ const std = @import("std"); const zap = @import("zap.zig"); const auth = @import("http_auth.zig"); -const Request = zap.SimpleRequest; -const ListenerSettings = zap.SimpleHttpListenerSettings; -const Listener = zap.SimpleHttpListener; +// zap types +const Request = zap.Request; +const ListenerSettings = zap.HttpListenerSettings; +const Listener = zap.HttpListener; -pub const RequestFn = *const fn (self: *SimpleEndpoint, r: Request) void; -pub const SimpleEndpointSettings = struct { +/// Type of the request function callbacks. +pub const RequestFn = *const fn (self: *Endpoint, r: Request) void; + +/// Settings to initialize an Endpoint +pub const EndpointSettings = struct { + /// path / slug of the endpoint path: []const u8, + /// callback to GET request handler get: ?RequestFn = null, + /// callback to POST request handler post: ?RequestFn = null, + /// callback to PUT request handler put: ?RequestFn = null, + /// callback to DELETE request handler delete: ?RequestFn = null, + /// callback to PATCH request handler patch: ?RequestFn = null, + /// callback to OPTIONS request handler options: ?RequestFn = null, - /// only applicable to AuthenticatingEndpoint + /// Only applicable to AuthenticatingEndpoint: handler for unauthorized requests unauthorized: ?RequestFn = null, }; -pub const SimpleEndpoint = struct { - settings: SimpleEndpointSettings, +/// The simple Endpoint struct. Create one and pass in your callbacks. Then, +/// pass it to a HttpListener's `register()` function to register with the +/// listener. +/// +/// **NOTE**: A common endpoint pattern for zap is to create your own struct +/// that embeds an Endpoint, provides specific callbacks, and uses +/// `@fieldParentPtr` to get a reference to itself. +/// +/// Example: +/// A simple endpoint listening on the /stop route that shuts down zap. +/// The main thread usually continues at the instructions after the call to zap.start(). +/// +/// ```zig +/// const StopEndpoint = struct { +/// ep: zap.Endpoint = undefined, +/// +/// pub fn init( +/// path: []const u8, +/// ) StopEndpoint { +/// return .{ +/// .ep = zap.Endpoint.init(.{ +/// .path = path, +/// .get = get, +/// }), +/// }; +/// } +/// +/// // access the internal Endpoint +/// pub fn endpoint(self: *StopEndpoint) *zap.Endpoint { +/// return &self.ep; +/// } +/// +/// fn get(e: *zap.Endpoint, r: zap.Request) void { +/// const self: *StopEndpoint = @fieldParentPtr(StopEndpoint, "ep", e); +/// _ = self; +/// _ = r; +/// zap.stop(); +/// } +/// }; +/// ``` +pub const Endpoint = struct { + settings: EndpointSettings, const Self = @This(); - pub fn init(s: SimpleEndpointSettings) Self { + /// Initialize the endpoint. + /// Set only the callbacks you need. Requests of HTTP methods without a + /// provided callback will be ignored. + pub fn init(s: EndpointSettings) Self { return .{ .settings = .{ .path = s.path, @@ -39,12 +93,14 @@ pub const SimpleEndpoint = struct { }; } - fn nop(self: *SimpleEndpoint, r: Request) void { + // no operation. Dummy handler function for ignoring unset request types. + fn nop(self: *Endpoint, r: Request) void { _ = self; _ = r; } - pub fn onRequest(self: *SimpleEndpoint, r: zap.SimpleRequest) void { + /// The global request handler for this Endpoint, called by the listener. + pub fn onRequest(self: *Endpoint, r: zap.Request) void { if (r.method) |m| { if (std.mem.eql(u8, m, "GET")) return self.settings.get.?(self, r); @@ -62,19 +118,23 @@ pub const SimpleEndpoint = struct { } }; -/// Wrap an endpoint with an authenticator +/// Wrap an endpoint with an Authenticator -> new Endpoint of type Endpoint +/// is available via the `endpoint()` function. pub fn AuthenticatingEndpoint(comptime Authenticator: type) type { return struct { authenticator: *Authenticator, - endpoint: *SimpleEndpoint, - auth_endpoint: SimpleEndpoint, + ep: *Endpoint, + auth_endpoint: Endpoint, const Self = @This(); - pub fn init(e: *SimpleEndpoint, authenticator: *Authenticator) Self { + /// Init the authenticating endpoint. Pass in a pointer to the endpoint + /// you want to wrap, and the Authenticator that takes care of authenticating + /// requests. + pub fn init(e: *Endpoint, authenticator: *Authenticator) Self { return .{ .authenticator = authenticator, - .endpoint = e, - .auth_endpoint = SimpleEndpoint.init(.{ + .ep = e, + .auth_endpoint = Endpoint.init(.{ .path = e.settings.path, // we override only the set ones. the other ones // are set to null anyway -> will be nopped out @@ -89,20 +149,21 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type { }; } - /// get the auth endpoint struct so we can be stored in the listener - /// when the listener calls the auth_endpoint, onRequest will have - /// access to all of us via fieldParentPtr - pub fn getEndpoint(self: *Self) *SimpleEndpoint { + /// Get the auth endpoint struct of type Endpoint so it can be stored in the listener. + /// When the listener calls the auth_endpoint, onRequest will have + /// access to all of this via fieldParentPtr + pub fn endpoint(self: *Self) *Endpoint { return &self.auth_endpoint; } - /// here, the auth_endpoint will be passed in - pub fn get(e: *SimpleEndpoint, r: zap.SimpleRequest) void { + /// GET: here, the auth_endpoint will be passed in as endpoint. + /// Authenticates GET requests using the Authenticator. + pub fn get(e: *Endpoint, r: zap.Request) void { const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { - unauthorized(authEp.endpoint, r); + unauthorized(authEp.ep, r); return; } else { r.setStatus(.unauthorized); @@ -110,18 +171,19 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type { return; } }, - .AuthOK => authEp.endpoint.settings.get.?(authEp.endpoint, r), + .AuthOK => authEp.ep.settings.get.?(authEp.ep, r), .Handled => {}, } } - /// here, the auth_endpoint will be passed in - pub fn post(e: *SimpleEndpoint, r: zap.SimpleRequest) void { + /// POST: here, the auth_endpoint will be passed in as endpoint. + /// Authenticates POST requests using the Authenticator. + pub fn post(e: *Endpoint, r: zap.Request) void { const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { - unauthorized(authEp.endpoint, r); + unauthorized(authEp.ep, r); return; } else { r.setStatus(.unauthorized); @@ -129,18 +191,19 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type { return; } }, - .AuthOK => authEp.endpoint.settings.post.?(authEp.endpoint, r), + .AuthOK => authEp.ep.settings.post.?(authEp.ep, r), .Handled => {}, } } - /// here, the auth_endpoint will be passed in - pub fn put(e: *SimpleEndpoint, r: zap.SimpleRequest) void { + /// PUT: here, the auth_endpoint will be passed in as endpoint. + /// Authenticates PUT requests using the Authenticator. + pub fn put(e: *Endpoint, r: zap.Request) void { const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { - unauthorized(authEp.endpoint, r); + unauthorized(authEp.ep, r); return; } else { r.setStatus(.unauthorized); @@ -148,18 +211,19 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type { return; } }, - .AuthOK => authEp.endpoint.settings.put.?(authEp.endpoint, r), + .AuthOK => authEp.ep.settings.put.?(authEp.ep, r), .Handled => {}, } } - /// here, the auth_endpoint will be passed in - pub fn delete(e: *SimpleEndpoint, r: zap.SimpleRequest) void { + /// DELETE: here, the auth_endpoint will be passed in as endpoint. + /// Authenticates DELETE requests using the Authenticator. + pub fn delete(e: *Endpoint, r: zap.Request) void { const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { - unauthorized(authEp.endpoint, r); + unauthorized(authEp.ep, r); return; } else { r.setStatus(.unauthorized); @@ -167,18 +231,19 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type { return; } }, - .AuthOK => authEp.endpoint.settings.delete.?(authEp.endpoint, r), + .AuthOK => authEp.ep.settings.delete.?(authEp.ep, r), .Handled => {}, } } - /// here, the auth_endpoint will be passed in - pub fn patch(e: *SimpleEndpoint, r: zap.SimpleRequest) void { + /// PATCH: here, the auth_endpoint will be passed in as endpoint. + /// Authenticates PATCH requests using the Authenticator. + pub fn patch(e: *Endpoint, r: zap.Request) void { const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { - unauthorized(authEp.endpoint, r); + unauthorized(authEp.ep, r); return; } else { r.setStatus(.unauthorized); @@ -186,18 +251,19 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type { return; } }, - .AuthOK => authEp.endpoint.settings.patch.?(authEp.endpoint, r), + .AuthOK => authEp.ep.settings.patch.?(authEp.ep, r), .Handled => {}, } } - /// here, the auth_endpoint will be passed in - pub fn options(e: *SimpleEndpoint, r: zap.SimpleRequest) void { + /// OPTIONS: here, the auth_endpoint will be passed in as endpoint. + /// Authenticates OPTIONS requests using the Authenticator. + pub fn options(e: *Endpoint, r: zap.Request) void { const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); switch (authEp.authenticator.authenticateRequest(&r)) { .AuthFailed => { if (e.settings.unauthorized) |unauthorized| { - unauthorized(authEp.endpoint, r); + unauthorized(authEp.ep, r); return; } else { r.setStatus(.unauthorized); @@ -205,7 +271,7 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type { return; } }, - .AuthOK => authEp.endpoint.settings.put.?(authEp.endpoint, r), + .AuthOK => authEp.ep.settings.put.?(authEp.ep, r), .Handled => {}, } } @@ -213,26 +279,44 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type { } pub const EndpointListenerError = error{ + /// Since we use .startsWith to check for matching paths, you cannot use + /// endpoint paths that overlap at the beginning. --> When trying to register + /// an endpoint whose path would shadow an already registered one, you will + /// receive this error. EndpointPathShadowError, }; -// NOTE: We switch on path.startsWith -> so use endpoints with distinctly -// starting names!! -pub const SimpleEndpointListener = struct { +/// The listener with ednpoint support +/// +/// NOTE: It switches on path.startsWith -> so use endpoints with distinctly starting names!! +pub const EndpointListener = struct { listener: Listener, allocator: std.mem.Allocator, const Self = @This(); - /// static struct member endpoints - var endpoints: std.ArrayList(*SimpleEndpoint) = undefined; - var on_request: ?zap.SimpleHttpRequestFn = null; + /// Internal static struct of member endpoints + var endpoints: std.ArrayList(*Endpoint) = undefined; + /// Internal, static request handler callback. Will be set to the optional, + /// user-defined request callback that only gets called if no endpoints match + /// a request. + var on_request: ?zap.HttpRequestFn = null; + + /// Initialize a new endpoint listener. Note, if you pass an `on_request` + /// callback in the provided ListenerSettings, this request callback will be + /// called every time a request arrives that no endpoint matches. pub fn init(a: std.mem.Allocator, l: ListenerSettings) Self { - endpoints = std.ArrayList(*SimpleEndpoint).init(a); + endpoints = std.ArrayList(*Endpoint).init(a); - var ls = l; // take copy of listener settings + // take copy of listener settings before modifying the callback field + var ls = l; + + // override the settings with our internal, actul callback function + // so that "we" will be called on request ls.on_request = onRequest; + + // store the settings-provided request callback for later use on_request = l.on_request; return .{ .listener = Listener.init(ls), @@ -240,16 +324,25 @@ pub const SimpleEndpointListener = struct { }; } + /// De-init the listener and free its resources. + /// Registered endpoints will not be de-initialized automatically; just removed + /// from the internal map. pub fn deinit(self: *Self) void { _ = self; endpoints.deinit(); } + /// Call this to start listening. After this, no more endpoints can be + /// registered. pub fn listen(self: *Self) !void { try self.listener.listen(); } - pub fn addEndpoint(self: *Self, e: *SimpleEndpoint) !void { + /// Register an endpoint with this listener. + /// NOTE: endpoint paths are matched with startsWith -> so use endpoints with distinctly starting names!! + /// If you try to register an endpoint whose path would shadow an already registered one, you will + /// receive an EndpointPathShadowError. + pub fn register(self: *Self, e: *Endpoint) !void { _ = self; for (endpoints.items) |other| { if (std.mem.startsWith( @@ -276,6 +369,7 @@ pub const SimpleEndpointListener = struct { } } } + // if set, call the user-provided default callback if (on_request) |foo| { foo(r); } diff --git a/src/http_auth.zig b/src/http_auth.zig index 3b833fd..3cc8ac7 100644 --- a/src/http_auth.zig +++ b/src/http_auth.zig @@ -1,6 +1,7 @@ const std = @import("std"); const zap = @import("zap.zig"); +/// Authentication Scheme enum: Basic or Bearer. pub const AuthScheme = enum { Basic, Bearer, @@ -27,6 +28,7 @@ pub const AuthScheme = enum { } }; +/// Used internally: check for presence of the requested auth header. pub fn checkAuthHeader(scheme: AuthScheme, auth_header: []const u8) bool { return switch (scheme) { .Basic => |b| std.mem.startsWith(u8, auth_header, b.str()) and auth_header.len > b.str().len, @@ -34,13 +36,15 @@ pub fn checkAuthHeader(scheme: AuthScheme, auth_header: []const u8) bool { }; } -pub fn extractAuthHeader(scheme: AuthScheme, r: *const zap.SimpleRequest) ?[]const u8 { +/// Used internally: return the requested auth header. +pub fn extractAuthHeader(scheme: AuthScheme, r: *const zap.Request) ?[]const u8 { return switch (scheme) { .Basic => |b| r.getHeader(b.headerFieldStrFio()), .Bearer => |b| r.getHeader(b.headerFieldStrFio()), }; } +/// Decoding Strategy for Basic Authentication const BasicAuthStrategy = enum { /// decode into user and pass, then check pass UserPass, @@ -48,20 +52,21 @@ const BasicAuthStrategy = enum { Token68, }; +/// Authentication result pub const AuthResult = enum { /// authentication / authorization was successful AuthOK, /// authentication / authorization failed AuthFailed, - /// the authenticator handled the request that didn't pass authentication / - /// authorization . - /// this is used to implement authenticators that redirect to a login + /// The authenticator handled the request that didn't pass authentication / + /// authorization. + /// This is used to implement authenticators that redirect to a login /// page. An AuthenticatingEndpoint will not do the default, which is trying - /// to call the `unauthorized` callback or. + /// to call the `unauthorized` callback if one exists orelse ignore the request. Handled, }; -/// HTTP Basic Authentication RFC 7617 +/// HTTP Basic Authentication RFC 7617. /// "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" /// user-pass strings: "$username:$password" -> base64 /// @@ -73,10 +78,9 @@ pub const AuthResult = enum { /// Errors: /// WWW-Authenticate: Basic realm="this" /// -/// T : any kind of map that implements get([]const u8) -> []const u8 +/// Lookup : any kind of map that implements get([]const u8) -> []const u8 pub fn BasicAuth(comptime Lookup: type, comptime kind: BasicAuthStrategy) type { return struct { - // kind: BasicAuthStrategy, allocator: std.mem.Allocator, realm: ?[]const u8, lookup: *Lookup, @@ -87,23 +91,24 @@ pub fn BasicAuth(comptime Lookup: type, comptime kind: BasicAuthStrategy) type { /// different implementations can /// - either decode, lookup and compare passwords /// - or just check for existence of the base64-encoded user:pass combination - /// if realm is provided (not null), a copy is taken -> call deinit() to clean up + /// if realm is provided (not null), a copy of it is taken -> call deinit() to clean up pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !Self { return .{ - // .kind = kind, .allocator = allocator, .lookup = lookup, .realm = if (realm) |the_realm| try allocator.dupe(u8, the_realm) else null, }; } + /// Deinit the authenticator. pub fn deinit(self: *Self) void { if (self.realm) |the_realm| { self.allocator.free(the_realm); } } - /// Use this to decode the auth_header into user:pass, lookup pass in lookup + /// Use this to decode the auth_header into user:pass, lookup pass in lookup. + /// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. pub fn authenticateUserPass(self: *Self, auth_header: []const u8) AuthResult { zap.debug("AuthenticateUserPass\n", .{}); const encoded = auth_header[AuthScheme.Basic.str().len..]; @@ -165,22 +170,28 @@ pub fn BasicAuth(comptime Lookup: type, comptime kind: BasicAuthStrategy) type { return .AuthFailed; } - /// Use this to just look up if the base64-encoded auth_header exists in lookup + /// Use this to just look up if the base64-encoded auth_header exists in lookup. + /// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. pub fn authenticateToken68(self: *Self, auth_header: []const u8) AuthResult { const token = auth_header[AuthScheme.Basic.str().len..]; return if (self.lookup.*.contains(token)) .AuthOK else .AuthFailed; } - // dispatch based on kind + /// dispatch based on kind (.UserPass / .Token689) and try to authenticate based on the header. + /// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult { zap.debug("AUTHENTICATE\n", .{}); - // switch (self.kind) { switch (kind) { .UserPass => return self.authenticateUserPass(auth_header), .Token68 => return self.authenticateToken68(auth_header), } } - pub fn authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult { + + /// The zap authentication request handler. + /// + /// Tries to extract the authentication header and perform the authentication. + /// If no authentication header is found, an authorization header is tried. + pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { zap.debug("AUTHENTICATE REQUEST\n", .{}); if (extractAuthHeader(.Basic, r)) |auth_header| { zap.debug("Authentication Header found!\n", .{}); @@ -215,10 +226,9 @@ pub const BearerAuthSingle = struct { const Self = @This(); - /// Creates a Single-Token Bearer Authenticator - /// takes a copy of the token - /// if realm is provided (not null), a copy is taken - /// call deinit() to clean up + /// Creates a Single-Token Bearer Authenticator. + /// Takes a copy of the token. + /// If realm is provided (not null), a copy is taken call deinit() to clean up. pub fn init(allocator: std.mem.Allocator, token: []const u8, realm: ?[]const u8) !Self { return .{ .allocator = allocator, @@ -226,6 +236,9 @@ pub const BearerAuthSingle = struct { .realm = if (realm) |the_realm| try allocator.dupe(u8, the_realm) else null, }; } + + /// Try to authenticate based on the header. + /// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult { if (checkAuthHeader(.Bearer, auth_header) == false) { return .AuthFailed; @@ -234,13 +247,17 @@ pub const BearerAuthSingle = struct { return if (std.mem.eql(u8, token, self.token)) .AuthOK else .AuthFailed; } - pub fn authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult { + /// The zap authentication request handler. + /// + /// Tries to extract the authentication header and perform the authentication. + pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { if (extractAuthHeader(.Bearer, r)) |auth_header| { return self.authenticate(auth_header); } return .AuthFailed; } + /// Deinits the authenticator. pub fn deinit(self: *Self) void { if (self.realm) |the_realm| { self.allocator.free(the_realm); @@ -267,9 +284,9 @@ pub fn BearerAuthMulti(comptime Lookup: type) type { const Self = @This(); - /// Creates a BasicAuth. `lookup` must implement `.get([]const u8) -> []const u8` - /// to look up tokens - /// if realm is provided (not null), a copy is taken -> call deinit() to clean up + /// Creates a Multi Token Bearer Authenticator. `lookup` must implement + /// `.get([]const u8) -> []const u8` to look up tokens. + /// If realm is provided (not null), a copy of it is taken -> call deinit() to clean up. pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !Self { return .{ .allocator = allocator, @@ -278,12 +295,16 @@ pub fn BearerAuthMulti(comptime Lookup: type) type { }; } + /// Deinit the authenticator. Only required if a realm was provided at + /// init() time. pub fn deinit(self: *Self) void { if (self.realm) |the_realm| { self.allocator.free(the_realm); } } + /// Try to authenticate based on the header. + /// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult { if (checkAuthHeader(.Bearer, auth_header) == false) { return .AuthFailed; @@ -292,7 +313,10 @@ pub fn BearerAuthMulti(comptime Lookup: type) type { return if (self.lookup.*.contains(token)) .AuthOK else .AuthFailed; } - pub fn authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult { + /// The zap authentication request handler. + /// + /// Tries to extract the authentication header and perform the authentication. + pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { if (extractAuthHeader(.Bearer, r)) |auth_header| { return self.authenticate(auth_header); } @@ -301,6 +325,7 @@ pub fn BearerAuthMulti(comptime Lookup: type) type { }; } +/// Settings to initialize a UserPassSessionAuth authenticator. pub const UserPassSessionAuthArgs = struct { /// username body parameter usernameParam: []const u8, @@ -308,7 +333,7 @@ pub const UserPassSessionAuthArgs = struct { passwordParam: []const u8, /// redirect to this page if auth fails loginPage: []const u8, - /// name of the cookie + /// name of the auth cookie cookieName: []const u8, /// cookie max age in seconds; 0 -> session cookie cookieMaxAge: u8 = 0, @@ -330,8 +355,8 @@ pub const UserPassSessionAuthArgs = struct { /// /// Please note the implications of this simple approach: IF YOU REUSE "username" /// and "password" body params for anything else in your application, then the -/// mechanisms described above will kick in. For that reason: please know what you're -/// doing. +/// mechanisms described above will still kick in. For that reason: please know what +/// you're doing. /// /// See UserPassSessionAuthArgs: /// - username & password param names can be defined by you @@ -357,7 +382,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool lookup: *Lookup, settings: UserPassSessionAuthArgs, - // TODO: cookie store per user + // TODO: cookie store per user? sessionTokens: SessionTokenMap, passwordLookupLock: std.Thread.Mutex = .{}, tokenLookupLock: std.Thread.Mutex = .{}, @@ -368,6 +393,8 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool const Token = [Hash.digest_length * 2]u8; + /// Construct this authenticator. See above and related types for more + /// information. pub fn init( allocator: std.mem.Allocator, lookup: *Lookup, @@ -390,6 +417,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool return ret; } + /// De-init this authenticator. pub fn deinit(self: *Self) void { self.allocator.free(self.settings.usernameParam); self.allocator.free(self.settings.passwordParam); @@ -405,8 +433,9 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool } /// Check for session token cookie, remove the token from the valid tokens - pub fn logout(self: *Self, r: *const zap.SimpleRequest) void { - // we erase the list of valid tokens server-side + pub fn logout(self: *Self, r: *const zap.Request) void { + // we erase the list of valid tokens server-side (later) and set the + // cookie to "invalid" on the client side. if (r.setCookie(.{ .name = self.settings.cookieName, .value = "invalid", @@ -420,7 +449,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool r.parseCookies(false); // check for session cookie - if (r.getCookieStr(self.settings.cookieName, self.allocator, false)) |maybe_cookie| { + if (r.getCookieStr(self.allocator, self.settings.cookieName, false)) |maybe_cookie| { if (maybe_cookie) |cookie| { defer cookie.deinit(); self.tokenLookupLock.lock(); @@ -439,7 +468,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool } } - fn _internal_authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult { + fn _internal_authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { // if we're requesting the login page, let the request through if (r.path) |p| { if (std.mem.startsWith(u8, p, self.settings.loginPage)) { @@ -456,7 +485,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool r.parseCookies(false); // check for session cookie - if (r.getCookieStr(self.settings.cookieName, self.allocator, false)) |maybe_cookie| { + if (r.getCookieStr(self.allocator, self.settings.cookieName, false)) |maybe_cookie| { if (maybe_cookie) |cookie| { defer cookie.deinit(); // locked or unlocked token lookup @@ -478,10 +507,10 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool } // get params of username and password - if (r.getParamStr(self.settings.usernameParam, self.allocator, false)) |maybe_username| { + if (r.getParamStr(self.allocator, self.settings.usernameParam, false)) |maybe_username| { if (maybe_username) |*username| { defer username.deinit(); - if (r.getParamStr(self.settings.passwordParam, self.allocator, false)) |maybe_pw| { + if (r.getParamStr(self.allocator, self.settings.passwordParam, false)) |maybe_pw| { if (maybe_pw) |*pw| { defer pw.deinit(); @@ -530,7 +559,10 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool return .AuthFailed; } - pub fn authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult { + /// The zap authentication request handler. + /// + /// See above for how it works. + pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { switch (self._internal_authenticateRequest(r)) { .AuthOK => { // username and pass are ok -> created token, set header, caller can continue @@ -550,7 +582,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool } } - fn redirect(self: *Self, r: *const zap.SimpleRequest) !void { + fn redirect(self: *Self, r: *const zap.Request) !void { try r.redirectTo(self.settings.loginPage, self.settings.redirectCode); } diff --git a/src/log.zig b/src/log.zig index 4c66c53..210eefc 100644 --- a/src/log.zig +++ b/src/log.zig @@ -1,7 +1,10 @@ const std = @import("std"); +// TODO: rework logging in zap + debugOn: bool, +/// Access to facil.io's logging facilities const Self = @This(); pub fn init(comptime debug: bool) Self { diff --git a/src/middleware.zig b/src/middleware.zig index 64bd08f..8862c1c 100644 --- a/src/middleware.zig +++ b/src/middleware.zig @@ -1,43 +1,14 @@ const std = @import("std"); const zap = @import("zap.zig"); -pub const ContextDescriptor = struct { - name: []const u8, - type: type, -}; - -/// Provide a tuple of structs of type like ContextDescriptor -/// a name starting with '?', such as "?user" will be treated as Optional with default `null`. -pub fn MixContexts(comptime context_tuple: anytype) type { - var fields: [context_tuple.len]std.builtin.Type.StructField = undefined; - for (context_tuple, 0..) |t, i| { - var fieldType: type = t.type; - var fieldName: []const u8 = t.name[0..]; - var isOptional: bool = false; - if (fieldName[0] == '?') { - fieldType = @Type(.{ .Optional = .{ .child = fieldType } }); - fieldName = fieldName[1..]; - isOptional = true; - } - fields[i] = .{ - .name = fieldName, - .type = fieldType, - .default_value = if (isOptional) &@as(fieldType, null) else null, - .is_comptime = false, - .alignment = 0, - }; - } - return @Type(.{ - .Struct = .{ - .layout = .Auto, - .fields = fields[0..], - .decls = &[_]std.builtin.Type.Declaration{}, - .is_tuple = false, - }, - }); -} - -/// Your middleware components need to contain a handler +/// Your middleware components need to contain a handler. +/// +/// A Handler is one element in the chain of request handlers that will be tried +/// by the listener when a request arrives. Handlers indicate to the previous +/// handler whether they processed a request by returning `true` from their +/// `on_request` function, in which case a typical request handler would stop +/// trying to pass the request on to the next handler in the chain. See +/// the `handle_other` function in this struct. pub fn Handler(comptime ContextType: anytype) type { return struct { other_handler: ?*Self = null, @@ -46,7 +17,7 @@ pub fn Handler(comptime ContextType: anytype) type { // will be set allocator: ?std.mem.Allocator = null, - pub const RequestFn = *const fn (*Self, zap.SimpleRequest, *ContextType) bool; + pub const RequestFn = *const fn (*Self, zap.Request, *ContextType) bool; const Self = @This(); pub fn init(on_request: RequestFn, other: ?*Self) Self { @@ -56,10 +27,10 @@ pub fn Handler(comptime ContextType: anytype) type { }; } - // example for handling request + // example for handling a request request // which you can use in your components, e.g.: // return self.handler.handleOther(r, context); - pub fn handleOther(self: *Self, r: zap.SimpleRequest, context: *ContextType) bool { + pub fn handleOther(self: *Self, r: zap.Request, context: *ContextType) bool { // in structs embedding a handler, we'd @fieldParentPtr the first // param to get to the real self @@ -80,16 +51,19 @@ pub fn Handler(comptime ContextType: anytype) type { }; } -/// A convenience handler for artibrary zap.SimpleEndpoint +/// A convenience handler for artibrary zap.Endpoint pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anytype) type { return struct { handler: HandlerType, - endpoint: *zap.SimpleEndpoint, + endpoint: *zap.Endpoint, breakOnFinish: bool, const Self = @This(); - pub fn init(endpoint: *zap.SimpleEndpoint, other: ?*HandlerType, breakOnFinish: bool) Self { + /// Create an endpointhandler from an endpoint and pass in the next (other) handler in the chain. + /// If `breakOnFinish` is `true`, the handler will stop handing requests down the chain if + /// the endpoint processed the request. + pub fn init(endpoint: *zap.Endpoint, other: ?*HandlerType, breakOnFinish: bool) Self { return .{ .handler = HandlerType.init(onRequest, other), .endpoint = endpoint, @@ -97,12 +71,17 @@ pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anyt }; } - // we need the handler as a common interface to chain stuff + /// Provides the handler as a common interface to chain stuff pub fn getHandler(self: *Self) *HandlerType { return &self.handler; } - pub fn onRequest(handler: *HandlerType, r: zap.SimpleRequest, context: *ContextType) bool { + /// The Handler's request handling function. Gets called from the listener + /// with the request and a context instance. Calls the endpoint. + /// + /// If `breakOnFinish` is `true`, the handler will stop handing requests down the chain if + /// the endpoint processed the request. + pub fn onRequest(handler: *HandlerType, r: zap.Request, context: *ContextType) bool { var self = @fieldParentPtr(Self, "handler", handler); r.setUserContext(context); self.endpoint.onRequest(r); @@ -117,15 +96,18 @@ pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anyt } pub const Error = error{ + /// The listener could not be created because the settings provided to its + /// init() function contained an `on_request` callback that was not null. InitOnRequestIsNotNull, }; pub const RequestAllocatorFn = *const fn () std.mem.Allocator; +/// Special Listener that supports chaining request handlers. pub fn Listener(comptime ContextType: anytype) type { return struct { - listener: zap.SimpleHttpListener = undefined, - settings: zap.SimpleHttpListenerSettings, + listener: zap.HttpListener = undefined, + settings: zap.HttpListenerSettings, // static initial handler var handler: ?*Handler(ContextType) = undefined; @@ -134,9 +116,10 @@ pub fn Listener(comptime ContextType: anytype) type { const Self = @This(); - /// initialize the middleware handler - /// the passed in settings must have on_request set to null - pub fn init(settings: zap.SimpleHttpListenerSettings, initial_handler: *Handler(ContextType), request_alloc: ?RequestAllocatorFn) Error!Self { + /// Construct and initialize a middleware handler. + /// The passed in settings must have on_request set to null! If that is + /// not the case, an InitOnRequestIsNotNull error will be returned. + pub fn init(settings: zap.HttpListenerSettings, initial_handler: *Handler(ContextType), request_alloc: ?RequestAllocatorFn) Error!Self { // override on_request with ourselves if (settings.on_request != null) { return Error.InitOnRequestIsNotNull; @@ -147,21 +130,26 @@ pub fn Listener(comptime ContextType: anytype) type { var ret: Self = .{ .settings = settings, }; + ret.settings.on_request = onRequest; - ret.listener = zap.SimpleHttpListener.init(ret.settings); + ret.listener = zap.HttpListener.init(ret.settings); handler = initial_handler; return ret; } + /// Start listening. pub fn listen(self: *Self) !void { try self.listener.listen(); } - // this is just a reference implementation - // but it's actually used obviously. Create your own listener if you - // want different behavior. - // Didn't want to make this a callback - pub fn onRequest(r: zap.SimpleRequest) void { + /// The listener's request handler, stepping through the chain of Handlers + /// by calling the initial one which takes it from there. + /// + /// This is just a reference implementation that you can use by default. + /// Create your own listener if you want different behavior. + /// (Didn't want to make this a callback. Submit an issue if you really + /// think that's an issue). + pub fn onRequest(r: zap.Request) void { // we are the 1st handler in the chain, so we create a context var context: ContextType = .{}; @@ -182,60 +170,3 @@ pub fn Listener(comptime ContextType: anytype) type { } }; } - -test "it" { - - // just some made-up struct - const User = struct { - name: []const u8, - email: []const u8, - }; - - // just some made-up struct - const Session = struct { - sessionType: []const u8, - token: []const u8, - valid: bool, - }; - - const Mixed = MixContexts( - .{ - .{ .name = "?user", .type = *User }, - .{ .name = "?session", .type = *Session }, - }, - ); - - std.debug.print("{any}\n", .{Mixed}); - inline for (@typeInfo(Mixed).Struct.fields, 0..) |f, i| { - std.debug.print("field {} : name = {s} : type = {any}\n", .{ i, f.name, f.type }); - } - - const mixed: Mixed = .{ - // it's all optionals which we made default to null in MixContexts - }; - std.debug.print("mixed = {any}\n", .{mixed}); - - const NonOpts = MixContexts( - .{ - .{ .name = "user", .type = *User }, - .{ .name = "session", .type = *Session }, - }, - ); - - var user: User = .{ - .name = "renerocksai", - .email = "secret", - }; - var session: Session = .{ - .sessionType = "bearerToken", - .token = "ABCDEFG", - .valid = false, - }; - - // this will fail if we don't specify - const nonOpts: NonOpts = .{ - .user = &user, - .session = &session, - }; - std.debug.print("nonOpts = {any}\n", .{nonOpts}); -} diff --git a/src/tests/test_auth.zig b/src/tests/test_auth.zig index a7b0d55..c4be1d9 100644 --- a/src/tests/test_auth.zig +++ b/src/tests/test_auth.zig @@ -106,21 +106,21 @@ const HTTP_RESPONSE: []const u8 = ; var received_response: []const u8 = "null"; -fn endpoint_http_get(e: *Endpoints.SimpleEndpoint, r: zap.SimpleRequest) void { +fn endpoint_http_get(e: *Endpoints.Endpoint, r: zap.Request) void { _ = e; r.sendBody(HTTP_RESPONSE) catch return; received_response = HTTP_RESPONSE; std.time.sleep(1 * std.time.ns_per_s); - zap.fio_stop(); + zap.stop(); } -fn endpoint_http_unauthorized(e: *Endpoints.SimpleEndpoint, r: zap.SimpleRequest) void { +fn endpoint_http_unauthorized(e: *Endpoints.Endpoint, r: zap.Request) void { _ = e; r.setStatus(.unauthorized); r.sendBody("UNAUTHORIZED ACCESS") catch return; received_response = "UNAUTHORIZED"; std.time.sleep(1 * std.time.ns_per_s); - zap.fio_stop(); + zap.stop(); } // @@ -165,7 +165,7 @@ fn makeRequest(a: std.mem.Allocator, url: []const u8, auth: ?ClientAuthReqHeader std.debug.print("RESPONSE:\n{s}\n", .{buffer[0..len]}); } - zap.fio_stop(); + zap.stop(); } fn makeRequestThread(a: std.mem.Allocator, url: []const u8, auth: ?ClientAuthReqHeaderFields) !std.Thread { @@ -181,7 +181,7 @@ test "BearerAuthSingle authenticateRequest OK" { const token = "ABCDEFG"; // setup listener - var listener = zap.SimpleEndpointListener.init( + var listener = zap.EndpointListener.init( a, .{ .port = 3000, @@ -194,7 +194,7 @@ test "BearerAuthSingle authenticateRequest OK" { defer listener.deinit(); // create mini endpoint - var ep = Endpoints.SimpleEndpoint.init(.{ + var ep = Endpoints.Endpoint.init(.{ .path = "/test", .get = endpoint_http_get, .unauthorized = endpoint_http_unauthorized, @@ -209,7 +209,7 @@ test "BearerAuthSingle authenticateRequest OK" { const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); - try listener.addEndpoint(auth_ep.getEndpoint()); + try listener.register(auth_ep.endpoint()); listener.listen() catch {}; // std.debug.print("\n\n*******************************************\n", .{}); @@ -234,7 +234,7 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" { const token = "ABCDEFG"; // setup listener - var listener = zap.SimpleEndpointListener.init( + var listener = zap.EndpointListener.init( a, .{ .port = 3000, @@ -247,7 +247,7 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" { defer listener.deinit(); // create mini endpoint - var ep = Endpoints.SimpleEndpoint.init(.{ + var ep = Endpoints.Endpoint.init(.{ .path = "/test", .get = endpoint_http_get, .unauthorized = endpoint_http_unauthorized, @@ -268,7 +268,7 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" { const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); - try listener.addEndpoint(auth_ep.getEndpoint()); + try listener.register(auth_ep.endpoint()); listener.listen() catch {}; // std.debug.print("Waiting for the following:\n", .{}); @@ -291,7 +291,7 @@ test "BearerAuthMulti authenticateRequest OK" { const token = "ABCDEFG"; // setup listener - var listener = zap.SimpleEndpointListener.init( + var listener = zap.EndpointListener.init( a, .{ .port = 3000, @@ -304,7 +304,7 @@ test "BearerAuthMulti authenticateRequest OK" { defer listener.deinit(); // create mini endpoint - var ep = Endpoints.SimpleEndpoint.init(.{ + var ep = Endpoints.Endpoint.init(.{ .path = "/test", .get = endpoint_http_get, .unauthorized = endpoint_http_unauthorized, @@ -319,7 +319,7 @@ test "BearerAuthMulti authenticateRequest OK" { const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); - try listener.addEndpoint(auth_ep.getEndpoint()); + try listener.register(auth_ep.endpoint()); listener.listen() catch {}; // std.debug.print("Waiting for the following:\n", .{}); @@ -342,7 +342,7 @@ test "BearerAuthMulti authenticateRequest test-unauthorized" { const token = "invalid"; // setup listener - var listener = zap.SimpleEndpointListener.init( + var listener = zap.EndpointListener.init( a, .{ .port = 3000, @@ -355,7 +355,7 @@ test "BearerAuthMulti authenticateRequest test-unauthorized" { defer listener.deinit(); // create mini endpoint - var ep = Endpoints.SimpleEndpoint.init(.{ + var ep = Endpoints.Endpoint.init(.{ .path = "/test", .get = endpoint_http_get, .unauthorized = endpoint_http_unauthorized, @@ -370,7 +370,7 @@ test "BearerAuthMulti authenticateRequest test-unauthorized" { const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); - try listener.addEndpoint(auth_ep.getEndpoint()); + try listener.register(auth_ep.endpoint()); listener.listen() catch {}; // std.debug.print("Waiting for the following:\n", .{}); @@ -393,7 +393,7 @@ test "BasicAuth Token68 authenticateRequest" { const token = "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; // setup listener - var listener = zap.SimpleEndpointListener.init( + var listener = zap.EndpointListener.init( a, .{ .port = 3000, @@ -406,7 +406,7 @@ test "BasicAuth Token68 authenticateRequest" { defer listener.deinit(); // create mini endpoint - var ep = Endpoints.SimpleEndpoint.init(.{ + var ep = Endpoints.Endpoint.init(.{ .path = "/test", .get = endpoint_http_get, .unauthorized = endpoint_http_unauthorized, @@ -426,7 +426,7 @@ test "BasicAuth Token68 authenticateRequest" { const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); - try listener.addEndpoint(auth_ep.getEndpoint()); + try listener.register(auth_ep.endpoint()); listener.listen() catch {}; // std.debug.print("Waiting for the following:\n", .{}); @@ -449,7 +449,7 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" { const token = "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; // setup listener - var listener = zap.SimpleEndpointListener.init( + var listener = zap.EndpointListener.init( a, .{ .port = 3000, @@ -462,7 +462,7 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" { defer listener.deinit(); // create mini endpoint - var ep = Endpoints.SimpleEndpoint.init(.{ + var ep = Endpoints.Endpoint.init(.{ .path = "/test", .get = endpoint_http_get, .unauthorized = endpoint_http_unauthorized, @@ -482,7 +482,7 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" { const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); - try listener.addEndpoint(auth_ep.getEndpoint()); + try listener.register(auth_ep.endpoint()); listener.listen() catch {}; // std.debug.print("Waiting for the following:\n", .{}); @@ -504,7 +504,7 @@ test "BasicAuth UserPass authenticateRequest" { const a = std.testing.allocator; // setup listener - var listener = zap.SimpleEndpointListener.init( + var listener = zap.EndpointListener.init( a, .{ .port = 3000, @@ -517,7 +517,7 @@ test "BasicAuth UserPass authenticateRequest" { defer listener.deinit(); // create mini endpoint - var ep = Endpoints.SimpleEndpoint.init(.{ + var ep = Endpoints.Endpoint.init(.{ .path = "/test", .get = endpoint_http_get, .unauthorized = endpoint_http_unauthorized, @@ -548,7 +548,7 @@ test "BasicAuth UserPass authenticateRequest" { const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); - try listener.addEndpoint(auth_ep.getEndpoint()); + try listener.register(auth_ep.endpoint()); listener.listen() catch {}; // std.debug.print("Waiting for the following:\n", .{}); @@ -570,7 +570,7 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" { const a = std.testing.allocator; // setup listener - var listener = zap.SimpleEndpointListener.init( + var listener = zap.EndpointListener.init( a, .{ .port = 3000, @@ -583,7 +583,7 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" { defer listener.deinit(); // create mini endpoint - var ep = Endpoints.SimpleEndpoint.init(.{ + var ep = Endpoints.Endpoint.init(.{ .path = "/test", .get = endpoint_http_get, .unauthorized = endpoint_http_unauthorized, @@ -615,7 +615,7 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" { const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); - try listener.addEndpoint(auth_ep.getEndpoint()); + try listener.register(auth_ep.endpoint()); listener.listen() catch {}; // std.debug.print("Waiting for the following:\n", .{}); diff --git a/src/tests/test_http_params.zig b/src/tests/test_http_params.zig index c22b57f..0bdc1f2 100644 --- a/src/tests/test_http_params.zig +++ b/src/tests/test_http_params.zig @@ -15,7 +15,7 @@ fn makeRequest(a: std.mem.Allocator, url: []const u8) !void { try req.send(.{}); try req.wait(); - zap.fio_stop(); + zap.stop(); } fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread { @@ -34,7 +34,7 @@ test "http parameters" { var params: ?zap.HttpParamKVList = null; var paramOneStr: ?zap.FreeOrNot = null; - pub fn on_request(r: zap.SimpleRequest) void { + pub fn on_request(r: zap.Request) void { ran = true; r.parseQuery(); param_count = r.getParamCount(); @@ -45,7 +45,7 @@ test "http parameters" { // true -> make copies of temp strings params = r.parametersToOwnedList(alloc, true) catch unreachable; - var maybe_str = r.getParamStr("one", alloc, true) catch unreachable; + var maybe_str = r.getParamStr(alloc, "one", true) catch unreachable; if (maybe_str) |*s| { paramOneStr = s.*; } @@ -55,7 +55,7 @@ test "http parameters" { Handler.alloc = allocator; // setup listener - var listener = zap.SimpleHttpListener.init( + var listener = zap.HttpListener.init( .{ .port = 3001, .on_request = Handler.on_request, diff --git a/src/tests/test_sendfile.zig b/src/tests/test_sendfile.zig index 95f49fc..d368696 100644 --- a/src/tests/test_sendfile.zig +++ b/src/tests/test_sendfile.zig @@ -22,13 +22,13 @@ fn makeRequest(a: std.mem.Allocator, url: []const u8) !void { try req.wait(); read_len = try req.readAll(&buffer); - zap.fio_stop(); + zap.stop(); } fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread { return try std.Thread.spawn(.{}, makeRequest, .{ a, url }); } -pub fn on_request(r: zap.SimpleRequest) void { +pub fn on_request(r: zap.Request) void { r.sendFile("src/tests/testfile.txt") catch unreachable; } @@ -36,7 +36,7 @@ test "send file" { const allocator = std.testing.allocator; // setup listener - var listener = zap.SimpleHttpListener.init( + var listener = zap.HttpListener.init( .{ .port = 3002, .on_request = on_request, diff --git a/src/util.zig b/src/util.zig index acd5022..d9ff0db 100644 --- a/src/util.zig +++ b/src/util.zig @@ -1,9 +1,10 @@ const std = @import("std"); const fio = @import("fio.zig"); +/// Used internally: convert a FIO object into its string representation. /// note: since this is called from within request functions, we don't make /// copies. Also, we return temp memory from fio. -> don't hold on to it outside -/// of a request function +/// of a request function. FIO temp memory strings do not need to be freed. pub fn fio2str(o: fio.FIOBJ) ?[]const u8 { if (o == 0) return null; const x: fio.fio_str_info_s = fio.fiobj_obj2cstr(o); @@ -12,6 +13,12 @@ pub fn fio2str(o: fio.FIOBJ) ?[]const u8 { return x.data[0..x.len]; } +/// A "string" type used internally that carries a flag whether its buffer needs +/// to be freed or not - and honors it in `deinit()`. That way, it's always +/// safe to call deinit(). +/// For instance, slices taken directly from the zap.Request need not be freed. +/// But the ad-hoc created string representation of a float parameter must be +/// freed after use. pub const FreeOrNot = struct { str: []const u8, freeme: bool, @@ -24,7 +31,10 @@ pub const FreeOrNot = struct { } }; -pub fn fio2strAllocOrNot(o: fio.FIOBJ, a: std.mem.Allocator, always_alloc: bool) !FreeOrNot { +/// Used internally: convert a FIO object into its string representation. +/// Depending on the type of the object, a buffer will be created. Hence a +/// FreeOrNot type is used as the return type. +pub fn fio2strAllocOrNot(a: std.mem.Allocator, o: fio.FIOBJ, always_alloc: bool) !FreeOrNot { if (o == 0) return .{ .str = "null", .freeme = false }; if (o == fio.FIOBJ_INVALID) return .{ .str = "invalid", .freeme = false }; return switch (fio.fiobj_type(o)) { @@ -38,6 +48,8 @@ pub fn fio2strAllocOrNot(o: fio.FIOBJ, a: std.mem.Allocator, always_alloc: bool) else => .{ .str = "unknown_type", .freeme = false }, }; } + +/// Used internally: convert a zig slice into a FIO string. pub fn str2fio(s: []const u8) fio.fio_str_info_s { return .{ .data = toCharPtr(s), @@ -46,6 +58,7 @@ pub fn str2fio(s: []const u8) fio.fio_str_info_s { }; } +/// Used internally: convert a zig slice into a C pointer pub fn toCharPtr(s: []const u8) [*c]u8 { return @as([*c]u8, @ptrFromInt(@intFromPtr(s.ptr))); } @@ -54,7 +67,8 @@ pub fn toCharPtr(s: []const u8) [*c]u8 { // JSON helpers // -/// provide your own buf, NOT mutex-protected! +/// Concenience: format an arbitrary value into a JSON string buffer. +/// Provide your own buf; this function is NOT mutex-protected! pub fn stringifyBuf( buffer: []u8, value: anytype, @@ -68,47 +82,3 @@ pub fn stringifyBuf( return null; } } - -// deprecated: - -// 1MB JSON buffer -// var jsonbuf: [1024 * 1024]u8 = undefined; -// var mutex: std.Thread.Mutex = .{}; - -// use default 1MB buffer, mutex-protected -// pub fn stringify( -// value: anytype, -// options: std.json.StringifyOptions, -// ) ?[]const u8 { -// mutex.lock(); -// defer mutex.unlock(); -// var fba = std.heap.FixedBufferAllocator.init(&jsonbuf); -// var string = std.ArrayList(u8).init(fba.allocator()); -// if (std.json.stringify(value, options, string.writer())) { -// return string.items; -// } else |_| { // error -// return null; -// } -// } - -// use default 1MB buffer, mutex-protected -// pub fn stringifyArrayList( -// comptime T: anytype, -// list: *std.ArrayList(T), -// options: std.json.StringifyOptions, -// ) !?[]const u8 { -// mutex.lock(); -// defer mutex.unlock(); -// var fba = std.heap.FixedBufferAllocator.init(&jsonbuf); -// var string = std.ArrayList(u8).init(fba.allocator()); -// var writer = string.writer(); -// try writer.writeByte('['); -// var first: bool = true; -// for (list.items) |user| { -// if (!first) try writer.writeByte(','); -// first = false; -// try std.json.stringify(user, options, string.writer()); -// } -// try writer.writeByte(']'); -// return string.items; -// } diff --git a/src/websockets.zig b/src/websockets.zig index 62afe92..c92ba37 100644 --- a/src/websockets.zig +++ b/src/websockets.zig @@ -3,10 +3,15 @@ const zap = @import("zap.zig"); const fio = @import("fio.zig"); const util = @import("util.zig"); +/// The Handle type used for WebSocket connections. Do not mess with this. pub const WsHandle = ?*fio.ws_s; + +/// WebSocket Handler. Pass in a Context type and it will give you a struct that +/// contains all the types and functions you need. See the websocket example +/// for more details. pub fn Handler(comptime ContextType: type) type { return struct { - /// OnMessage Callback on a websocket + /// OnMessage Callback on a websocket, type. pub const WsOnMessageFn = *const fn ( /// user-provided context, passed in from websocketHttpUpgrade() context: ?*ContextType, @@ -18,14 +23,16 @@ pub fn Handler(comptime ContextType: type) type { is_text: bool, ) void; - /// Callback when websocket is closed. uuid is a connection identifier, + /// Callback (type) when websocket is closed. uuid is a connection identifier, /// it is -1 if a connection could not be established pub const WsOnCloseFn = *const fn (context: ?*ContextType, uuid: isize) void; - /// A websocket callback function. provides the context passed in at + /// A websocket callback function type. provides the context passed in at /// websocketHttpUpgrade(). pub const WsFn = *const fn (context: ?*ContextType, handle: WsHandle) void; + /// Websocket connection handler creation settings. Provide the callbacks you need, + /// and an optional context. pub const WebSocketSettings = struct { /// on_message(context, handle, message, is_text) on_message: ?WsOnMessageFn = null, @@ -102,12 +109,13 @@ pub fn Handler(comptime ContextType: type) type { } } - const WebSocketError = error{ + pub const WebSocketError = error{ WriteError, UpgradeError, SubscribeError, }; + /// Write to the websocket identified by the handle. pub inline fn write(handle: WsHandle, message: []const u8, is_text: bool) WebSocketError!void { if (fio.websocket_write( handle, @@ -118,21 +126,26 @@ pub fn Handler(comptime ContextType: type) type { } } + /// The context pointer is stored in facilio's udata pointer. Use + /// this function to turn that pointer into a pointer to your + /// ContextType. pub fn udataToContext(udata: *anyopaque) *ContextType { return @as(*ContextType, @ptrCast(@alignCast(udata))); } + /// Close the websocket connection. pub inline fn close(handle: WsHandle) void { fio.websocket_close(handle); } + /// Settings for publishing a message in a channel. const PublishArgs = struct { channel: []const u8, message: []const u8, is_json: bool = false, }; - /// publish a message in a channel + /// Publish a message in a channel. pub inline fn publish(args: PublishArgs) void { fio.fio_publish(.{ .channel = util.str2fio(args.channel), @@ -141,12 +154,19 @@ pub fn Handler(comptime ContextType: type) type { }); } + /// Type for callback on subscription message. pub const SubscriptionOnMessageFn = *const fn (context: ?*ContextType, handle: WsHandle, channel: []const u8, message: []const u8) void; + + /// Type for callback on unsubscribe message. pub const SubscriptionOnUnsubscribeFn = *const fn (context: ?*ContextType) void; + /// Settings for subscribing to a channel. pub const SubscribeArgs = struct { + /// channel name channel: []const u8, + /// on message callback on_message: ?SubscriptionOnMessageFn = null, + /// on unsubscribe callback on_unsubscribe: ?SubscriptionOnUnsubscribeFn = null, /// this is not wrapped nicely yet match: fio.fio_match_fn = null, @@ -162,9 +182,11 @@ pub fn Handler(comptime ContextType: type) type { /// above ~32Kb might be assumed to be binary rather than tested. force_binary has /// precedence over force_text. force_text: bool = false, + /// your provided arbitrary context context: ?*ContextType = null, }; + /// Subscribe to a channel. /// Returns a subscription ID on success and 0 on failure. /// we copy the pointer so make sure the struct stays valid. /// we need it to look up the ziggified callbacks. diff --git a/src/zap.zig b/src/zap.zig index 4973c7e..ff5a16f 100644 --- a/src/zap.zig +++ b/src/zap.zig @@ -2,18 +2,28 @@ // or maybe let's just make it zap directly... const std = @import("std"); -const fio = @import("fio.zig"); + +/// The facilio C API. No need to use this. +pub const fio = @import("fio.zig"); /// Server-Side TLS function wrapper pub const Tls = @import("tls.zig"); -pub usingnamespace @import("fio.zig"); +// pub usingnamespace @import("fio.zig"); pub usingnamespace @import("endpoint.zig"); pub usingnamespace @import("util.zig"); pub usingnamespace @import("http.zig"); pub usingnamespace @import("mustache.zig"); pub usingnamespace @import("http_auth.zig"); + +/// Middleware support. +/// Contains a special Listener and a Handler struct that support chaining +/// requests handlers, with an optional stop once a handler indicates it +/// processed the request. Also sports an EndpointHandler for using regular zap +/// Endpoints as Handlers. pub const Middleware = @import("middleware.zig"); + +/// Websocket API pub const WebSockets = @import("websockets.zig"); pub const Log = @import("log.zig"); @@ -40,16 +50,20 @@ pub fn stop() void { fio.fio_stop(); } +/// Extremely simplistic zap debug function. +/// TODO: re-wwrite logging or use std.log pub fn debug(comptime fmt: []const u8, args: anytype) void { if (_debug) { std.debug.print("[zap] - " ++ fmt, args); } } +/// Enable zap debug logging pub fn enableDebugLog() void { _debug = true; } +/// start Zap with debug logging on pub fn startWithLogging(args: fio.fio_start_args) void { debug = true; fio.fio_start(args); @@ -70,13 +84,17 @@ pub const HttpError = error{ SendFile, }; +/// Http Content Type enum. +/// Needs some love. pub const ContentType = enum { TEXT, HTML, JSON, + // TODO: more content types }; -pub const SimpleRequest = struct { +/// HttpRequest passed to request callback functions. +pub const Request = struct { path: ?[]const u8, query: ?[]const u8, body: ?[]const u8, @@ -90,7 +108,7 @@ pub const SimpleRequest = struct { /// NEVER touch this field!!!! /// use markAsFinished() and isFinished() instead /// this is a hack: the listener will put a pointer to this into the udata - /// field of `h`. So copies of the SimpleRequest will all have way to the + /// field of `h`. So copies of the Request will all have way to the /// same instance of this field. _is_finished_request_global: bool, /// NEVER touch this field!!!! @@ -103,22 +121,27 @@ pub const SimpleRequest = struct { const Self = @This(); + /// mark the current request as finished. Important for middleware-style + /// request handler chaining. Called when sending a body, redirecting, etc. pub fn markAsFinished(self: *const Self, finished: bool) void { // we might be a copy self._is_finished.* = finished; } + /// tell whether request processing has finished. (e.g. response sent, + /// redirected, ...) pub fn isFinished(self: *const Self) bool { // we might be a copy return self._is_finished.*; } - /// if you absolutely must, you can set any context here - // (note, this line is linked to from the readme) + /// if you absolutely must, you can set any context on the request here + // (note, this line is linked to from the readme) -- TODO: sync pub fn setUserContext(self: *const Self, context: *anyopaque) void { self._user_context.*.user_context = context; } + /// get the associated user context of the request. pub fn getUserContext(self: *const Self, comptime Context: type) ?*Context { if (self._user_context.*.user_context) |ptr| { return @as(*Context, @ptrCast(@alignCast(ptr))); @@ -127,6 +150,7 @@ pub const SimpleRequest = struct { } } + /// Tries to send an error stack trace. pub fn sendError(self: *const Self, err: anyerror, errorcode_num: usize) void { // TODO: query accept headers if (self._internal_sendError(err, errorcode_num)) { @@ -135,6 +159,8 @@ pub const SimpleRequest = struct { self.sendBody(@errorName(err)) catch return; } } + + /// Used internally. Probably does not need to be public. pub fn _internal_sendError(self: *const Self, err: anyerror, errorcode_num: usize) !void { // TODO: query accept headers // TODO: let's hope 20k is enough. Maybe just really allocate here @@ -151,16 +177,18 @@ pub const SimpleRequest = struct { try self.sendBody(string.items); } + /// Send body. pub fn sendBody(self: *const Self, body: []const u8) HttpError!void { const ret = fio.http_send_body(self.h, @as( *anyopaque, @ptrFromInt(@intFromPtr(body.ptr)), ), body.len); - debug("SimpleRequest.sendBody(): ret = {}\n", .{ret}); + debug("Request.sendBody(): ret = {}\n", .{ret}); if (ret == -1) return error.HttpSendBody; self.markAsFinished(true); } + /// Set content type and send json buffer. pub fn sendJson(self: *const Self, json: []const u8) HttpError!void { if (self.setContentType(.JSON)) { if (fio.http_send_body(self.h, @as( @@ -171,6 +199,7 @@ pub const SimpleRequest = struct { } else |err| return err; } + /// Set content type. pub fn setContentType(self: *const Self, c: ContentType) HttpError!void { const s = switch (c) { .TEXT => "text/plain", @@ -181,7 +210,7 @@ pub const SimpleRequest = struct { return self.setHeader("content-type", s); } - // redirect to path with status code 302 by default + /// redirect to path with status code 302 by default pub fn redirectTo(self: *const Self, path: []const u8, code: ?http.StatusCode) HttpError!void { self.setStatus(if (code) |status| status else .found); try self.setHeader("Location", path); @@ -242,6 +271,7 @@ pub const SimpleRequest = struct { return util.fio2str(fio.fiobj_hash_get(self.h.*.headers, hname)); } + /// Set header. pub fn setHeader(self: *const Self, name: []const u8, value: []const u8) HttpError!void { const hname: fio.fio_str_info_s = .{ .data = util.toCharPtr(name), @@ -261,7 +291,7 @@ pub const SimpleRequest = struct { // FIXME without the following if, we get errors in release builds // at least we don't have to log unconditionally if (ret == -1) { - std.debug.print("***************** zap.zig:145\n", .{}); + std.debug.print("***************** zap.zig:274\n", .{}); } debug("setHeader: ret = {}\n", .{ret}); @@ -269,10 +299,12 @@ pub const SimpleRequest = struct { return error.HttpSetHeader; } + /// Set status by numeric value. pub fn setStatusNumeric(self: *const Self, status: usize) void { self.h.*.status = status; } + /// Set status by enum. pub fn setStatus(self: *const Self, status: http.StatusCode) void { self.h.*.status = @as(usize, @intCast(@intFromEnum(status))); } @@ -315,11 +347,12 @@ pub const SimpleRequest = struct { fio.http_parse_query(self.h); } + /// Parse received cookie headers pub fn parseCookies(self: *const Self, url_encoded: bool) void { fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0); } - // Set a response cookie + /// Set a response cookie pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void { const c: fio.http_cookie_args_s = .{ .name = util.toCharPtr(args.name), @@ -340,6 +373,7 @@ pub const SimpleRequest = struct { // if(fio.http_set_cookie(...) == -1) // instead of capturing it in `ret` first and then checking it, // all ReleaseXXX builds return an error! + // TODO: still happening? const ret = fio.http_set_cookie(self.h, c); if (ret == -1) { std.log.err("fio.http_set_cookie returned: {}\n", .{ret}); @@ -347,8 +381,8 @@ pub const SimpleRequest = struct { } } - /// Returns named cookie. Works like getParamStr() - pub fn getCookieStr(self: *const Self, name: []const u8, a: std.mem.Allocator, always_alloc: bool) !?util.FreeOrNot { + /// Returns named cookie. Works like getParamStr(). + pub fn getCookieStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot { if (self.h.*.cookies == 0) return null; const key = fio.fiobj_str_new(name.ptr, name.len); defer fio.fiobj_free_wrapped(key); @@ -356,10 +390,10 @@ pub const SimpleRequest = struct { if (value == fio.FIOBJ_INVALID) { return null; } - return try util.fio2strAllocOrNot(value, a, always_alloc); + return try util.fio2strAllocOrNot(a, value, always_alloc); } - /// Returns the number of parameters after parsing. + /// Returns the number of cookies after parsing. /// /// Parse with parseCookies() pub fn getCookiesCount(self: *const Self) isize { @@ -440,11 +474,11 @@ pub const SimpleRequest = struct { // this is thread-safe, guaranteed by fio const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop(); ctx.params.append(.{ - .key = util.fio2strAllocOrNot(fiobj_key, ctx.allocator, ctx.always_alloc) catch |err| { + .key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.always_alloc) catch |err| { ctx.last_error = err; return -1; }, - .value = util.fio2strAllocOrNot(fiobj_value, ctx.allocator, ctx.always_alloc) catch |err| { + .value = util.fio2strAllocOrNot(ctx.allocator, fiobj_value, ctx.always_alloc) catch |err| { ctx.last_error = err; return -1; }, @@ -493,11 +527,11 @@ pub const SimpleRequest = struct { // this is thread-safe, guaranteed by fio const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop(); ctx.params.append(.{ - .key = util.fio2strAllocOrNot(fiobj_key, ctx.allocator, ctx.dupe_strings) catch |err| { + .key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.dupe_strings) catch |err| { ctx.last_error = err; return -1; }, - .value = Fiobj2HttpParam(fiobj_value, ctx.allocator, ctx.dupe_strings) catch |err| { + .value = Fiobj2HttpParam(ctx.allocator, fiobj_value, ctx.dupe_strings) catch |err| { ctx.last_error = err; return -1; }, @@ -524,7 +558,7 @@ pub const SimpleRequest = struct { /// /// Requires parseBody() and/or parseQuery() have been called. /// The returned string needs to be deinited with .deinit() - pub fn getParamStr(self: *const Self, name: []const u8, a: std.mem.Allocator, always_alloc: bool) !?util.FreeOrNot { + pub fn getParamStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot { if (self.h.*.params == 0) return null; const key = fio.fiobj_str_new(name.ptr, name.len); defer fio.fiobj_free_wrapped(key); @@ -532,7 +566,7 @@ pub const SimpleRequest = struct { if (value == fio.FIOBJ_INVALID) { return null; } - return try util.fio2strAllocOrNot(value, a, always_alloc); + return try util.fio2strAllocOrNot(a, value, always_alloc); } }; @@ -546,6 +580,7 @@ pub const HttpParamStrKV = struct { } }; +/// List of key value pairs of Http param strings. pub const HttpParamStrKVList = struct { items: []HttpParamStrKV, allocator: std.mem.Allocator, @@ -557,6 +592,7 @@ pub const HttpParamStrKVList = struct { } }; +/// List of key value pairs of Http params (might be of different types). pub const HttpParamKVList = struct { items: []HttpParamKV, allocator: std.mem.Allocator, @@ -568,6 +604,7 @@ pub const HttpParamKVList = struct { } }; +/// Enum for HttpParam tagged union pub const HttpParamValueType = enum { // Null, Bool, @@ -579,6 +616,7 @@ pub const HttpParamValueType = enum { Array_Binfile, }; +/// Tagged union holding a typed Http param pub const HttpParam = union(HttpParamValueType) { Bool: bool, Int: isize, @@ -593,6 +631,7 @@ pub const HttpParam = union(HttpParamValueType) { Array_Binfile: std.ArrayList(HttpParamBinaryFile), }; +/// Key value pair of one typed Http param pub const HttpParamKV = struct { key: util.FreeOrNot, value: ?HttpParam, @@ -607,6 +646,7 @@ pub const HttpParamKV = struct { } }; +/// Struct representing an uploaded file. pub const HttpParamBinaryFile = struct { /// file contents data: ?[]const u8 = null, @@ -615,6 +655,7 @@ pub const HttpParamBinaryFile = struct { /// filename filename: ?[]const u8 = null, + /// format function for printing file upload data pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) std.os.WriteError!void { const d = value.data orelse "\\0"; const m = value.mimetype orelse "null"; @@ -735,14 +776,15 @@ fn parseBinfilesFrom(a: std.mem.Allocator, o: fio.FIOBJ) !HttpParam { } } -pub fn Fiobj2HttpParam(o: fio.FIOBJ, a: std.mem.Allocator, dupe_string: bool) !?HttpParam { +/// Parse FIO object into a typed Http param. Supports file uploads. +pub fn Fiobj2HttpParam(a: std.mem.Allocator, o: fio.FIOBJ, dupe_string: bool) !?HttpParam { return switch (fio.fiobj_type(o)) { fio.FIOBJ_T_NULL => null, fio.FIOBJ_T_TRUE => .{ .Bool = true }, fio.FIOBJ_T_FALSE => .{ .Bool = false }, fio.FIOBJ_T_NUMBER => .{ .Int = fio.fiobj_obj2num(o) }, fio.FIOBJ_T_FLOAT => .{ .Float = fio.fiobj_obj2float(o) }, - fio.FIOBJ_T_STRING => .{ .String = try util.fio2strAllocOrNot(o, a, dupe_string) }, + fio.FIOBJ_T_STRING => .{ .String = try util.fio2strAllocOrNot(a, o, dupe_string) }, fio.FIOBJ_T_ARRAY => { return .{ .Unsupported = null }; }, @@ -754,6 +796,7 @@ pub fn Fiobj2HttpParam(o: fio.FIOBJ, a: std.mem.Allocator, dupe_string: bool) !? }; } +/// Args for setting a cookie pub const CookieArgs = struct { name: []const u8, value: []const u8, @@ -765,25 +808,31 @@ pub const CookieArgs = struct { http_only: bool = true, }; -pub const HttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void; -pub const SimpleHttpRequestFn = *const fn (SimpleRequest) void; +/// Used internally: facilio Http request callback function type +pub const FioHttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void; -/// websocket connection upgrade +/// Zap Http request callback function type. +pub const HttpRequestFn = *const fn (Request) void; + +/// websocket connection upgrade callback type /// fn(request, targetstring) -pub const SimpleHttpUpgradeFn = *const fn (r: SimpleRequest, target_protocol: []const u8) void; +pub const HttpUpgradeFn = *const fn (r: Request, target_protocol: []const u8) void; /// http finish, called when zap finishes. You get your udata back in the -/// struct. -pub const SimpleHttpFinishSettings = [*c]fio.struct_http_settings_s; -pub const SimpleHttpFinishFn = *const fn (SimpleHttpFinishSettings) void; +/// HttpFinishSetting struct. +pub const HttpFinishSettings = [*c]fio.struct_http_settings_s; -pub const SimpleHttpListenerSettings = struct { +/// Http finish callback type +pub const HttpFinishFn = *const fn (HttpFinishSettings) void; + +/// Listener settings +pub const HttpListenerSettings = struct { port: usize, interface: [*c]const u8 = null, - on_request: ?SimpleHttpRequestFn, - on_response: ?SimpleHttpRequestFn = null, - on_upgrade: ?SimpleHttpUpgradeFn = null, - on_finish: ?SimpleHttpFinishFn = null, + on_request: ?HttpRequestFn, + on_response: ?HttpRequestFn = null, + on_upgrade: ?HttpUpgradeFn = null, + on_finish: ?HttpFinishFn = null, // provide any pointer in there for "user data". it will be passed pack in // on_finish()'s copy of the struct_http_settings_s udata: ?*anyopaque = null, @@ -797,26 +846,26 @@ pub const SimpleHttpListenerSettings = struct { tls: ?Tls = null, }; -pub const SimpleHttpListener = struct { - settings: SimpleHttpListenerSettings, +/// Http listener +pub const HttpListener = struct { + settings: HttpListenerSettings, const Self = @This(); - var the_one_and_only_listener: ?*SimpleHttpListener = null; + var the_one_and_only_listener: ?*HttpListener = null; - pub fn init(settings: SimpleHttpListenerSettings) Self { + /// Create a listener + pub fn init(settings: HttpListenerSettings) Self { std.debug.assert(settings.on_request != null); return .{ .settings = settings, }; } - // on_upgrade: ?*const fn ([*c]fio.http_s, [*c]u8, usize) callconv(.C) void = null, - // on_finish: ?*const fn ([*c]fio.struct_http_settings_s) callconv(.C) void = null, - - // we could make it dynamic by passing a SimpleHttpListener via udata + // we could make it dynamic by passing a HttpListener via udata + /// Used internally: the listener's facilio request callback pub fn theOneAndOnlyRequestCallBack(r: [*c]fio.http_s) callconv(.C) void { if (the_one_and_only_listener) |l| { - var req: SimpleRequest = .{ + var req: Request = .{ .path = util.fio2str(r.*.path), .query = util.fio2str(r.*.query), .body = util.fio2str(r.*.body), @@ -827,7 +876,7 @@ pub const SimpleHttpListener = struct { }; req._is_finished = &req._is_finished_request_global; - var user_context: SimpleRequest.UserContext = .{}; + var user_context: Request.UserContext = .{}; req._user_context = &user_context; req.markAsFinished(false); @@ -839,9 +888,10 @@ pub const SimpleHttpListener = struct { } } + /// Used internally: the listener's facilio response callback pub fn theOneAndOnlyResponseCallBack(r: [*c]fio.http_s) callconv(.C) void { if (the_one_and_only_listener) |l| { - var req: SimpleRequest = .{ + var req: Request = .{ .path = util.fio2str(r.*.path), .query = util.fio2str(r.*.query), .body = util.fio2str(r.*.body), @@ -852,16 +902,17 @@ pub const SimpleHttpListener = struct { }; req._is_finished = &req._is_finished_request_global; - var user_context: SimpleRequest.UserContext = .{}; + var user_context: Request.UserContext = .{}; req._user_context = &user_context; l.settings.on_response.?(req); } } + /// Used internally: the listener's facilio upgrade callback pub fn theOneAndOnlyUpgradeCallBack(r: [*c]fio.http_s, target: [*c]u8, target_len: usize) callconv(.C) void { if (the_one_and_only_listener) |l| { - var req: SimpleRequest = .{ + var req: Request = .{ .path = util.fio2str(r.*.path), .query = util.fio2str(r.*.query), .body = util.fio2str(r.*.body), @@ -873,25 +924,27 @@ pub const SimpleHttpListener = struct { const zigtarget: []u8 = target[0..target_len]; req._is_finished = &req._is_finished_request_global; - var user_context: SimpleRequest.UserContext = .{}; + var user_context: Request.UserContext = .{}; req._user_context = &user_context; l.settings.on_upgrade.?(req, zigtarget); } } + /// Used internally: the listener's facilio finish callback pub fn theOneAndOnlyFinishCallBack(s: [*c]fio.struct_http_settings_s) callconv(.C) void { if (the_one_and_only_listener) |l| { l.settings.on_finish.?(s); } } + /// Start listening pub fn listen(self: *Self) !void { var pfolder: [*c]const u8 = null; var pfolder_len: usize = 0; if (self.settings.public_folder) |pf| { - debug("SimpleHttpListener.listen(): public folder is {s}\n", .{pf}); + debug("HttpListener.listen(): public folder is {s}\n", .{pf}); pfolder_len = pf.len; pfolder = pf.ptr; } @@ -921,15 +974,12 @@ pub const SimpleHttpListener = struct { // TODO: BUG: without this print/sleep statement, -Drelease* loop forever // in debug2 and debug3 of hello example // std.debug.print("X\n", .{}); - std.time.sleep(500 * 1000 * 1000); + // TODO: still happening? + std.time.sleep(500 * std.time.ns_per_ms); var portbuf: [100]u8 = undefined; const printed_port = try std.fmt.bufPrintZ(&portbuf, "{d}", .{self.settings.port}); - // pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintError![:0]u8 { - // const result = try bufPrint(buf, fmt ++ "\x00", args); - // return result[0 .. result.len - 1 :0]; - // } const ret = fio.http_listen(printed_port.ptr, self.settings.interface, x); if (ret == -1) { return error.ListenError; @@ -937,7 +987,7 @@ pub const SimpleHttpListener = struct { // set ourselves up to handle requests: // TODO: do we mind the race condition? - // the SimpleHttpRequestFn will check if this is null and not process + // the HttpRequestFn will check if this is null and not process // the request if it isn't set. hence, if started under full load, the // first request(s) might not be serviced, as long as it takes from // fio.http_listen() to here @@ -945,73 +995,78 @@ pub const SimpleHttpListener = struct { } }; -// -// lower level listening -// -pub const ListenSettings = struct { - on_request: ?*const fn ([*c]fio.http_s) callconv(.C) void = null, - on_upgrade: ?*const fn ([*c]fio.http_s, [*c]u8, usize) callconv(.C) void = null, - on_response: ?*const fn ([*c]fio.http_s) callconv(.C) void = null, - on_finish: ?*const fn ([*c]fio.struct_http_settings_s) callconv(.C) void = null, - public_folder: ?[]const u8 = null, - max_header_size: usize = 32 * 1024, - max_body_size: usize = 50 * 1024 * 1024, - max_clients: isize = 100, - keepalive_timeout_s: u8 = 5, - log: bool = false, +/// Low-level API +pub const LowLevel = struct { + /// lower level listening, if you don't want to use a listener but rather use + /// the listen() function. + pub const ListenSettings = struct { + on_request: ?FioHttpRequestFn = null, + on_upgrade: ?FioHttpRequestFn = null, + on_response: ?FioHttpRequestFn = null, + on_finish: ?FioHttpRequestFn = null, + public_folder: ?[]const u8 = null, + max_header_size: usize = 32 * 1024, + max_body_size: usize = 50 * 1024 * 1024, + max_clients: isize = 100, + keepalive_timeout_s: u8 = 5, + log: bool = false, - const Self = @This(); + const Self = @This(); - pub fn init() Self { - return .{}; + /// Create settings with defaults + pub fn init() Self { + return .{}; + } + }; + + /// Low level listen function + pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSettings) ListenError!void { + var pfolder: [*c]const u8 = null; + var pfolder_len: usize = 0; + + if (settings.public_folder) |pf| { + pfolder_len = pf.len; + pfolder = pf.ptr; + } + const x: fio.http_settings_s = .{ + .on_request = settings.on_request, + .on_upgrade = settings.on_upgrade, + .on_response = settings.on_response, + .on_finish = settings.on_finish, + .udata = null, + .public_folder = pfolder, + .public_folder_length = pfolder_len, + .max_header_size = settings.max_header_size, + .max_body_size = settings.max_body_size, + .max_clients = settings.max_clients, + .tls = null, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + .ws_max_msg_size = settings.ws_max_msg_size, + .timeout = settings.keepalive_timeout_s, + .ws_timeout = 0, + .log = if (settings.log) 1 else 0, + .is_client = 0, + }; + // TODO: BUG: without this print/sleep statement, -Drelease* loop forever + // in debug2 and debug3 of hello example + // std.debug.print("X\n", .{}); + // TODO: still happening? + std.time.sleep(500 * std.time.ns_per_ms); + + if (fio.http_listen(port, interface, x) == -1) { + return error.ListenError; + } + } + + /// lower level sendBody + pub fn sendBody(request: [*c]fio.http_s, body: []const u8) HttpError!void { + const ret = fio.http_send_body(request, @as( + *anyopaque, + @ptrFromInt(@intFromPtr(body.ptr)), + ), body.len); + debug("sendBody(): ret = {}\n", .{ret}); + if (ret != -1) return error.HttpSendBody; } }; - -pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSettings) ListenError!void { - var pfolder: [*c]const u8 = null; - var pfolder_len: usize = 0; - - if (settings.public_folder) |pf| { - pfolder_len = pf.len; - pfolder = pf.ptr; - } - const x: fio.http_settings_s = .{ - .on_request = settings.on_request, - .on_upgrade = settings.on_upgrade, - .on_response = settings.on_response, - .on_finish = settings.on_finish, - .udata = null, - .public_folder = pfolder, - .public_folder_length = pfolder_len, - .max_header_size = settings.max_header_size, - .max_body_size = settings.max_body_size, - .max_clients = settings.max_clients, - .tls = null, - .reserved1 = 0, - .reserved2 = 0, - .reserved3 = 0, - .ws_max_msg_size = settings.ws_max_msg_size, - .timeout = settings.keepalive_timeout_s, - .ws_timeout = 0, - .log = if (settings.log) 1 else 0, - .is_client = 0, - }; - // TODO: BUG: without this print/sleep statement, -Drelease* loop forever - // in debug2 and debug3 of hello example - // std.debug.print("X\n", .{}); - std.time.sleep(500 * 1000 * 1000); - - if (fio.http_listen(port, interface, x) == -1) { - return error.ListenError; - } -} - -// lower level sendBody -pub fn sendBody(request: [*c]fio.http_s, body: []const u8) HttpError!void { - const ret = fio.http_send_body(request, @as( - *anyopaque, - @ptrFromInt(@intFromPtr(body.ptr)), - ), body.len); - debug("sendBody(): ret = {}\n", .{ret}); - if (ret != -1) return error.HttpSendBody; -} diff --git a/tools/docserver.zig b/tools/docserver.zig index 7697e19..85b5167 100644 --- a/tools/docserver.zig +++ b/tools/docserver.zig @@ -1,7 +1,7 @@ const std = @import("std"); const zap = @import("zap"); -fn on_request(r: zap.SimpleRequest) void { +fn on_request(r: zap.Request) void { r.setStatus(.not_found); r.sendBody("

404 - File not found

") catch return; } @@ -26,7 +26,7 @@ pub fn main() !void { } } - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = port, .on_request = on_request, .public_folder = docs_dir, diff --git a/wrk/zig/main.zig b/wrk/zig/main.zig index 555cf71..20fa3f3 100644 --- a/wrk/zig/main.zig +++ b/wrk/zig/main.zig @@ -1,12 +1,12 @@ const std = @import("std"); const zap = @import("zap"); -fn on_request_minimal(r: zap.SimpleRequest) void { +fn on_request_minimal(r: zap.Request) void { r.sendBody("Hello from ZAP!!!") catch return; } pub fn main() !void { - var listener = zap.SimpleHttpListener.init(.{ + var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = on_request_minimal, .log = false,