1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 23:24:09 +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 -->
```
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 -->
@ -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 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);
@ -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(&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.
@ -241,8 +211,6 @@ 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

View file

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

View file

@ -58,6 +58,14 @@ 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 {
@ -75,8 +83,8 @@ pub fn main() !void {
// App is the type
// app is the instance
const App = zap.App.Create(MyContext);
try App.init(allocator, &my_context, .{});
defer App.deinit();
var app = try App.init(allocator, &my_context, .{});
defer app.deinit();
// create mini endpoint
var ep: MyEndpoint = .{
@ -93,10 +101,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,
});

View file

@ -59,7 +59,15 @@ 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 {
path: []const u8,
@ -74,6 +82,12 @@ 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 {
@ -90,19 +104,19 @@ pub fn main() !void {
// create an App instance
const App = zap.App.Create(MyContext);
try App.init(allocator, &my_context, .{});
defer App.deinit();
var app = 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,
});

View file

@ -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);
try App.init(allocator, &my_context, .{});
defer App.deinit();
var app = 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,
});

View file

@ -25,7 +25,7 @@ const Handler = struct {
};
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)
@ -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}", .{ kv.key, v });
std.log.info("Param `{s}` in owned list is {any}\n", .{ kv.key, v });
switch (v) {
// single-file upload
zap.Request.HttpParam.Hash_Binfile => |*file| {
@ -51,9 +51,9 @@ const Handler = struct {
const mimetype = file.mimetype orelse "(no mimetype)";
const data = file.data orelse "";
std.log.debug(" filename: `{s}`", .{filename});
std.log.debug(" mimetype: {s}", .{mimetype});
std.log.debug(" contents: {any}", .{data});
std.log.debug(" filename: `{s}`\n", .{filename});
std.log.debug(" mimetype: {s}\n", .{mimetype});
std.log.debug(" contents: {any}\n", .{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}`", .{filename});
std.log.debug(" mimetype: {s}", .{mimetype});
std.log.debug(" contents: {any}", .{data});
std.log.debug(" filename: `{s}`\n", .{filename});
std.log.debug(" mimetype: {s}\n", .{mimetype});
std.log.debug(" contents: {any}\n", .{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}", .{str});
std.log.info("?terminate={s}\n", .{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", .{});
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", .{});
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", .{});
zap.start(.{
.threads = 1,

View file

@ -87,8 +87,8 @@ pub fn main() !void {
std.log.info("Cookie ZIG_ZAP not found!", .{});
}
} else |err| {
std.log.err("ERROR!", .{});
std.log.err("cannot check for `ZIG_ZAP` cookie: {any}", .{err});
std.log.err("ERROR!\n", .{});
std.log.err("cannot check for `ZIG_ZAP` cookie: {any}\n", .{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!", .{});
std.log.err("cannot set cookie: {any}", .{err});
std.log.err("ERROR!\n", .{});
std.log.err("cannot set cookie: {any}\n", .{err});
};
r.sendBody("Hello") catch unreachable;

View file

@ -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: {}", .{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!", .{});
}
} 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
@ -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", .{});
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");
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 {
// TODO: simplitfy this with @tagName?
const ret = switch (r.methodAsEnum()) {
.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),
.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),
else => error.UnsupportedHtmlRequestMethod,
};
if (ret) {
@ -228,6 +227,8 @@ 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 ++ "`");
}
}
}
@ -256,8 +257,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 => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
.AuthOK => callHandlerIfExist("get", self.ep, arena, context, request),
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.get(arena, context, request),
.Handled => {},
};
}
@ -265,8 +266,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 => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
.AuthOK => callHandlerIfExist("post", self.ep, arena, context, request),
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.post(arena, context, request),
.Handled => {},
};
}
@ -274,8 +275,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 => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
.AuthOK => callHandlerIfExist("put", self.ep, arena, context, request),
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.put(arena, context, request),
.Handled => {},
};
}
@ -283,8 +284,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 => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
.AuthOK => callHandlerIfExist("delete", self.ep, arena, context, request),
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.delete(arena, context, request),
.Handled => {},
};
}
@ -292,8 +293,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 => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
.AuthOK => callHandlerIfExist("patch", self.ep, arena, context, request),
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.patch(arena, context, request),
.Handled => {},
};
}
@ -301,8 +302,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 => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
.AuthOK => callHandlerIfExist("options", self.ep, arena, context, request),
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.put(arena, context, request),
.Handled => {},
};
}
@ -310,8 +311,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 => callHandlerIfExist("unauthorized", self.ep, arena, context, request),
.AuthOK => callHandlerIfExist("head", self.ep, arena, context, request),
.AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => self.ep.*.head(arena, context, request),
.Handled => {},
};
}
@ -331,7 +332,7 @@ pub fn Create(
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) {
return error.OnlyOneAppAllowed;
}
@ -359,9 +360,10 @@ pub fn Create(
}
_static.unhandled_error = Context.unhandledError;
}
return .{};
}
pub fn deinit() void {
pub fn deinit(_: *App) void {
// we created endpoint wrappers but only tracked their interfaces
// hence, we need to destroy the wrappers through their interfaces
if (false) {
@ -387,21 +389,6 @@ 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();
@ -424,7 +411,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(endpoint: anytype) !void {
pub fn register(_: *App, endpoint: anytype) !void {
for (_static.endpoints.items) |other| {
if (std.mem.startsWith(
u8,
@ -445,7 +432,7 @@ pub fn Create(
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(.{
.interface = l.interface,
.port = l.port,

View file

@ -440,9 +440,9 @@ pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) ty
.value = "invalid",
.max_age_s = -1,
})) {
zap.debug("logout ok", .{});
zap.debug("logout ok\n", .{});
} else |err| {
zap.debug("logout cookie setting failed: {any}", .{err});
zap.debug("logout cookie setting failed: {any}\n", .{err});
}
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_data);
fio.fiobj_free_wrapped(key_type);
} // 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) {
} // 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) {
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", .{});
zap.log.warn("HTTP param binary file is not a data object\n", .{});
} 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}", .{data_len});
zap.log.warn("FIOBJ_TYPE of data is: {d}", .{fio.fiobj_type(data)});
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)});
} else {
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) {
data_slice = data_buf.data[0..data_buf.len];
} 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)";
}
}
@ -228,7 +221,7 @@ fn parseBinfilesFrom(a: Allocator, o: fio.FIOBJ) !HttpParam {
return .{ .Hash_Binfile = .{
.filename = filename.data[0..filename.len],
.mimetype = mimetype,
.mimetype = mimetype.data[0..mimetype.len],
.data = data_slice,
} };
} else {
@ -386,7 +379,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}", .{s});
zap.log.debug("setting content-type to {s}\n", .{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
// at least we don't have to log unconditionally
if (ret == -1) {
zap.log.debug("***************** zap.zig:274", .{});
zap.log.debug("***************** zap.zig:274\n", .{});
}
if (ret == 0) return;
@ -687,7 +680,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: {}", .{ret});
zap.log.err("fio.http_set_cookie returned: {}\n", .{ret});
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;
}
}