1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-21 07:34:08 +00:00

Compare commits

..

No commits in common. "905b376c21550b04431db0a60b25586f3682a705" and "ec7cac6f6ab8e1892fe6fc499fd37cd93f7b2256" have entirely different histories.

15 changed files with 103 additions and 400 deletions

View file

@ -298,7 +298,7 @@ In your zig project folder (where `build.zig` is located), run:
<!-- INSERT_DEP_BEGIN --> <!-- INSERT_DEP_BEGIN -->
``` ```
zig fetch --save "git+https://github.com/zigzap/zap#v0.10.4" zig fetch --save "git+https://github.com/zigzap/zap#v0.10.1"
``` ```
<!-- INSERT_DEP_END --> <!-- INSERT_DEP_END -->
@ -409,6 +409,3 @@ pub fn main() !void {

View file

@ -186,28 +186,6 @@ pub fn build(b: *std.Build) !void {
const run_sendfile_tests = b.addRunArtifact(sendfile_tests); const run_sendfile_tests = b.addRunArtifact(sendfile_tests);
const install_sendfile_tests = b.addInstallArtifact(sendfile_tests, .{}); const install_sendfile_tests = b.addInstallArtifact(sendfile_tests, .{});
const recvfile_tests = b.addTest(.{
.name = "recv_tests",
.root_source_file = b.path("src/tests/test_recvfile.zig"),
.target = target,
.optimize = optimize,
});
recvfile_tests.root_module.addImport("zap", zap_module);
const run_recvfile_tests = b.addRunArtifact(recvfile_tests);
const install_recvfile_tests = b.addInstallArtifact(recvfile_tests, .{});
const recvfile_notype_tests = b.addTest(.{
.name = "recv_tests",
.root_source_file = b.path("src/tests/test_recvfile_notype.zig"),
.target = target,
.optimize = optimize,
});
recvfile_notype_tests.root_module.addImport("zap", zap_module);
const run_recvfile_notype_tests = b.addRunArtifact(recvfile_notype_tests);
const install_recvfile_notype_tests = b.addInstallArtifact(recvfile_notype_tests, .{});
// test commands // test commands
const run_auth_test_step = b.step("test-authentication", "Run auth unit tests [REMOVE zig-cache!]"); const run_auth_test_step = b.step("test-authentication", "Run auth unit tests [REMOVE zig-cache!]");
run_auth_test_step.dependOn(&run_auth_tests.step); run_auth_test_step.dependOn(&run_auth_tests.step);
@ -225,14 +203,6 @@ pub fn build(b: *std.Build) !void {
run_sendfile_test_step.dependOn(&run_sendfile_tests.step); run_sendfile_test_step.dependOn(&run_sendfile_tests.step);
run_sendfile_test_step.dependOn(&install_sendfile_tests.step); run_sendfile_test_step.dependOn(&install_sendfile_tests.step);
const run_recvfile_test_step = b.step("test-recvfile", "Run http param unit tests [REMOVE zig-cache!]");
run_recvfile_test_step.dependOn(&run_recvfile_tests.step);
run_recvfile_test_step.dependOn(&install_recvfile_tests.step);
const run_recvfile_notype_test_step = b.step("test-recvfile_notype", "Run http param unit tests [REMOVE zig-cache!]");
run_recvfile_notype_test_step.dependOn(&run_recvfile_notype_tests.step);
run_recvfile_notype_test_step.dependOn(&install_recvfile_notype_tests.step);
// Similar to creating the run step earlier, this exposes a `test` step to // Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the participant to request // the `zig build --help` menu, providing a way for the participant to request
// running the unit tests. // running the unit tests.
@ -241,8 +211,6 @@ pub fn build(b: *std.Build) !void {
test_step.dependOn(&run_mustache_tests.step); test_step.dependOn(&run_mustache_tests.step);
test_step.dependOn(&run_httpparams_tests.step); test_step.dependOn(&run_httpparams_tests.step);
test_step.dependOn(&run_sendfile_tests.step); test_step.dependOn(&run_sendfile_tests.step);
test_step.dependOn(&run_recvfile_tests.step);
test_step.dependOn(&run_recvfile_notype_tests.step);
// //
// docserver // docserver

View file

@ -1,6 +1,6 @@
.{ .{
.name = .zap, .name = .zap,
.version = "0.10.4", .version = "0.9.1",
.paths = .{ .paths = .{
"build.zig", "build.zig",
"build.zig.zon", "build.zig.zon",

View file

@ -58,6 +58,14 @@ const MyEndpoint = struct {
); );
try r.sendBody(response); try r.sendBody(response);
} }
// not implemented, don't care
pub fn post(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn put(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn delete(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn patch(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn options(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn head(_: *MyEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
}; };
pub fn main() !void { pub fn main() !void {
@ -75,8 +83,8 @@ pub fn main() !void {
// App is the type // App is the type
// app is the instance // app is the instance
const App = zap.App.Create(MyContext); const App = zap.App.Create(MyContext);
try App.init(allocator, &my_context, .{}); var app = try App.init(allocator, &my_context, .{});
defer App.deinit(); defer app.deinit();
// create mini endpoint // create mini endpoint
var ep: MyEndpoint = .{ var ep: MyEndpoint = .{
@ -93,10 +101,10 @@ pub fn main() !void {
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
// make the authenticating endpoint known to the app // make the authenticating endpoint known to the app
try App.register(&auth_ep); try app.register(&auth_ep);
// listen // listen
try App.listen(.{ try app.listen(.{
.interface = "0.0.0.0", .interface = "0.0.0.0",
.port = 3000, .port = 3000,
}); });

View file

@ -59,7 +59,15 @@ const SimpleEndpoint = struct {
try r.sendBody(response_text); try r.sendBody(response_text);
std.time.sleep(std.time.ns_per_ms * 300); std.time.sleep(std.time.ns_per_ms * 300);
} }
};
// empty stubs for all other request methods
pub fn post(_: *SimpleEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn put(_: *SimpleEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn delete(_: *SimpleEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn patch(_: *SimpleEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn options(_: *SimpleEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn head(_: *SimpleEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
};
const StopEndpoint = struct { const StopEndpoint = struct {
path: []const u8, path: []const u8,
@ -74,6 +82,12 @@ const StopEndpoint = struct {
, .{context.*.db_connection}); , .{context.*.db_connection});
zap.stop(); zap.stop();
} }
pub fn post(_: *StopEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn put(_: *StopEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn delete(_: *StopEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn patch(_: *StopEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn options(_: *StopEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
}; };
pub fn main() !void { pub fn main() !void {
@ -90,19 +104,19 @@ pub fn main() !void {
// create an App instance // create an App instance
const App = zap.App.Create(MyContext); const App = zap.App.Create(MyContext);
try App.init(allocator, &my_context, .{}); var app = try App.init(allocator, &my_context, .{});
defer App.deinit(); defer app.deinit();
// create the endpoints // create the endpoints
var my_endpoint = SimpleEndpoint.init("/test", "some endpoint specific data"); var my_endpoint = SimpleEndpoint.init("/test", "some endpoint specific data");
var stop_endpoint: StopEndpoint = .{ .path = "/stop" }; var stop_endpoint: StopEndpoint = .{ .path = "/stop" };
// //
// register the endpoints with the App // register the endpoints with the app
try App.register(&my_endpoint); try app.register(&my_endpoint);
try App.register(&stop_endpoint); try app.register(&stop_endpoint);
// listen on the network // listen on the network
try App.listen(.{ try app.listen(.{
.interface = "0.0.0.0", .interface = "0.0.0.0",
.port = 3000, .port = 3000,
}); });

View file

@ -86,24 +86,24 @@ pub fn main() !void {
defer std.debug.print("\n\nLeaks detected: {}\n\n", .{gpa.deinit() != .ok}); defer std.debug.print("\n\nLeaks detected: {}\n\n", .{gpa.deinit() != .ok});
const allocator = gpa.allocator(); const allocator = gpa.allocator();
// create an App context // create an app context
var my_context: MyContext = .{ .db_connection = "db connection established!" }; var my_context: MyContext = .{ .db_connection = "db connection established!" };
// create an App instance // create an App instance
const App = zap.App.Create(MyContext); const App = zap.App.Create(MyContext);
try App.init(allocator, &my_context, .{}); var app = try App.init(allocator, &my_context, .{});
defer App.deinit(); defer app.deinit();
// create the endpoints // create the endpoints
var my_endpoint = ErrorEndpoint.init("/error", "some endpoint specific data"); var my_endpoint = ErrorEndpoint.init("/error", "some endpoint specific data");
var stop_endpoint: StopEndpoint = .{ .path = "/stop" }; var stop_endpoint: StopEndpoint = .{ .path = "/stop" };
// //
// register the endpoints with the App // register the endpoints with the app
try App.register(&my_endpoint); try app.register(&my_endpoint);
try App.register(&stop_endpoint); try app.register(&stop_endpoint);
// listen on the network // listen on the network
try App.listen(.{ try app.listen(.{
.interface = "0.0.0.0", .interface = "0.0.0.0",
.port = 3000, .port = 3000,
}); });

View file

@ -25,7 +25,7 @@ const Handler = struct {
}; };
if (r.body) |body| { if (r.body) |body| {
std.log.info("Body length is {any}", .{body.len}); std.log.info("Body length is {any}\n", .{body.len});
} }
// parse potential query params (for ?terminate=true) // parse potential query params (for ?terminate=true)
@ -43,7 +43,7 @@ const Handler = struct {
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}", .{ kv.key, 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| {
@ -51,9 +51,9 @@ const Handler = struct {
const mimetype = file.mimetype orelse "(no mimetype)"; const mimetype = file.mimetype orelse "(no mimetype)";
const data = file.data orelse ""; const data = file.data orelse "";
std.log.debug(" filename: `{s}`", .{filename}); std.log.debug(" filename: `{s}`\n", .{filename});
std.log.debug(" mimetype: {s}", .{mimetype}); std.log.debug(" mimetype: {s}\n", .{mimetype});
std.log.debug(" contents: {any}", .{data}); std.log.debug(" contents: {any}\n", .{data});
}, },
// multi-file upload // multi-file upload
zap.Request.HttpParam.Array_Binfile => |*files| { zap.Request.HttpParam.Array_Binfile => |*files| {
@ -62,9 +62,9 @@ const Handler = struct {
const mimetype = file.mimetype orelse "(no mimetype)"; const mimetype = file.mimetype orelse "(no mimetype)";
const data = file.data orelse ""; const data = file.data orelse "";
std.log.debug(" filename: `{s}`", .{filename}); std.log.debug(" filename: `{s}`\n", .{filename});
std.log.debug(" mimetype: {s}", .{mimetype}); std.log.debug(" mimetype: {s}\n", .{mimetype});
std.log.debug(" contents: {any}", .{data}); std.log.debug(" contents: {any}\n", .{data});
} }
files.*.deinit(); files.*.deinit();
}, },
@ -79,7 +79,7 @@ const Handler = struct {
// check if we received a terminate=true parameter // check if we received a terminate=true parameter
if (r.getParamSlice("terminate")) |str| { if (r.getParamSlice("terminate")) |str| {
std.log.info("?terminate={s}", .{str}); std.log.info("?terminate={s}\n", .{str});
if (std.mem.eql(u8, str, "true")) { if (std.mem.eql(u8, str, "true")) {
zap.stop(); zap.stop();
} }
@ -110,9 +110,9 @@ pub fn main() !void {
); );
try listener.listen(); try listener.listen();
std.log.info("\n\nURL is http://localhost:3000", .{}); std.log.info("\n\nURL is http://localhost:3000\n", .{});
std.log.info("\ncurl -v --request POST -F img=@test012345.bin http://127.0.0.1:3000", .{}); std.log.info("\ncurl -v --request POST -F img=@test012345.bin http://127.0.0.1:3000\n", .{});
std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true", .{}); std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true\n", .{});
zap.start(.{ zap.start(.{
.threads = 1, .threads = 1,

View file

@ -87,8 +87,8 @@ pub fn main() !void {
std.log.info("Cookie ZIG_ZAP not found!", .{}); std.log.info("Cookie ZIG_ZAP not found!", .{});
} }
} else |err| { } else |err| {
std.log.err("ERROR!", .{}); std.log.err("ERROR!\n", .{});
std.log.err("cannot check for `ZIG_ZAP` cookie: {any}", .{err}); std.log.err("cannot check for `ZIG_ZAP` cookie: {any}\n", .{err});
} }
r.setCookie(.{ r.setCookie(.{
@ -101,8 +101,8 @@ pub fn main() !void {
// //
// check out other params: domain, path, secure, http_only // check out other params: domain, path, secure, http_only
}) catch |err| { }) catch |err| {
std.log.err("ERROR!", .{}); std.log.err("ERROR!\n", .{});
std.log.err("cannot set cookie: {any}", .{err}); std.log.err("cannot set cookie: {any}\n", .{err});
}; };
r.sendBody("Hello") catch unreachable; r.sendBody("Hello") catch unreachable;

View file

@ -86,5 +86,5 @@ pub fn main() !void {
// show potential memory leaks when ZAP is shut down // show potential memory leaks when ZAP is shut down
const has_leaked = gpa.detectLeaks(); const has_leaked = gpa.detectLeaks();
std.log.debug("Has leaked: {}", .{has_leaked}); std.log.debug("Has leaked: {}\n", .{has_leaked});
} }

View file

@ -111,7 +111,7 @@ pub fn main() !void {
std.log.info("Param one not found!", .{}); std.log.info("Param one not found!", .{});
} }
} else |err| { } else |err| {
std.log.err("cannot check for `one` param: {any}", .{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
@ -137,7 +137,7 @@ pub fn main() !void {
); );
try listener.listen(); try listener.listen();
std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true", .{}); std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true\n", .{});
const thread = try makeRequestThread(allocator, "http://127.0.0.1:3000/?one=1&two=2&string=hello+world&float=6.28&bool=true"); const thread = try makeRequestThread(allocator, "http://127.0.0.1:3000/?one=1&two=2&string=hello+world&float=6.28&bool=true");
defer thread.join(); defer thread.join();

View file

@ -110,15 +110,14 @@ pub fn Create(
} }
pub fn onRequest(self: *Bound, arena: Allocator, app_context: *Context, r: Request) !void { pub fn onRequest(self: *Bound, arena: Allocator, app_context: *Context, r: Request) !void {
// TODO: simplitfy this with @tagName?
const ret = switch (r.methodAsEnum()) { const ret = switch (r.methodAsEnum()) {
.GET => callHandlerIfExist("get", self.endpoint, arena, app_context, r), .GET => self.endpoint.*.get(arena, app_context, r),
.POST => callHandlerIfExist("post", self.endpoint, arena, app_context, r), .POST => self.endpoint.*.post(arena, app_context, r),
.PUT => callHandlerIfExist("put", self.endpoint, arena, app_context, r), .PUT => self.endpoint.*.put(arena, app_context, r),
.DELETE => callHandlerIfExist("delete", self.endpoint, arena, app_context, r), .DELETE => self.endpoint.*.delete(arena, app_context, r),
.PATCH => callHandlerIfExist("patch", self.endpoint, arena, app_context, r), .PATCH => self.endpoint.*.patch(arena, app_context, r),
.OPTIONS => callHandlerIfExist("options", self.endpoint, arena, app_context, r), .OPTIONS => self.endpoint.*.options(arena, app_context, r),
.HEAD => callHandlerIfExist("head", self.endpoint, arena, app_context, r), .HEAD => self.endpoint.*.head(arena, app_context, r),
else => error.UnsupportedHtmlRequestMethod, else => error.UnsupportedHtmlRequestMethod,
}; };
if (ret) { if (ret) {
@ -228,6 +227,8 @@ pub fn Create(
if (ret_info.error_union.payload != void) { if (ret_info.error_union.payload != void) {
@compileError("Expected return type of method `" ++ @typeName(T) ++ "." ++ method ++ "` to be !void, got: !" ++ @typeName(ret_info.error_union.payload)); @compileError("Expected return type of method `" ++ @typeName(T) ++ "." ++ method ++ "` to be !void, got: !" ++ @typeName(ret_info.error_union.payload));
} }
} else {
@compileError(@typeName(T) ++ " has no method named `" ++ method ++ "`");
} }
} }
} }
@ -256,8 +257,8 @@ pub fn Create(
/// Authenticates GET requests using the Authenticator. /// Authenticates GET requests using the Authenticator.
pub fn get(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void { pub fn get(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("get", self.ep, arena, context, request), .AuthOK => self.ep.*.get(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -265,8 +266,8 @@ pub fn Create(
/// Authenticates POST requests using the Authenticator. /// Authenticates POST requests using the Authenticator.
pub fn post(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void { pub fn post(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("post", self.ep, arena, context, request), .AuthOK => self.ep.*.post(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -274,8 +275,8 @@ pub fn Create(
/// Authenticates PUT requests using the Authenticator. /// Authenticates PUT requests using the Authenticator.
pub fn put(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void { pub fn put(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("put", self.ep, arena, context, request), .AuthOK => self.ep.*.put(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -283,8 +284,8 @@ pub fn Create(
/// Authenticates DELETE requests using the Authenticator. /// Authenticates DELETE requests using the Authenticator.
pub fn delete(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void { pub fn delete(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("delete", self.ep, arena, context, request), .AuthOK => self.ep.*.delete(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -292,8 +293,8 @@ pub fn Create(
/// Authenticates PATCH requests using the Authenticator. /// Authenticates PATCH requests using the Authenticator.
pub fn patch(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void { pub fn patch(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("patch", self.ep, arena, context, request), .AuthOK => self.ep.*.patch(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -301,8 +302,8 @@ pub fn Create(
/// Authenticates OPTIONS requests using the Authenticator. /// Authenticates OPTIONS requests using the Authenticator.
pub fn options(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void { pub fn options(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("options", self.ep, arena, context, request), .AuthOK => self.ep.*.put(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -310,8 +311,8 @@ pub fn Create(
/// Authenticates HEAD requests using the Authenticator. /// Authenticates HEAD requests using the Authenticator.
pub fn head(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void { pub fn head(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("head", self.ep, arena, context, request), .AuthOK => self.ep.*.head(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -331,7 +332,7 @@ pub fn Create(
tls: ?zap.Tls = null, tls: ?zap.Tls = null,
}; };
pub fn init(gpa_: Allocator, context_: *Context, opts_: AppOpts) !void { pub fn init(gpa_: Allocator, context_: *Context, opts_: AppOpts) !App {
if (_static.there_can_be_only_one) { if (_static.there_can_be_only_one) {
return error.OnlyOneAppAllowed; return error.OnlyOneAppAllowed;
} }
@ -359,9 +360,10 @@ pub fn Create(
} }
_static.unhandled_error = Context.unhandledError; _static.unhandled_error = Context.unhandledError;
} }
return .{};
} }
pub fn deinit() void { pub fn deinit(_: *App) void {
// we created endpoint wrappers but only tracked their interfaces // we created endpoint wrappers but only tracked their interfaces
// hence, we need to destroy the wrappers through their interfaces // hence, we need to destroy the wrappers through their interfaces
if (false) { if (false) {
@ -387,21 +389,6 @@ pub fn Create(
_static.track_arenas.deinit(_static.gpa); _static.track_arenas.deinit(_static.gpa);
} }
// This can be resolved at comptime so *perhaps it does affect optimiazation
pub fn callHandlerIfExist(comptime fn_name: []const u8, e: anytype, arena: Allocator, ctx: *Context, r: Request) anyerror!void {
const EndPoint = @TypeOf(e.*);
if (@hasDecl(EndPoint, fn_name)) {
return @field(EndPoint, fn_name)(e, arena, ctx, r);
}
zap.log.debug(
"Unhandled `{s}` {s} request ({s} not implemented in {s})",
.{ r.method orelse "<unknown>", r.path orelse "", fn_name, @typeName(Endpoint) },
);
r.setStatus(.method_not_allowed);
try r.sendBody("405 - method not allowed\r\n");
return;
}
pub fn get_arena() !*ArenaAllocator { pub fn get_arena() !*ArenaAllocator {
const thread_id = std.Thread.getCurrentId(); const thread_id = std.Thread.getCurrentId();
_static.track_arena_lock.lockShared(); _static.track_arena_lock.lockShared();
@ -424,7 +411,7 @@ pub fn Create(
/// If you try to register an endpoint whose path would shadow an /// If you try to register an endpoint whose path would shadow an
/// already registered one, you will receive an /// already registered one, you will receive an
/// EndpointPathShadowError. /// EndpointPathShadowError.
pub fn register(endpoint: anytype) !void { pub fn register(_: *App, endpoint: anytype) !void {
for (_static.endpoints.items) |other| { for (_static.endpoints.items) |other| {
if (std.mem.startsWith( if (std.mem.startsWith(
u8, u8,
@ -445,7 +432,7 @@ pub fn Create(
try _static.endpoints.append(_static.gpa, &bound.interface); try _static.endpoints.append(_static.gpa, &bound.interface);
} }
pub fn listen(l: ListenerSettings) !void { pub fn listen(_: *App, l: ListenerSettings) !void {
_static.listener = HttpListener.init(.{ _static.listener = HttpListener.init(.{
.interface = l.interface, .interface = l.interface,
.port = l.port, .port = l.port,

View file

@ -440,9 +440,9 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
.value = "invalid", .value = "invalid",
.max_age_s = -1, .max_age_s = -1,
})) { })) {
zap.debug("logout ok", .{}); zap.debug("logout ok\n", .{});
} else |err| { } else |err| {
zap.debug("logout cookie setting failed: {any}", .{err}); zap.debug("logout cookie setting failed: {any}\n", .{err});
} }
r.parseCookies(false); r.parseCookies(false);

View file

@ -125,43 +125,36 @@ fn parseBinfilesFrom(a: Allocator, o: fio.FIOBJ) !HttpParam {
fio.fiobj_free_wrapped(key_name); fio.fiobj_free_wrapped(key_name);
fio.fiobj_free_wrapped(key_data); fio.fiobj_free_wrapped(key_data);
fio.fiobj_free_wrapped(key_type); fio.fiobj_free_wrapped(key_type);
} // files: they should have "data" and "filename" keys } // files: they should have "data", "type", and "filename" keys
if (fio.fiobj_hash_haskey(o, key_data) == 1 and fio.fiobj_hash_haskey(o, key_name) == 1) { if (fio.fiobj_hash_haskey(o, key_data) == 1 and fio.fiobj_hash_haskey(o, key_type) == 1 and fio.fiobj_hash_haskey(o, key_name) == 1) {
const filename = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_name)); const filename = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_name));
const mimetype = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_type));
const data = fio.fiobj_hash_get(o, key_data); const data = fio.fiobj_hash_get(o, key_data);
var mimetype: []const u8 = undefined;
if (fio.fiobj_hash_haskey(o, key_type) == 1) {
const mt = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_type));
mimetype = mt.data[0..mt.len];
} else {
mimetype = &"application/octet-stream".*;
}
var data_slice: ?[]const u8 = null; var data_slice: ?[]const u8 = null;
switch (fio.fiobj_type(data)) { switch (fio.fiobj_type(data)) {
fio.FIOBJ_T_DATA => { fio.FIOBJ_T_DATA => {
if (fio.is_invalid(data) == 1) { if (fio.is_invalid(data) == 1) {
data_slice = "(zap: invalid data)"; data_slice = "(zap: invalid data)";
zap.log.warn("HTTP param binary file is not a data object", .{}); zap.log.warn("HTTP param binary file is not a data object\n", .{});
} else { } else {
// the data // the data
const data_len = fio.fiobj_data_len(data); const data_len = fio.fiobj_data_len(data);
var data_buf = fio.fiobj_data_read(data, data_len); var data_buf = fio.fiobj_data_read(data, data_len);
if (data_len < 0) { if (data_len < 0) {
zap.log.warn("HTTP param binary file size negative: {d}", .{data_len}); zap.log.warn("HTTP param binary file size negative: {d}\n", .{data_len});
zap.log.warn("FIOBJ_TYPE of data is: {d}", .{fio.fiobj_type(data)}); zap.log.warn("FIOBJ_TYPE of data is: {d}\n", .{fio.fiobj_type(data)});
} else { } else {
if (data_buf.len != data_len) { if (data_buf.len != data_len) {
zap.log.warn("HTTP param binary file size mismatch: should {d}, is: {d}", .{ data_len, data_buf.len }); zap.log.warn("HTTP param binary file size mismatch: should {d}, is: {d}\n", .{ data_len, data_buf.len });
} }
if (data_buf.len > 0) { if (data_buf.len > 0) {
data_slice = data_buf.data[0..data_buf.len]; data_slice = data_buf.data[0..data_buf.len];
} else { } else {
zap.log.warn("HTTP param binary file buffer size negative: {d}", .{data_buf.len}); zap.log.warn("HTTP param binary file buffer size negative: {d}\n", .{data_buf.len});
data_slice = "(zap: invalid data: negative BUFFER size)"; data_slice = "(zap: invalid data: negative BUFFER size)";
} }
} }
@ -228,7 +221,7 @@ fn parseBinfilesFrom(a: Allocator, o: fio.FIOBJ) !HttpParam {
return .{ .Hash_Binfile = .{ return .{ .Hash_Binfile = .{
.filename = filename.data[0..filename.len], .filename = filename.data[0..filename.len],
.mimetype = mimetype, .mimetype = mimetype.data[0..mimetype.len],
.data = data_slice, .data = data_slice,
} }; } };
} else { } else {
@ -386,7 +379,7 @@ pub fn setContentType(self: *const Request, c: ContentType) HttpError!void {
.JSON => "application/json", .JSON => "application/json",
else => "text/html", else => "text/html",
}; };
zap.log.debug("setting content-type to {s}", .{s}); zap.log.debug("setting content-type to {s}\n", .{s});
return self.setHeader("content-type", s); return self.setHeader("content-type", s);
} }
@ -518,7 +511,7 @@ pub fn setHeader(self: *const Request, name: []const u8, value: []const u8) Http
// FIXME without the following if, we get errors in release builds // FIXME without the following if, we get errors in release builds
// at least we don't have to log unconditionally // at least we don't have to log unconditionally
if (ret == -1) { if (ret == -1) {
zap.log.debug("***************** zap.zig:274", .{}); zap.log.debug("***************** zap.zig:274\n", .{});
} }
if (ret == 0) return; if (ret == 0) return;
@ -687,7 +680,7 @@ pub fn setCookie(self: *const Request, args: CookieArgs) HttpError!void {
// TODO: still happening? // TODO: still happening?
const ret = fio.http_set_cookie(self.h, c); const ret = fio.http_set_cookie(self.h, c);
if (ret == -1) { if (ret == -1) {
zap.log.err("fio.http_set_cookie returned: {}", .{ret}); zap.log.err("fio.http_set_cookie returned: {}\n", .{ret});
return error.SetCookie; return error.SetCookie;
} }
} }

View file

@ -1,133 +0,0 @@
const std = @import("std");
const zap = @import("zap");
// set default log level to .info and ZAP log level to .debug
pub const std_options: std.Options = .{
.log_level = .info,
.log_scope_levels = &[_]std.log.ScopeLevel{
.{ .scope = .zap, .level = .debug },
},
};
const BOUNDARY = "---XcPmntPm3EGd9NaxNUPFFL";
const PARAM_NAME = "file";
const EXPECTED_CONTENT = "Hello, this is a test file.";
const EXPECTED_MIMETYPE = "text/plain";
const EXPECTED_FILENAME = "myfile.txt";
var test_error: ?anyerror = null;
fn makeRequest(allocator: std.mem.Allocator, url: []const u8) !void {
var http_client: std.http.Client = .{ .allocator = allocator };
defer http_client.deinit();
const payload_wrong_line_ending = try std.fmt.allocPrint(allocator,
\\--{s}
\\Content-Disposition: form-data; name="{s}"; filename="{s}"
\\Content-Type: {s}
\\
\\{s}
\\--{s}--
\\
, .{ BOUNDARY, PARAM_NAME, EXPECTED_FILENAME, EXPECTED_MIMETYPE, EXPECTED_CONTENT.*, BOUNDARY });
defer allocator.free(payload_wrong_line_ending);
const payload = try std.mem.replaceOwned(u8, allocator, payload_wrong_line_ending, "\n", "\r\n");
defer allocator.free(payload);
const request_content_type = try std.fmt.allocPrint(
allocator,
"multipart/form-data; boundary={s}",
.{BOUNDARY},
);
defer allocator.free(request_content_type);
// Allocate a buffer for server headers
var buf: [4096]u8 = undefined;
_ = try http_client.fetch(.{
.method = .POST,
.location = .{ .url = url },
.headers = .{
.content_type = .{ .override = request_content_type },
},
.payload = payload,
.server_header_buffer = &buf,
});
zap.stop();
}
pub fn on_request(r: zap.Request) !void {
on_request_inner(r) catch |err| {
test_error = err;
return;
};
}
pub fn on_request_inner(r: zap.Request) !void {
try r.parseBody();
const params = try r.parametersToOwnedList(std.testing.allocator);
defer params.deinit();
std.testing.expect(params.items.len == 1) catch |err| {
std.debug.print("Expected exactly one parameter, got {}\n", .{params.items.len});
return err;
};
const param = params.items[0];
std.testing.expect(param.value != null) catch |err| {
std.debug.print("Expected parameter value to be non-null, got null\n", .{});
return err;
};
const value = param.value.?;
std.testing.expect(value == .Hash_Binfile) catch |err| {
std.debug.print("Expected parameter type to be Hash_Binfile, got {}\n", .{value});
return err;
};
const file = value.Hash_Binfile;
std.testing.expect(file.data != null) catch |err| {
std.debug.print("Expected file data to be non-null, got null\n", .{});
return err;
};
std.testing.expect(file.mimetype != null) catch |err| {
std.debug.print("Expected file mimetype to be non-null, got null\n", .{});
return err;
};
std.testing.expect(file.filename != null) catch |err| {
std.debug.print("Expected file filename to be non-null, got null\n", .{});
return err;
};
// These will print the error if the test fails
try std.testing.expectEqualStrings(file.data.?, &EXPECTED_CONTENT.*);
try std.testing.expectEqualStrings(file.mimetype.?, &EXPECTED_MIMETYPE.*);
try std.testing.expectEqualStrings(file.filename.?, &EXPECTED_FILENAME.*);
}
test "recv file" {
const allocator = std.testing.allocator;
var listener = zap.HttpListener.init(
.{
.port = 3003,
.on_request = on_request,
.log = false,
.max_clients = 10,
.max_body_size = 1 * 1024,
},
);
try listener.listen();
const t1 = try std.Thread.spawn(.{}, makeRequest, .{ allocator, "http://127.0.0.1:3003" });
defer t1.join();
zap.start(.{
.threads = 1,
.workers = 1,
});
if (test_error) |err| {
return err;
}
}

View file

@ -1,131 +0,0 @@
const std = @import("std");
const zap = @import("zap");
// set default log level to .info and ZAP log level to .debug
pub const std_options: std.Options = .{
.log_level = .info,
.log_scope_levels = &[_]std.log.ScopeLevel{
.{ .scope = .zap, .level = .debug },
},
};
const BOUNDARY = "---XcPmntPm3EGd9NaxNUPFFL";
const PARAM_NAME = "file";
const EXPECTED_CONTENT = "Hello, this is a test file.";
const EXPECTED_MIMETYPE = "application/octet-stream";
const EXPECTED_FILENAME = "myfile.txt";
var test_error: ?anyerror = null;
fn makeRequest(allocator: std.mem.Allocator, url: []const u8) !void {
var http_client: std.http.Client = .{ .allocator = allocator };
defer http_client.deinit();
const payload_wrong_line_ending = try std.fmt.allocPrint(allocator,
\\--{s}
\\Content-Disposition: form-data; name={s}"; filename="{s}"
\\
\\{s}
\\--{s}--
\\
, .{ BOUNDARY, PARAM_NAME, EXPECTED_FILENAME, EXPECTED_CONTENT.*, BOUNDARY });
defer allocator.free(payload_wrong_line_ending);
const payload = try std.mem.replaceOwned(u8, allocator, payload_wrong_line_ending, "\n", "\r\n");
defer allocator.free(payload);
const request_content_type = try std.fmt.allocPrint(
allocator,
"multipart/form-data; boundary={s}",
.{BOUNDARY},
);
defer allocator.free(request_content_type);
// Allocate a buffer for server headers
var buf: [4096]u8 = undefined;
_ = try http_client.fetch(.{
.method = .POST,
.location = .{ .url = url },
.headers = .{
.content_type = .{ .override = request_content_type },
},
.payload = payload,
.server_header_buffer = &buf,
});
zap.stop();
}
pub fn on_request(r: zap.Request) !void {
on_request_inner(r) catch |err| {
test_error = err;
return;
};
}
pub fn on_request_inner(r: zap.Request) !void {
try r.parseBody();
const params = try r.parametersToOwnedList(std.testing.allocator);
defer params.deinit();
std.testing.expect(params.items.len == 1) catch |err| {
std.debug.print("Expected exactly one parameter, got {}\n", .{params.items.len});
return err;
};
const param = params.items[0];
std.testing.expect(param.value != null) catch |err| {
std.debug.print("Expected parameter value to be non-null, got null\n", .{});
return err;
};
const value = param.value.?;
std.testing.expect(value == .Hash_Binfile) catch |err| {
std.debug.print("Expected parameter type to be Hash_Binfile, got {}\n", .{value});
return err;
};
const file = value.Hash_Binfile;
std.testing.expect(file.data != null) catch |err| {
std.debug.print("Expected file data to be non-null, got null\n", .{});
return err;
};
std.testing.expect(file.mimetype != null) catch |err| {
std.debug.print("Expected file mimetype to be non-null, got null\n", .{});
return err;
};
std.testing.expect(file.filename != null) catch |err| {
std.debug.print("Expected file filename to be non-null, got null\n", .{});
return err;
};
// These will print the error if the test fails
try std.testing.expectEqualStrings(file.data.?, &EXPECTED_CONTENT.*);
try std.testing.expectEqualStrings(file.mimetype.?, &EXPECTED_MIMETYPE.*);
try std.testing.expectEqualStrings(file.filename.?, &EXPECTED_FILENAME.*);
}
test "recv file" {
const allocator = std.testing.allocator;
var listener = zap.HttpListener.init(
.{
.port = 3003,
.on_request = on_request,
.log = false,
.max_clients = 10,
.max_body_size = 1 * 1024,
},
);
try listener.listen();
const t1 = try std.Thread.spawn(.{}, makeRequest, .{ allocator, "http://127.0.0.1:3003" });
defer t1.join();
zap.start(.{
.threads = 1,
.workers = 1,
});
if (test_error) |err| {
return err;
}
}