1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 15:14:08 +00:00

rename types for endpoint wrapping

This commit is contained in:
Rene Schallner 2025-03-21 20:00:39 +01:00
commit 029c8ff069
25 changed files with 485 additions and 478 deletions

View file

@ -21,6 +21,21 @@ fn on_request_verbose(r: zap.Request) !void {
break :content_type .HTML; 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); try r.setContentType(content_type);
switch (content_type) { switch (content_type) {
.TEXT => { .TEXT => {
@ -66,7 +81,18 @@ pub fn main() !void {
}); });
try listener.listen(); 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 // start worker threads
zap.start(.{ zap.start(.{

View file

@ -24,12 +24,12 @@ const Handler = struct {
// //
// HERE WE HANDLE THE BINARY FILE // HERE WE HANDLE THE BINARY FILE
// //
const params = try r.parametersToOwnedList(Handler.alloc, false); const params = try r.parametersToOwnedList(Handler.alloc);
defer params.deinit(); defer params.deinit();
for (params.items) |kv| { for (params.items) |kv| {
if (kv.value) |v| { if (kv.value) |v| {
std.debug.print("\n", .{}); 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) { switch (v) {
// single-file upload // single-file upload
zap.Request.HttpParam.Hash_Binfile => |*file| { zap.Request.HttpParam.Hash_Binfile => |*file| {
@ -55,32 +55,20 @@ const Handler = struct {
files.*.deinit(); files.*.deinit();
}, },
else => { else => {
// might be a string param, we don't care // let's just get it as its raw slice
// let's just get it as string const value: []const u8 = r.getParamSlice(kv.key) orelse "(no value)";
// always_alloc param = false -> the string will be a slice from the request buffer std.log.debug(" {s} = {s}", .{ kv.key, value });
// --> 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});
}
}, },
} }
} }
} }
// check if we received a terminate=true parameter // check if we received a terminate=true parameter
if (r.getParamStr(Handler.alloc, "terminate", false)) |maybe_str| { if (r.getParamSlice("terminate")) |str| {
if (maybe_str) |*s| { std.log.info("?terminate={s}\n", .{str});
std.log.info("?terminate={s}\n", .{s.str}); if (std.mem.eql(u8, str, "true")) {
if (std.mem.eql(u8, s.str, "true")) { zap.stop();
zap.stop();
}
} }
} else |err| {
std.log.err("cannot check for terminate param: {any}\n", .{err});
} }
try r.sendJson("{ \"ok\": true }"); try r.sendJson("{ \"ok\": true }");
} }
@ -90,6 +78,7 @@ pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{ var gpa = std.heap.GeneralPurposeAllocator(.{
.thread_safe = true, .thread_safe = true,
}){}; }){};
defer _ = gpa.detectLeaks();
const allocator = gpa.allocator(); const allocator = gpa.allocator();
Handler.alloc = allocator; Handler.alloc = allocator;

View file

@ -44,12 +44,12 @@ pub fn main() !void {
const cookie_count = r.getCookiesCount(); const cookie_count = r.getCookiesCount();
std.log.info("cookie_count: {}", .{cookie_count}); std.log.info("cookie_count: {}", .{cookie_count});
// iterate over all cookies as strings (always_alloc=false) // iterate over all cookies as strings
var strCookies = r.cookiesToOwnedStrList(alloc, false) catch unreachable; var strCookies = r.cookiesToOwnedStrList(alloc) catch unreachable;
defer strCookies.deinit(); defer strCookies.deinit();
std.debug.print("\n", .{}); std.debug.print("\n", .{});
for (strCookies.items) |kv| { 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 // 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 // so they are just slices into the request buffer
} }
@ -57,26 +57,22 @@ pub fn main() !void {
std.debug.print("\n", .{}); std.debug.print("\n", .{});
// // iterate over all cookies // // iterate over all cookies
const cookies = r.cookiesToOwnedList(alloc, false) catch unreachable; const cookies = r.cookiesToOwnedList(alloc) catch unreachable;
defer cookies.deinit(); defer cookies.deinit();
for (cookies.items) |kv| { 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 // let's get cookie "ZIG_ZAP" by name
std.debug.print("\n", .{}); std.debug.print("\n", .{});
if (r.getCookieStr(alloc, "ZIG_ZAP", false)) |maybe_str| { if (r.getCookieStr(alloc, "ZIG_ZAP")) |maybe_str| {
if (maybe_str) |*s| { if (maybe_str) |s| {
defer s.deinit(); // unnecessary because always_alloc=false defer alloc.free(s);
std.log.info("Cookie ZIG_ZAP = {s}", .{s});
std.log.info("Cookie ZIG_ZAP = {s}", .{s.str});
} else { } else {
std.log.info("Cookie ZIG_ZAP not found!", .{}); std.log.info("Cookie ZIG_ZAP not found!", .{});
} }
} } else |err| {
// since we provided "false" for duplicating strings in the call
// to getCookieStr(), there won't be an allocation error
else |err| {
std.log.err("ERROR!\n", .{}); std.log.err("ERROR!\n", .{});
std.log.err("cannot check for `ZIG_ZAP` cookie: {any}\n", .{err}); std.log.err("cannot check for `ZIG_ZAP` cookie: {any}\n", .{err});
} }

View file

@ -3,25 +3,25 @@ const zap = @import("zap");
/// A simple endpoint listening on the /stop route that shuts down 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(). /// 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, path: []const u8,
error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response, error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response,
pub fn init(path: []const u8) Self { pub fn init(path: []const u8) StopEndpoint {
return .{ return .{
.path = path, .path = path,
}; };
} }
pub fn get(e: *Self, r: zap.Request) anyerror!void { pub fn get(e: *StopEndpoint, r: zap.Request) anyerror!void {
_ = e; _ = e;
_ = r; _ = r;
zap.stop(); zap.stop();
} }
pub fn post(_: *Self, _: zap.Request) anyerror!void {} pub fn post(_: *StopEndpoint, _: zap.Request) anyerror!void {}
pub fn put(_: *Self, _: zap.Request) anyerror!void {} pub fn put(_: *StopEndpoint, _: zap.Request) anyerror!void {}
pub fn delete(_: *Self, _: zap.Request) anyerror!void {} pub fn delete(_: *StopEndpoint, _: zap.Request) anyerror!void {}
pub fn patch(_: *Self, _: zap.Request) anyerror!void {} pub fn patch(_: *StopEndpoint, _: zap.Request) anyerror!void {}
pub fn options(_: *Self, _: zap.Request) anyerror!void {} pub fn options(_: *StopEndpoint, _: zap.Request) anyerror!void {}

View file

@ -5,7 +5,7 @@ users: std.AutoHashMap(usize, InternalUser) = undefined,
lock: std.Thread.Mutex = undefined, lock: std.Thread.Mutex = undefined,
count: usize = 0, count: usize = 0,
pub const Self = @This(); pub const Users = @This();
const InternalUser = struct { const InternalUser = struct {
id: usize = 0, id: usize = 0,
@ -21,7 +21,7 @@ pub const User = struct {
last_name: []const u8, last_name: []const u8,
}; };
pub fn init(a: std.mem.Allocator) Self { pub fn init(a: std.mem.Allocator) Users {
return .{ return .{
.alloc = a, .alloc = a,
.users = std.AutoHashMap(usize, InternalUser).init(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(); self.users.deinit();
} }
// the request will be freed (and its mem reused by facilio) when it's // the request will be freed (and its mem reused by facilio) when it's
// completed, so we take copies of the names // 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; var user: InternalUser = undefined;
user.firstnamelen = 0; user.firstnamelen = 0;
user.lastnamelen = 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 // We lock only on insertion, deletion, and listing
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();
@ -76,7 +76,7 @@ pub fn delete(self: *Self, id: usize) bool {
return ret; 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 // we don't care about locking here, as our usage-pattern is unlikely to
// get a user by id that is not known yet // get a user by id that is not known yet
if (self.users.getPtr(id)) |pUser| { if (self.users.getPtr(id)) |pUser| {
@ -90,7 +90,7 @@ pub fn get(self: *Self, id: usize) ?User {
} }
pub fn update( pub fn update(
self: *Self, self: *Users,
id: usize, id: usize,
first: ?[]const u8, first: ?[]const u8,
last: ?[]const u8, last: ?[]const u8,
@ -112,7 +112,7 @@ pub fn update(
return false; return false;
} }
pub fn toJSON(self: *Self) ![]const u8 { pub fn toJSON(self: *Users) ![]const u8 {
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); 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 // 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 // We lock only on insertion, deletion, and listing
// //
// NOTE: race condition: // NOTE: race condition:
@ -169,18 +169,14 @@ pub fn listWithRaceCondition(self: *Self, out: *std.ArrayList(User)) !void {
const JsonUserIteratorWithRaceCondition = struct { const JsonUserIteratorWithRaceCondition = struct {
it: std.AutoHashMap(usize, InternalUser).ValueIterator = undefined, it: std.AutoHashMap(usize, InternalUser).ValueIterator = undefined,
const This = @This();
// careful: pub fn init(internal_users: *std.AutoHashMap(usize, InternalUser)) JsonUserIteratorWithRaceCondition {
// - Self refers to the file's struct
// - This refers to the JsonUserIterator struct
pub fn init(internal_users: *std.AutoHashMap(usize, InternalUser)) This {
return .{ return .{
.it = internal_users.valueIterator(), .it = internal_users.valueIterator(),
}; };
} }
pub fn next(this: *This) ?User { pub fn next(this: *JsonUserIteratorWithRaceCondition) ?User {
if (this.it.next()) |pUser| { if (this.it.next()) |pUser| {
// we get a pointer to the internal user. so it should be safe to // we get a pointer to the internal user. so it should be safe to
// create slices from its first and last name buffers // create slices from its first and last name buffers

View file

@ -5,7 +5,7 @@ const User = Users.User;
// an Endpoint // an Endpoint
pub const Self = @This(); pub const UserWeb = @This();
alloc: std.mem.Allocator = undefined, alloc: std.mem.Allocator = undefined,
_users: Users = undefined, _users: Users = undefined,
@ -16,7 +16,7 @@ error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response,
pub fn init( pub fn init(
a: std.mem.Allocator, a: std.mem.Allocator,
user_path: []const u8, user_path: []const u8,
) Self { ) UserWeb {
return .{ return .{
.alloc = a, .alloc = a,
._users = Users.init(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(); self._users.deinit();
} }
pub fn users(self: *Self) *Users { pub fn users(self: *UserWeb) *Users {
return &self._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.len >= self.path.len + 2) {
if (path[self.path.len] != '/') { if (path[self.path.len] != '/') {
return null; return null;
@ -43,8 +43,8 @@ fn userIdFromPath(self: *Self, path: []const u8) ?usize {
return null; return null;
} }
pub fn put(_: *Self, _: zap.Request) anyerror!void {} pub fn put(_: *UserWeb, _: zap.Request) anyerror!void {}
pub fn get(self: *Self, r: zap.Request) anyerror!void { pub fn get(self: *UserWeb, r: zap.Request) anyerror!void {
if (r.path) |path| { if (r.path) |path| {
// /users // /users
if (path.len == self.path.len) { 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| { if (self._users.toJSON()) |json| {
defer self.alloc.free(json); defer self.alloc.free(json);
try r.sendJson(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| { if (r.body) |body| {
const maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null; const maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null;
if (maybe_user) |u| { 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 (r.path) |path| {
if (self.userIdFromPath(path)) |id| { if (self.userIdFromPath(path)) |id| {
if (self._users.get(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 (r.path) |path| {
if (self.userIdFromPath(path)) |id| { if (self.userIdFromPath(path)) |id| {
var jsonbuf: [128]u8 = undefined; 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-Origin", "*");
try r.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS"); try r.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
r.setStatus(zap.http.StatusCode.no_content); r.setStatus(zap.http.StatusCode.no_content);

View file

@ -43,7 +43,18 @@ pub fn main() !void {
}); });
try listener.listen(); 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 // start worker threads
zap.start(.{ zap.start(.{

View file

@ -43,6 +43,7 @@ fn setupUserData(a: std.mem.Allocator) !void {
pub fn main() !void { pub fn main() !void {
const a = std.heap.page_allocator; const a = std.heap.page_allocator;
try setupUserData(a); try setupUserData(a);
defer users.deinit();
var listener = zap.HttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,

View file

@ -27,6 +27,8 @@ pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{ var gpa = std.heap.GeneralPurposeAllocator(.{
.thread_safe = true, .thread_safe = true,
}){}; }){};
defer _ = gpa.detectLeaks();
const allocator = gpa.allocator(); const allocator = gpa.allocator();
const Handler = struct { const Handler = struct {
@ -69,42 +71,38 @@ pub fn main() !void {
// ================================================================ // ================================================================
// iterate over all params as strings // iterate over all params as strings
var strparams = try r.parametersToOwnedStrList(alloc, false); var strparams = try r.parametersToOwnedStrList(alloc);
defer strparams.deinit(); defer strparams.deinit();
std.debug.print("\n", .{}); std.debug.print("\n", .{});
for (strparams.items) |kv| { 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", .{}); std.debug.print("\n", .{});
// iterate over all params // iterate over all params
const params = try r.parametersToOwnedList(alloc, false); const params = try r.parametersToOwnedList(alloc);
defer params.deinit(); defer params.deinit();
for (params.items) |kv| { 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 // let's get param "one" by name
std.debug.print("\n", .{}); std.debug.print("\n", .{});
if (r.getParamStr(alloc, "one", false)) |maybe_str| { if (r.getParamStr(alloc, "one")) |maybe_str| {
if (maybe_str) |*s| { if (maybe_str) |s| {
defer s.deinit(); defer alloc.free(s);
std.log.info("Param one = {s}", .{s});
std.log.info("Param one = {s}", .{s.str});
} else { } else {
std.log.info("Param one not found!", .{}); std.log.info("Param one not found!", .{});
} }
} } else |err| {
// since we provided "false" for duplicating strings in the call
// to getParamStr(), there won't be an allocation error
else |err| {
std.log.err("cannot check for `one` param: {any}\n", .{err}); std.log.err("cannot check for `one` param: {any}\n", .{err});
} }
// check if we received a terminate=true parameter // check if we received a terminate=true parameter
if (r.getParamSlice("terminate")) |maybe_str| { if (r.getParamSlice("terminate")) |s| {
if (std.mem.eql(u8, maybe_str, "true")) { if (std.mem.eql(u8, s, "true")) {
zap.stop(); zap.stop();
} }
} }

View file

@ -6,8 +6,6 @@ const SharedAllocator = struct {
// static // static
var allocator: std.mem.Allocator = undefined; var allocator: std.mem.Allocator = undefined;
const Self = @This();
// just a convenience function // just a convenience function
pub fn init(a: std.mem.Allocator) void { pub fn init(a: std.mem.Allocator) void {
allocator = a; allocator = a;
@ -43,8 +41,6 @@ const Handler = zap.Middleware.Handler(Context);
const UserMiddleWare = struct { const UserMiddleWare = struct {
handler: Handler, handler: Handler,
const Self = @This();
// Just some arbitrary struct we want in the per-request context // 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!!!
// 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, email: []const u8 = undefined,
}; };
pub fn init(other: ?*Handler) Self { pub fn init(other: ?*Handler) UserMiddleWare {
return .{ return .{
.handler = Handler.init(onRequest, other), .handler = Handler.init(onRequest, other),
}; };
} }
// we need the handler as a common interface to chain stuff // 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; 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 { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self: *Self = @fieldParentPtr("handler", handler); const self: *UserMiddleWare = @fieldParentPtr("handler", handler);
_ = self; _ = self;
// do our work: fill in the user field of the context // do our work: fill in the user field of the context
@ -92,8 +88,6 @@ const UserMiddleWare = struct {
const SessionMiddleWare = struct { const SessionMiddleWare = struct {
handler: Handler, handler: Handler,
const Self = @This();
// Just some arbitrary struct we want in the per-request context // 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!!!
const Session = struct { const Session = struct {
@ -101,21 +95,21 @@ const SessionMiddleWare = struct {
token: []const u8 = undefined, token: []const u8 = undefined,
}; };
pub fn init(other: ?*Handler) Self { pub fn init(other: ?*Handler) SessionMiddleWare {
return .{ return .{
.handler = Handler.init(onRequest, other), .handler = Handler.init(onRequest, other),
}; };
} }
// we need the handler as a common interface to chain stuff // 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; 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 { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self: *Self = @fieldParentPtr("handler", handler); const self: *SessionMiddleWare = @fieldParentPtr("handler", handler);
_ = self; _ = self;
context.session = Session{ context.session = Session{
@ -134,24 +128,22 @@ const SessionMiddleWare = struct {
const HtmlMiddleWare = struct { const HtmlMiddleWare = struct {
handler: Handler, handler: Handler,
const Self = @This(); pub fn init(other: ?*Handler) HtmlMiddleWare {
pub fn init(other: ?*Handler) Self {
return .{ return .{
.handler = Handler.init(onRequest, other), .handler = Handler.init(onRequest, other),
}; };
} }
// we need the handler as a common interface to chain stuff // 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; 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 { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self: *Self = @fieldParentPtr("handler", handler); const self: *HtmlMiddleWare = @fieldParentPtr("handler", handler);
_ = self; _ = self;
std.debug.print("\n\nHtmlMiddleware: handling request with context: {any}\n\n", .{context}); std.debug.print("\n\nHtmlMiddleware: handling request with context: {any}\n\n", .{context});

View file

@ -6,8 +6,6 @@ const SharedAllocator = struct {
// static // static
var allocator: std.mem.Allocator = undefined; var allocator: std.mem.Allocator = undefined;
const Self = @This();
// just a convenience function // just a convenience function
pub fn init(a: std.mem.Allocator) void { pub fn init(a: std.mem.Allocator) void {
allocator = a; allocator = a;
@ -35,8 +33,6 @@ const Handler = zap.Middleware.Handler(Context);
const UserMiddleWare = struct { const UserMiddleWare = struct {
handler: Handler, handler: Handler,
const Self = @This();
// Just some arbitrary struct we want in the per-request context // Just some arbitrary struct we want in the per-request context
// This is so that it can be constructed via .{} // This is so that it can be constructed via .{}
// as we can't expect the listener to know how to initialize our context structs // 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, email: []const u8 = undefined,
}; };
pub fn init(other: ?*Handler) Self { pub fn init(other: ?*Handler) UserMiddleWare {
return .{ return .{
.handler = Handler.init(onRequest, other), .handler = Handler.init(onRequest, other),
}; };
} }
// we need the handler as a common interface to chain stuff // 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; 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 { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self: *Self = @fieldParentPtr("handler", handler); const self: *UserMiddleWare = @fieldParentPtr("handler", handler);
_ = self; _ = self;
// do our work: fill in the user field of the context // do our work: fill in the user field of the context
@ -82,8 +78,6 @@ const UserMiddleWare = struct {
const SessionMiddleWare = struct { const SessionMiddleWare = struct {
handler: Handler, handler: Handler,
const Self = @This();
// Just some arbitrary struct we want in the per-request context // 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!!!
const Session = struct { const Session = struct {
@ -91,21 +85,21 @@ const SessionMiddleWare = struct {
token: []const u8 = undefined, token: []const u8 = undefined,
}; };
pub fn init(other: ?*Handler) Self { pub fn init(other: ?*Handler) SessionMiddleWare {
return .{ return .{
.handler = Handler.init(onRequest, other), .handler = Handler.init(onRequest, other),
}; };
} }
// we need the handler as a common interface to chain stuff // 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; 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 { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) !bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self: *Self = @fieldParentPtr("handler", handler); const self: *SessionMiddleWare = @fieldParentPtr("handler", handler);
_ = self; _ = self;
context.session = Session{ context.session = Session{
@ -136,11 +130,9 @@ const SessionMiddleWare = struct {
// `breakOnFinish` parameter. // `breakOnFinish` parameter.
// //
const HtmlEndpoint = struct { const HtmlEndpoint = struct {
const Self = @This();
path: []const u8 = "(undefined)", path: []const u8 = "(undefined)",
pub fn init() Self { pub fn init() HtmlEndpoint {
return .{ return .{
.path = "/doesn't+matter", .path = "/doesn't+matter",
}; };
@ -152,7 +144,7 @@ const HtmlEndpoint = struct {
pub fn patch(_: *HtmlEndpoint, _: zap.Request) !void {} pub fn patch(_: *HtmlEndpoint, _: zap.Request) !void {}
pub fn options(_: *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 buf: [1024]u8 = undefined;
var userFound: bool = false; var userFound: bool = false;
var sessionFound: bool = false; var sessionFound: bool = false;

View file

@ -14,13 +14,11 @@ fn on_request_verbose(r: zap.Request) !void {
} }
pub const SomePackage = struct { pub const SomePackage = struct {
const Self = @This();
allocator: Allocator, allocator: Allocator,
a: i8, a: i8,
b: i8, b: i8,
pub fn init(allocator: Allocator, a: i8, b: i8) Self { pub fn init(allocator: Allocator, a: i8, b: i8) SomePackage {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.a = a, .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", .{}); std.log.warn("get_a_requested", .{});
const string = std.fmt.allocPrint( const string = std.fmt.allocPrint(
@ -41,7 +39,7 @@ pub const SomePackage = struct {
req.sendBody(string) catch return; 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", .{}); std.log.warn("get_b_requested", .{});
const string = std.fmt.allocPrint( const string = std.fmt.allocPrint(
@ -54,7 +52,7 @@ pub const SomePackage = struct {
req.sendBody(string) catch return; 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", .{}); std.log.warn("increment_a_requested", .{});
self.a += 1; self.a += 1;
@ -98,8 +96,17 @@ pub fn main() !void {
}); });
try listener.listen(); 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 // start worker threads
zap.start(.{ zap.start(.{
.threads = 2, .threads = 2,

View file

@ -72,6 +72,7 @@ fn on_request(r: zap.Request) !void {
.AuthOK => { .AuthOK => {
// the authenticator says it is ok to proceed as usual // the authenticator says it is ok to proceed as usual
std.log.info("Auth OK", .{}); std.log.info("Auth OK", .{});
// dispatch to target path // dispatch to target path
if (r.path) |p| { if (r.path) |p| {
// used in the login page // used in the login page
@ -124,6 +125,7 @@ pub fn main() !void {
// to detect leaks // to detect leaks
{ {
const allocator = gpa.allocator(); const allocator = gpa.allocator();
var listener = zap.HttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,

View file

@ -20,13 +20,11 @@ const ContextManager = struct {
lock: std.Thread.Mutex = .{}, lock: std.Thread.Mutex = .{},
contexts: ContextList = undefined, contexts: ContextList = undefined,
const Self = @This();
pub fn init( pub fn init(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
channelName: []const u8, channelName: []const u8,
usernamePrefix: []const u8, usernamePrefix: []const u8,
) Self { ) ContextManager {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.channel = channelName, .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| { for (self.contexts.items) |ctx| {
self.allocator.free(ctx.userName); self.allocator.free(ctx.userName);
} }
self.contexts.deinit(); self.contexts.deinit();
} }
pub fn newContext(self: *Self) !*Context { pub fn newContext(self: *ContextManager) !*Context {
self.lock.lock(); self.lock.lock();
defer self.lock.unlock(); defer self.lock.unlock();

View file

@ -26,9 +26,5 @@ pub fn create(comptime Context: type, context: *Context, opts: Opts) type {
context: *Context = context, context: *Context = context,
error_strategy: @TypeOf(opts.request_error_strategy) = opts.request_error_strategy, error_strategy: @TypeOf(opts.request_error_strategy) = opts.request_error_strategy,
endpoints: std.StringArrayHashMapUnmanaged(*zap.Endpoint.Wrapper.Internal) = .empty, endpoints: std.StringArrayHashMapUnmanaged(*zap.Endpoint.Wrapper.Internal) = .empty,
// pub fn addEndpoint(slug: []const u8, endpoint: anytype) !zap.Endpoint {
// // TODO: inspect endpoint: does it have
// }
}; };
} }

View file

@ -37,11 +37,11 @@
//! }; //! };
//! } //! }
//! //!
//! pub fn post(_: *Self, _: zap.Request) void {} //! pub fn post(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn put(_: *Self, _: zap.Request) void {} //! pub fn put(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn delete(_: *Self, _: zap.Request) void {} //! pub fn delete(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn patch(_: *Self, _: zap.Request) void {} //! pub fn patch(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn options(_: *Self, _: zap.Request) void {} //! pub fn options(_: *StopEndpoint, _: zap.Request) void {}
//! //!
//! pub fn get(self: *StopEndpoint, r: zap.Request) void { //! pub fn get(self: *StopEndpoint, r: zap.Request) void {
//! _ = self; //! _ = self;
@ -107,34 +107,34 @@ pub fn checkEndpointType(T: type) void {
} }
pub const Wrapper = struct { pub const Wrapper = struct {
pub const Internal = struct { pub const Interface = struct {
call: *const fn (*Internal, zap.Request) anyerror!void = undefined, call: *const fn (*Interface, zap.Request) anyerror!void = undefined,
path: []const u8, 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 { pub fn Wrap(T: type) type {
return struct { return struct {
wrapped: *T, wrapped: *T,
wrapper: Internal, wrapper: Interface,
const Self = @This(); const Wrapped = @This();
pub fn unwrap(wrapper: *Internal) *Self { pub fn unwrap(wrapper: *Interface) *Wrapped {
const self: *Self = @alignCast(@fieldParentPtr("wrapper", wrapper)); const self: *Wrapped = @alignCast(@fieldParentPtr("wrapper", wrapper));
return self; return self;
} }
pub fn destroy(allocator: std.mem.Allocator, wrapper: *Internal) void { pub fn destroy(allocator: std.mem.Allocator, wrapper: *Interface) void {
const self: *Self = @alignCast(@fieldParentPtr("wrapper", wrapper)); const self: *Wrapped = @alignCast(@fieldParentPtr("wrapper", wrapper));
allocator.destroy(self); allocator.destroy(self);
} }
pub fn onRequestWrapped(wrapper: *Internal, r: zap.Request) !void { pub fn onRequestWrapped(wrapper: *Interface, r: zap.Request) !void {
var self: *Self = Self.unwrap(wrapper); var self: *Wrapped = Wrapped.unwrap(wrapper);
try self.onRequest(r); 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()) { const ret = switch (r.methodAsEnum()) {
.GET => self.wrapped.*.get(r), .GET => self.wrapped.*.get(r),
.POST => self.wrapped.*.post(r), .POST => self.wrapped.*.post(r),
@ -150,7 +150,7 @@ pub const Wrapper = struct {
switch (self.wrapped.*.error_strategy) { switch (self.wrapped.*.error_strategy) {
.raise => return err, .raise => return err,
.log_to_response => return r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505), .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, ep: *EndpointType,
path: []const u8, path: []const u8,
error_strategy: ErrorStrategy, error_strategy: ErrorStrategy,
const Self = @This(); const AuthenticatingEndpoint = @This();
/// Init the authenticating endpoint. Pass in a pointer to the endpoint /// Init the authenticating endpoint. Pass in a pointer to the endpoint
/// you want to wrap, and the Authenticator that takes care of authenticating /// you want to wrap, and the Authenticator that takes care of authenticating
/// requests. /// requests.
pub fn init(e: *EndpointType, authenticator: *Authenticator) Self { pub fn init(e: *EndpointType, authenticator: *Authenticator) AuthenticatingEndpoint {
return .{ return .{
.authenticator = authenticator, .authenticator = authenticator,
.ep = e, .ep = e,
@ -191,7 +191,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type {
} }
/// Authenticates GET requests using the Authenticator. /// 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)) { try switch (self.authenticator.authenticateRequest(&r)) {
.AuthFailed => return self.ep.*.unauthorized(r), .AuthFailed => return self.ep.*.unauthorized(r),
.AuthOK => self.ep.*.get(r), .AuthOK => self.ep.*.get(r),
@ -200,7 +200,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type {
} }
/// Authenticates POST requests using the Authenticator. /// 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)) { try switch (self.authenticator.authenticateRequest(&r)) {
.AuthFailed => return self.ep.*.unauthorized(r), .AuthFailed => return self.ep.*.unauthorized(r),
.AuthOK => self.ep.*.post(r), .AuthOK => self.ep.*.post(r),
@ -209,7 +209,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type {
} }
/// Authenticates PUT requests using the Authenticator. /// 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)) { try switch (self.authenticator.authenticateRequest(&r)) {
.AuthFailed => return self.ep.*.unauthorized(r), .AuthFailed => return self.ep.*.unauthorized(r),
.AuthOK => self.ep.*.put(r), .AuthOK => self.ep.*.put(r),
@ -218,7 +218,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type {
} }
/// Authenticates DELETE requests using the Authenticator. /// 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)) { try switch (self.authenticator.authenticateRequest(&r)) {
.AuthFailed => return self.ep.*.unauthorized(r), .AuthFailed => return self.ep.*.unauthorized(r),
.AuthOK => self.ep.*.delete(r), .AuthOK => self.ep.*.delete(r),
@ -227,7 +227,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type {
} }
/// Authenticates PATCH requests using the Authenticator. /// 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)) { try switch (self.authenticator.authenticateRequest(&r)) {
.AuthFailed => return self.ep.*.unauthorized(r), .AuthFailed => return self.ep.*.unauthorized(r),
.AuthOK => self.ep.*.patch(r), .AuthOK => self.ep.*.patch(r),
@ -236,7 +236,7 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type {
} }
/// Authenticates OPTIONS requests using the Authenticator. /// 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)) { try switch (self.authenticator.authenticateRequest(&r)) {
.AuthFailed => return self.ep.*.unauthorized(r), .AuthFailed => return self.ep.*.unauthorized(r),
.AuthOK => self.ep.*.put(r), .AuthOK => self.ep.*.put(r),
@ -261,10 +261,8 @@ pub const Listener = struct {
listener: HttpListener, listener: HttpListener,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
const Self = @This(); /// Internal static interface struct of member endpoints
var endpoints: std.ArrayListUnmanaged(*Wrapper.Interface) = .empty;
/// Internal static struct of member endpoints
var endpoints: std.ArrayListUnmanaged(*Wrapper.Internal) = .empty;
/// Internal, static request handler callback. Will be set to the optional, /// Internal, static request handler callback. Will be set to the optional,
/// user-defined request callback that only gets called if no endpoints match /// 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` /// Initialize a new endpoint listener. Note, if you pass an `on_request`
/// callback in the provided ListenerSettings, this request callback will be /// callback in the provided ListenerSettings, this request callback will be
/// called every time a request arrives that no endpoint matches. /// 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 // reset the global in case init is called multiple times, as is the
// case in the authentication tests // case in the authentication tests
endpoints = .empty; endpoints = .empty;
@ -297,7 +295,7 @@ pub const Listener = struct {
/// De-init the listener and free its resources. /// De-init the listener and free its resources.
/// Registered endpoints will not be de-initialized automatically; just removed /// Registered endpoints will not be de-initialized automatically; just removed
/// from the internal map. /// from the internal map.
pub fn deinit(self: *Self) void { pub fn deinit(self: *Listener) void {
for (endpoints.items) |endpoint_wrapper| { for (endpoints.items) |endpoint_wrapper| {
endpoint_wrapper.destroy(self.allocator, 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 /// Call this to start listening. After this, no more endpoints can be
/// registered. /// registered.
pub fn listen(self: *Self) !void { pub fn listen(self: *Listener) !void {
try self.listener.listen(); 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!! /// 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 /// If you try to register an endpoint whose path would shadow an already registered one, you will
/// receive an EndpointPathShadowError. /// receive an EndpointPathShadowError.
pub fn register(self: *Self, e: anytype) !void { pub fn register(self: *Listener, e: anytype) !void {
for (endpoints.items) |other| { for (endpoints.items) |other| {
if (std.mem.startsWith( if (std.mem.startsWith(
u8, u8,

View file

@ -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; 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. /// 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. /// 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 { pub const struct_fio_start_args = extern struct {
/// The number of threads to run in the thread pool. /// 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 const http_cookie_args_s = opaque {};
pub extern fn http_set_header(h: [*c]http_s, name: FIOBJ, value: FIOBJ) c_int; 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; 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_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_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; 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;

View file

@ -86,14 +86,14 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
realm: ?[]const u8, realm: ?[]const u8,
lookup: *Lookup, lookup: *Lookup,
const Self = @This(); const BasicAuth = @This();
/// Creates a BasicAuth. `lookup` must implement `.get([]const u8) -> []const u8` /// Creates a BasicAuth. `lookup` must implement `.get([]const u8) -> []const u8`
/// different implementations can /// different implementations can
/// - either decode, lookup and compare passwords /// - either decode, lookup and compare passwords
/// - or just check for existence of the base64-encoded user:pass combination /// - 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 /// 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 .{ return .{
.allocator = allocator, .allocator = allocator,
.lookup = lookup, .lookup = lookup,
@ -102,7 +102,7 @@ pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
} }
/// Deinit the authenticator. /// Deinit the authenticator.
pub fn deinit(self: *Self) void { pub fn deinit(self: *BasicAuth) void {
if (self.realm) |the_realm| { if (self.realm) |the_realm| {
self.allocator.free(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. /// 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()`. /// 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", .{}); zap.debug("AuthenticateUserPass\n", .{});
const encoded = auth_header[AuthScheme.Basic.str().len..]; const encoded = auth_header[AuthScheme.Basic.str().len..];
const decoder = std.base64.standard.Decoder; 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. /// 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()`. /// 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..]; const token = auth_header[AuthScheme.Basic.str().len..];
return if (self.lookup.*.contains(token)) .AuthOK else .AuthFailed; return if (self.lookup.*.contains(token)) .AuthOK else .AuthFailed;
} }
/// dispatch based on kind (.UserPass / .Token689) and try to authenticate based on the header. /// 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()`. /// 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", .{}); zap.debug("AUTHENTICATE\n", .{});
switch (kind) { switch (kind) {
.UserPass => return self.authenticateUserPass(auth_header), .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. /// Tries to extract the authentication header and perform the authentication.
/// If no authentication header is found, an authorization header is tried. /// 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", .{}); zap.debug("AUTHENTICATE REQUEST\n", .{});
if (extractAuthHeader(.Basic, r)) |auth_header| { if (extractAuthHeader(.Basic, r)) |auth_header| {
zap.debug("Authentication Header found!\n", .{}); zap.debug("Authentication Header found!\n", .{});
@ -225,12 +225,10 @@ pub const BearerSingle = struct {
token: []const u8, token: []const u8,
realm: ?[]const u8, realm: ?[]const u8,
const Self = @This();
/// Creates a Single-Token Bearer Authenticator. /// Creates a Single-Token Bearer Authenticator.
/// Takes a copy of the token. /// Takes a copy of the token.
/// If realm is provided (not null), a copy is taken call deinit() to clean up. /// 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 .{ return .{
.allocator = allocator, .allocator = allocator,
.token = try allocator.dupe(u8, token), .token = try allocator.dupe(u8, token),
@ -240,7 +238,7 @@ pub const BearerSingle = struct {
/// Try to authenticate based on the header. /// Try to authenticate based on the header.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. /// 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) { if (checkAuthHeader(.Bearer, auth_header) == false) {
return .AuthFailed; return .AuthFailed;
} }
@ -251,7 +249,7 @@ pub const BearerSingle = struct {
/// The zap authentication request handler. /// The zap authentication request handler.
/// ///
/// Tries to extract the authentication header and perform the authentication. /// 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| { if (extractAuthHeader(.Bearer, r)) |auth_header| {
return self.authenticate(auth_header); return self.authenticate(auth_header);
} }
@ -259,7 +257,7 @@ pub const BearerSingle = struct {
} }
/// Deinits the authenticator. /// Deinits the authenticator.
pub fn deinit(self: *Self) void { pub fn deinit(self: *BearerSingle) void {
if (self.realm) |the_realm| { if (self.realm) |the_realm| {
self.allocator.free(the_realm); self.allocator.free(the_realm);
} }
@ -283,12 +281,12 @@ pub fn BearerMulti(comptime Lookup: type) type {
lookup: *Lookup, lookup: *Lookup,
realm: ?[]const u8, realm: ?[]const u8,
const Self = @This(); const BearerMultiAuth = @This();
/// Creates a Multi Token Bearer Authenticator. `lookup` must implement /// Creates a Multi Token Bearer Authenticator. `lookup` must implement
/// `.get([]const u8) -> []const u8` to look up tokens. /// `.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. /// 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 .{ return .{
.allocator = allocator, .allocator = allocator,
.lookup = lookup, .lookup = lookup,
@ -298,7 +296,7 @@ pub fn BearerMulti(comptime Lookup: type) type {
/// Deinit the authenticator. Only required if a realm was provided at /// Deinit the authenticator. Only required if a realm was provided at
/// init() time. /// init() time.
pub fn deinit(self: *Self) void { pub fn deinit(self: *BearerMultiAuth) void {
if (self.realm) |the_realm| { if (self.realm) |the_realm| {
self.allocator.free(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. /// Try to authenticate based on the header.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`. /// 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) { if (checkAuthHeader(.Bearer, auth_header) == false) {
return .AuthFailed; return .AuthFailed;
} }
@ -317,7 +315,7 @@ pub fn BearerMulti(comptime Lookup: type) type {
/// The zap authentication request handler. /// The zap authentication request handler.
/// ///
/// Tries to extract the authentication header and perform the authentication. /// 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| { if (extractAuthHeader(.Bearer, r)) |auth_header| {
return self.authenticate(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 = .{}, passwordLookupLock: std.Thread.Mutex = .{},
tokenLookupLock: std.Thread.Mutex = .{}, tokenLookupLock: std.Thread.Mutex = .{},
const Self = @This(); const UserPassSessionAuth = @This();
const SessionTokenMap = std.StringHashMap(void); const SessionTokenMap = std.StringHashMap(void);
const Hash = std.crypto.hash.sha2.Sha256; 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, allocator: std.mem.Allocator,
lookup: *Lookup, lookup: *Lookup,
args: UserPassSessionArgs, args: UserPassSessionArgs,
) !Self { ) !UserPassSessionAuth {
const ret: Self = .{ const ret: UserPassSessionAuth = .{
.allocator = allocator, .allocator = allocator,
.settings = .{ .settings = .{
.usernameParam = try allocator.dupe(u8, args.usernameParam), .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. /// 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.usernameParam);
self.allocator.free(self.settings.passwordParam); self.allocator.free(self.settings.passwordParam);
self.allocator.free(self.settings.loginPage); 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 /// 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 // we erase the list of valid tokens server-side (later) and set the
// cookie to "invalid" on the client side. // cookie to "invalid" on the client side.
if (r.setCookie(.{ if (r.setCookie(.{
@ -450,15 +448,15 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
r.parseCookies(false); r.parseCookies(false);
// check for session cookie // 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| { if (maybe_cookie) |cookie| {
defer cookie.deinit(); defer self.allocator.free(cookie);
self.tokenLookupLock.lock(); self.tokenLookupLock.lock();
defer self.tokenLookupLock.unlock(); defer self.tokenLookupLock.unlock();
if (self.sessionTokens.getKeyPtr(cookie.str)) |keyPtr| { if (self.sessionTokens.getKeyPtr(cookie)) |keyPtr| {
const keySlice = keyPtr.*; const keySlice = keyPtr.*;
// if cookie is a valid session, remove it! // 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 // only now can we let go of the cookie str slice that
// was used as the key // was used as the key
self.allocator.free(keySlice); 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 we're requesting the login page, let the request through
if (r.path) |p| { if (r.path) |p| {
if (std.mem.startsWith(u8, p, self.settings.loginPage)) { 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); r.parseCookies(false);
// check for session cookie // 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| { if (maybe_cookie) |cookie| {
defer cookie.deinit(); defer self.allocator.free(cookie);
// locked or unlocked token lookup // locked or unlocked token lookup
self.tokenLookupLock.lock(); self.tokenLookupLock.lock();
defer self.tokenLookupLock.unlock(); defer self.tokenLookupLock.unlock();
if (self.sessionTokens.contains(cookie.str)) { if (self.sessionTokens.contains(cookie)) {
// cookie is a valid session! // 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; return .AuthOK;
} else { } 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 // this is not necessarily a bad thing. it could be a
// stale cookie from a previous session. So let's check // stale cookie from a previous session. So let's check
// if username and password are being sent and correct. // 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 // get params of username and password
if (r.getParamStr(self.allocator, self.settings.usernameParam, false)) |maybe_username| { if (r.getParamStr(self.allocator, self.settings.usernameParam)) |maybe_username| {
if (maybe_username) |*username| { if (maybe_username) |username| {
defer username.deinit(); defer self.allocator.free(username);
if (r.getParamStr(self.allocator, self.settings.passwordParam, false)) |maybe_pw| { if (r.getParamStr(self.allocator, self.settings.passwordParam)) |maybe_pw| {
if (maybe_pw) |*pw| { if (maybe_pw) |pw| {
defer pw.deinit(); defer self.allocator.free(pw);
// now check // now check
const correct_pw_optional = brk: { const correct_pw_optional = brk: {
if (lockedPwLookups) { if (lockedPwLookups) {
self.passwordLookupLock.lock(); self.passwordLookupLock.lock();
defer self.passwordLookupLock.unlock(); defer self.passwordLookupLock.unlock();
break :brk self.lookup.*.get(username.str); break :brk self.lookup.*.get(username);
} else { } else {
break :brk self.lookup.*.get(username.str); break :brk self.lookup.*.get(username);
} }
}; };
if (correct_pw_optional) |correct_pw| { 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 // create session token
if (self.createAndStoreSessionToken(username.str, pw.str)) |token| { if (self.createAndStoreSessionToken(username, pw)) |token| {
defer self.allocator.free(token); defer self.allocator.free(token);
// now set the cookie header // now set the cookie header
if (r.setCookie(.{ if (r.setCookie(.{
@ -549,12 +546,12 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
} }
} }
} else |err| { } else |err| {
zap.debug("getParamSt() for password failed in UserPassSession: {any}", .{err}); zap.debug("getParamStr() for password failed in UserPassSession: {any}", .{err});
return .AuthFailed; return .AuthFailed;
} }
} }
} else |err| { } 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;
} }
return .AuthFailed; return .AuthFailed;
@ -563,7 +560,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
/// The zap authentication request handler. /// The zap authentication request handler.
/// ///
/// See above for how it works. /// 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)) { switch (self._internal_authenticateRequest(r)) {
.AuthOK => { .AuthOK => {
// username and pass are ok -> created token, set header, caller can continue // 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); 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(.{}); var hasher = Hash.init(.{});
hasher.update(username); hasher.update(username);
hasher.update(password); hasher.update(password);
@ -603,7 +600,7 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
return token_str; 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); const token = try self.createSessionToken(username, password);
self.tokenLookupLock.lock(); self.tokenLookupLock.lock();
defer self.tokenLookupLock.unlock(); defer self.tokenLookupLock.unlock();

View file

@ -5,15 +5,15 @@ const std = @import("std");
debugOn: bool, debugOn: bool,
/// Access to facil.io's logging facilities /// 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 .{ return .{
.debugOn = debug, .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) { if (self.debugOn) {
std.debug.print("[zap] - " ++ fmt, args); std.debug.print("[zap] - " ++ fmt, args);
} }

View file

@ -2,7 +2,7 @@ const std = @import("std");
const fio = @import("fio.zig"); const fio = @import("fio.zig");
const util = @import("util.zig"); const util = @import("util.zig");
const Self = @This(); const Mustache = @This();
const struct_mustache_s = opaque {}; const struct_mustache_s = opaque {};
const mustache_s = struct_mustache_s; 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 /// Create a new `Mustache` instance; `deinit()` should be called to free
/// the object after usage. /// 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; var err: mustache_error_en = undefined;
const args: MustacheLoadArgsFio = .{ const args: MustacheLoadArgsFio = .{
@ -72,7 +72,7 @@ pub fn init(load_args: LoadArgs) Error!Self {
const ret = fiobj_mustache_new(args); const ret = fiobj_mustache_new(args);
switch (err) { switch (err) {
0 => return Self{ 0 => return Mustache{
.handle = ret.?, .handle = ret.?,
}, },
1 => return Error.MUSTACHE_ERR_TOO_DEEP, 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; /// Convenience function to create a new `Mustache` instance with in-memory data loaded;
/// `deinit()` should be called to free the object after usage.. /// `deinit()` should be called to free the object after usage..
pub fn fromData(data: []const u8) Error!Self { pub fn fromData(data: []const u8) Error!Mustache {
return Self.init(.{ .data = data }); return Mustache.init(.{ .data = data });
} }
/// Convenience function to create a new `Mustache` instance with file-based data loaded; /// Convenience function to create a new `Mustache` instance with file-based data loaded;
/// `deinit()` should be called to free the object after usage.. /// `deinit()` should be called to free the object after usage..
pub fn fromFile(filename: []const u8) Error!Self { pub fn fromFile(filename: []const u8) Error!Mustache {
return Self.init(.{ .filename = filename }); return Mustache.init(.{ .filename = filename });
} }
/// Free the data backing a `Mustache` instance. /// Free the data backing a `Mustache` instance.
pub fn deinit(self: *Self) void { pub fn deinit(self: *Mustache) void {
fiobj_mustache_free(self.handle); 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 // TODO: The build may be slow because it needs to convert zig types to facil.io
// types. However, this needs to be investigated into. // types. However, this needs to be investigated into.
// See `fiobjectify` for more information. // 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); const T = @TypeOf(data);
if (@typeInfo(T) != .@"struct") { if (@typeInfo(T) != .@"struct") {
@compileError("No struct: '" ++ @typeName(T) ++ "'"); @compileError("No struct: '" ++ @typeName(T) ++ "'");

View file

@ -1,4 +1,6 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator;
const Log = @import("log.zig"); const Log = @import("log.zig");
const http = @import("http.zig"); const http = @import("http.zig");
const fio = @import("fio.zig"); const fio = @import("fio.zig");
@ -20,21 +22,19 @@ pub const HttpError = error{
/// Key value pair of strings from HTTP parameters /// Key value pair of strings from HTTP parameters
pub const HttpParamStrKV = struct { pub const HttpParamStrKV = struct {
key: util.FreeOrNot, key: []const u8,
value: util.FreeOrNot, value: []const u8,
pub fn deinit(self: *@This()) void {
self.key.deinit();
self.value.deinit();
}
}; };
/// List of key value pairs of Http param strings. /// List of key value pairs of Http param strings.
pub const HttpParamStrKVList = struct { pub const HttpParamStrKVList = struct {
items: []HttpParamStrKV, items: []HttpParamStrKV,
allocator: std.mem.Allocator, allocator: Allocator,
pub fn deinit(self: *@This()) void {
for (self.items) |*item| { pub fn deinit(self: *HttpParamStrKVList) void {
item.deinit(); for (self.items) |item| {
self.allocator.free(item.key);
self.allocator.free(item.value);
} }
self.allocator.free(self.items); 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). /// List of key value pairs of Http params (might be of different types).
pub const HttpParamKVList = struct { pub const HttpParamKVList = struct {
items: []HttpParamKV, items: []HttpParamKV,
allocator: std.mem.Allocator, allocator: Allocator,
pub fn deinit(self: *const @This()) void { pub fn deinit(self: *const HttpParamKVList) void {
for (self.items) |*item| { for (self.items) |item| {
item.deinit(); self.allocator.free(item.key);
if (item.value) |v| {
v.free(self.allocator);
}
} }
self.allocator.free(self.items); self.allocator.free(self.items);
} }
@ -70,28 +73,31 @@ pub const HttpParam = union(HttpParamValueType) {
Int: isize, Int: isize,
Float: f64, Float: f64,
/// we don't do writable strings here /// we don't do writable strings here
String: util.FreeOrNot, String: []const u8,
/// value will always be null /// value will always be null
Unsupported: ?void, Unsupported: ?void,
/// we assume hashes are because of file transmissions /// we assume hashes are because of file transmissions
Hash_Binfile: HttpParamBinaryFile, Hash_Binfile: HttpParamBinaryFile,
/// value will always be null /// value will always be null
Array_Binfile: std.ArrayList(HttpParamBinaryFile), 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 /// Key value pair of one typed Http param
pub const HttpParamKV = struct { pub const HttpParamKV = struct {
key: util.FreeOrNot, key: []const u8,
value: ?HttpParam, 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. /// Struct representing an uploaded file.
@ -104,7 +110,7 @@ pub const HttpParamBinaryFile = struct {
filename: ?[]const u8 = null, filename: ?[]const u8 = null,
/// format function for printing file upload data /// 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 d = value.data orelse "\\0";
const m = value.mimetype orelse "null"; const m = value.mimetype orelse "null";
const f = value.filename 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_name = fio.fiobj_str_new("name", 4);
const key_data = fio.fiobj_str_new("data", 4); const key_data = fio.fiobj_str_new("data", 4);
const key_type = fio.fiobj_str_new("type", 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. /// 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)) { return switch (fio.fiobj_type(o)) {
fio.FIOBJ_T_NULL => null, fio.FIOBJ_T_NULL => null,
fio.FIOBJ_T_TRUE => .{ .Bool = true }, fio.FIOBJ_T_TRUE => .{ .Bool = true },
fio.FIOBJ_T_FALSE => .{ .Bool = false }, fio.FIOBJ_T_FALSE => .{ .Bool = false },
fio.FIOBJ_T_NUMBER => .{ .Int = fio.fiobj_obj2num(o) }, fio.FIOBJ_T_NUMBER => .{ .Int = fio.fiobj_obj2num(o) },
fio.FIOBJ_T_FLOAT => .{ .Float = fio.fiobj_obj2float(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 => { fio.FIOBJ_T_ARRAY => {
return .{ .Unsupported = null }; return .{ .Unsupported = null };
}, },
@ -280,30 +287,30 @@ pub const UserContext = struct {
user_context: ?*anyopaque = null, user_context: ?*anyopaque = null,
}; };
const Self = @This(); const Request = @This();
/// mark the current request as finished. Important for middleware-style /// mark the current request as finished. Important for middleware-style
/// request handler chaining. Called when sending a body, redirecting, etc. /// 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 // we might be a copy
self._is_finished.* = finished; self._is_finished.* = finished;
} }
/// tell whether request processing has finished. (e.g. response sent, /// tell whether request processing has finished. (e.g. response sent,
/// redirected, ...) /// redirected, ...)
pub fn isFinished(self: *const Self) bool { pub fn isFinished(self: *const Request) bool {
// we might be a copy // we might be a copy
return self._is_finished.*; return self._is_finished.*;
} }
/// if you absolutely must, you can set any context on the request here /// if you absolutely must, you can set any context on the request here
// (note, this line is linked to from the readme) -- TODO: sync // (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; self._user_context.*.user_context = context;
} }
/// get the associated user context of the request. /// 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| { if (self._user_context.*.user_context) |ptr| {
return @as(*Context, @ptrCast(@alignCast(ptr))); return @as(*Context, @ptrCast(@alignCast(ptr)));
} else { } 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 /// const err = zap.HttpError; // this is to show that `err` is an Error
/// r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505); /// 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 // TODO: query accept headers
if (self._internal_sendError(err, err_trace, errorcode_num)) { if (self._internal_sendError(err, err_trace, errorcode_num)) {
return; 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. /// 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: query accept headers
// TODO: let's hope 20k is enough. Maybe just really allocate here // TODO: let's hope 20k is enough. Maybe just really allocate here
self.h.*.status = errorcode_num; self.h.*.status = errorcode_num;
@ -346,7 +353,7 @@ pub fn _internal_sendError(self: *const Self, err: anyerror, err_trace: ?std.bui
} }
/// Send body. /// 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( const ret = fio.http_send_body(self.h, @as(
*anyopaque, *anyopaque,
@ptrFromInt(@intFromPtr(body.ptr)), @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. /// 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 (self.setContentType(.JSON)) {
if (fio.http_send_body(self.h, @as( if (fio.http_send_body(self.h, @as(
*anyopaque, *anyopaque,
@ -368,7 +375,7 @@ pub fn sendJson(self: *const Self, json: []const u8) HttpError!void {
} }
/// Set content type. /// 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) { const s = switch (c) {
.TEXT => "text/plain", .TEXT => "text/plain",
.JSON => "application/json", .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 /// 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); self.setStatus(if (code) |status| status else .found);
try self.setHeader("Location", path); try self.setHeader("Location", path);
try self.sendBody("moved"); 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 /// shows how to use the logger
pub fn setContentTypeWithLogger( pub fn setContentTypeWithLogger(
self: *const Self, self: *const Request,
c: ContentType, c: ContentType,
logger: *const Log, logger: *const Log,
) HttpError!void { ) HttpError!void {
@ -402,7 +409,7 @@ pub fn setContentTypeWithLogger(
} }
/// Tries to determine the content type by file extension of request path, and sets it. /// 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); const t = fio.http_mimetype_find2(self.h.*.path);
if (fio.is_invalid(t) == 1) return error.HttpSetContentType; if (fio.is_invalid(t) == 1) return error.HttpSetContentType;
const ret = fio.fiobj_hash_set( 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. /// Tries to determine the content type by filename extension, and sets it.
/// If the extension cannot be determined, NoExtensionInFilename error is /// If the extension cannot be determined, NoExtensionInFilename error is
/// returned. /// 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); const ext = std.fs.path.extension(filename);
if (ext.len > 1) { if (ext.len > 1) {
const e = ext[1..]; const e = ext[1..];
const obj = fio.http_mimetype_find(@constCast(e.ptr), e.len); 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| { if (util.fio2str(obj)) |mime_str| {
try self.setHeader("content-type", 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. /// NOTE that header-names are lowerased automatically while parsing the request.
/// so please only use lowercase keys! /// so please only use lowercase keys!
/// Returned mem is temp. Do not free it. /// 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); const hname = fio.fiobj_str_new(util.toCharPtr(name), name.len);
defer fio.fiobj_free_wrapped(hname); 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)); 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 /// Returns the header value of a given common header key. Returned memory
/// should not be freed. /// 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) { const field = switch (which) {
.accept => fio.HTTP_HEADER_ACCEPT, .accept => fio.HTTP_HEADER_ACCEPT,
.cache_control => fio.HTTP_HEADER_CACHE_CONTROL, .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, .upgrade => fio.HTTP_HEADER_UPGRADE,
}; };
const fiobj = zap.fio.fiobj_hash_get(self.h.*.headers, field); 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); return zap.util.fio2str(fiobj);
} }
/// Set header. /// 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 = .{ const hname: fio.fio_str_info_s = .{
.data = util.toCharPtr(name), .data = util.toCharPtr(name),
.len = name.len, .len = name.len,
@ -526,13 +538,26 @@ pub fn setHeader(self: *const Self, name: []const u8, value: []const u8) HttpErr
return error.HttpSetHeader; 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. /// 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; self.h.*.status = status;
} }
/// Set status by enum. /// 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))); 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! /// 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") /// 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) if (fio.http_sendfile2(self.h, util.toCharPtr(file_path), file_path.len, null, 0) != 0)
return error.SendFile; return error.SendFile;
self.markAsFinished(true); self.markAsFinished(true);
@ -561,7 +586,7 @@ pub fn sendFile(self: *const Self, file_path: []const u8) !void {
/// - application/x-www-form-urlencoded /// - application/x-www-form-urlencoded
/// - application/json /// - application/json
/// - multipart/form-data /// - 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; 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. /// object that doesn't have a hash map at its root.
/// ///
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice() /// 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); fio.http_parse_query(self.h);
} }
/// Parse received cookie headers /// 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); 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); const AcceptHeaderList = std.ArrayList(AcceptItem);
/// Parses `Accept:` http header into `list`, ordered from highest q factor to lowest /// 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 accept_str = self.getHeaderCommon(.accept) orelse return error.NoAcceptHeader;
const comma_count = std.mem.count(u8, accept_str, ","); 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 /// 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 = .{ const c: fio.http_cookie_args_s = .{
.name = util.toCharPtr(args.name), .name = util.toCharPtr(args.name),
.name_len = @as(isize, @intCast(args.name.len)), .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(). /// 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; if (self.h.*.cookies == 0) return null;
const key = fio.fiobj_str_new(name.ptr, name.len); const key = fio.fiobj_str_new(name.ptr, name.len);
defer fio.fiobj_free_wrapped(key); 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) { if (value == fio.FIOBJ_INVALID) {
return null; 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. /// Returns the number of cookies after parsing.
/// ///
/// Parse with parseCookies() /// Parse with parseCookies()
pub fn getCookiesCount(self: *const Self) isize { pub fn getCookiesCount(self: *const Request) isize {
if (self.h.*.cookies == 0) return 0; if (self.h.*.cookies == 0) return 0;
return fio.fiobj_obj2num(self.h.*.cookies); 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. /// Returns the number of parameters after parsing.
/// ///
/// Parse with parseBody() and / or parseQuery() /// 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; if (self.h.*.params == 0) return 0;
return fio.fiobj_obj2num(self.h.*.params); 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 /// 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 params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount())));
var context: _parametersToOwnedStrSliceContext = .{ var context: CallbackContext_StrKV = .{
.params = &params, .params = &params,
.allocator = a, .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()) { if (howmany != self.getCookiesCount()) {
return error.HttpIterParams; 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 /// 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 params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount())));
var context: _parametersToOwnedSliceContext = .{ .params = &params, .allocator = a, .dupe_strings = dupe_strings }; var context: CallbackContext_KV = .{ .params = &params, .allocator = a };
const howmany = fio.fiobj_each1(self.h.*.cookies, 0, _each_nextParam, &context); const howmany = fio.fiobj_each1(self.h.*.cookies, 0, CallbackContext_KV.callback, &context);
if (howmany != self.getCookiesCount()) { if (howmany != self.getCookiesCount()) {
return error.HttpIterParams; 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. /// Requires parseBody() and/or parseQuery() have been called.
/// Returned list needs to be deinited. /// 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 params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getParamCount())));
var context: _parametersToOwnedStrSliceContext = .{
var context: CallbackContext_StrKV = .{
.params = &params, .params = &params,
.allocator = a, .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()) { if (howmany != self.getParamCount()) {
return error.HttpIterParams; return error.HttpIterParams;
} }
return .{ .items = try params.toOwnedSlice(), .allocator = a }; 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 /// Returns the query / body parameters as key/value pairs
/// Supported param types that will be converted: /// 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. /// Requires parseBody() and/or parseQuery() have been called.
/// Returned slice needs to be freed. /// 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 params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getParamCount())));
var context: _parametersToOwnedSliceContext = .{ .params = &params, .allocator = a, .dupe_strings = dupe_strings };
const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParam, &context); var context: CallbackContext_KV = .{ .params = &params, .allocator = a };
const howmany = fio.fiobj_each1(self.h.*.params, 0, CallbackContext_KV.callback, &context);
if (howmany != self.getParamCount()) { if (howmany != self.getParamCount()) {
return error.HttpIterParams; return error.HttpIterParams;
} }
return .{ .items = try params.toOwnedSlice(), .allocator = a }; return .{ .items = try params.toOwnedSlice(), .allocator = a };
} }
const _parametersToOwnedSliceContext = struct { /// get named parameter (parsed) as string
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
/// Supported param types that will be converted: /// Supported param types that will be converted:
/// ///
/// - Bool /// - 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. /// So, for JSON body payloads: parse the body instead.
/// ///
/// Requires parseBody() and/or parseQuery() have been called. /// Requires parseBody() and/or parseQuery() have been called.
/// The returned string needs to be deinited with .deinit() /// The returned string needs to be deallocated.
pub fn getParamStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot { pub fn getParamStr(self: *const Request, a: Allocator, name: []const u8) !?[]const u8 {
if (self.h.*.params == 0) return null; if (self.h.*.params == 0) return null;
const key = fio.fiobj_str_new(name.ptr, name.len); const key = fio.fiobj_str_new(name.ptr, name.len);
defer fio.fiobj_free_wrapped(key); 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) { if (value == fio.FIOBJ_INVALID) {
return null; 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 /// similar to getParamStr, except it will return the part of the querystring
/// after the equals sign, non-decoded, and always as character slice. /// after the equals sign, non-decoded, and always as character slice.
/// - no allocation! /// - no allocation!
/// - does not requre parseQuery() or anything to be called in advance /// - 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| { if (self.query) |query| {
var amp_it = std.mem.tokenizeScalar(u8, query, '&'); var amp_it = std.mem.tokenizeScalar(u8, query, '&');
while (amp_it.next()) |maybe_pair| { while (amp_it.next()) |maybe_pair| {
@ -895,13 +922,13 @@ pub const ParameterSlices = struct { name: []const u8, value: []const u8 };
pub const ParamSliceIterator = struct { pub const ParamSliceIterator = struct {
amp_it: std.mem.TokenIterator(u8, .scalar), amp_it: std.mem.TokenIterator(u8, .scalar),
pub fn init(query: []const u8) @This() { pub fn init(query: []const u8) ParamSliceIterator {
return .{ return .{
.amp_it = std.mem.tokenizeScalar(u8, query, '&'), .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| { while (self.amp_it.next()) |maybe_pair| {
if (std.mem.indexOfScalar(u8, maybe_pair, '=')) |pos_of_eq| { if (std.mem.indexOfScalar(u8, maybe_pair, '=')) |pos_of_eq| {
const pname = maybe_pair[0..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 /// Returns an iterator that yields all query parameters on next() in the
/// form of a ParameterSlices struct { .name, .value } /// form of a ParameterSlices struct { .name, .value }
/// As with getParamSlice(), the value is not decoded /// 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 ""; const query = self.query orelse "";
return ParamSliceIterator.init(query); 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); return http.methodToEnum(self.method);
} }

View file

@ -9,10 +9,10 @@ const RouterError = error{
EmptyPath, EmptyPath,
}; };
const Self = @This(); const Router = @This();
/// This is a singleton /// This is a singleton
var _instance: *Self = undefined; var _instance: *Router = undefined;
/// Options to pass to init() /// Options to pass to init()
pub const Options = struct { pub const Options = struct {
@ -31,7 +31,7 @@ routes: std.StringHashMap(Callback),
not_found: ?zap.HttpRequestFn, not_found: ?zap.HttpRequestFn,
/// Create a new Router /// Create a new Router
pub fn init(allocator: Allocator, options: Options) Self { pub fn init(allocator: Allocator, options: Options) Router {
return .{ return .{
.routes = std.StringHashMap(Callback).init(allocator), .routes = std.StringHashMap(Callback).init(allocator),
@ -40,12 +40,12 @@ pub fn init(allocator: Allocator, options: Options) Self {
} }
/// Deinit the router /// Deinit the router
pub fn deinit(self: *Self) void { pub fn deinit(self: *Router) void {
self.routes.deinit(); self.routes.deinit();
} }
/// Call this to add a route with an unbound handler: a handler that is not member of a struct. /// 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) { if (path.len == 0) {
return RouterError.EmptyPath; 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); /// 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 // TODO: assert type of instance has handler
if (path.len == 0) { 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 /// 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; _instance = self;
return zap_on_request; return zap_on_request;
} }
@ -98,7 +98,7 @@ fn zap_on_request(r: zap.Request) !void {
return serve(_instance, r); 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 "/"; const path = r.path orelse "/";
if (self.routes.get(path)) |routeInfo| { if (self.routes.get(path)) |routeInfo| {

View file

@ -26,7 +26,7 @@ test "http parameters" {
var strParams: ?zap.Request.HttpParamStrKVList = null; var strParams: ?zap.Request.HttpParamStrKVList = null;
var params: ?zap.Request.HttpParamKVList = null; var params: ?zap.Request.HttpParamKVList = null;
var paramOneStr: ?zap.util.FreeOrNot = null; var paramOneStr: ?[]const u8 = null;
var paramOneSlice: ?[]const u8 = null; var paramOneSlice: ?[]const u8 = null;
var paramSlices: zap.Request.ParamSliceIterator = undefined; var paramSlices: zap.Request.ParamSliceIterator = undefined;
@ -36,20 +36,17 @@ test "http parameters" {
param_count = r.getParamCount(); param_count = r.getParamCount();
// true -> make copies of temp strings // 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 // 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; paramOneStr = r.getParamStr(alloc, "one") catch unreachable;
if (maybe_str) |*s| {
paramOneStr = s.*;
}
paramOneSlice = blk: { // we need to dupe it here because the request object r will get
if (r.getParamSlice("one")) |val| break :blk alloc.dupe(u8, val) catch unreachable; // invalidated at the end of the function but we need to check
break :blk null; // its correctness later in the test
}; paramOneSlice = if (r.getParamSlice("one")) |slice| alloc.dupe(u8, slice) catch unreachable else null;
paramSlices = r.getParamSlices(); paramSlices = r.getParamSlices();
} }
@ -84,44 +81,44 @@ test "http parameters" {
if (Handler.params) |*p| { if (Handler.params) |*p| {
p.deinit(); p.deinit();
} }
if (Handler.paramOneStr) |*p| {
// allocator.free(p); if (Handler.paramOneStr) |p| {
p.deinit(); allocator.free(p);
} }
if (Handler.paramOneSlice) |p| { if (Handler.paramOneSlice) |p| {
Handler.alloc.free(p); allocator.free(p);
} }
} }
try std.testing.expectEqual(Handler.ran, true); try std.testing.expectEqual(true, Handler.ran);
try std.testing.expectEqual(Handler.param_count, 5); try std.testing.expectEqual(5, Handler.param_count);
try std.testing.expect(Handler.paramOneStr != null); 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.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); try std.testing.expect(Handler.strParams != null);
for (Handler.strParams.?.items, 0..) |kv, i| { for (Handler.strParams.?.items, 0..) |kv, i| {
switch (i) { switch (i) {
0 => { 0 => {
try std.testing.expectEqualStrings(kv.key.str, "one"); try std.testing.expectEqualStrings("one", kv.key);
try std.testing.expectEqualStrings(kv.value.str, "1"); try std.testing.expectEqualStrings("1", kv.value);
}, },
1 => { 1 => {
try std.testing.expectEqualStrings(kv.key.str, "two"); try std.testing.expectEqualStrings("two", kv.key);
try std.testing.expectEqualStrings(kv.value.str, "2"); try std.testing.expectEqualStrings("2", kv.value);
}, },
2 => { 2 => {
try std.testing.expectEqualStrings(kv.key.str, "string"); try std.testing.expectEqualStrings("string", kv.key);
try std.testing.expectEqualStrings(kv.value.str, "hello world"); try std.testing.expectEqualStrings("hello world", kv.value);
}, },
3 => { 3 => {
try std.testing.expectEqualStrings(kv.key.str, "float"); try std.testing.expectEqualStrings("float", kv.key);
try std.testing.expectEqualStrings(kv.value.str, "6.28"); try std.testing.expectEqualStrings("6.28", kv.value);
}, },
4 => { 4 => {
try std.testing.expectEqualStrings(kv.key.str, "bool"); try std.testing.expectEqualStrings("bool", kv.key);
try std.testing.expectEqualStrings(kv.value.str, "true"); try std.testing.expectEqualStrings("true", kv.value);
}, },
else => return error.TooManyArgs, else => return error.TooManyArgs,
} }
@ -131,24 +128,24 @@ test "http parameters" {
while (Handler.paramSlices.next()) |param| { while (Handler.paramSlices.next()) |param| {
switch (pindex) { switch (pindex) {
0 => { 0 => {
try std.testing.expectEqualStrings(param.name, "one"); try std.testing.expectEqualStrings("one", param.name);
try std.testing.expectEqualStrings(param.value, "1"); try std.testing.expectEqualStrings("1", param.value);
}, },
1 => { 1 => {
try std.testing.expectEqualStrings(param.name, "two"); try std.testing.expectEqualStrings("two", param.name);
try std.testing.expectEqualStrings(param.value, "2"); try std.testing.expectEqualStrings("2", param.value);
}, },
2 => { 2 => {
try std.testing.expectEqualStrings(param.name, "string"); try std.testing.expectEqualStrings("string", param.name);
try std.testing.expectEqualStrings(param.value, "hello+world"); try std.testing.expectEqualStrings("hello+world", param.value);
}, },
3 => { 3 => {
try std.testing.expectEqualStrings(param.name, "float"); try std.testing.expectEqualStrings("float", param.name);
try std.testing.expectEqualStrings(param.value, "6.28"); try std.testing.expectEqualStrings("6.28", param.value);
}, },
4 => { 4 => {
try std.testing.expectEqualStrings(param.name, "bool"); try std.testing.expectEqualStrings("bool", param.name);
try std.testing.expectEqualStrings(param.value, "true"); try std.testing.expectEqualStrings("true", param.value);
}, },
else => return error.TooManyArgs, else => return error.TooManyArgs,
} }
@ -158,42 +155,42 @@ test "http parameters" {
for (Handler.params.?.items, 0..) |kv, i| { for (Handler.params.?.items, 0..) |kv, i| {
switch (i) { switch (i) {
0 => { 0 => {
try std.testing.expectEqualStrings(kv.key.str, "one"); try std.testing.expectEqualStrings("one", kv.key);
try std.testing.expect(kv.value != null); try std.testing.expect(kv.value != null);
switch (kv.value.?) { switch (kv.value.?) {
.Int => |n| try std.testing.expectEqual(n, 1), .Int => |n| try std.testing.expectEqual(1, n),
else => return error.InvalidHttpParamType, else => return error.InvalidHttpParamType,
} }
}, },
1 => { 1 => {
try std.testing.expectEqualStrings(kv.key.str, "two"); try std.testing.expectEqualStrings("two", kv.key);
try std.testing.expect(kv.value != null); try std.testing.expect(kv.value != null);
switch (kv.value.?) { switch (kv.value.?) {
.Int => |n| try std.testing.expectEqual(n, 2), .Int => |n| try std.testing.expectEqual(2, n),
else => return error.InvalidHttpParamType, else => return error.InvalidHttpParamType,
} }
}, },
2 => { 2 => {
try std.testing.expectEqualStrings(kv.key.str, "string"); try std.testing.expectEqualStrings("string", kv.key);
try std.testing.expect(kv.value != null); try std.testing.expect(kv.value != null);
switch (kv.value.?) { 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, else => return error.InvalidHttpParamType,
} }
}, },
3 => { 3 => {
try std.testing.expectEqualStrings(kv.key.str, "float"); try std.testing.expectEqualStrings("float", kv.key);
try std.testing.expect(kv.value != null); try std.testing.expect(kv.value != null);
switch (kv.value.?) { 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, else => return error.InvalidHttpParamType,
} }
}, },
4 => { 4 => {
try std.testing.expectEqualStrings(kv.key.str, "bool"); try std.testing.expectEqualStrings("bool", kv.key);
try std.testing.expect(kv.value != null); try std.testing.expect(kv.value != null);
switch (kv.value.?) { switch (kv.value.?) {
.Bool => |b| try std.testing.expectEqual(b, true), .Bool => |b| try std.testing.expectEqual(true, b),
else => return error.InvalidHttpParamType, else => return error.InvalidHttpParamType,
} }
}, },

View file

@ -6,6 +6,9 @@ const zap = @import("zap.zig");
/// note: since this is called from within request functions, we don't make /// 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 /// 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. /// 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 { pub fn fio2str(o: fio.FIOBJ) ?[]const u8 {
if (o == 0) return null; if (o == 0) return null;
const x: fio.fio_str_info_s = fio.fiobj_obj2cstr(o); 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]; 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. /// Used internally: convert a FIO object into its string representation.
/// Depending on the type of the object, a buffer will be created. Hence a /// This always allocates, so choose your allocator wisely.
/// FreeOrNot type is used as the return type. /// Let's never use that
pub fn fio2strAllocOrNot(a: std.mem.Allocator, o: fio.FIOBJ, always_alloc: bool) !FreeOrNot { pub fn fio2strAlloc(a: std.mem.Allocator, o: fio.FIOBJ) ![]const u8 {
if (o == 0) return .{ .str = "null", .freeme = false }; if (o == 0) return try a.dupe(u8, "null");
if (o == fio.FIOBJ_INVALID) return .{ .str = "invalid", .freeme = false }; if (o == fio.FIOBJ_INVALID) return try a.dupe(u8, "invalid");
return switch (fio.fiobj_type(o)) { return switch (fio.fiobj_type(o)) {
fio.FIOBJ_T_TRUE => .{ .str = "true", .freeme = false }, fio.FIOBJ_T_TRUE => try a.dupe(u8, "true"),
fio.FIOBJ_T_FALSE => .{ .str = "false", .freeme = false }, fio.FIOBJ_T_FALSE => try a.dupe(u8, "false"),
// according to fio2str above, the orelse should never happen // 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_NUMBER => try a.dupe(u8, fio2str(o) orelse "null"),
fio.FIOBJ_T_FLOAT => .{ .str = try a.dupe(u8, fio2str(o) orelse "null"), .freeme = true, .allocator = a }, fio.FIOBJ_T_FLOAT => try a.dupe(u8, fio2str(o) orelse "null"),
// the string comes out of the request, so it is safe to not make a copy // if the string comes out of the request, 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 }, fio.FIOBJ_T_STRING => try a.dupe(u8, fio2str(o) orelse ""),
else => .{ .str = "unknown_type", .freeme = false }, else => try a.dupe(u8, "unknown_type"),
}; };
} }

View file

@ -170,11 +170,10 @@ pub const HttpListenerSettings = struct {
pub const HttpListener = struct { pub const HttpListener = struct {
settings: HttpListenerSettings, settings: HttpListenerSettings,
const Self = @This();
var the_one_and_only_listener: ?*HttpListener = null; var the_one_and_only_listener: ?*HttpListener = null;
/// Create a listener /// Create a listener
pub fn init(settings: HttpListenerSettings) Self { pub fn init(settings: HttpListenerSettings) HttpListener {
std.debug.assert(settings.on_request != null); std.debug.assert(settings.on_request != null);
return .{ return .{
.settings = settings, .settings = settings,
@ -264,7 +263,7 @@ pub const HttpListener = struct {
} }
/// Start listening /// Start listening
pub fn listen(self: *Self) !void { pub fn listen(self: *HttpListener) !void {
var pfolder: [*c]const u8 = null; var pfolder: [*c]const u8 = null;
var pfolder_len: usize = 0; var pfolder_len: usize = 0;
@ -275,10 +274,10 @@ pub const HttpListener = struct {
} }
const x: fio.http_settings_s = .{ const x: fio.http_settings_s = .{
.on_request = if (self.settings.on_request) |_| Self.theOneAndOnlyRequestCallBack else null, .on_request = if (self.settings.on_request) |_| HttpListener.theOneAndOnlyRequestCallBack else null,
.on_upgrade = if (self.settings.on_upgrade) |_| Self.theOneAndOnlyUpgradeCallBack else null, .on_upgrade = if (self.settings.on_upgrade) |_| HttpListener.theOneAndOnlyUpgradeCallBack else null,
.on_response = if (self.settings.on_response) |_| Self.theOneAndOnlyResponseCallBack else null, .on_response = if (self.settings.on_response) |_| HttpListener.theOneAndOnlyResponseCallBack else null,
.on_finish = if (self.settings.on_finish) |_| Self.theOneAndOnlyFinishCallBack else null, .on_finish = if (self.settings.on_finish) |_| HttpListener.theOneAndOnlyFinishCallBack else null,
.udata = null, .udata = null,
.public_folder = pfolder, .public_folder = pfolder,
.public_folder_length = pfolder_len, .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 // 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 // first request(s) might not be serviced, as long as it takes from
// fio.http_listen() to here // 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, keepalive_timeout_s: u8 = 5,
log: bool = false, log: bool = false,
const Self = @This();
/// Create settings with defaults /// Create settings with defaults
pub fn init() Self { pub fn init() ListenSettings {
return .{}; return .{};
} }
}; };