diff --git a/examples/accept/accept.zig b/examples/accept/accept.zig index a9e268c..8e29350 100644 --- a/examples/accept/accept.zig +++ b/examples/accept/accept.zig @@ -21,6 +21,21 @@ fn on_request_verbose(r: zap.Request) !void { break :content_type .HTML; }; + // just for fun: print ALL headers + var maybe_headers: ?zap.Request.HttpParamStrKVList = blk: { + const h = r.headersToOwnedList(gpa.allocator()) catch |err| { + std.debug.print("Error getting headers: {}\n", .{err}); + break :blk null; + }; + break :blk h; + }; + if (maybe_headers) |*headers| { + defer headers.deinit(); + for (headers.items) |header| { + std.debug.print("Header {s} = {s}\n", .{ header.key, header.value }); + } + } + try r.setContentType(content_type); switch (content_type) { .TEXT => { @@ -66,7 +81,18 @@ pub fn main() !void { }); try listener.listen(); - std.debug.print("Listening on 0.0.0.0:3000\n", .{}); + std.debug.print( + \\ Listening on 0.0.0.0:3000 + \\ + \\ Test me with: + \\ curl --header "Accept: text/plain" localhost:3000 + \\ curl --header "Accept: text/html" localhost:3000 + \\ curl --header "Accept: application/xml" localhost:3000 + \\ curl --header "Accept: application/json" localhost:3000 + \\ curl --header "Accept: application/xhtml+xml" localhost:3000 + \\ + \\ + , .{}); // start worker threads zap.start(.{ diff --git a/examples/bindataformpost/bindataformpost.zig b/examples/bindataformpost/bindataformpost.zig index 9cc0eda..c1d5fe8 100644 --- a/examples/bindataformpost/bindataformpost.zig +++ b/examples/bindataformpost/bindataformpost.zig @@ -24,12 +24,12 @@ const Handler = struct { // // HERE WE HANDLE THE BINARY FILE // - const params = try r.parametersToOwnedList(Handler.alloc, false); + const params = try r.parametersToOwnedList(Handler.alloc); defer params.deinit(); for (params.items) |kv| { if (kv.value) |v| { std.debug.print("\n", .{}); - std.log.info("Param `{s}` in owned list is {any}\n", .{ kv.key.str, v }); + std.log.info("Param `{s}` in owned list is {any}\n", .{ kv.key, v }); switch (v) { // single-file upload zap.Request.HttpParam.Hash_Binfile => |*file| { @@ -55,32 +55,20 @@ const Handler = struct { files.*.deinit(); }, else => { - // might be a string param, we don't care - // let's just get it as string - // always_alloc param = false -> the string will be a slice from the request buffer - // --> no deinit necessary - if (r.getParamStr(Handler.alloc, kv.key.str, false)) |maybe_str| { - const value: []const u8 = if (maybe_str) |s| s.str else "(no value)"; - // above, we didn't defer s.deinit because the string is just a slice from the request buffer - std.log.debug(" {s} = {s}", .{ kv.key.str, value }); - } else |err| { - std.log.err("Error: {any}\n", .{err}); - } + // let's just get it as its raw slice + const value: []const u8 = r.getParamSlice(kv.key) orelse "(no value)"; + std.log.debug(" {s} = {s}", .{ kv.key, value }); }, } } } // check if we received a terminate=true parameter - if (r.getParamStr(Handler.alloc, "terminate", false)) |maybe_str| { - if (maybe_str) |*s| { - std.log.info("?terminate={s}\n", .{s.str}); - if (std.mem.eql(u8, s.str, "true")) { - zap.stop(); - } + if (r.getParamSlice("terminate")) |str| { + std.log.info("?terminate={s}\n", .{str}); + if (std.mem.eql(u8, str, "true")) { + zap.stop(); } - } else |err| { - std.log.err("cannot check for terminate param: {any}\n", .{err}); } try r.sendJson("{ \"ok\": true }"); } @@ -90,6 +78,7 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }){}; + defer _ = gpa.detectLeaks(); const allocator = gpa.allocator(); Handler.alloc = allocator; diff --git a/examples/cookies/cookies.zig b/examples/cookies/cookies.zig index 16fc9a8..22d8e18 100644 --- a/examples/cookies/cookies.zig +++ b/examples/cookies/cookies.zig @@ -44,12 +44,12 @@ pub fn main() !void { const cookie_count = r.getCookiesCount(); std.log.info("cookie_count: {}", .{cookie_count}); - // iterate over all cookies as strings (always_alloc=false) - var strCookies = r.cookiesToOwnedStrList(alloc, false) catch unreachable; + // iterate over all cookies as strings + var strCookies = r.cookiesToOwnedStrList(alloc) catch unreachable; defer strCookies.deinit(); std.debug.print("\n", .{}); for (strCookies.items) |kv| { - std.log.info("CookieStr `{s}` is `{s}`", .{ kv.key.str, kv.value.str }); + std.log.info("CookieStr `{s}` is `{s}`", .{ kv.key, kv.value }); // we don't need to deinit kv.key and kv.value because we requested always_alloc=false // so they are just slices into the request buffer } @@ -57,26 +57,22 @@ pub fn main() !void { std.debug.print("\n", .{}); // // iterate over all cookies - const cookies = r.cookiesToOwnedList(alloc, false) catch unreachable; + const cookies = r.cookiesToOwnedList(alloc) catch unreachable; defer cookies.deinit(); for (cookies.items) |kv| { - std.log.info("cookie `{s}` is {any}", .{ kv.key.str, kv.value }); + std.log.info("cookie `{s}` is {any}", .{ kv.key, kv.value }); } // let's get cookie "ZIG_ZAP" by name std.debug.print("\n", .{}); - if (r.getCookieStr(alloc, "ZIG_ZAP", false)) |maybe_str| { - if (maybe_str) |*s| { - defer s.deinit(); // unnecessary because always_alloc=false - - std.log.info("Cookie ZIG_ZAP = {s}", .{s.str}); + if (r.getCookieStr(alloc, "ZIG_ZAP")) |maybe_str| { + if (maybe_str) |s| { + defer alloc.free(s); + std.log.info("Cookie ZIG_ZAP = {s}", .{s}); } else { std.log.info("Cookie ZIG_ZAP not found!", .{}); } - } - // since we provided "false" for duplicating strings in the call - // to getCookieStr(), there won't be an allocation error - else |err| { + } else |err| { std.log.err("ERROR!\n", .{}); std.log.err("cannot check for `ZIG_ZAP` cookie: {any}\n", .{err}); } diff --git a/examples/endpoint/stopendpoint.zig b/examples/endpoint/stopendpoint.zig index d0e0a12..f96ec37 100644 --- a/examples/endpoint/stopendpoint.zig +++ b/examples/endpoint/stopendpoint.zig @@ -3,25 +3,25 @@ const zap = @import("zap"); /// 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(). -pub const Self = @This(); +pub const StopEndpoint = @This(); path: []const u8, error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response, -pub fn init(path: []const u8) Self { +pub fn init(path: []const u8) StopEndpoint { return .{ .path = path, }; } -pub fn get(e: *Self, r: zap.Request) anyerror!void { +pub fn get(e: *StopEndpoint, r: zap.Request) anyerror!void { _ = e; _ = r; zap.stop(); } -pub fn post(_: *Self, _: zap.Request) anyerror!void {} -pub fn put(_: *Self, _: zap.Request) anyerror!void {} -pub fn delete(_: *Self, _: zap.Request) anyerror!void {} -pub fn patch(_: *Self, _: zap.Request) anyerror!void {} -pub fn options(_: *Self, _: zap.Request) anyerror!void {} +pub fn post(_: *StopEndpoint, _: zap.Request) anyerror!void {} +pub fn put(_: *StopEndpoint, _: zap.Request) anyerror!void {} +pub fn delete(_: *StopEndpoint, _: zap.Request) anyerror!void {} +pub fn patch(_: *StopEndpoint, _: zap.Request) anyerror!void {} +pub fn options(_: *StopEndpoint, _: zap.Request) anyerror!void {} diff --git a/examples/endpoint/users.zig b/examples/endpoint/users.zig index 2ab7a17..3238fd2 100644 --- a/examples/endpoint/users.zig +++ b/examples/endpoint/users.zig @@ -5,7 +5,7 @@ users: std.AutoHashMap(usize, InternalUser) = undefined, lock: std.Thread.Mutex = undefined, count: usize = 0, -pub const Self = @This(); +pub const Users = @This(); const InternalUser = struct { id: usize = 0, @@ -21,7 +21,7 @@ pub const User = struct { last_name: []const u8, }; -pub fn init(a: std.mem.Allocator) Self { +pub fn init(a: std.mem.Allocator) Users { return .{ .alloc = a, .users = std.AutoHashMap(usize, InternalUser).init(a), @@ -29,13 +29,13 @@ pub fn init(a: std.mem.Allocator) Self { }; } -pub fn deinit(self: *Self) void { +pub fn deinit(self: *Users) void { self.users.deinit(); } // the request will be freed (and its mem reused by facilio) when it's // completed, so we take copies of the names -pub fn addByName(self: *Self, first: ?[]const u8, last: ?[]const u8) !usize { +pub fn addByName(self: *Users, first: ?[]const u8, last: ?[]const u8) !usize { var user: InternalUser = undefined; user.firstnamelen = 0; user.lastnamelen = 0; @@ -64,7 +64,7 @@ pub fn addByName(self: *Self, first: ?[]const u8, last: ?[]const u8) !usize { } } -pub fn delete(self: *Self, id: usize) bool { +pub fn delete(self: *Users, id: usize) bool { // We lock only on insertion, deletion, and listing self.lock.lock(); defer self.lock.unlock(); @@ -76,7 +76,7 @@ pub fn delete(self: *Self, id: usize) bool { return ret; } -pub fn get(self: *Self, id: usize) ?User { +pub fn get(self: *Users, id: usize) ?User { // we don't care about locking here, as our usage-pattern is unlikely to // get a user by id that is not known yet if (self.users.getPtr(id)) |pUser| { @@ -90,7 +90,7 @@ pub fn get(self: *Self, id: usize) ?User { } pub fn update( - self: *Self, + self: *Users, id: usize, first: ?[]const u8, last: ?[]const u8, @@ -112,7 +112,7 @@ pub fn update( return false; } -pub fn toJSON(self: *Self) ![]const u8 { +pub fn toJSON(self: *Users) ![]const u8 { self.lock.lock(); defer self.lock.unlock(); @@ -137,7 +137,7 @@ pub fn toJSON(self: *Self) ![]const u8 { // // Note: the following code is kept in here because it taught us a lesson // -pub fn listWithRaceCondition(self: *Self, out: *std.ArrayList(User)) !void { +pub fn listWithRaceCondition(self: *Users, out: *std.ArrayList(User)) !void { // We lock only on insertion, deletion, and listing // // NOTE: race condition: @@ -169,18 +169,14 @@ pub fn listWithRaceCondition(self: *Self, out: *std.ArrayList(User)) !void { const JsonUserIteratorWithRaceCondition = struct { it: std.AutoHashMap(usize, InternalUser).ValueIterator = undefined, - const This = @This(); - // careful: - // - Self refers to the file's struct - // - This refers to the JsonUserIterator struct - pub fn init(internal_users: *std.AutoHashMap(usize, InternalUser)) This { + pub fn init(internal_users: *std.AutoHashMap(usize, InternalUser)) JsonUserIteratorWithRaceCondition { return .{ .it = internal_users.valueIterator(), }; } - pub fn next(this: *This) ?User { + pub fn next(this: *JsonUserIteratorWithRaceCondition) ?User { if (this.it.next()) |pUser| { // we get a pointer to the internal user. so it should be safe to // create slices from its first and last name buffers diff --git a/examples/endpoint/userweb.zig b/examples/endpoint/userweb.zig index e492408..d45370c 100644 --- a/examples/endpoint/userweb.zig +++ b/examples/endpoint/userweb.zig @@ -5,7 +5,7 @@ const User = Users.User; // an Endpoint -pub const Self = @This(); +pub const UserWeb = @This(); alloc: std.mem.Allocator = undefined, _users: Users = undefined, @@ -16,7 +16,7 @@ error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response, pub fn init( a: std.mem.Allocator, user_path: []const u8, -) Self { +) UserWeb { return .{ .alloc = a, ._users = Users.init(a), @@ -24,15 +24,15 @@ pub fn init( }; } -pub fn deinit(self: *Self) void { +pub fn deinit(self: *UserWeb) void { self._users.deinit(); } -pub fn users(self: *Self) *Users { +pub fn users(self: *UserWeb) *Users { return &self._users; } -fn userIdFromPath(self: *Self, path: []const u8) ?usize { +fn userIdFromPath(self: *UserWeb, path: []const u8) ?usize { if (path.len >= self.path.len + 2) { if (path[self.path.len] != '/') { return null; @@ -43,8 +43,8 @@ fn userIdFromPath(self: *Self, path: []const u8) ?usize { return null; } -pub fn put(_: *Self, _: zap.Request) anyerror!void {} -pub fn get(self: *Self, r: zap.Request) anyerror!void { +pub fn put(_: *UserWeb, _: zap.Request) anyerror!void {} +pub fn get(self: *UserWeb, r: zap.Request) anyerror!void { if (r.path) |path| { // /users if (path.len == self.path.len) { @@ -60,7 +60,7 @@ pub fn get(self: *Self, r: zap.Request) anyerror!void { } } -fn listUsers(self: *Self, r: zap.Request) !void { +fn listUsers(self: *UserWeb, r: zap.Request) !void { if (self._users.toJSON()) |json| { defer self.alloc.free(json); try r.sendJson(json); @@ -69,7 +69,7 @@ fn listUsers(self: *Self, r: zap.Request) !void { } } -pub fn post(self: *Self, r: zap.Request) anyerror!void { +pub fn post(self: *UserWeb, r: zap.Request) anyerror!void { if (r.body) |body| { const maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null; if (maybe_user) |u| { @@ -86,7 +86,7 @@ pub fn post(self: *Self, r: zap.Request) anyerror!void { } } -pub fn patch(self: *Self, r: zap.Request) anyerror!void { +pub fn patch(self: *UserWeb, r: zap.Request) anyerror!void { if (r.path) |path| { if (self.userIdFromPath(path)) |id| { if (self._users.get(id)) |_| { @@ -109,7 +109,7 @@ pub fn patch(self: *Self, r: zap.Request) anyerror!void { } } -pub fn delete(self: *Self, r: zap.Request) anyerror!void { +pub fn delete(self: *UserWeb, r: zap.Request) anyerror!void { if (r.path) |path| { if (self.userIdFromPath(path)) |id| { var jsonbuf: [128]u8 = undefined; @@ -124,7 +124,7 @@ pub fn delete(self: *Self, r: zap.Request) anyerror!void { } } -pub fn options(_: *Self, r: zap.Request) anyerror!void { +pub fn options(_: *UserWeb, r: zap.Request) anyerror!void { try r.setHeader("Access-Control-Allow-Origin", "*"); try r.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS"); r.setStatus(zap.http.StatusCode.no_content); diff --git a/examples/hello2/hello2.zig b/examples/hello2/hello2.zig index 51e8faa..5302c4c 100644 --- a/examples/hello2/hello2.zig +++ b/examples/hello2/hello2.zig @@ -43,7 +43,18 @@ pub fn main() !void { }); try listener.listen(); - std.debug.print("Listening on 0.0.0.0:3000\n", .{}); + std.debug.print( + \\ Listening on 0.0.0.0:3000 + \\ + \\ Test me with: + \\ curl http://localhost:3000 + \\ curl --header "special-header: test" localhost:3000 + \\ + \\ ... or open http://localhost:3000 in the browser + \\ and watch the log output here + \\ + \\ + , .{}); // start worker threads zap.start(.{ diff --git a/examples/hello_json/hello_json.zig b/examples/hello_json/hello_json.zig index 21f9f46..af693ef 100644 --- a/examples/hello_json/hello_json.zig +++ b/examples/hello_json/hello_json.zig @@ -43,6 +43,7 @@ fn setupUserData(a: std.mem.Allocator) !void { pub fn main() !void { const a = std.heap.page_allocator; try setupUserData(a); + defer users.deinit(); var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = on_request, diff --git a/examples/http_params/http_params.zig b/examples/http_params/http_params.zig index 66976f6..4034eaf 100644 --- a/examples/http_params/http_params.zig +++ b/examples/http_params/http_params.zig @@ -27,6 +27,8 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true, }){}; + defer _ = gpa.detectLeaks(); + const allocator = gpa.allocator(); const Handler = struct { @@ -69,42 +71,38 @@ pub fn main() !void { // ================================================================ // iterate over all params as strings - var strparams = try r.parametersToOwnedStrList(alloc, false); + var strparams = try r.parametersToOwnedStrList(alloc); defer strparams.deinit(); std.debug.print("\n", .{}); for (strparams.items) |kv| { - std.log.info("ParamStr `{s}` is `{s}`", .{ kv.key.str, kv.value.str }); + std.log.info("ParamStr `{s}` is `{s}`", .{ kv.key, kv.value }); } std.debug.print("\n", .{}); // iterate over all params - const params = try r.parametersToOwnedList(alloc, false); + const params = try r.parametersToOwnedList(alloc); defer params.deinit(); for (params.items) |kv| { - std.log.info("Param `{s}` is {any}", .{ kv.key.str, kv.value }); + std.log.info("Param `{s}` is {any}", .{ kv.key, kv.value }); } // let's get param "one" by name std.debug.print("\n", .{}); - if (r.getParamStr(alloc, "one", false)) |maybe_str| { - if (maybe_str) |*s| { - defer s.deinit(); - - std.log.info("Param one = {s}", .{s.str}); + if (r.getParamStr(alloc, "one")) |maybe_str| { + if (maybe_str) |s| { + defer alloc.free(s); + std.log.info("Param one = {s}", .{s}); } else { std.log.info("Param one not found!", .{}); } - } - // since we provided "false" for duplicating strings in the call - // to getParamStr(), there won't be an allocation error - else |err| { + } else |err| { std.log.err("cannot check for `one` param: {any}\n", .{err}); } // check if we received a terminate=true parameter - if (r.getParamSlice("terminate")) |maybe_str| { - if (std.mem.eql(u8, maybe_str, "true")) { + if (r.getParamSlice("terminate")) |s| { + if (std.mem.eql(u8, s, "true")) { zap.stop(); } } diff --git a/examples/middleware/middleware.zig b/examples/middleware/middleware.zig index a02895f..844272e 100644 --- a/examples/middleware/middleware.zig +++ b/examples/middleware/middleware.zig @@ -6,8 +6,6 @@ const SharedAllocator = struct { // static var allocator: std.mem.Allocator = undefined; - const Self = @This(); - // just a convenience function pub fn init(a: std.mem.Allocator) void { allocator = a; @@ -43,8 +41,6 @@ const Handler = zap.Middleware.Handler(Context); const UserMiddleWare = struct { handler: Handler, - const Self = @This(); - // Just some arbitrary struct we want in the per-request context // note: it MUST have all default values!!! // note: it MUST have all default values!!! @@ -57,22 +53,22 @@ const UserMiddleWare = struct { email: []const u8 = undefined, }; - pub fn init(other: ?*Handler) Self { + pub fn init(other: ?*Handler) UserMiddleWare { return .{ .handler = Handler.init(onRequest, other), }; } // we need the handler as a common interface to chain stuff - pub fn getHandler(self: *Self) *Handler { + pub fn getHandler(self: *UserMiddleWare) *Handler { return &self.handler; } - // note that the first parameter is of type *Handler, not *Self !!! + // note that the first parameter is of type *Handler, not *UserMiddleWare !!! pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool { // this is how we would get our self pointer - const self: *Self = @fieldParentPtr("handler", handler); + const self: *UserMiddleWare = @fieldParentPtr("handler", handler); _ = self; // do our work: fill in the user field of the context @@ -92,8 +88,6 @@ const UserMiddleWare = struct { const SessionMiddleWare = struct { handler: Handler, - const Self = @This(); - // Just some arbitrary struct we want in the per-request context // note: it MUST have all default values!!! const Session = struct { @@ -101,21 +95,21 @@ const SessionMiddleWare = struct { token: []const u8 = undefined, }; - pub fn init(other: ?*Handler) Self { + pub fn init(other: ?*Handler) SessionMiddleWare { return .{ .handler = Handler.init(onRequest, other), }; } // we need the handler as a common interface to chain stuff - pub fn getHandler(self: *Self) *Handler { + pub fn getHandler(self: *SessionMiddleWare) *Handler { return &self.handler; } - // note that the first parameter is of type *Handler, not *Self !!! + // note that the first parameter is of type *Handler, not *SessionMiddleWare !!! pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool { // this is how we would get our self pointer - const self: *Self = @fieldParentPtr("handler", handler); + const self: *SessionMiddleWare = @fieldParentPtr("handler", handler); _ = self; context.session = Session{ @@ -134,24 +128,22 @@ const SessionMiddleWare = struct { const HtmlMiddleWare = struct { handler: Handler, - const Self = @This(); - - pub fn init(other: ?*Handler) Self { + pub fn init(other: ?*Handler) HtmlMiddleWare { return .{ .handler = Handler.init(onRequest, other), }; } // we need the handler as a common interface to chain stuff - pub fn getHandler(self: *Self) *Handler { + pub fn getHandler(self: *HtmlMiddleWare) *Handler { return &self.handler; } - // note that the first parameter is of type *Handler, not *Self !!! + // note that the first parameter is of type *Handler, not *HtmlMiddleWare !!! pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool { // this is how we would get our self pointer - const self: *Self = @fieldParentPtr("handler", handler); + const self: *HtmlMiddleWare = @fieldParentPtr("handler", handler); _ = self; std.debug.print("\n\nHtmlMiddleware: handling request with context: {any}\n\n", .{context}); diff --git a/examples/middleware_with_endpoint/middleware_with_endpoint.zig b/examples/middleware_with_endpoint/middleware_with_endpoint.zig index 4d5f8b4..108cf28 100644 --- a/examples/middleware_with_endpoint/middleware_with_endpoint.zig +++ b/examples/middleware_with_endpoint/middleware_with_endpoint.zig @@ -6,8 +6,6 @@ const SharedAllocator = struct { // static var allocator: std.mem.Allocator = undefined; - const Self = @This(); - // just a convenience function pub fn init(a: std.mem.Allocator) void { allocator = a; @@ -35,8 +33,6 @@ const Handler = zap.Middleware.Handler(Context); const UserMiddleWare = struct { handler: Handler, - const Self = @This(); - // Just some arbitrary struct we want in the per-request context // This is so that it can be constructed via .{} // as we can't expect the listener to know how to initialize our context structs @@ -45,22 +41,22 @@ const UserMiddleWare = struct { email: []const u8 = undefined, }; - pub fn init(other: ?*Handler) Self { + pub fn init(other: ?*Handler) UserMiddleWare { return .{ .handler = Handler.init(onRequest, other), }; } // we need the handler as a common interface to chain stuff - pub fn getHandler(self: *Self) *Handler { + pub fn getHandler(self: *UserMiddleWare) *Handler { return &self.handler; } - // note that the first parameter is of type *Handler, not *Self !!! + // note that the first parameter is of type *Handler, not *UserMiddleWare !!! pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool { // this is how we would get our self pointer - const self: *Self = @fieldParentPtr("handler", handler); + const self: *UserMiddleWare = @fieldParentPtr("handler", handler); _ = self; // do our work: fill in the user field of the context @@ -82,8 +78,6 @@ const UserMiddleWare = struct { const SessionMiddleWare = struct { handler: Handler, - const Self = @This(); - // Just some arbitrary struct we want in the per-request context // note: it MUST have all default values!!! const Session = struct { @@ -91,21 +85,21 @@ const SessionMiddleWare = struct { token: []const u8 = undefined, }; - pub fn init(other: ?*Handler) Self { + pub fn init(other: ?*Handler) SessionMiddleWare { return .{ .handler = Handler.init(onRequest, other), }; } // we need the handler as a common interface to chain stuff - pub fn getHandler(self: *Self) *Handler { + pub fn getHandler(self: *SessionMiddleWare) *Handler { return &self.handler; } - // note that the first parameter is of type *Handler, not *Self !!! + // note that the first parameter is of type *Handler, not *SessionMiddleWare !!! pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool { // this is how we would get our self pointer - const self: *Self = @fieldParentPtr("handler", handler); + const self: *SessionMiddleWare = @fieldParentPtr("handler", handler); _ = self; context.session = Session{ @@ -136,11 +130,9 @@ const SessionMiddleWare = struct { // `breakOnFinish` parameter. // const HtmlEndpoint = struct { - const Self = @This(); - path: []const u8 = "(undefined)", - pub fn init() Self { + pub fn init() HtmlEndpoint { return .{ .path = "/doesn't+matter", }; @@ -152,7 +144,7 @@ const HtmlEndpoint = struct { pub fn patch(_: *HtmlEndpoint, _: zap.Request) !void {} pub fn options(_: *HtmlEndpoint, _: zap.Request) !void {} - pub fn get(_: *Self, r: zap.Request) !void { + pub fn get(_: *HtmlEndpoint, r: zap.Request) !void { var buf: [1024]u8 = undefined; var userFound: bool = false; var sessionFound: bool = false; diff --git a/examples/simple_router/simple_router.zig b/examples/simple_router/simple_router.zig index cacbcc3..72658ba 100644 --- a/examples/simple_router/simple_router.zig +++ b/examples/simple_router/simple_router.zig @@ -14,13 +14,11 @@ fn on_request_verbose(r: zap.Request) !void { } pub const SomePackage = struct { - const Self = @This(); - allocator: Allocator, a: i8, b: i8, - pub fn init(allocator: Allocator, a: i8, b: i8) Self { + pub fn init(allocator: Allocator, a: i8, b: i8) SomePackage { return .{ .allocator = allocator, .a = a, @@ -28,7 +26,7 @@ pub const SomePackage = struct { }; } - pub fn getA(self: *Self, req: zap.Request) !void { + pub fn getA(self: *SomePackage, req: zap.Request) !void { std.log.warn("get_a_requested", .{}); const string = std.fmt.allocPrint( @@ -41,7 +39,7 @@ pub const SomePackage = struct { req.sendBody(string) catch return; } - pub fn getB(self: *Self, req: zap.Request) !void { + pub fn getB(self: *SomePackage, req: zap.Request) !void { std.log.warn("get_b_requested", .{}); const string = std.fmt.allocPrint( @@ -54,7 +52,7 @@ pub const SomePackage = struct { req.sendBody(string) catch return; } - pub fn incrementA(self: *Self, req: zap.Request) !void { + pub fn incrementA(self: *SomePackage, req: zap.Request) !void { std.log.warn("increment_a_requested", .{}); self.a += 1; @@ -98,8 +96,17 @@ pub fn main() !void { }); try listener.listen(); - std.debug.print("Listening on 0.0.0.0:3000\n", .{}); - + std.debug.print( + \\ Listening on 0.0.0.0:3000 + \\ + \\ Test me with: + \\ curl http://localhost:3000/ + \\ curl http://localhost:3000/geta + \\ curl http://localhost:3000/getb + \\ curl http://localhost:3000/inca + \\ + \\ + , .{}); // start worker threads zap.start(.{ .threads = 2, diff --git a/examples/userpass_session_auth/userpass_session_auth.zig b/examples/userpass_session_auth/userpass_session_auth.zig index c421ddc..fcf7744 100644 --- a/examples/userpass_session_auth/userpass_session_auth.zig +++ b/examples/userpass_session_auth/userpass_session_auth.zig @@ -72,6 +72,7 @@ fn on_request(r: zap.Request) !void { .AuthOK => { // the authenticator says it is ok to proceed as usual std.log.info("Auth OK", .{}); + // dispatch to target path if (r.path) |p| { // used in the login page @@ -124,6 +125,7 @@ pub fn main() !void { // to detect leaks { const allocator = gpa.allocator(); + var listener = zap.HttpListener.init(.{ .port = 3000, .on_request = on_request, diff --git a/examples/websockets/websockets.zig b/examples/websockets/websockets.zig index d0e3cee..7e44452 100644 --- a/examples/websockets/websockets.zig +++ b/examples/websockets/websockets.zig @@ -20,13 +20,11 @@ const ContextManager = struct { lock: std.Thread.Mutex = .{}, contexts: ContextList = undefined, - const Self = @This(); - pub fn init( allocator: std.mem.Allocator, channelName: []const u8, usernamePrefix: []const u8, - ) Self { + ) ContextManager { return .{ .allocator = allocator, .channel = channelName, @@ -35,14 +33,14 @@ const ContextManager = struct { }; } - pub fn deinit(self: *Self) void { + pub fn deinit(self: *ContextManager) void { for (self.contexts.items) |ctx| { self.allocator.free(ctx.userName); } self.contexts.deinit(); } - pub fn newContext(self: *Self) !*Context { + pub fn newContext(self: *ContextManager) !*Context { self.lock.lock(); defer self.lock.unlock(); diff --git a/src/App.zig b/src/App.zig index 317e2ea..8b3e98d 100644 --- a/src/App.zig +++ b/src/App.zig @@ -26,9 +26,5 @@ pub fn create(comptime Context: type, context: *Context, opts: Opts) type { context: *Context = context, error_strategy: @TypeOf(opts.request_error_strategy) = opts.request_error_strategy, endpoints: std.StringArrayHashMapUnmanaged(*zap.Endpoint.Wrapper.Internal) = .empty, - - // pub fn addEndpoint(slug: []const u8, endpoint: anytype) !zap.Endpoint { - // // TODO: inspect endpoint: does it have - // } }; } diff --git a/src/endpoint.zig b/src/endpoint.zig index 6b7dd95..b004788 100644 --- a/src/endpoint.zig +++ b/src/endpoint.zig @@ -37,11 +37,11 @@ //! }; //! } //! -//! pub fn post(_: *Self, _: zap.Request) void {} -//! pub fn put(_: *Self, _: zap.Request) void {} -//! pub fn delete(_: *Self, _: zap.Request) void {} -//! pub fn patch(_: *Self, _: zap.Request) void {} -//! pub fn options(_: *Self, _: zap.Request) void {} +//! pub fn post(_: *StopEndpoint, _: zap.Request) void {} +//! pub fn put(_: *StopEndpoint, _: zap.Request) void {} +//! pub fn delete(_: *StopEndpoint, _: zap.Request) void {} +//! pub fn patch(_: *StopEndpoint, _: zap.Request) void {} +//! pub fn options(_: *StopEndpoint, _: zap.Request) void {} //! //! pub fn get(self: *StopEndpoint, r: zap.Request) void { //! _ = self; @@ -107,34 +107,34 @@ pub fn checkEndpointType(T: type) void { } pub const Wrapper = struct { - pub const Internal = struct { - call: *const fn (*Internal, zap.Request) anyerror!void = undefined, + pub const Interface = struct { + call: *const fn (*Interface, zap.Request) anyerror!void = undefined, path: []const u8, - destroy: *const fn (allocator: std.mem.Allocator, *Internal) void = undefined, + destroy: *const fn (allocator: std.mem.Allocator, *Interface) void = undefined, }; pub fn Wrap(T: type) type { return struct { wrapped: *T, - wrapper: Internal, + wrapper: Interface, - const Self = @This(); + const Wrapped = @This(); - pub fn unwrap(wrapper: *Internal) *Self { - const self: *Self = @alignCast(@fieldParentPtr("wrapper", wrapper)); + pub fn unwrap(wrapper: *Interface) *Wrapped { + const self: *Wrapped = @alignCast(@fieldParentPtr("wrapper", wrapper)); return self; } - pub fn destroy(allocator: std.mem.Allocator, wrapper: *Internal) void { - const self: *Self = @alignCast(@fieldParentPtr("wrapper", wrapper)); + pub fn destroy(allocator: std.mem.Allocator, wrapper: *Interface) void { + const self: *Wrapped = @alignCast(@fieldParentPtr("wrapper", wrapper)); allocator.destroy(self); } - pub fn onRequestWrapped(wrapper: *Internal, r: zap.Request) !void { - var self: *Self = Self.unwrap(wrapper); + pub fn onRequestWrapped(wrapper: *Interface, r: zap.Request) !void { + var self: *Wrapped = Wrapped.unwrap(wrapper); try self.onRequest(r); } - pub fn onRequest(self: *Self, r: zap.Request) !void { + pub fn onRequest(self: *Wrapped, r: zap.Request) !void { const ret = switch (r.methodAsEnum()) { .GET => self.wrapped.*.get(r), .POST => self.wrapped.*.post(r), @@ -150,7 +150,7 @@ pub const Wrapper = struct { switch (self.wrapped.*.error_strategy) { .raise => return err, .log_to_response => return r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505), - .log_to_console => zap.debug("Error in {} {s} : {}", .{ Self, r.method orelse "(no method)", err }), + .log_to_console => zap.debug("Error in {} {s} : {}", .{ Wrapped, r.method orelse "(no method)", err }), } } } @@ -176,12 +176,12 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { ep: *EndpointType, path: []const u8, error_strategy: ErrorStrategy, - const Self = @This(); + const AuthenticatingEndpoint = @This(); /// 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: *EndpointType, authenticator: *Authenticator) Self { + pub fn init(e: *EndpointType, authenticator: *Authenticator) AuthenticatingEndpoint { return .{ .authenticator = authenticator, .ep = e, @@ -191,7 +191,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { } /// Authenticates GET requests using the Authenticator. - pub fn get(self: *Self, r: zap.Request) anyerror!void { + pub fn get(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r), .AuthOK => self.ep.*.get(r), @@ -200,7 +200,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { } /// Authenticates POST requests using the Authenticator. - pub fn post(self: *Self, r: zap.Request) anyerror!void { + pub fn post(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r), .AuthOK => self.ep.*.post(r), @@ -209,7 +209,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { } /// Authenticates PUT requests using the Authenticator. - pub fn put(self: *Self, r: zap.Request) anyerror!void { + pub fn put(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r), .AuthOK => self.ep.*.put(r), @@ -218,7 +218,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { } /// Authenticates DELETE requests using the Authenticator. - pub fn delete(self: *Self, r: zap.Request) anyerror!void { + pub fn delete(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r), .AuthOK => self.ep.*.delete(r), @@ -227,7 +227,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { } /// Authenticates PATCH requests using the Authenticator. - pub fn patch(self: *Self, r: zap.Request) anyerror!void { + pub fn patch(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r), .AuthOK => self.ep.*.patch(r), @@ -236,7 +236,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type { } /// Authenticates OPTIONS requests using the Authenticator. - pub fn options(self: *Self, r: zap.Request) anyerror!void { + pub fn options(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void { try switch (self.authenticator.authenticateRequest(&r)) { .AuthFailed => return self.ep.*.unauthorized(r), .AuthOK => self.ep.*.put(r), @@ -261,10 +261,8 @@ pub const Listener = struct { listener: HttpListener, allocator: std.mem.Allocator, - const Self = @This(); - - /// Internal static struct of member endpoints - var endpoints: std.ArrayListUnmanaged(*Wrapper.Internal) = .empty; + /// Internal static interface struct of member endpoints + var endpoints: std.ArrayListUnmanaged(*Wrapper.Interface) = .empty; /// Internal, static request handler callback. Will be set to the optional, /// user-defined request callback that only gets called if no endpoints match @@ -274,7 +272,7 @@ pub const Listener = struct { /// 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 { + pub fn init(a: std.mem.Allocator, l: ListenerSettings) Listener { // reset the global in case init is called multiple times, as is the // case in the authentication tests endpoints = .empty; @@ -297,7 +295,7 @@ pub const Listener = 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 { + pub fn deinit(self: *Listener) void { for (endpoints.items) |endpoint_wrapper| { endpoint_wrapper.destroy(self.allocator, endpoint_wrapper); } @@ -306,7 +304,7 @@ pub const Listener = struct { /// Call this to start listening. After this, no more endpoints can be /// registered. - pub fn listen(self: *Self) !void { + pub fn listen(self: *Listener) !void { try self.listener.listen(); } @@ -314,7 +312,7 @@ pub const Listener = struct { /// 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: anytype) !void { + pub fn register(self: *Listener, e: anytype) !void { for (endpoints.items) |other| { if (std.mem.startsWith( u8, diff --git a/src/fio.zig b/src/fio.zig index 7696989..4b6b3cd 100644 --- a/src/fio.zig +++ b/src/fio.zig @@ -13,7 +13,7 @@ pub const fio_url_s = extern struct { pub extern fn fio_url_parse(url: [*c]const u8, length: usize) fio_url_s; /// Negative thread / worker values indicate a fraction of the number of CPU cores. i.e., -2 will normally indicate "half" (1/2) the number of cores. -/// +/// /// If one value is set to zero, it will be the absolute value of the other value. i.e.: if .threads == -2 and .workers == 0, than facil.io will run 2 worker processes with (cores/2) threads per process. pub const struct_fio_start_args = extern struct { /// The number of threads to run in the thread pool. @@ -420,7 +420,9 @@ pub fn fiobj_obj2cstr(o: FIOBJ) callconv(.C) fio_str_info_s { // pub const http_cookie_args_s = opaque {}; pub extern fn http_set_header(h: [*c]http_s, name: FIOBJ, value: FIOBJ) c_int; +/// set header, copying the data pub extern fn http_set_header2(h: [*c]http_s, name: fio_str_info_s, value: fio_str_info_s) c_int; +/// set cookie, taking ownership of data pub extern fn http_set_cookie(h: [*c]http_s, http_cookie_args_s) c_int; pub extern fn http_sendfile(h: [*c]http_s, fd: c_int, length: usize, offset: usize) c_int; pub extern fn http_sendfile2(h: [*c]http_s, prefix: [*c]const u8, prefix_len: usize, encoded: [*c]const u8, encoded_len: usize) c_int; diff --git a/src/http_auth.zig b/src/http_auth.zig index 894b74e..d7cd127 100644 --- a/src/http_auth.zig +++ b/src/http_auth.zig @@ -86,14 +86,14 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type { realm: ?[]const u8, lookup: *Lookup, - const Self = @This(); + const BasicAuth = @This(); /// Creates a BasicAuth. `lookup` must implement `.get([]const u8) -> []const u8` /// 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 of it is taken -> call deinit() to clean up - pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !Self { + pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !BasicAuth { return .{ .allocator = allocator, .lookup = lookup, @@ -102,7 +102,7 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type { } /// Deinit the authenticator. - pub fn deinit(self: *Self) void { + pub fn deinit(self: *BasicAuth) void { if (self.realm) |the_realm| { self.allocator.free(the_realm); } @@ -110,7 +110,7 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type { /// 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 { + pub fn authenticateUserPass(self: *BasicAuth, auth_header: []const u8) AuthResult { zap.debug("AuthenticateUserPass\n", .{}); const encoded = auth_header[AuthScheme.Basic.str().len..]; const decoder = std.base64.standard.Decoder; @@ -173,14 +173,14 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type { /// 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 { + pub fn authenticateToken68(self: *BasicAuth, 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 (.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 { + pub fn authenticate(self: *BasicAuth, auth_header: []const u8) AuthResult { zap.debug("AUTHENTICATE\n", .{}); switch (kind) { .UserPass => return self.authenticateUserPass(auth_header), @@ -192,7 +192,7 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type { /// /// 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 { + pub fn authenticateRequest(self: *BasicAuth, r: *const zap.Request) AuthResult { zap.debug("AUTHENTICATE REQUEST\n", .{}); if (extractAuthHeader(.Basic, r)) |auth_header| { zap.debug("Authentication Header found!\n", .{}); @@ -225,12 +225,10 @@ pub const BearerSingle = struct { token: []const u8, realm: ?[]const u8, - 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. - pub fn init(allocator: std.mem.Allocator, token: []const u8, realm: ?[]const u8) !Self { + pub fn init(allocator: std.mem.Allocator, token: []const u8, realm: ?[]const u8) !BearerSingle { return .{ .allocator = allocator, .token = try allocator.dupe(u8, token), @@ -240,7 +238,7 @@ pub const BearerSingle = struct { /// 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 { + pub fn authenticate(self: *BearerSingle, auth_header: []const u8) AuthResult { if (checkAuthHeader(.Bearer, auth_header) == false) { return .AuthFailed; } @@ -251,7 +249,7 @@ pub const BearerSingle = struct { /// 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 { + pub fn authenticateRequest(self: *BearerSingle, r: *const zap.Request) AuthResult { if (extractAuthHeader(.Bearer, r)) |auth_header| { return self.authenticate(auth_header); } @@ -259,7 +257,7 @@ pub const BearerSingle = struct { } /// Deinits the authenticator. - pub fn deinit(self: *Self) void { + pub fn deinit(self: *BearerSingle) void { if (self.realm) |the_realm| { self.allocator.free(the_realm); } @@ -283,12 +281,12 @@ pub fn BearerMulti(comptime Lookup: type) type { lookup: *Lookup, realm: ?[]const u8, - const Self = @This(); + const BearerMultiAuth = @This(); /// 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 { + pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !BearerMultiAuth { return .{ .allocator = allocator, .lookup = lookup, @@ -298,7 +296,7 @@ pub fn BearerMulti(comptime Lookup: type) type { /// Deinit the authenticator. Only required if a realm was provided at /// init() time. - pub fn deinit(self: *Self) void { + pub fn deinit(self: *BearerMultiAuth) void { if (self.realm) |the_realm| { self.allocator.free(the_realm); } @@ -306,7 +304,7 @@ pub fn BearerMulti(comptime Lookup: type) type { /// 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 { + pub fn authenticate(self: *BearerMultiAuth, auth_header: []const u8) AuthResult { if (checkAuthHeader(.Bearer, auth_header) == false) { return .AuthFailed; } @@ -317,7 +315,7 @@ pub fn BearerMulti(comptime Lookup: type) type { /// 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 { + pub fn authenticateRequest(self: *BearerMultiAuth, r: *const zap.Request) AuthResult { if (extractAuthHeader(.Bearer, r)) |auth_header| { return self.authenticate(auth_header); } @@ -388,7 +386,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty passwordLookupLock: std.Thread.Mutex = .{}, tokenLookupLock: std.Thread.Mutex = .{}, - const Self = @This(); + const UserPassSessionAuth = @This(); const SessionTokenMap = std.StringHashMap(void); const Hash = std.crypto.hash.sha2.Sha256; @@ -400,8 +398,8 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty allocator: std.mem.Allocator, lookup: *Lookup, args: UserPassSessionArgs, - ) !Self { - const ret: Self = .{ + ) !UserPassSessionAuth { + const ret: UserPassSessionAuth = .{ .allocator = allocator, .settings = .{ .usernameParam = try allocator.dupe(u8, args.usernameParam), @@ -419,7 +417,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty } /// De-init this authenticator. - pub fn deinit(self: *Self) void { + pub fn deinit(self: *UserPassSessionAuth) void { self.allocator.free(self.settings.usernameParam); self.allocator.free(self.settings.passwordParam); self.allocator.free(self.settings.loginPage); @@ -434,7 +432,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty } /// Check for session token cookie, remove the token from the valid tokens - pub fn logout(self: *Self, r: *const zap.Request) void { + pub fn logout(self: *UserPassSessionAuth, 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(.{ @@ -450,15 +448,15 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty r.parseCookies(false); // check for session cookie - if (r.getCookieStr(self.allocator, self.settings.cookieName, false)) |maybe_cookie| { + if (r.getCookieStr(self.allocator, self.settings.cookieName)) |maybe_cookie| { if (maybe_cookie) |cookie| { - defer cookie.deinit(); + defer self.allocator.free(cookie); self.tokenLookupLock.lock(); defer self.tokenLookupLock.unlock(); - if (self.sessionTokens.getKeyPtr(cookie.str)) |keyPtr| { + if (self.sessionTokens.getKeyPtr(cookie)) |keyPtr| { const keySlice = keyPtr.*; // if cookie is a valid session, remove it! - _ = self.sessionTokens.remove(cookie.str); + _ = self.sessionTokens.remove(cookie); // only now can we let go of the cookie str slice that // was used as the key self.allocator.free(keySlice); @@ -469,7 +467,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty } } - fn _internal_authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { + fn _internal_authenticateRequest(self: *UserPassSessionAuth, 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)) { @@ -486,18 +484,18 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty r.parseCookies(false); // check for session cookie - if (r.getCookieStr(self.allocator, self.settings.cookieName, false)) |maybe_cookie| { + if (r.getCookieStr(self.allocator, self.settings.cookieName)) |maybe_cookie| { if (maybe_cookie) |cookie| { - defer cookie.deinit(); + defer self.allocator.free(cookie); // locked or unlocked token lookup self.tokenLookupLock.lock(); defer self.tokenLookupLock.unlock(); - if (self.sessionTokens.contains(cookie.str)) { + if (self.sessionTokens.contains(cookie)) { // cookie is a valid session! - zap.debug("Auth: COOKIE IS OK!!!!: {s}\n", .{cookie.str}); + zap.debug("Auth: COOKIE IS OK!!!!: {s}\n", .{cookie}); return .AuthOK; } else { - zap.debug("Auth: COOKIE IS BAD!!!!: {s}\n", .{cookie.str}); + zap.debug("Auth: COOKIE IS BAD!!!!: {s}\n", .{cookie}); // this is not necessarily a bad thing. it could be a // stale cookie from a previous session. So let's check // if username and password are being sent and correct. @@ -508,27 +506,26 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty } // get params of username and password - if (r.getParamStr(self.allocator, self.settings.usernameParam, false)) |maybe_username| { - if (maybe_username) |*username| { - defer username.deinit(); - if (r.getParamStr(self.allocator, self.settings.passwordParam, false)) |maybe_pw| { - if (maybe_pw) |*pw| { - defer pw.deinit(); - + if (r.getParamStr(self.allocator, self.settings.usernameParam)) |maybe_username| { + if (maybe_username) |username| { + defer self.allocator.free(username); + if (r.getParamStr(self.allocator, self.settings.passwordParam)) |maybe_pw| { + if (maybe_pw) |pw| { + defer self.allocator.free(pw); // now check const correct_pw_optional = brk: { if (lockedPwLookups) { self.passwordLookupLock.lock(); defer self.passwordLookupLock.unlock(); - break :brk self.lookup.*.get(username.str); + break :brk self.lookup.*.get(username); } else { - break :brk self.lookup.*.get(username.str); + break :brk self.lookup.*.get(username); } }; if (correct_pw_optional) |correct_pw| { - if (std.mem.eql(u8, pw.str, correct_pw)) { + if (std.mem.eql(u8, pw, correct_pw)) { // create session token - if (self.createAndStoreSessionToken(username.str, pw.str)) |token| { + if (self.createAndStoreSessionToken(username, pw)) |token| { defer self.allocator.free(token); // now set the cookie header if (r.setCookie(.{ @@ -549,12 +546,12 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty } } } else |err| { - zap.debug("getParamSt() for password failed in UserPassSession: {any}", .{err}); + zap.debug("getParamStr() for password failed in UserPassSession: {any}", .{err}); return .AuthFailed; } } } else |err| { - zap.debug("getParamSt() for user failed in UserPassSession: {any}", .{err}); + zap.debug("getParamStr() for user failed in UserPassSession: {any}", .{err}); return .AuthFailed; } return .AuthFailed; @@ -563,7 +560,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty /// The zap authentication request handler. /// /// See above for how it works. - pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult { + pub fn authenticateRequest(self: *UserPassSessionAuth, r: *const zap.Request) AuthResult { switch (self._internal_authenticateRequest(r)) { .AuthOK => { // username and pass are ok -> created token, set header, caller can continue @@ -583,11 +580,11 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty } } - fn redirect(self: *Self, r: *const zap.Request) !void { + fn redirect(self: *UserPassSessionAuth, r: *const zap.Request) !void { try r.redirectTo(self.settings.loginPage, self.settings.redirectCode); } - fn createSessionToken(self: *Self, username: []const u8, password: []const u8) ![]const u8 { + fn createSessionToken(self: *UserPassSessionAuth, username: []const u8, password: []const u8) ![]const u8 { var hasher = Hash.init(.{}); hasher.update(username); hasher.update(password); @@ -603,7 +600,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty return token_str; } - fn createAndStoreSessionToken(self: *Self, username: []const u8, password: []const u8) ![]const u8 { + fn createAndStoreSessionToken(self: *UserPassSessionAuth, username: []const u8, password: []const u8) ![]const u8 { const token = try self.createSessionToken(username, password); self.tokenLookupLock.lock(); defer self.tokenLookupLock.unlock(); diff --git a/src/log.zig b/src/log.zig index 210eefc..8736511 100644 --- a/src/log.zig +++ b/src/log.zig @@ -5,15 +5,15 @@ const std = @import("std"); debugOn: bool, /// Access to facil.io's logging facilities -const Self = @This(); +const Log = @This(); -pub fn init(comptime debug: bool) Self { +pub fn init(comptime debug: bool) Log { return .{ .debugOn = debug, }; } -pub fn log(self: *const Self, comptime fmt: []const u8, args: anytype) void { +pub fn log(self: *const Log, comptime fmt: []const u8, args: anytype) void { if (self.debugOn) { std.debug.print("[zap] - " ++ fmt, args); } diff --git a/src/mustache.zig b/src/mustache.zig index 761b25f..82eeffe 100644 --- a/src/mustache.zig +++ b/src/mustache.zig @@ -2,7 +2,7 @@ const std = @import("std"); const fio = @import("fio.zig"); const util = @import("util.zig"); -const Self = @This(); +const Mustache = @This(); const struct_mustache_s = opaque {}; const mustache_s = struct_mustache_s; @@ -51,7 +51,7 @@ pub const Error = error{ /// Create a new `Mustache` instance; `deinit()` should be called to free /// the object after usage. -pub fn init(load_args: LoadArgs) Error!Self { +pub fn init(load_args: LoadArgs) Error!Mustache { var err: mustache_error_en = undefined; const args: MustacheLoadArgsFio = .{ @@ -72,7 +72,7 @@ pub fn init(load_args: LoadArgs) Error!Self { const ret = fiobj_mustache_new(args); switch (err) { - 0 => return Self{ + 0 => return Mustache{ .handle = ret.?, }, 1 => return Error.MUSTACHE_ERR_TOO_DEEP, @@ -93,18 +93,18 @@ pub fn init(load_args: LoadArgs) Error!Self { /// Convenience function to create a new `Mustache` instance with in-memory data loaded; /// `deinit()` should be called to free the object after usage.. -pub fn fromData(data: []const u8) Error!Self { - return Self.init(.{ .data = data }); +pub fn fromData(data: []const u8) Error!Mustache { + return Mustache.init(.{ .data = data }); } /// Convenience function to create a new `Mustache` instance with file-based data loaded; /// `deinit()` should be called to free the object after usage.. -pub fn fromFile(filename: []const u8) Error!Self { - return Self.init(.{ .filename = filename }); +pub fn fromFile(filename: []const u8) Error!Mustache { + return Mustache.init(.{ .filename = filename }); } /// Free the data backing a `Mustache` instance. -pub fn deinit(self: *Self) void { +pub fn deinit(self: *Mustache) void { fiobj_mustache_free(self.handle); } @@ -137,7 +137,7 @@ pub const BuildResult = struct { // TODO: The build may be slow because it needs to convert zig types to facil.io // types. However, this needs to be investigated into. // See `fiobjectify` for more information. -pub fn build(self: *Self, data: anytype) BuildResult { +pub fn build(self: *Mustache, data: anytype) BuildResult { const T = @TypeOf(data); if (@typeInfo(T) != .@"struct") { @compileError("No struct: '" ++ @typeName(T) ++ "'"); diff --git a/src/request.zig b/src/request.zig index a0c2a7e..74a84ef 100644 --- a/src/request.zig +++ b/src/request.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const Allocator = std.mem.Allocator; + const Log = @import("log.zig"); const http = @import("http.zig"); const fio = @import("fio.zig"); @@ -20,21 +22,19 @@ pub const HttpError = error{ /// Key value pair of strings from HTTP parameters pub const HttpParamStrKV = struct { - key: util.FreeOrNot, - value: util.FreeOrNot, - pub fn deinit(self: *@This()) void { - self.key.deinit(); - self.value.deinit(); - } + key: []const u8, + value: []const u8, }; /// List of key value pairs of Http param strings. pub const HttpParamStrKVList = struct { items: []HttpParamStrKV, - allocator: std.mem.Allocator, - pub fn deinit(self: *@This()) void { - for (self.items) |*item| { - item.deinit(); + allocator: Allocator, + + pub fn deinit(self: *HttpParamStrKVList) void { + for (self.items) |item| { + self.allocator.free(item.key); + self.allocator.free(item.value); } self.allocator.free(self.items); } @@ -43,10 +43,13 @@ 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, - pub fn deinit(self: *const @This()) void { - for (self.items) |*item| { - item.deinit(); + allocator: Allocator, + pub fn deinit(self: *const HttpParamKVList) void { + for (self.items) |item| { + self.allocator.free(item.key); + if (item.value) |v| { + v.free(self.allocator); + } } self.allocator.free(self.items); } @@ -70,28 +73,31 @@ pub const HttpParam = union(HttpParamValueType) { Int: isize, Float: f64, /// we don't do writable strings here - String: util.FreeOrNot, + String: []const u8, /// value will always be null Unsupported: ?void, /// we assume hashes are because of file transmissions Hash_Binfile: HttpParamBinaryFile, /// value will always be null Array_Binfile: std.ArrayList(HttpParamBinaryFile), + + pub fn free(self: HttpParam, alloc: Allocator) void { + switch (self) { + .String => |s| alloc.free(s), + .Array_Binfile => |a| { + a.deinit(); + }, + else => { + // nothing to free + }, + } + } }; /// Key value pair of one typed Http param pub const HttpParamKV = struct { - key: util.FreeOrNot, + key: []const u8, value: ?HttpParam, - pub fn deinit(self: *@This()) void { - self.key.deinit(); - if (self.value) |p| { - switch (p) { - .String => |*s| s.deinit(), - else => {}, - } - } - } }; /// Struct representing an uploaded file. @@ -104,7 +110,7 @@ pub const HttpParamBinaryFile = struct { filename: ?[]const u8 = null, /// format function for printing file upload data - pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + pub fn format(value: HttpParamBinaryFile, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { const d = value.data orelse "\\0"; const m = value.mimetype orelse "null"; const f = value.filename orelse "null"; @@ -112,7 +118,7 @@ pub const HttpParamBinaryFile = struct { } }; -fn parseBinfilesFrom(a: std.mem.Allocator, o: fio.FIOBJ) !HttpParam { +fn parseBinfilesFrom(a: Allocator, o: fio.FIOBJ) !HttpParam { const key_name = fio.fiobj_str_new("name", 4); const key_data = fio.fiobj_str_new("data", 4); const key_type = fio.fiobj_str_new("type", 4); @@ -225,14 +231,15 @@ fn parseBinfilesFrom(a: std.mem.Allocator, o: fio.FIOBJ) !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 { +/// Allocator is only used for file uploads. +pub fn fiobj2HttpParam(a: Allocator, o: fio.FIOBJ) !?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(a, o, dupe_string) }, + fio.FIOBJ_T_STRING => .{ .String = try util.fio2strAlloc(a, o) }, fio.FIOBJ_T_ARRAY => { return .{ .Unsupported = null }; }, @@ -280,30 +287,30 @@ pub const UserContext = struct { user_context: ?*anyopaque = null, }; -const Self = @This(); +const Request = @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 { +pub fn markAsFinished(self: *const Request, 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 { +pub fn isFinished(self: *const Request) bool { // we might be a copy return self._is_finished.*; } /// 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 { +pub fn setUserContext(self: *const Request, 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 { +pub fn getUserContext(self: *const Request, comptime Context: type) ?*Context { if (self._user_context.*.user_context) |ptr| { return @as(*Context, @ptrCast(@alignCast(ptr))); } else { @@ -316,7 +323,7 @@ pub fn getUserContext(self: *const Self, comptime Context: type) ?*Context { /// const err = zap.HttpError; // this is to show that `err` is an Error /// r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505); /// ``` -pub fn sendError(self: *const Self, err: anyerror, err_trace: ?std.builtin.StackTrace, errorcode_num: usize) void { +pub fn sendError(self: *const Request, err: anyerror, err_trace: ?std.builtin.StackTrace, errorcode_num: usize) void { // TODO: query accept headers if (self._internal_sendError(err, err_trace, errorcode_num)) { return; @@ -326,7 +333,7 @@ pub fn sendError(self: *const Self, err: anyerror, err_trace: ?std.builtin.Stack } /// Used internally. Probably does not need to be public. -pub fn _internal_sendError(self: *const Self, err: anyerror, err_trace: ?std.builtin.StackTrace, errorcode_num: usize) !void { +pub fn _internal_sendError(self: *const Request, err: anyerror, err_trace: ?std.builtin.StackTrace, errorcode_num: usize) !void { // TODO: query accept headers // TODO: let's hope 20k is enough. Maybe just really allocate here self.h.*.status = errorcode_num; @@ -346,7 +353,7 @@ pub fn _internal_sendError(self: *const Self, err: anyerror, err_trace: ?std.bui } /// Send body. -pub fn sendBody(self: *const Self, body: []const u8) HttpError!void { +pub fn sendBody(self: *const Request, body: []const u8) HttpError!void { const ret = fio.http_send_body(self.h, @as( *anyopaque, @ptrFromInt(@intFromPtr(body.ptr)), @@ -357,7 +364,7 @@ pub fn sendBody(self: *const Self, body: []const u8) HttpError!void { } /// Set content type and send json buffer. -pub fn sendJson(self: *const Self, json: []const u8) HttpError!void { +pub fn sendJson(self: *const Request, json: []const u8) HttpError!void { if (self.setContentType(.JSON)) { if (fio.http_send_body(self.h, @as( *anyopaque, @@ -368,7 +375,7 @@ pub fn sendJson(self: *const Self, json: []const u8) HttpError!void { } /// Set content type. -pub fn setContentType(self: *const Self, c: ContentType) HttpError!void { +pub fn setContentType(self: *const Request, c: ContentType) HttpError!void { const s = switch (c) { .TEXT => "text/plain", .JSON => "application/json", @@ -379,7 +386,7 @@ pub fn setContentType(self: *const Self, c: ContentType) HttpError!void { } /// redirect to path with status code 302 by default -pub fn redirectTo(self: *const Self, path: []const u8, code: ?http.StatusCode) HttpError!void { +pub fn redirectTo(self: *const Request, path: []const u8, code: ?http.StatusCode) HttpError!void { self.setStatus(if (code) |status| status else .found); try self.setHeader("Location", path); try self.sendBody("moved"); @@ -388,7 +395,7 @@ pub fn redirectTo(self: *const Self, path: []const u8, code: ?http.StatusCode) H /// shows how to use the logger pub fn setContentTypeWithLogger( - self: *const Self, + self: *const Request, c: ContentType, logger: *const Log, ) HttpError!void { @@ -402,7 +409,7 @@ pub fn setContentTypeWithLogger( } /// Tries to determine the content type by file extension of request path, and sets it. -pub fn setContentTypeFromPath(self: *const Self) !void { +pub fn setContentTypeFromPath(self: *const Request) !void { const t = fio.http_mimetype_find2(self.h.*.path); if (fio.is_invalid(t) == 1) return error.HttpSetContentType; const ret = fio.fiobj_hash_set( @@ -416,13 +423,14 @@ pub fn setContentTypeFromPath(self: *const Self) !void { /// Tries to determine the content type by filename extension, and sets it. /// If the extension cannot be determined, NoExtensionInFilename error is /// returned. -pub fn setContentTypeFromFilename(self: *const Self, filename: []const u8) !void { +pub fn setContentTypeFromFilename(self: *const Request, filename: []const u8) !void { const ext = std.fs.path.extension(filename); if (ext.len > 1) { const e = ext[1..]; const obj = fio.http_mimetype_find(@constCast(e.ptr), e.len); + // fio2str is OK since setHeader takes a copy if (util.fio2str(obj)) |mime_str| { try self.setHeader("content-type", mime_str); } @@ -435,9 +443,11 @@ pub fn setContentTypeFromFilename(self: *const Self, filename: []const u8) !void /// NOTE that header-names are lowerased automatically while parsing the request. /// so please only use lowercase keys! /// Returned mem is temp. Do not free it. -pub fn getHeader(self: *const Self, name: []const u8) ?[]const u8 { +pub fn getHeader(self: *const Request, name: []const u8) ?[]const u8 { const hname = fio.fiobj_str_new(util.toCharPtr(name), name.len); defer fio.fiobj_free_wrapped(hname); + // fio2str is OK since headers are always strings -> slice is returned + // (and not a slice into some threadlocal "global") return util.fio2str(fio.fiobj_hash_get(self.h.*.headers, hname)); } @@ -476,7 +486,7 @@ pub const HttpHeaderCommon = enum(usize) { /// Returns the header value of a given common header key. Returned memory /// should not be freed. -pub fn getHeaderCommon(self: *const Self, which: HttpHeaderCommon) ?[]const u8 { +pub fn getHeaderCommon(self: *const Request, which: HttpHeaderCommon) ?[]const u8 { const field = switch (which) { .accept => fio.HTTP_HEADER_ACCEPT, .cache_control => fio.HTTP_HEADER_CACHE_CONTROL, @@ -495,11 +505,13 @@ pub fn getHeaderCommon(self: *const Self, which: HttpHeaderCommon) ?[]const u8 { .upgrade => fio.HTTP_HEADER_UPGRADE, }; const fiobj = zap.fio.fiobj_hash_get(self.h.*.headers, field); + // fio2str is OK since headers are always strings -> slice is returned + // (and not a slice into some threadlocal "global") return zap.util.fio2str(fiobj); } /// Set header. -pub fn setHeader(self: *const Self, name: []const u8, value: []const u8) HttpError!void { +pub fn setHeader(self: *const Request, name: []const u8, value: []const u8) HttpError!void { const hname: fio.fio_str_info_s = .{ .data = util.toCharPtr(name), .len = name.len, @@ -526,13 +538,26 @@ pub fn setHeader(self: *const Self, name: []const u8, value: []const u8) HttpErr return error.HttpSetHeader; } +pub fn headersToOwnedList(self: *const Request, a: Allocator) !HttpParamStrKVList { + var headers = std.ArrayList(HttpParamStrKV).init(a); + var context: CallbackContext_StrKV = .{ + .params = &headers, + .allocator = a, + }; + const howmany = fio.fiobj_each1(self.h.*.headers, 0, CallbackContext_StrKV.callback, &context); + if (howmany != headers.items.len) { + return error.HttpIterHeaders; + } + return .{ .items = try headers.toOwnedSlice(), .allocator = a }; +} + /// Set status by numeric value. -pub fn setStatusNumeric(self: *const Self, status: usize) void { +pub fn setStatusNumeric(self: *const Request, status: usize) void { self.h.*.status = status; } /// Set status by enum. -pub fn setStatus(self: *const Self, status: http.StatusCode) void { +pub fn setStatus(self: *const Request, status: http.StatusCode) void { self.h.*.status = @as(usize, @intCast(@intFromEnum(status))); } @@ -547,7 +572,7 @@ pub fn setStatus(self: *const Self, status: http.StatusCode) void { /// /// Important: sets last-modified and cache-control headers with a max-age value of 1 hour! /// You can override that by setting those headers yourself, e.g.: setHeader("Cache-Control", "no-cache") -pub fn sendFile(self: *const Self, file_path: []const u8) !void { +pub fn sendFile(self: *const Request, file_path: []const u8) !void { if (fio.http_sendfile2(self.h, util.toCharPtr(file_path), file_path.len, null, 0) != 0) return error.SendFile; self.markAsFinished(true); @@ -561,7 +586,7 @@ pub fn sendFile(self: *const Self, file_path: []const u8) !void { /// - application/x-www-form-urlencoded /// - application/json /// - multipart/form-data -pub fn parseBody(self: *const Self) HttpError!void { +pub fn parseBody(self: *const Request) HttpError!void { if (fio.http_parse_body(self.h) == -1) return error.HttpParseBody; } @@ -570,12 +595,12 @@ pub fn parseBody(self: *const Self) HttpError!void { /// object that doesn't have a hash map at its root. /// /// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice() -pub fn parseQuery(self: *const Self) void { +pub fn parseQuery(self: *const Request) void { fio.http_parse_query(self.h); } /// Parse received cookie headers -pub fn parseCookies(self: *const Self, url_encoded: bool) void { +pub fn parseCookies(self: *const Request, url_encoded: bool) void { fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0); } @@ -606,7 +631,7 @@ pub const AcceptItem = struct { const AcceptHeaderList = std.ArrayList(AcceptItem); /// Parses `Accept:` http header into `list`, ordered from highest q factor to lowest -pub fn parseAcceptHeaders(self: *const Self, allocator: std.mem.Allocator) !AcceptHeaderList { +pub fn parseAcceptHeaders(self: *const Request, allocator: Allocator) !AcceptHeaderList { const accept_str = self.getHeaderCommon(.accept) orelse return error.NoAcceptHeader; const comma_count = std.mem.count(u8, accept_str, ","); @@ -652,7 +677,7 @@ pub fn parseAcceptHeaders(self: *const Self, allocator: std.mem.Allocator) !Acce } /// Set a response cookie -pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void { +pub fn setCookie(self: *const Request, args: CookieArgs) HttpError!void { const c: fio.http_cookie_args_s = .{ .name = util.toCharPtr(args.name), .name_len = @as(isize, @intCast(args.name.len)), @@ -681,7 +706,7 @@ pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void { } /// Returns named cookie. Works like getParamStr(). -pub fn getCookieStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot { +pub fn getCookieStr(self: *const Request, a: Allocator, name: []const u8) !?[]const u8 { if (self.h.*.cookies == 0) return null; const key = fio.fiobj_str_new(name.ptr, name.len); defer fio.fiobj_free_wrapped(key); @@ -689,13 +714,15 @@ pub fn getCookieStr(self: *const Self, a: std.mem.Allocator, name: []const u8, a if (value == fio.FIOBJ_INVALID) { return null; } - return try util.fio2strAllocOrNot(a, value, always_alloc); + // we are not entirely sure if cookies fiobjs are always strings + // hence. fio2strAlloc + return try util.fio2strAlloc(a, value); } /// Returns the number of cookies after parsing. /// /// Parse with parseCookies() -pub fn getCookiesCount(self: *const Self) isize { +pub fn getCookiesCount(self: *const Request) isize { if (self.h.*.cookies == 0) return 0; return fio.fiobj_obj2num(self.h.*.cookies); } @@ -703,20 +730,77 @@ pub fn getCookiesCount(self: *const Self) isize { /// Returns the number of parameters after parsing. /// /// Parse with parseBody() and / or parseQuery() -pub fn getParamCount(self: *const Self) isize { +pub fn getParamCount(self: *const Request) isize { if (self.h.*.params == 0) return 0; return fio.fiobj_obj2num(self.h.*.params); } +const CallbackContext_KV = struct { + allocator: Allocator, + params: *std.ArrayList(HttpParamKV), + last_error: ?anyerror = null, + + pub fn callback(fiobj_value: fio.FIOBJ, context_: ?*anyopaque) callconv(.C) c_int { + const ctx: *CallbackContext_KV = @as(*CallbackContext_KV, @ptrCast(@alignCast(context_))); + // this is thread-safe, guaranteed by fio + const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop(); + ctx.params.append(.{ + .key = util.fio2strAlloc(ctx.allocator, fiobj_key) catch |err| { + ctx.last_error = err; + return -1; + }, + .value = fiobj2HttpParam(ctx.allocator, fiobj_value) catch |err| { + ctx.last_error = err; + return -1; + }, + }) catch |err| { + // what to do? + // signal the caller that an error occured by returning -1 + // also, set the error + ctx.last_error = err; + return -1; + }; + return 0; + } +}; + +const CallbackContext_StrKV = struct { + allocator: Allocator, + params: *std.ArrayList(HttpParamStrKV), + last_error: ?anyerror = null, + + pub fn callback(fiobj_value: fio.FIOBJ, context_: ?*anyopaque) callconv(.C) c_int { + const ctx: *CallbackContext_StrKV = @as(*CallbackContext_StrKV, @ptrCast(@alignCast(context_))); + // this is thread-safe, guaranteed by fio + const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop(); + ctx.params.append(.{ + .key = util.fio2strAlloc(ctx.allocator, fiobj_key) catch |err| { + ctx.last_error = err; + return -1; + }, + .value = util.fio2strAlloc(ctx.allocator, fiobj_value) catch |err| { + ctx.last_error = err; + return -1; + }, + }) catch |err| { + // what to do? + // signal the caller that an error occured by returning -1 + // also, set the error + ctx.last_error = err; + return -1; + }; + return 0; + } +}; + /// Same as parametersToOwnedStrList() but for cookies -pub fn cookiesToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_alloc: bool) anyerror!HttpParamStrKVList { +pub fn cookiesToOwnedStrList(self: *const Request, a: Allocator) anyerror!HttpParamStrKVList { var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount()))); - var context: _parametersToOwnedStrSliceContext = .{ + var context: CallbackContext_StrKV = .{ .params = ¶ms, .allocator = a, - .always_alloc = always_alloc, }; - const howmany = fio.fiobj_each1(self.h.*.cookies, 0, _each_nextParamStr, &context); + const howmany = fio.fiobj_each1(self.h.*.cookies, 0, CallbackContext_StrKV.callback, &context); if (howmany != self.getCookiesCount()) { return error.HttpIterParams; } @@ -724,10 +808,10 @@ pub fn cookiesToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_all } /// Same as parametersToOwnedList() but for cookies -pub fn cookiesToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: bool) !HttpParamKVList { +pub fn cookiesToOwnedList(self: *const Request, a: Allocator) !HttpParamKVList { var params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount()))); - var context: _parametersToOwnedSliceContext = .{ .params = ¶ms, .allocator = a, .dupe_strings = dupe_strings }; - const howmany = fio.fiobj_each1(self.h.*.cookies, 0, _each_nextParam, &context); + var context: CallbackContext_KV = .{ .params = ¶ms, .allocator = a }; + const howmany = fio.fiobj_each1(self.h.*.cookies, 0, CallbackContext_KV.callback, &context); if (howmany != self.getCookiesCount()) { return error.HttpIterParams; } @@ -747,50 +831,21 @@ pub fn cookiesToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: /// /// Requires parseBody() and/or parseQuery() have been called. /// Returned list needs to be deinited. -pub fn parametersToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_alloc: bool) anyerror!HttpParamStrKVList { +pub fn parametersToOwnedStrList(self: *const Request, a: Allocator) anyerror!HttpParamStrKVList { var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getParamCount()))); - var context: _parametersToOwnedStrSliceContext = .{ + + var context: CallbackContext_StrKV = .{ .params = ¶ms, .allocator = a, - .always_alloc = always_alloc, }; - const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParamStr, &context); + + const howmany = fio.fiobj_each1(self.h.*.params, 0, CallbackContext_StrKV.callback, &context); if (howmany != self.getParamCount()) { return error.HttpIterParams; } return .{ .items = try params.toOwnedSlice(), .allocator = a }; } -const _parametersToOwnedStrSliceContext = struct { - allocator: std.mem.Allocator, - params: *std.ArrayList(HttpParamStrKV), - last_error: ?anyerror = null, - always_alloc: bool, -}; - -fn _each_nextParamStr(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int { - const ctx: *_parametersToOwnedStrSliceContext = @as(*_parametersToOwnedStrSliceContext, @ptrCast(@alignCast(context))); - // this is thread-safe, guaranteed by fio - const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop(); - ctx.params.append(.{ - .key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.always_alloc) catch |err| { - ctx.last_error = err; - return -1; - }, - .value = util.fio2strAllocOrNot(ctx.allocator, fiobj_value, ctx.always_alloc) catch |err| { - ctx.last_error = err; - return -1; - }, - }) catch |err| { - // what to do? - // signal the caller that an error occured by returning -1 - // also, set the error - ctx.last_error = err; - return -1; - }; - return 0; -} - /// Returns the query / body parameters as key/value pairs /// Supported param types that will be converted: /// @@ -804,47 +859,19 @@ fn _each_nextParamStr(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) /// /// Requires parseBody() and/or parseQuery() have been called. /// Returned slice needs to be freed. -pub fn parametersToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: bool) !HttpParamKVList { +pub fn parametersToOwnedList(self: *const Request, a: Allocator) !HttpParamKVList { var params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getParamCount()))); - var context: _parametersToOwnedSliceContext = .{ .params = ¶ms, .allocator = a, .dupe_strings = dupe_strings }; - const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParam, &context); + + var context: CallbackContext_KV = .{ .params = ¶ms, .allocator = a }; + + const howmany = fio.fiobj_each1(self.h.*.params, 0, CallbackContext_KV.callback, &context); if (howmany != self.getParamCount()) { return error.HttpIterParams; } return .{ .items = try params.toOwnedSlice(), .allocator = a }; } -const _parametersToOwnedSliceContext = struct { - params: *std.ArrayList(HttpParamKV), - last_error: ?anyerror = null, - allocator: std.mem.Allocator, - dupe_strings: bool, -}; - -fn _each_nextParam(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int { - const ctx: *_parametersToOwnedSliceContext = @as(*_parametersToOwnedSliceContext, @ptrCast(@alignCast(context))); - // this is thread-safe, guaranteed by fio - const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop(); - ctx.params.append(.{ - .key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.dupe_strings) catch |err| { - ctx.last_error = err; - return -1; - }, - .value = Fiobj2HttpParam(ctx.allocator, fiobj_value, ctx.dupe_strings) catch |err| { - ctx.last_error = err; - return -1; - }, - }) catch |err| { - // what to do? - // signal the caller that an error occured by returning -1 - // also, set the error - ctx.last_error = err; - return -1; - }; - return 0; -} - -/// get named parameter as string +/// get named parameter (parsed) as string /// Supported param types that will be converted: /// /// - Bool @@ -856,8 +883,8 @@ fn _each_nextParam(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_ /// So, for JSON body payloads: parse the body instead. /// /// Requires parseBody() and/or parseQuery() have been called. -/// The returned string needs to be deinited with .deinit() -pub fn getParamStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot { +/// The returned string needs to be deallocated. +pub fn getParamStr(self: *const Request, a: Allocator, name: []const u8) !?[]const u8 { if (self.h.*.params == 0) return null; const key = fio.fiobj_str_new(name.ptr, name.len); defer fio.fiobj_free_wrapped(key); @@ -865,14 +892,14 @@ pub fn getParamStr(self: *const Self, a: std.mem.Allocator, name: []const u8, al if (value == fio.FIOBJ_INVALID) { return null; } - return try util.fio2strAllocOrNot(a, value, always_alloc); + return try util.fio2strAlloc(a, value); } /// similar to getParamStr, except it will return the part of the querystring /// after the equals sign, non-decoded, and always as character slice. /// - no allocation! /// - does not requre parseQuery() or anything to be called in advance -pub fn getParamSlice(self: *const Self, name: []const u8) ?[]const u8 { +pub fn getParamSlice(self: *const Request, name: []const u8) ?[]const u8 { if (self.query) |query| { var amp_it = std.mem.tokenizeScalar(u8, query, '&'); while (amp_it.next()) |maybe_pair| { @@ -895,13 +922,13 @@ pub const ParameterSlices = struct { name: []const u8, value: []const u8 }; pub const ParamSliceIterator = struct { amp_it: std.mem.TokenIterator(u8, .scalar), - pub fn init(query: []const u8) @This() { + pub fn init(query: []const u8) ParamSliceIterator { return .{ .amp_it = std.mem.tokenizeScalar(u8, query, '&'), }; } - pub fn next(self: *@This()) ?ParameterSlices { + pub fn next(self: *ParamSliceIterator) ?ParameterSlices { while (self.amp_it.next()) |maybe_pair| { if (std.mem.indexOfScalar(u8, maybe_pair, '=')) |pos_of_eq| { const pname = maybe_pair[0..pos_of_eq]; @@ -918,11 +945,11 @@ pub const ParamSliceIterator = struct { /// Returns an iterator that yields all query parameters on next() in the /// form of a ParameterSlices struct { .name, .value } /// As with getParamSlice(), the value is not decoded -pub fn getParamSlices(self: *const Self) ParamSliceIterator { +pub fn getParamSlices(self: *const Request) ParamSliceIterator { const query = self.query orelse ""; return ParamSliceIterator.init(query); } -pub fn methodAsEnum(self: *const Self) http.Method { +pub fn methodAsEnum(self: *const Request) http.Method { return http.methodToEnum(self.method); } diff --git a/src/router.zig b/src/router.zig index 53500a6..f13d0a9 100644 --- a/src/router.zig +++ b/src/router.zig @@ -9,10 +9,10 @@ const RouterError = error{ EmptyPath, }; -const Self = @This(); +const Router = @This(); /// This is a singleton -var _instance: *Self = undefined; +var _instance: *Router = undefined; /// Options to pass to init() pub const Options = struct { @@ -31,7 +31,7 @@ routes: std.StringHashMap(Callback), not_found: ?zap.HttpRequestFn, /// Create a new Router -pub fn init(allocator: Allocator, options: Options) Self { +pub fn init(allocator: Allocator, options: Options) Router { return .{ .routes = std.StringHashMap(Callback).init(allocator), @@ -40,12 +40,12 @@ pub fn init(allocator: Allocator, options: Options) Self { } /// Deinit the router -pub fn deinit(self: *Self) void { +pub fn deinit(self: *Router) void { self.routes.deinit(); } /// Call this to add a route with an unbound handler: a handler that is not member of a struct. -pub fn handle_func_unbound(self: *Self, path: []const u8, h: zap.HttpRequestFn) !void { +pub fn handle_func_unbound(self: *Router, path: []const u8, h: zap.HttpRequestFn) !void { if (path.len == 0) { return RouterError.EmptyPath; } @@ -71,7 +71,7 @@ pub fn handle_func_unbound(self: *Self, path: []const u8, h: zap.HttpRequestFn) /// /// my_router.handle_func("/getA", &handler_instance, HandlerType.getA); /// ``` -pub fn handle_func(self: *Self, path: []const u8, instance: *anyopaque, handler: anytype) !void { +pub fn handle_func(self: *Router, path: []const u8, instance: *anyopaque, handler: anytype) !void { // TODO: assert type of instance has handler if (path.len == 0) { @@ -89,7 +89,7 @@ pub fn handle_func(self: *Self, path: []const u8, instance: *anyopaque, handler: } /// Get the zap request handler function needed for a listener -pub fn on_request_handler(self: *Self) zap.HttpRequestFn { +pub fn on_request_handler(self: *Router) zap.HttpRequestFn { _instance = self; return zap_on_request; } @@ -98,7 +98,7 @@ fn zap_on_request(r: zap.Request) !void { return serve(_instance, r); } -fn serve(self: *Self, r: zap.Request) !void { +fn serve(self: *Router, r: zap.Request) !void { const path = r.path orelse "/"; if (self.routes.get(path)) |routeInfo| { diff --git a/src/tests/test_http_params.zig b/src/tests/test_http_params.zig index 167cbc0..02d7519 100644 --- a/src/tests/test_http_params.zig +++ b/src/tests/test_http_params.zig @@ -26,7 +26,7 @@ test "http parameters" { var strParams: ?zap.Request.HttpParamStrKVList = null; var params: ?zap.Request.HttpParamKVList = null; - var paramOneStr: ?zap.util.FreeOrNot = null; + var paramOneStr: ?[]const u8 = null; var paramOneSlice: ?[]const u8 = null; var paramSlices: zap.Request.ParamSliceIterator = undefined; @@ -36,20 +36,17 @@ test "http parameters" { param_count = r.getParamCount(); // true -> make copies of temp strings - strParams = r.parametersToOwnedStrList(alloc, true) catch unreachable; + strParams = r.parametersToOwnedStrList(alloc) catch unreachable; // true -> make copies of temp strings - params = r.parametersToOwnedList(alloc, true) catch unreachable; + params = r.parametersToOwnedList(alloc) catch unreachable; - var maybe_str = r.getParamStr(alloc, "one", true) catch unreachable; - if (maybe_str) |*s| { - paramOneStr = s.*; - } + paramOneStr = r.getParamStr(alloc, "one") catch unreachable; - paramOneSlice = blk: { - if (r.getParamSlice("one")) |val| break :blk alloc.dupe(u8, val) catch unreachable; - break :blk null; - }; + // we need to dupe it here because the request object r will get + // invalidated at the end of the function but we need to check + // its correctness later in the test + paramOneSlice = if (r.getParamSlice("one")) |slice| alloc.dupe(u8, slice) catch unreachable else null; paramSlices = r.getParamSlices(); } @@ -84,44 +81,44 @@ test "http parameters" { if (Handler.params) |*p| { p.deinit(); } - if (Handler.paramOneStr) |*p| { - // allocator.free(p); - p.deinit(); + + if (Handler.paramOneStr) |p| { + allocator.free(p); } if (Handler.paramOneSlice) |p| { - Handler.alloc.free(p); + allocator.free(p); } } - try std.testing.expectEqual(Handler.ran, true); - try std.testing.expectEqual(Handler.param_count, 5); + try std.testing.expectEqual(true, Handler.ran); + try std.testing.expectEqual(5, Handler.param_count); try std.testing.expect(Handler.paramOneStr != null); - try std.testing.expectEqualStrings(Handler.paramOneStr.?.str, "1"); + try std.testing.expectEqualStrings("1", Handler.paramOneStr.?); try std.testing.expect(Handler.paramOneSlice != null); - try std.testing.expectEqualStrings(Handler.paramOneSlice.?, "1"); + try std.testing.expectEqualStrings("1", Handler.paramOneSlice.?); try std.testing.expect(Handler.strParams != null); for (Handler.strParams.?.items, 0..) |kv, i| { switch (i) { 0 => { - try std.testing.expectEqualStrings(kv.key.str, "one"); - try std.testing.expectEqualStrings(kv.value.str, "1"); + try std.testing.expectEqualStrings("one", kv.key); + try std.testing.expectEqualStrings("1", kv.value); }, 1 => { - try std.testing.expectEqualStrings(kv.key.str, "two"); - try std.testing.expectEqualStrings(kv.value.str, "2"); + try std.testing.expectEqualStrings("two", kv.key); + try std.testing.expectEqualStrings("2", kv.value); }, 2 => { - try std.testing.expectEqualStrings(kv.key.str, "string"); - try std.testing.expectEqualStrings(kv.value.str, "hello world"); + try std.testing.expectEqualStrings("string", kv.key); + try std.testing.expectEqualStrings("hello world", kv.value); }, 3 => { - try std.testing.expectEqualStrings(kv.key.str, "float"); - try std.testing.expectEqualStrings(kv.value.str, "6.28"); + try std.testing.expectEqualStrings("float", kv.key); + try std.testing.expectEqualStrings("6.28", kv.value); }, 4 => { - try std.testing.expectEqualStrings(kv.key.str, "bool"); - try std.testing.expectEqualStrings(kv.value.str, "true"); + try std.testing.expectEqualStrings("bool", kv.key); + try std.testing.expectEqualStrings("true", kv.value); }, else => return error.TooManyArgs, } @@ -131,24 +128,24 @@ test "http parameters" { while (Handler.paramSlices.next()) |param| { switch (pindex) { 0 => { - try std.testing.expectEqualStrings(param.name, "one"); - try std.testing.expectEqualStrings(param.value, "1"); + try std.testing.expectEqualStrings("one", param.name); + try std.testing.expectEqualStrings("1", param.value); }, 1 => { - try std.testing.expectEqualStrings(param.name, "two"); - try std.testing.expectEqualStrings(param.value, "2"); + try std.testing.expectEqualStrings("two", param.name); + try std.testing.expectEqualStrings("2", param.value); }, 2 => { - try std.testing.expectEqualStrings(param.name, "string"); - try std.testing.expectEqualStrings(param.value, "hello+world"); + try std.testing.expectEqualStrings("string", param.name); + try std.testing.expectEqualStrings("hello+world", param.value); }, 3 => { - try std.testing.expectEqualStrings(param.name, "float"); - try std.testing.expectEqualStrings(param.value, "6.28"); + try std.testing.expectEqualStrings("float", param.name); + try std.testing.expectEqualStrings("6.28", param.value); }, 4 => { - try std.testing.expectEqualStrings(param.name, "bool"); - try std.testing.expectEqualStrings(param.value, "true"); + try std.testing.expectEqualStrings("bool", param.name); + try std.testing.expectEqualStrings("true", param.value); }, else => return error.TooManyArgs, } @@ -158,42 +155,42 @@ test "http parameters" { for (Handler.params.?.items, 0..) |kv, i| { switch (i) { 0 => { - try std.testing.expectEqualStrings(kv.key.str, "one"); + try std.testing.expectEqualStrings("one", kv.key); try std.testing.expect(kv.value != null); switch (kv.value.?) { - .Int => |n| try std.testing.expectEqual(n, 1), + .Int => |n| try std.testing.expectEqual(1, n), else => return error.InvalidHttpParamType, } }, 1 => { - try std.testing.expectEqualStrings(kv.key.str, "two"); + try std.testing.expectEqualStrings("two", kv.key); try std.testing.expect(kv.value != null); switch (kv.value.?) { - .Int => |n| try std.testing.expectEqual(n, 2), + .Int => |n| try std.testing.expectEqual(2, n), else => return error.InvalidHttpParamType, } }, 2 => { - try std.testing.expectEqualStrings(kv.key.str, "string"); + try std.testing.expectEqualStrings("string", kv.key); try std.testing.expect(kv.value != null); switch (kv.value.?) { - .String => |s| try std.testing.expectEqualStrings(s.str, "hello world"), + .String => |s| try std.testing.expectEqualStrings("hello world", s), else => return error.InvalidHttpParamType, } }, 3 => { - try std.testing.expectEqualStrings(kv.key.str, "float"); + try std.testing.expectEqualStrings("float", kv.key); try std.testing.expect(kv.value != null); switch (kv.value.?) { - .Float => |f| try std.testing.expectEqual(f, 6.28), + .Float => |f| try std.testing.expectEqual(6.28, f), else => return error.InvalidHttpParamType, } }, 4 => { - try std.testing.expectEqualStrings(kv.key.str, "bool"); + try std.testing.expectEqualStrings("bool", kv.key); try std.testing.expect(kv.value != null); switch (kv.value.?) { - .Bool => |b| try std.testing.expectEqual(b, true), + .Bool => |b| try std.testing.expectEqual(true, b), else => return error.InvalidHttpParamType, } }, diff --git a/src/util.zig b/src/util.zig index 663155c..1719fe9 100644 --- a/src/util.zig +++ b/src/util.zig @@ -6,6 +6,9 @@ const zap = @import("zap.zig"); /// 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. FIO temp memory strings do not need to be freed. +/// +/// IMPORTANT!!! The "temp" memory can refer to a shared buffer that subsequent +/// calls to this function will **overwrite**!!! pub fn fio2str(o: fio.FIOBJ) ?[]const u8 { if (o == 0) return null; const x: fio.fio_str_info_s = fio.fiobj_obj2cstr(o); @@ -14,39 +17,21 @@ 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, - allocator: ?std.mem.Allocator = null, - - pub fn deinit(self: *const @This()) void { - if (self.freeme) { - self.allocator.?.free(self.str); - } - } -}; - /// 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 }; +/// This always allocates, so choose your allocator wisely. +/// Let's never use that +pub fn fio2strAlloc(a: std.mem.Allocator, o: fio.FIOBJ) ![]const u8 { + if (o == 0) return try a.dupe(u8, "null"); + if (o == fio.FIOBJ_INVALID) return try a.dupe(u8, "invalid"); return switch (fio.fiobj_type(o)) { - fio.FIOBJ_T_TRUE => .{ .str = "true", .freeme = false }, - fio.FIOBJ_T_FALSE => .{ .str = "false", .freeme = false }, + fio.FIOBJ_T_TRUE => try a.dupe(u8, "true"), + fio.FIOBJ_T_FALSE => try a.dupe(u8, "false"), // according to fio2str above, the orelse should never happen - fio.FIOBJ_T_NUMBER => .{ .str = try a.dupe(u8, fio2str(o) orelse "null"), .freeme = true, .allocator = a }, - fio.FIOBJ_T_FLOAT => .{ .str = try a.dupe(u8, fio2str(o) orelse "null"), .freeme = true, .allocator = a }, - // the string comes out of the request, so it is safe to not make a copy - fio.FIOBJ_T_STRING => .{ .str = if (always_alloc) try a.dupe(u8, fio2str(o) orelse "") else fio2str(o) orelse "", .freeme = if (always_alloc) true else false, .allocator = a }, - else => .{ .str = "unknown_type", .freeme = false }, + fio.FIOBJ_T_NUMBER => try a.dupe(u8, fio2str(o) orelse "null"), + fio.FIOBJ_T_FLOAT => try a.dupe(u8, fio2str(o) orelse "null"), + // if the string comes out of the request, it is safe to not make a copy + fio.FIOBJ_T_STRING => try a.dupe(u8, fio2str(o) orelse ""), + else => try a.dupe(u8, "unknown_type"), }; } diff --git a/src/zap.zig b/src/zap.zig index 93d3ecb..9389d84 100644 --- a/src/zap.zig +++ b/src/zap.zig @@ -170,11 +170,10 @@ pub const HttpListenerSettings = struct { pub const HttpListener = struct { settings: HttpListenerSettings, - const Self = @This(); var the_one_and_only_listener: ?*HttpListener = null; /// Create a listener - pub fn init(settings: HttpListenerSettings) Self { + pub fn init(settings: HttpListenerSettings) HttpListener { std.debug.assert(settings.on_request != null); return .{ .settings = settings, @@ -264,7 +263,7 @@ pub const HttpListener = struct { } /// Start listening - pub fn listen(self: *Self) !void { + pub fn listen(self: *HttpListener) !void { var pfolder: [*c]const u8 = null; var pfolder_len: usize = 0; @@ -275,10 +274,10 @@ pub const HttpListener = struct { } const x: fio.http_settings_s = .{ - .on_request = if (self.settings.on_request) |_| Self.theOneAndOnlyRequestCallBack else null, - .on_upgrade = if (self.settings.on_upgrade) |_| Self.theOneAndOnlyUpgradeCallBack else null, - .on_response = if (self.settings.on_response) |_| Self.theOneAndOnlyResponseCallBack else null, - .on_finish = if (self.settings.on_finish) |_| Self.theOneAndOnlyFinishCallBack else null, + .on_request = if (self.settings.on_request) |_| HttpListener.theOneAndOnlyRequestCallBack else null, + .on_upgrade = if (self.settings.on_upgrade) |_| HttpListener.theOneAndOnlyUpgradeCallBack else null, + .on_response = if (self.settings.on_response) |_| HttpListener.theOneAndOnlyResponseCallBack else null, + .on_finish = if (self.settings.on_finish) |_| HttpListener.theOneAndOnlyFinishCallBack else null, .udata = null, .public_folder = pfolder, .public_folder_length = pfolder_len, @@ -316,7 +315,7 @@ pub const HttpListener = struct { // 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 - Self.the_one_and_only_listener = self; + HttpListener.the_one_and_only_listener = self; } }; @@ -336,10 +335,8 @@ pub const LowLevel = struct { keepalive_timeout_s: u8 = 5, log: bool = false, - const Self = @This(); - /// Create settings with defaults - pub fn init() Self { + pub fn init() ListenSettings { return .{}; } };