mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 15:14:08 +00:00
Compare commits
14 commits
ec7cac6f6a
...
905b376c21
Author | SHA1 | Date | |
---|---|---|---|
![]() |
905b376c21 | ||
![]() |
87415e1686 | ||
![]() |
6105c2d4ed | ||
![]() |
ddcf3899c1 | ||
![]() |
0fc425a95d | ||
![]() |
ef523d7767 | ||
![]() |
29d339892e | ||
![]() |
35634a97cd | ||
![]() |
041ca3e301 | ||
![]() |
c0cc025eda | ||
![]() |
fd567f3c29 | ||
![]() |
283e0d60d0 | ||
![]() |
a7a904aea4 | ||
![]() |
4e1b50aca8 |
15 changed files with 400 additions and 103 deletions
|
@ -298,7 +298,7 @@ In your zig project folder (where `build.zig` is located), run:
|
|||
|
||||
<!-- INSERT_DEP_BEGIN -->
|
||||
```
|
||||
zig fetch --save "git+https://github.com/zigzap/zap#v0.10.1"
|
||||
zig fetch --save "git+https://github.com/zigzap/zap#v0.10.4"
|
||||
```
|
||||
<!-- INSERT_DEP_END -->
|
||||
|
||||
|
@ -409,3 +409,6 @@ pub fn main() !void {
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
32
build.zig
32
build.zig
|
@ -186,6 +186,28 @@ pub fn build(b: *std.Build) !void {
|
|||
const run_sendfile_tests = b.addRunArtifact(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
|
||||
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);
|
||||
|
@ -203,6 +225,14 @@ pub fn build(b: *std.Build) !void {
|
|||
run_sendfile_test_step.dependOn(&run_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
|
||||
// the `zig build --help` menu, providing a way for the participant to request
|
||||
// running the unit tests.
|
||||
|
@ -211,6 +241,8 @@ pub fn build(b: *std.Build) !void {
|
|||
test_step.dependOn(&run_mustache_tests.step);
|
||||
test_step.dependOn(&run_httpparams_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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.{
|
||||
.name = .zap,
|
||||
.version = "0.9.1",
|
||||
.version = "0.10.4",
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
|
|
|
@ -58,14 +58,6 @@ const MyEndpoint = struct {
|
|||
);
|
||||
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 {
|
||||
|
@ -83,8 +75,8 @@ pub fn main() !void {
|
|||
// App is the type
|
||||
// app is the instance
|
||||
const App = zap.App.Create(MyContext);
|
||||
var app = try App.init(allocator, &my_context, .{});
|
||||
defer app.deinit();
|
||||
try App.init(allocator, &my_context, .{});
|
||||
defer App.deinit();
|
||||
|
||||
// create mini endpoint
|
||||
var ep: MyEndpoint = .{
|
||||
|
@ -101,10 +93,10 @@ pub fn main() !void {
|
|||
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
||||
|
||||
// make the authenticating endpoint known to the app
|
||||
try app.register(&auth_ep);
|
||||
try App.register(&auth_ep);
|
||||
|
||||
// listen
|
||||
try app.listen(.{
|
||||
try App.listen(.{
|
||||
.interface = "0.0.0.0",
|
||||
.port = 3000,
|
||||
});
|
||||
|
|
|
@ -59,14 +59,6 @@ const SimpleEndpoint = struct {
|
|||
try r.sendBody(response_text);
|
||||
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 {
|
||||
|
@ -82,12 +74,6 @@ const StopEndpoint = struct {
|
|||
, .{context.*.db_connection});
|
||||
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 {
|
||||
|
@ -104,19 +90,19 @@ pub fn main() !void {
|
|||
|
||||
// create an App instance
|
||||
const App = zap.App.Create(MyContext);
|
||||
var app = try App.init(allocator, &my_context, .{});
|
||||
defer app.deinit();
|
||||
try App.init(allocator, &my_context, .{});
|
||||
defer App.deinit();
|
||||
|
||||
// create the endpoints
|
||||
var my_endpoint = SimpleEndpoint.init("/test", "some endpoint specific data");
|
||||
var stop_endpoint: StopEndpoint = .{ .path = "/stop" };
|
||||
//
|
||||
// register the endpoints with the app
|
||||
try app.register(&my_endpoint);
|
||||
try app.register(&stop_endpoint);
|
||||
// register the endpoints with the App
|
||||
try App.register(&my_endpoint);
|
||||
try App.register(&stop_endpoint);
|
||||
|
||||
// listen on the network
|
||||
try app.listen(.{
|
||||
try App.listen(.{
|
||||
.interface = "0.0.0.0",
|
||||
.port = 3000,
|
||||
});
|
||||
|
|
|
@ -86,24 +86,24 @@ pub fn main() !void {
|
|||
defer std.debug.print("\n\nLeaks detected: {}\n\n", .{gpa.deinit() != .ok});
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
// create an app context
|
||||
// create an App context
|
||||
var my_context: MyContext = .{ .db_connection = "db connection established!" };
|
||||
|
||||
// create an App instance
|
||||
const App = zap.App.Create(MyContext);
|
||||
var app = try App.init(allocator, &my_context, .{});
|
||||
defer app.deinit();
|
||||
try App.init(allocator, &my_context, .{});
|
||||
defer App.deinit();
|
||||
|
||||
// create the endpoints
|
||||
var my_endpoint = ErrorEndpoint.init("/error", "some endpoint specific data");
|
||||
var stop_endpoint: StopEndpoint = .{ .path = "/stop" };
|
||||
//
|
||||
// register the endpoints with the app
|
||||
try app.register(&my_endpoint);
|
||||
try app.register(&stop_endpoint);
|
||||
// register the endpoints with the App
|
||||
try App.register(&my_endpoint);
|
||||
try App.register(&stop_endpoint);
|
||||
|
||||
// listen on the network
|
||||
try app.listen(.{
|
||||
try App.listen(.{
|
||||
.interface = "0.0.0.0",
|
||||
.port = 3000,
|
||||
});
|
||||
|
|
|
@ -25,7 +25,7 @@ const Handler = struct {
|
|||
};
|
||||
|
||||
if (r.body) |body| {
|
||||
std.log.info("Body length is {any}\n", .{body.len});
|
||||
std.log.info("Body length is {any}", .{body.len});
|
||||
}
|
||||
|
||||
// parse potential query params (for ?terminate=true)
|
||||
|
@ -43,7 +43,7 @@ const Handler = struct {
|
|||
for (params.items) |kv| {
|
||||
if (kv.value) |v| {
|
||||
std.debug.print("\n", .{});
|
||||
std.log.info("Param `{s}` in owned list is {any}\n", .{ kv.key, v });
|
||||
std.log.info("Param `{s}` in owned list is {any}", .{ kv.key, v });
|
||||
switch (v) {
|
||||
// single-file upload
|
||||
zap.Request.HttpParam.Hash_Binfile => |*file| {
|
||||
|
@ -51,9 +51,9 @@ const Handler = struct {
|
|||
const mimetype = file.mimetype orelse "(no mimetype)";
|
||||
const data = file.data orelse "";
|
||||
|
||||
std.log.debug(" filename: `{s}`\n", .{filename});
|
||||
std.log.debug(" mimetype: {s}\n", .{mimetype});
|
||||
std.log.debug(" contents: {any}\n", .{data});
|
||||
std.log.debug(" filename: `{s}`", .{filename});
|
||||
std.log.debug(" mimetype: {s}", .{mimetype});
|
||||
std.log.debug(" contents: {any}", .{data});
|
||||
},
|
||||
// multi-file upload
|
||||
zap.Request.HttpParam.Array_Binfile => |*files| {
|
||||
|
@ -62,9 +62,9 @@ const Handler = struct {
|
|||
const mimetype = file.mimetype orelse "(no mimetype)";
|
||||
const data = file.data orelse "";
|
||||
|
||||
std.log.debug(" filename: `{s}`\n", .{filename});
|
||||
std.log.debug(" mimetype: {s}\n", .{mimetype});
|
||||
std.log.debug(" contents: {any}\n", .{data});
|
||||
std.log.debug(" filename: `{s}`", .{filename});
|
||||
std.log.debug(" mimetype: {s}", .{mimetype});
|
||||
std.log.debug(" contents: {any}", .{data});
|
||||
}
|
||||
files.*.deinit();
|
||||
},
|
||||
|
@ -79,7 +79,7 @@ const Handler = struct {
|
|||
|
||||
// check if we received a terminate=true parameter
|
||||
if (r.getParamSlice("terminate")) |str| {
|
||||
std.log.info("?terminate={s}\n", .{str});
|
||||
std.log.info("?terminate={s}", .{str});
|
||||
if (std.mem.eql(u8, str, "true")) {
|
||||
zap.stop();
|
||||
}
|
||||
|
@ -110,9 +110,9 @@ pub fn main() !void {
|
|||
);
|
||||
|
||||
try listener.listen();
|
||||
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\n", .{});
|
||||
std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true\n", .{});
|
||||
std.log.info("\n\nURL is http://localhost:3000", .{});
|
||||
std.log.info("\ncurl -v --request POST -F img=@test012345.bin http://127.0.0.1:3000", .{});
|
||||
std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true", .{});
|
||||
|
||||
zap.start(.{
|
||||
.threads = 1,
|
||||
|
|
|
@ -87,8 +87,8 @@ pub fn main() !void {
|
|||
std.log.info("Cookie ZIG_ZAP not found!", .{});
|
||||
}
|
||||
} else |err| {
|
||||
std.log.err("ERROR!\n", .{});
|
||||
std.log.err("cannot check for `ZIG_ZAP` cookie: {any}\n", .{err});
|
||||
std.log.err("ERROR!", .{});
|
||||
std.log.err("cannot check for `ZIG_ZAP` cookie: {any}", .{err});
|
||||
}
|
||||
|
||||
r.setCookie(.{
|
||||
|
@ -101,8 +101,8 @@ pub fn main() !void {
|
|||
//
|
||||
// check out other params: domain, path, secure, http_only
|
||||
}) catch |err| {
|
||||
std.log.err("ERROR!\n", .{});
|
||||
std.log.err("cannot set cookie: {any}\n", .{err});
|
||||
std.log.err("ERROR!", .{});
|
||||
std.log.err("cannot set cookie: {any}", .{err});
|
||||
};
|
||||
|
||||
r.sendBody("Hello") catch unreachable;
|
||||
|
|
|
@ -86,5 +86,5 @@ pub fn main() !void {
|
|||
|
||||
// show potential memory leaks when ZAP is shut down
|
||||
const has_leaked = gpa.detectLeaks();
|
||||
std.log.debug("Has leaked: {}\n", .{has_leaked});
|
||||
std.log.debug("Has leaked: {}", .{has_leaked});
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ pub fn main() !void {
|
|||
std.log.info("Param one not found!", .{});
|
||||
}
|
||||
} else |err| {
|
||||
std.log.err("cannot check for `one` param: {any}\n", .{err});
|
||||
std.log.err("cannot check for `one` param: {any}", .{err});
|
||||
}
|
||||
|
||||
// check if we received a terminate=true parameter
|
||||
|
@ -137,7 +137,7 @@ pub fn main() !void {
|
|||
);
|
||||
|
||||
try listener.listen();
|
||||
std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true\n", .{});
|
||||
std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=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();
|
||||
|
|
69
src/App.zig
69
src/App.zig
|
@ -110,14 +110,15 @@ pub fn Create(
|
|||
}
|
||||
|
||||
pub fn onRequest(self: *Bound, arena: Allocator, app_context: *Context, r: Request) !void {
|
||||
// TODO: simplitfy this with @tagName?
|
||||
const ret = switch (r.methodAsEnum()) {
|
||||
.GET => self.endpoint.*.get(arena, app_context, r),
|
||||
.POST => self.endpoint.*.post(arena, app_context, r),
|
||||
.PUT => self.endpoint.*.put(arena, app_context, r),
|
||||
.DELETE => self.endpoint.*.delete(arena, app_context, r),
|
||||
.PATCH => self.endpoint.*.patch(arena, app_context, r),
|
||||
.OPTIONS => self.endpoint.*.options(arena, app_context, r),
|
||||
.HEAD => self.endpoint.*.head(arena, app_context, r),
|
||||
.GET => callHandlerIfExist("get", self.endpoint, arena, app_context, r),
|
||||
.POST => callHandlerIfExist("post", self.endpoint, arena, app_context, r),
|
||||
.PUT => callHandlerIfExist("put", self.endpoint, arena, app_context, r),
|
||||
.DELETE => callHandlerIfExist("delete", self.endpoint, arena, app_context, r),
|
||||
.PATCH => callHandlerIfExist("patch", self.endpoint, arena, app_context, r),
|
||||
.OPTIONS => callHandlerIfExist("options", self.endpoint, arena, app_context, r),
|
||||
.HEAD => callHandlerIfExist("head", self.endpoint, arena, app_context, r),
|
||||
else => error.UnsupportedHtmlRequestMethod,
|
||||
};
|
||||
if (ret) {
|
||||
|
@ -227,8 +228,6 @@ pub fn Create(
|
|||
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));
|
||||
}
|
||||
} else {
|
||||
@compileError(@typeName(T) ++ " has no method named `" ++ method ++ "`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -257,8 +256,8 @@ pub fn Create(
|
|||
/// Authenticates GET requests using the Authenticator.
|
||||
pub fn get(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void {
|
||||
try switch (self.authenticator.authenticateRequest(&request)) {
|
||||
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
|
||||
.AuthOK => self.ep.*.get(arena, context, request),
|
||||
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||
.AuthOK => callHandlerIfExist("get", self.ep, arena, context, request),
|
||||
.Handled => {},
|
||||
};
|
||||
}
|
||||
|
@ -266,8 +265,8 @@ pub fn Create(
|
|||
/// Authenticates POST requests using the Authenticator.
|
||||
pub fn post(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void {
|
||||
try switch (self.authenticator.authenticateRequest(&request)) {
|
||||
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
|
||||
.AuthOK => self.ep.*.post(arena, context, request),
|
||||
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||
.AuthOK => callHandlerIfExist("post", self.ep, arena, context, request),
|
||||
.Handled => {},
|
||||
};
|
||||
}
|
||||
|
@ -275,8 +274,8 @@ pub fn Create(
|
|||
/// Authenticates PUT requests using the Authenticator.
|
||||
pub fn put(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
|
||||
try switch (self.authenticator.authenticateRequest(&request)) {
|
||||
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
|
||||
.AuthOK => self.ep.*.put(arena, context, request),
|
||||
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||
.AuthOK => callHandlerIfExist("put", self.ep, arena, context, request),
|
||||
.Handled => {},
|
||||
};
|
||||
}
|
||||
|
@ -284,8 +283,8 @@ pub fn Create(
|
|||
/// Authenticates DELETE requests using the Authenticator.
|
||||
pub fn delete(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
|
||||
try switch (self.authenticator.authenticateRequest(&request)) {
|
||||
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
|
||||
.AuthOK => self.ep.*.delete(arena, context, request),
|
||||
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||
.AuthOK => callHandlerIfExist("delete", self.ep, arena, context, request),
|
||||
.Handled => {},
|
||||
};
|
||||
}
|
||||
|
@ -293,8 +292,8 @@ pub fn Create(
|
|||
/// Authenticates PATCH requests using the Authenticator.
|
||||
pub fn patch(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
|
||||
try switch (self.authenticator.authenticateRequest(&request)) {
|
||||
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
|
||||
.AuthOK => self.ep.*.patch(arena, context, request),
|
||||
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||
.AuthOK => callHandlerIfExist("patch", self.ep, arena, context, request),
|
||||
.Handled => {},
|
||||
};
|
||||
}
|
||||
|
@ -302,8 +301,8 @@ pub fn Create(
|
|||
/// Authenticates OPTIONS requests using the Authenticator.
|
||||
pub fn options(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
|
||||
try switch (self.authenticator.authenticateRequest(&request)) {
|
||||
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
|
||||
.AuthOK => self.ep.*.put(arena, context, request),
|
||||
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||
.AuthOK => callHandlerIfExist("options", self.ep, arena, context, request),
|
||||
.Handled => {},
|
||||
};
|
||||
}
|
||||
|
@ -311,8 +310,8 @@ pub fn Create(
|
|||
/// Authenticates HEAD requests using the Authenticator.
|
||||
pub fn head(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
|
||||
try switch (self.authenticator.authenticateRequest(&request)) {
|
||||
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
|
||||
.AuthOK => self.ep.*.head(arena, context, request),
|
||||
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||
.AuthOK => callHandlerIfExist("head", self.ep, arena, context, request),
|
||||
.Handled => {},
|
||||
};
|
||||
}
|
||||
|
@ -332,7 +331,7 @@ pub fn Create(
|
|||
tls: ?zap.Tls = null,
|
||||
};
|
||||
|
||||
pub fn init(gpa_: Allocator, context_: *Context, opts_: AppOpts) !App {
|
||||
pub fn init(gpa_: Allocator, context_: *Context, opts_: AppOpts) !void {
|
||||
if (_static.there_can_be_only_one) {
|
||||
return error.OnlyOneAppAllowed;
|
||||
}
|
||||
|
@ -360,10 +359,9 @@ pub fn Create(
|
|||
}
|
||||
_static.unhandled_error = Context.unhandledError;
|
||||
}
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn deinit(_: *App) void {
|
||||
pub fn deinit() void {
|
||||
// we created endpoint wrappers but only tracked their interfaces
|
||||
// hence, we need to destroy the wrappers through their interfaces
|
||||
if (false) {
|
||||
|
@ -389,6 +387,21 @@ pub fn Create(
|
|||
_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 {
|
||||
const thread_id = std.Thread.getCurrentId();
|
||||
_static.track_arena_lock.lockShared();
|
||||
|
@ -411,7 +424,7 @@ pub fn Create(
|
|||
/// If you try to register an endpoint whose path would shadow an
|
||||
/// already registered one, you will receive an
|
||||
/// EndpointPathShadowError.
|
||||
pub fn register(_: *App, endpoint: anytype) !void {
|
||||
pub fn register(endpoint: anytype) !void {
|
||||
for (_static.endpoints.items) |other| {
|
||||
if (std.mem.startsWith(
|
||||
u8,
|
||||
|
@ -432,7 +445,7 @@ pub fn Create(
|
|||
try _static.endpoints.append(_static.gpa, &bound.interface);
|
||||
}
|
||||
|
||||
pub fn listen(_: *App, l: ListenerSettings) !void {
|
||||
pub fn listen(l: ListenerSettings) !void {
|
||||
_static.listener = HttpListener.init(.{
|
||||
.interface = l.interface,
|
||||
.port = l.port,
|
||||
|
|
|
@ -440,9 +440,9 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
|
|||
.value = "invalid",
|
||||
.max_age_s = -1,
|
||||
})) {
|
||||
zap.debug("logout ok\n", .{});
|
||||
zap.debug("logout ok", .{});
|
||||
} else |err| {
|
||||
zap.debug("logout cookie setting failed: {any}\n", .{err});
|
||||
zap.debug("logout cookie setting failed: {any}", .{err});
|
||||
}
|
||||
|
||||
r.parseCookies(false);
|
||||
|
|
|
@ -125,36 +125,43 @@ fn parseBinfilesFrom(a: Allocator, o: fio.FIOBJ) !HttpParam {
|
|||
fio.fiobj_free_wrapped(key_name);
|
||||
fio.fiobj_free_wrapped(key_data);
|
||||
fio.fiobj_free_wrapped(key_type);
|
||||
} // 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_type) == 1 and fio.fiobj_hash_haskey(o, key_name) == 1) {
|
||||
} // files: they should have "data" and "filename" keys
|
||||
if (fio.fiobj_hash_haskey(o, key_data) == 1 and fio.fiobj_hash_haskey(o, key_name) == 1) {
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
switch (fio.fiobj_type(data)) {
|
||||
fio.FIOBJ_T_DATA => {
|
||||
if (fio.is_invalid(data) == 1) {
|
||||
data_slice = "(zap: invalid data)";
|
||||
zap.log.warn("HTTP param binary file is not a data object\n", .{});
|
||||
zap.log.warn("HTTP param binary file is not a data object", .{});
|
||||
} else {
|
||||
// the data
|
||||
const data_len = fio.fiobj_data_len(data);
|
||||
var data_buf = fio.fiobj_data_read(data, data_len);
|
||||
|
||||
if (data_len < 0) {
|
||||
zap.log.warn("HTTP param binary file size negative: {d}\n", .{data_len});
|
||||
zap.log.warn("FIOBJ_TYPE of data is: {d}\n", .{fio.fiobj_type(data)});
|
||||
zap.log.warn("HTTP param binary file size negative: {d}", .{data_len});
|
||||
zap.log.warn("FIOBJ_TYPE of data is: {d}", .{fio.fiobj_type(data)});
|
||||
} else {
|
||||
if (data_buf.len != data_len) {
|
||||
zap.log.warn("HTTP param binary file size mismatch: should {d}, is: {d}\n", .{ data_len, data_buf.len });
|
||||
zap.log.warn("HTTP param binary file size mismatch: should {d}, is: {d}", .{ data_len, data_buf.len });
|
||||
}
|
||||
|
||||
if (data_buf.len > 0) {
|
||||
data_slice = data_buf.data[0..data_buf.len];
|
||||
} else {
|
||||
zap.log.warn("HTTP param binary file buffer size negative: {d}\n", .{data_buf.len});
|
||||
zap.log.warn("HTTP param binary file buffer size negative: {d}", .{data_buf.len});
|
||||
data_slice = "(zap: invalid data: negative BUFFER size)";
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +228,7 @@ fn parseBinfilesFrom(a: Allocator, o: fio.FIOBJ) !HttpParam {
|
|||
|
||||
return .{ .Hash_Binfile = .{
|
||||
.filename = filename.data[0..filename.len],
|
||||
.mimetype = mimetype.data[0..mimetype.len],
|
||||
.mimetype = mimetype,
|
||||
.data = data_slice,
|
||||
} };
|
||||
} else {
|
||||
|
@ -379,7 +386,7 @@ pub fn setContentType(self: *const Request, c: ContentType) HttpError!void {
|
|||
.JSON => "application/json",
|
||||
else => "text/html",
|
||||
};
|
||||
zap.log.debug("setting content-type to {s}\n", .{s});
|
||||
zap.log.debug("setting content-type to {s}", .{s});
|
||||
return self.setHeader("content-type", s);
|
||||
}
|
||||
|
||||
|
@ -511,7 +518,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
|
||||
// at least we don't have to log unconditionally
|
||||
if (ret == -1) {
|
||||
zap.log.debug("***************** zap.zig:274\n", .{});
|
||||
zap.log.debug("***************** zap.zig:274", .{});
|
||||
}
|
||||
|
||||
if (ret == 0) return;
|
||||
|
@ -680,7 +687,7 @@ pub fn setCookie(self: *const Request, args: CookieArgs) HttpError!void {
|
|||
// TODO: still happening?
|
||||
const ret = fio.http_set_cookie(self.h, c);
|
||||
if (ret == -1) {
|
||||
zap.log.err("fio.http_set_cookie returned: {}\n", .{ret});
|
||||
zap.log.err("fio.http_set_cookie returned: {}", .{ret});
|
||||
return error.SetCookie;
|
||||
}
|
||||
}
|
||||
|
|
133
src/tests/test_recvfile.zig
Normal file
133
src/tests/test_recvfile.zig
Normal file
|
@ -0,0 +1,133 @@
|
|||
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;
|
||||
}
|
||||
}
|
131
src/tests/test_recvfile_notype.zig
Normal file
131
src/tests/test_recvfile_notype.zig
Normal file
|
@ -0,0 +1,131 @@
|
|||
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;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue