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 -->
|
<!-- 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 -->
|
<!-- 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 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);
|
||||||
|
@ -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(&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.
|
||||||
|
@ -211,6 +241,8 @@ 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
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.{
|
.{
|
||||||
.name = .zap,
|
.name = .zap,
|
||||||
.version = "0.9.1",
|
.version = "0.10.4",
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
"build.zig.zon",
|
"build.zig.zon",
|
||||||
|
|
|
@ -58,14 +58,6 @@ 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 {
|
||||||
|
@ -83,8 +75,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);
|
||||||
var app = try App.init(allocator, &my_context, .{});
|
try App.init(allocator, &my_context, .{});
|
||||||
defer app.deinit();
|
defer App.deinit();
|
||||||
|
|
||||||
// create mini endpoint
|
// create mini endpoint
|
||||||
var ep: MyEndpoint = .{
|
var ep: MyEndpoint = .{
|
||||||
|
@ -101,10 +93,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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -59,15 +59,7 @@ 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,
|
||||||
|
@ -82,12 +74,6 @@ 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 {
|
||||||
|
@ -104,19 +90,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);
|
||||||
var app = try App.init(allocator, &my_context, .{});
|
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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
var app = try App.init(allocator, &my_context, .{});
|
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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,7 +25,7 @@ const Handler = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (r.body) |body| {
|
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)
|
// 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}\n", .{ kv.key, v });
|
std.log.info("Param `{s}` in owned list is {any}", .{ 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}`\n", .{filename});
|
std.log.debug(" filename: `{s}`", .{filename});
|
||||||
std.log.debug(" mimetype: {s}\n", .{mimetype});
|
std.log.debug(" mimetype: {s}", .{mimetype});
|
||||||
std.log.debug(" contents: {any}\n", .{data});
|
std.log.debug(" contents: {any}", .{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}`\n", .{filename});
|
std.log.debug(" filename: `{s}`", .{filename});
|
||||||
std.log.debug(" mimetype: {s}\n", .{mimetype});
|
std.log.debug(" mimetype: {s}", .{mimetype});
|
||||||
std.log.debug(" contents: {any}\n", .{data});
|
std.log.debug(" contents: {any}", .{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}\n", .{str});
|
std.log.info("?terminate={s}", .{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\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\n", .{});
|
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\n", .{});
|
std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true", .{});
|
||||||
|
|
||||||
zap.start(.{
|
zap.start(.{
|
||||||
.threads = 1,
|
.threads = 1,
|
||||||
|
|
|
@ -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!\n", .{});
|
std.log.err("ERROR!", .{});
|
||||||
std.log.err("cannot check for `ZIG_ZAP` cookie: {any}\n", .{err});
|
std.log.err("cannot check for `ZIG_ZAP` cookie: {any}", .{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!\n", .{});
|
std.log.err("ERROR!", .{});
|
||||||
std.log.err("cannot set cookie: {any}\n", .{err});
|
std.log.err("cannot set cookie: {any}", .{err});
|
||||||
};
|
};
|
||||||
|
|
||||||
r.sendBody("Hello") catch unreachable;
|
r.sendBody("Hello") catch unreachable;
|
||||||
|
|
|
@ -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: {}\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!", .{});
|
std.log.info("Param one not found!", .{});
|
||||||
}
|
}
|
||||||
} else |err| {
|
} 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
|
// 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\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");
|
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();
|
||||||
|
|
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 {
|
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 => self.endpoint.*.get(arena, app_context, r),
|
.GET => callHandlerIfExist("get", self.endpoint, arena, app_context, r),
|
||||||
.POST => self.endpoint.*.post(arena, app_context, r),
|
.POST => callHandlerIfExist("post", self.endpoint, arena, app_context, r),
|
||||||
.PUT => self.endpoint.*.put(arena, app_context, r),
|
.PUT => callHandlerIfExist("put", self.endpoint, arena, app_context, r),
|
||||||
.DELETE => self.endpoint.*.delete(arena, app_context, r),
|
.DELETE => callHandlerIfExist("delete", self.endpoint, arena, app_context, r),
|
||||||
.PATCH => self.endpoint.*.patch(arena, app_context, r),
|
.PATCH => callHandlerIfExist("patch", self.endpoint, arena, app_context, r),
|
||||||
.OPTIONS => self.endpoint.*.options(arena, app_context, r),
|
.OPTIONS => callHandlerIfExist("options", self.endpoint, arena, app_context, r),
|
||||||
.HEAD => self.endpoint.*.head(arena, app_context, r),
|
.HEAD => callHandlerIfExist("head", self.endpoint, arena, app_context, r),
|
||||||
else => error.UnsupportedHtmlRequestMethod,
|
else => error.UnsupportedHtmlRequestMethod,
|
||||||
};
|
};
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
@ -227,8 +228,6 @@ 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 ++ "`");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,8 +256,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 => return self.ep.*.unauthorized(arena, context, request),
|
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||||
.AuthOK => self.ep.*.get(arena, context, request),
|
.AuthOK => callHandlerIfExist("get", self.ep, arena, context, request),
|
||||||
.Handled => {},
|
.Handled => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -266,8 +265,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 => return self.ep.*.unauthorized(arena, context, request),
|
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||||
.AuthOK => self.ep.*.post(arena, context, request),
|
.AuthOK => callHandlerIfExist("post", self.ep, arena, context, request),
|
||||||
.Handled => {},
|
.Handled => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -275,8 +274,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 => return self.ep.*.unauthorized(arena, context, request),
|
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||||
.AuthOK => self.ep.*.put(arena, context, request),
|
.AuthOK => callHandlerIfExist("put", self.ep, arena, context, request),
|
||||||
.Handled => {},
|
.Handled => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -284,8 +283,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 => return self.ep.*.unauthorized(arena, context, request),
|
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||||
.AuthOK => self.ep.*.delete(arena, context, request),
|
.AuthOK => callHandlerIfExist("delete", self.ep, arena, context, request),
|
||||||
.Handled => {},
|
.Handled => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -293,8 +292,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 => return self.ep.*.unauthorized(arena, context, request),
|
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||||
.AuthOK => self.ep.*.patch(arena, context, request),
|
.AuthOK => callHandlerIfExist("patch", self.ep, arena, context, request),
|
||||||
.Handled => {},
|
.Handled => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -302,8 +301,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 => return self.ep.*.unauthorized(arena, context, request),
|
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||||
.AuthOK => self.ep.*.put(arena, context, request),
|
.AuthOK => callHandlerIfExist("options", self.ep, arena, context, request),
|
||||||
.Handled => {},
|
.Handled => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -311,8 +310,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 => return self.ep.*.unauthorized(arena, context, request),
|
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
|
||||||
.AuthOK => self.ep.*.head(arena, context, request),
|
.AuthOK => callHandlerIfExist("head", self.ep, arena, context, request),
|
||||||
.Handled => {},
|
.Handled => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -332,7 +331,7 @@ pub fn Create(
|
||||||
tls: ?zap.Tls = null,
|
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) {
|
if (_static.there_can_be_only_one) {
|
||||||
return error.OnlyOneAppAllowed;
|
return error.OnlyOneAppAllowed;
|
||||||
}
|
}
|
||||||
|
@ -360,10 +359,9 @@ pub fn Create(
|
||||||
}
|
}
|
||||||
_static.unhandled_error = Context.unhandledError;
|
_static.unhandled_error = Context.unhandledError;
|
||||||
}
|
}
|
||||||
return .{};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(_: *App) void {
|
pub fn deinit() 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) {
|
||||||
|
@ -389,6 +387,21 @@ 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();
|
||||||
|
@ -411,7 +424,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(_: *App, endpoint: anytype) !void {
|
pub fn register(endpoint: anytype) !void {
|
||||||
for (_static.endpoints.items) |other| {
|
for (_static.endpoints.items) |other| {
|
||||||
if (std.mem.startsWith(
|
if (std.mem.startsWith(
|
||||||
u8,
|
u8,
|
||||||
|
@ -432,7 +445,7 @@ pub fn Create(
|
||||||
try _static.endpoints.append(_static.gpa, &bound.interface);
|
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(.{
|
_static.listener = HttpListener.init(.{
|
||||||
.interface = l.interface,
|
.interface = l.interface,
|
||||||
.port = l.port,
|
.port = l.port,
|
||||||
|
|
|
@ -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\n", .{});
|
zap.debug("logout ok", .{});
|
||||||
} else |err| {
|
} else |err| {
|
||||||
zap.debug("logout cookie setting failed: {any}\n", .{err});
|
zap.debug("logout cookie setting failed: {any}", .{err});
|
||||||
}
|
}
|
||||||
|
|
||||||
r.parseCookies(false);
|
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_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", "type", and "filename" keys
|
} // files: they should have "data" 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) {
|
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 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\n", .{});
|
zap.log.warn("HTTP param binary file is not a data object", .{});
|
||||||
} 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}\n", .{data_len});
|
zap.log.warn("HTTP param binary file size negative: {d}", .{data_len});
|
||||||
zap.log.warn("FIOBJ_TYPE of data is: {d}\n", .{fio.fiobj_type(data)});
|
zap.log.warn("FIOBJ_TYPE of data is: {d}", .{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}\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) {
|
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}\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)";
|
data_slice = "(zap: invalid data: negative BUFFER size)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +228,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.data[0..mimetype.len],
|
.mimetype = mimetype,
|
||||||
.data = data_slice,
|
.data = data_slice,
|
||||||
} };
|
} };
|
||||||
} else {
|
} else {
|
||||||
|
@ -379,7 +386,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}\n", .{s});
|
zap.log.debug("setting content-type to {s}", .{s});
|
||||||
return self.setHeader("content-type", 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
|
// 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\n", .{});
|
zap.log.debug("***************** zap.zig:274", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret == 0) return;
|
if (ret == 0) return;
|
||||||
|
@ -680,7 +687,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: {}\n", .{ret});
|
zap.log.err("fio.http_set_cookie returned: {}", .{ret});
|
||||||
return error.SetCookie;
|
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