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 "24dfcbaeaa6517824c377374dcdb573da8d68dab" have entirely different histories.

35 changed files with 296 additions and 907 deletions

View file

@ -39,7 +39,7 @@ jobs:
tag_name: ${{ github.ref }} tag_name: ${{ github.ref }}
release_name: Release ${{ steps.tag.outputs.version }} # Remove 'refs/tags/' from the tag name release_name: Release ${{ steps.tag.outputs.version }} # Remove 'refs/tags/' from the tag name
body_path: relnotes.md body_path: relnotes.md
prerelease: false prerelease: true
- name: Announce Release - name: Announce Release
env: env:

View file

@ -215,7 +215,7 @@ really promising.
### 📣 Shout-Outs ### 📣 Shout-Outs
- [http.zig](https://github.com/karlseguin/http.zig) : Pure Zig! Close to Zap's - [httpz](https://github.com/karlseguin/http.zig) : Pure Zig! Close to Zap's
model. Performance = good! model. Performance = good!
- [jetzig](https://github.com/jetzig-framework/jetzig) : Comfortably develop - [jetzig](https://github.com/jetzig-framework/jetzig) : Comfortably develop
modern web applications quickly, using http.zig under the hood modern web applications quickly, using http.zig under the hood
@ -298,7 +298,7 @@ In your zig project folder (where `build.zig` is located), run:
<!-- INSERT_DEP_BEGIN --> <!-- INSERT_DEP_BEGIN -->
``` ```
zig fetch --save "git+https://github.com/zigzap/zap#v0.10.4" zig fetch --save "git+https://github.com/zigzap/zap#v0.10.0"
``` ```
<!-- INSERT_DEP_END --> <!-- INSERT_DEP_END -->
@ -376,7 +376,7 @@ $ zig build run-routes
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request(r: zap.Request) !void { fn on_request(r: zap.Request) void {
if (r.path) |the_path| { if (r.path) |the_path| {
std.debug.print("PATH: {s}\n", .{the_path}); std.debug.print("PATH: {s}\n", .{the_path});
} }
@ -408,7 +408,3 @@ pub fn main() !void {

View file

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

View file

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

View file

@ -58,6 +58,13 @@ 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 main() !void { pub fn main() !void {
@ -75,8 +82,8 @@ pub fn main() !void {
// App is the type // App is the type
// app is the instance // app is the instance
const App = zap.App.Create(MyContext); const App = zap.App.Create(MyContext);
try App.init(allocator, &my_context, .{}); var app = try App.init(allocator, &my_context, .{});
defer App.deinit(); defer app.deinit();
// create mini endpoint // create mini endpoint
var ep: MyEndpoint = .{ var ep: MyEndpoint = .{
@ -93,10 +100,10 @@ pub fn main() !void {
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
// make the authenticating endpoint known to the app // make the authenticating endpoint known to the app
try App.register(&auth_ep); try app.register(&auth_ep);
// listen // listen
try App.listen(.{ try app.listen(.{
.interface = "0.0.0.0", .interface = "0.0.0.0",
.port = 3000, .port = 3000,
}); });

View file

@ -59,7 +59,14 @@ 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 {}
};
const StopEndpoint = struct { const StopEndpoint = struct {
path: []const u8, path: []const u8,
@ -74,6 +81,12 @@ const StopEndpoint = struct {
, .{context.*.db_connection}); , .{context.*.db_connection});
zap.stop(); zap.stop();
} }
pub fn post(_: *StopEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn put(_: *StopEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn delete(_: *StopEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn patch(_: *StopEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn options(_: *StopEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
}; };
pub fn main() !void { pub fn main() !void {
@ -90,19 +103,19 @@ pub fn main() !void {
// create an App instance // create an App instance
const App = zap.App.Create(MyContext); const App = zap.App.Create(MyContext);
try App.init(allocator, &my_context, .{}); var app = try App.init(allocator, &my_context, .{});
defer App.deinit(); defer app.deinit();
// create the endpoints // create the endpoints
var my_endpoint = SimpleEndpoint.init("/test", "some endpoint specific data"); var my_endpoint = SimpleEndpoint.init("/test", "some endpoint specific data");
var stop_endpoint: StopEndpoint = .{ .path = "/stop" }; var stop_endpoint: StopEndpoint = .{ .path = "/stop" };
// //
// register the endpoints with the App // register the endpoints with the app
try App.register(&my_endpoint); try app.register(&my_endpoint);
try App.register(&stop_endpoint); try app.register(&stop_endpoint);
// listen on the network // listen on the network
try App.listen(.{ try app.listen(.{
.interface = "0.0.0.0", .interface = "0.0.0.0",
.port = 3000, .port = 3000,
}); });

View file

@ -1,125 +0,0 @@
//!
//! Part of the Zap examples.
//!
//! Build me with `zig build app_errors`.
//! Run me with `zig build run-app_errors`.
//!
const std = @import("std");
const Allocator = std.mem.Allocator;
const zap = @import("zap");
// The global Application Context
const MyContext = struct {
db_connection: []const u8,
// we don't use this
pub fn unhandledRequest(_: *MyContext, _: Allocator, _: zap.Request) anyerror!void {}
pub fn unhandledError(_: *MyContext, _: zap.Request, err: anyerror) void {
std.debug.print("\n\n\nUNHANDLED ERROR: {} !!! \n\n\n", .{err});
}
};
// A very simple endpoint handling only GET requests
const ErrorEndpoint = struct {
// zap.App.Endpoint Interface part
path: []const u8,
error_strategy: zap.Endpoint.ErrorStrategy = .raise,
// data specific for this endpoint
some_data: []const u8,
pub fn init(path: []const u8, data: []const u8) ErrorEndpoint {
return .{
.path = path,
.some_data = data,
};
}
// handle GET requests
pub fn get(_: *ErrorEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {
// we just return an error
// our error_strategy = .raise
// -> error will be raised and dispatched to MyContext.unhandledError
return error.@"Oh-No!";
}
// empty stubs for all other request methods
pub fn post(_: *ErrorEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn put(_: *ErrorEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn delete(_: *ErrorEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn patch(_: *ErrorEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn options(_: *ErrorEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
pub fn head(_: *ErrorEndpoint, _: Allocator, _: *MyContext, _: zap.Request) !void {}
};
const StopEndpoint = struct {
path: []const u8,
error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response,
pub fn get(_: *StopEndpoint, _: Allocator, context: *MyContext, _: zap.Request) !void {
std.debug.print(
\\Before I stop, let me dump the app context:
\\db_connection='{s}'
\\
\\
, .{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 {
// setup allocations
var gpa: std.heap.GeneralPurposeAllocator(.{
// just to be explicit
.thread_safe = true,
}) = .{};
defer std.debug.print("\n\nLeaks detected: {}\n\n", .{gpa.deinit() != .ok});
const allocator = gpa.allocator();
// 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();
// 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);
// listen on the network
try App.listen(.{
.interface = "0.0.0.0",
.port = 3000,
});
std.debug.print("Listening on 0.0.0.0:3000\n", .{});
std.debug.print(
\\ Try me via:
\\ curl http://localhost:3000/error
\\ Stop me via:
\\ curl http://localhost:3000/stop
\\
, .{});
// start worker threads -- only 1 process!!!
zap.start(.{
.threads = 2,
.workers = 1,
});
}

View file

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

View file

@ -7,14 +7,6 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); 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 },
},
};
// We send ourselves a request with a cookie // We send ourselves a request with a cookie
fn makeRequest(a: std.mem.Allocator, url: []const u8) !void { fn makeRequest(a: std.mem.Allocator, url: []const u8) !void {
const uri = try std.Uri.parse(url); const uri = try std.Uri.parse(url);
@ -87,8 +79,8 @@ pub fn main() !void {
std.log.info("Cookie ZIG_ZAP not found!", .{}); std.log.info("Cookie ZIG_ZAP not found!", .{});
} }
} else |err| { } else |err| {
std.log.err("ERROR!", .{}); std.log.err("ERROR!\n", .{});
std.log.err("cannot check for `ZIG_ZAP` cookie: {any}", .{err}); std.log.err("cannot check for `ZIG_ZAP` cookie: {any}\n", .{err});
} }
r.setCookie(.{ r.setCookie(.{
@ -101,8 +93,8 @@ pub fn main() !void {
// //
// check out other params: domain, path, secure, http_only // check out other params: domain, path, secure, http_only
}) catch |err| { }) catch |err| {
std.log.err("ERROR!", .{}); std.log.err("ERROR!\n", .{});
std.log.err("cannot set cookie: {any}", .{err}); std.log.err("cannot set cookie: {any}\n", .{err});
}; };
r.sendBody("Hello") catch unreachable; r.sendBody("Hello") catch unreachable;
@ -121,6 +113,7 @@ pub fn main() !void {
.max_body_size = 1 * 1024, .max_body_size = 1 * 1024,
}, },
); );
zap.enableDebugLog();
try listener.listen(); try listener.listen();
std.log.info("\n\nTerminate with CTRL+C", .{}); std.log.info("\n\nTerminate with CTRL+C", .{});

View file

@ -9,8 +9,6 @@ path: []const u8 = "/error",
error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response, error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response,
pub fn get(_: *ErrorEndpoint, _: zap.Request) !void { pub fn get(_: *ErrorEndpoint, _: zap.Request) !void {
// error_strategy is set to .log_to_response
// --> this error will be shown in the browser, with a nice error trace
return error.@"Oh-no!"; return error.@"Oh-no!";
} }
@ -20,4 +18,3 @@ pub fn put(_: *ErrorEndpoint, _: zap.Request) !void {}
pub fn delete(_: *ErrorEndpoint, _: zap.Request) !void {} pub fn delete(_: *ErrorEndpoint, _: zap.Request) !void {}
pub fn patch(_: *ErrorEndpoint, _: zap.Request) !void {} pub fn patch(_: *ErrorEndpoint, _: zap.Request) !void {}
pub fn options(_: *ErrorEndpoint, _: zap.Request) !void {} pub fn options(_: *ErrorEndpoint, _: zap.Request) !void {}
pub fn head(_: *ErrorEndpoint, _: zap.Request) !void {}

View file

@ -19,11 +19,6 @@ fn on_request(r: zap.Request) !void {
try r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>"); try r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>");
} }
// this is just to demo that we could catch arbitrary errors as fallback
fn on_error(_: zap.Request, err: anyerror) void {
std.debug.print("\n\n\nOh no!!! We didn't chatch this error: {}\n\n\n", .{err});
}
pub fn main() !void { pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{ var gpa = std.heap.GeneralPurposeAllocator(.{
.thread_safe = true, .thread_safe = true,
@ -38,8 +33,6 @@ pub fn main() !void {
.{ .{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,
// optional
.on_error = on_error,
.log = true, .log = true,
.public_folder = "examples/endpoint/html", .public_folder = "examples/endpoint/html",
.max_clients = 100000, .max_clients = 100000,
@ -54,13 +47,11 @@ pub fn main() !void {
var stopEp = StopEndpoint.init("/stop"); var stopEp = StopEndpoint.init("/stop");
var errorEp: ErrorEndpoint = .{}; var errorEp: ErrorEndpoint = .{};
var unhandledErrorEp: ErrorEndpoint = .{ .error_strategy = .raise, .path = "/unhandled" };
// register endpoints with the listener // register endpoints with the listener
try listener.register(&userWeb); try listener.register(&userWeb);
try listener.register(&stopEp); try listener.register(&stopEp);
try listener.register(&errorEp); try listener.register(&errorEp);
try listener.register(&unhandledErrorEp);
// fake some users // fake some users
var uid: usize = undefined; var uid: usize = undefined;
@ -86,5 +77,5 @@ pub fn main() !void {
// show potential memory leaks when ZAP is shut down // show potential memory leaks when ZAP is shut down
const has_leaked = gpa.detectLeaks(); const has_leaked = gpa.detectLeaks();
std.log.debug("Has leaked: {}", .{has_leaked}); std.log.debug("Has leaked: {}\n", .{has_leaked});
} }

View file

@ -23,4 +23,3 @@ pub fn put(_: *StopEndpoint, _: zap.Request) !void {}
pub fn delete(_: *StopEndpoint, _: zap.Request) !void {} pub fn delete(_: *StopEndpoint, _: zap.Request) !void {}
pub fn patch(_: *StopEndpoint, _: zap.Request) !void {} pub fn patch(_: *StopEndpoint, _: zap.Request) !void {}
pub fn options(_: *StopEndpoint, _: zap.Request) !void {} pub fn options(_: *StopEndpoint, _: zap.Request) !void {}
pub fn head(_: *StopEndpoint, _: zap.Request) !void {}

View file

@ -127,12 +127,7 @@ pub fn delete(self: *UserWeb, r: zap.Request) !void {
pub fn options(_: *UserWeb, r: zap.Request) !void { pub fn options(_: *UserWeb, r: zap.Request) !void {
try r.setHeader("Access-Control-Allow-Origin", "*"); try r.setHeader("Access-Control-Allow-Origin", "*");
try r.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD"); try r.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
r.setStatus(zap.http.StatusCode.no_content);
r.markAsFinished(true);
}
pub fn head(_: *UserWeb, r: zap.Request) !void {
r.setStatus(zap.http.StatusCode.no_content); r.setStatus(zap.http.StatusCode.no_content);
r.markAsFinished(true); r.markAsFinished(true);
} }

View file

@ -38,7 +38,6 @@ const Endpoint = struct {
pub fn delete(_: *Endpoint, _: zap.Request) !void {} pub fn delete(_: *Endpoint, _: zap.Request) !void {}
pub fn patch(_: *Endpoint, _: zap.Request) !void {} pub fn patch(_: *Endpoint, _: zap.Request) !void {}
pub fn options(_: *Endpoint, _: zap.Request) !void {} pub fn options(_: *Endpoint, _: zap.Request) !void {}
pub fn head(_: *Endpoint, _: zap.Request) !void {}
}; };
pub fn main() !void { pub fn main() !void {

View file

@ -7,14 +7,6 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); 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 },
},
};
// We send ourselves a request // We send ourselves a request
fn makeRequest(a: std.mem.Allocator, url: []const u8) !void { fn makeRequest(a: std.mem.Allocator, url: []const u8) !void {
const uri = try std.Uri.parse(url); const uri = try std.Uri.parse(url);
@ -111,7 +103,7 @@ pub fn main() !void {
std.log.info("Param one not found!", .{}); std.log.info("Param one not found!", .{});
} }
} else |err| { } else |err| {
std.log.err("cannot check for `one` param: {any}", .{err}); std.log.err("cannot check for `one` param: {any}\n", .{err});
} }
// check if we received a terminate=true parameter // check if we received a terminate=true parameter
@ -135,9 +127,9 @@ pub fn main() !void {
.max_body_size = 1 * 1024, .max_body_size = 1 * 1024,
}, },
); );
zap.enableDebugLog();
try listener.listen(); try listener.listen();
std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true", .{}); std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true\n", .{});
const thread = try makeRequestThread(allocator, "http://127.0.0.1:3000/?one=1&two=2&string=hello+world&float=6.28&bool=true"); const thread = try makeRequestThread(allocator, "http://127.0.0.1:3000/?one=1&two=2&string=hello+world&float=6.28&bool=true");
defer thread.join(); defer thread.join();

View file

@ -7,14 +7,6 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); 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 },
},
};
// just a way to share our allocator via callback // just a way to share our allocator via callback
const SharedAllocator = struct { const SharedAllocator = struct {
// static // static
@ -220,7 +212,7 @@ pub fn main() !void {
userHandler.getHandler(), userHandler.getHandler(),
SharedAllocator.getAllocator, SharedAllocator.getAllocator,
); );
zap.enableDebugLog();
listener.listen() catch |err| { listener.listen() catch |err| {
std.debug.print("\nLISTEN ERROR: {any}\n", .{err}); std.debug.print("\nLISTEN ERROR: {any}\n", .{err});
return; return;

View file

@ -7,14 +7,6 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); 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 },
},
};
// just a way to share our allocator via callback // just a way to share our allocator via callback
const SharedAllocator = struct { const SharedAllocator = struct {
// static // static
@ -157,7 +149,6 @@ const HtmlEndpoint = struct {
pub fn delete(_: *HtmlEndpoint, _: zap.Request) !void {} pub fn delete(_: *HtmlEndpoint, _: zap.Request) !void {}
pub fn patch(_: *HtmlEndpoint, _: zap.Request) !void {} pub fn patch(_: *HtmlEndpoint, _: zap.Request) !void {}
pub fn options(_: *HtmlEndpoint, _: zap.Request) !void {} pub fn options(_: *HtmlEndpoint, _: zap.Request) !void {}
pub fn head(_: *HtmlEndpoint, _: zap.Request) !void {}
pub fn get(_: *HtmlEndpoint, r: zap.Request) !void { pub fn get(_: *HtmlEndpoint, r: zap.Request) !void {
var buf: [1024]u8 = undefined; var buf: [1024]u8 = undefined;
@ -233,7 +224,7 @@ pub fn main() !void {
userHandler.getHandler(), userHandler.getHandler(),
SharedAllocator.getAllocator, SharedAllocator.getAllocator,
); );
zap.enableDebugLog();
listener.listen() catch |err| { listener.listen() catch |err| {
std.debug.print("\nLISTEN ERROR: {any}\n", .{err}); std.debug.print("\nLISTEN ERROR: {any}\n", .{err});
return; return;

View file

@ -8,14 +8,6 @@ const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
const Mustache = @import("zap").Mustache; const Mustache = @import("zap").Mustache;
// 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 },
},
};
fn on_request(r: zap.Request) !void { fn on_request(r: zap.Request) !void {
const template = const template =
\\ {{=<< >>=}} \\ {{=<< >>=}}
@ -71,9 +63,12 @@ pub fn main() !void {
}); });
try listener.listen(); try listener.listen();
// zap.enableDebugLog();
// zap.debug("ZAP debug logging is on\n", .{});
// we can also use facilio logging // we can also use facilio logging
// zap.Logging.fio_set_log_level(zap.Log.fio_log_level_debug); // zap.Log.fio_set_log_level(zap.Log.fio_log_level_debug);
// zap.Logging.fio_log_debug("hello from fio\n"); // zap.Log.fio_log_debug("hello from fio\n");
std.debug.print("Listening on 0.0.0.0:3000\n", .{}); std.debug.print("Listening on 0.0.0.0:3000\n", .{});

View file

@ -7,14 +7,6 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); 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 },
},
};
var buffer: [1024]u8 = undefined; var buffer: [1024]u8 = undefined;
var read_len: ?usize = null; var read_len: ?usize = null;
@ -51,6 +43,7 @@ pub fn main() !void {
}, },
); );
zap.enableDebugLog();
try listener.listen(); try listener.listen();
std.debug.print("Visit me on http://127.0.0.1:3000\n", .{}); std.debug.print("Visit me on http://127.0.0.1:3000\n", .{});

View file

@ -7,14 +7,6 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); 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 Lookup = std.StringHashMap([]const u8); const Lookup = std.StringHashMap([]const u8);
const auth_lock_pw_table = false; const auth_lock_pw_table = false;
@ -148,6 +140,8 @@ pub fn main() !void {
}); });
try listener.listen(); try listener.listen();
zap.enableDebugLog();
// Usernames -> Passwords for the /login page // Usernames -> Passwords for the /login page
// ------------------------------------------ // ------------------------------------------
var userpass = Lookup.init(allocator); var userpass = Lookup.init(allocator);

View file

@ -81,17 +81,20 @@ const ContextManager = struct {
// //
// Websocket Callbacks // Websocket Callbacks
// //
fn on_open_websocket(context: ?*Context, handle: WebSockets.WsHandle) !void { fn on_open_websocket(context: ?*Context, handle: WebSockets.WsHandle) void {
if (context) |ctx| { if (context) |ctx| {
_ = try WebsocketHandler.subscribe(handle, &ctx.subscribeArgs); _ = WebsocketHandler.subscribe(handle, &ctx.subscribeArgs) catch |err| {
std.log.err("Error opening websocket: {any}", .{err});
return;
};
// say hello // say hello
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
const message = try std.fmt.bufPrint( const message = std.fmt.bufPrint(
&buf, &buf,
"{s} joined the chat.", "{s} joined the chat.",
.{ctx.userName}, .{ctx.userName},
); ) catch unreachable;
// send notification to all others // send notification to all others
WebsocketHandler.publish(.{ .channel = ctx.channel, .message = message }); WebsocketHandler.publish(.{ .channel = ctx.channel, .message = message });
@ -99,16 +102,16 @@ fn on_open_websocket(context: ?*Context, handle: WebSockets.WsHandle) !void {
} }
} }
fn on_close_websocket(context: ?*Context, uuid: isize) !void { fn on_close_websocket(context: ?*Context, uuid: isize) void {
_ = uuid; _ = uuid;
if (context) |ctx| { if (context) |ctx| {
// say goodbye // say goodbye
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
const message = try std.fmt.bufPrint( const message = std.fmt.bufPrint(
&buf, &buf,
"{s} left the chat.", "{s} left the chat.",
.{ctx.userName}, .{ctx.userName},
); ) catch unreachable;
// send notification to all others // send notification to all others
WebsocketHandler.publish(.{ .channel = ctx.channel, .message = message }); WebsocketHandler.publish(.{ .channel = ctx.channel, .message = message });
@ -121,7 +124,7 @@ fn handle_websocket_message(
handle: WebSockets.WsHandle, handle: WebSockets.WsHandle,
message: []const u8, message: []const u8,
is_text: bool, is_text: bool,
) !void { ) void {
_ = handle; _ = handle;
_ = is_text; _ = is_text;
@ -142,11 +145,11 @@ fn handle_websocket_message(
if (message.len > max_msg_len) { if (message.len > max_msg_len) {
trimmed_message = message[0..max_msg_len]; trimmed_message = message[0..max_msg_len];
} }
const chat_message = try std.fmt.bufPrint( const chat_message = std.fmt.bufPrint(
&buf, &buf,
format_string, format_string,
.{ ctx.userName, trimmed_message }, .{ ctx.userName, trimmed_message },
); ) catch unreachable;
// send notification to all others // send notification to all others
WebsocketHandler.publish( WebsocketHandler.publish(
@ -166,7 +169,7 @@ fn handle_websocket_message(
// HTTP stuff // HTTP stuff
// //
fn on_request(r: zap.Request) !void { fn on_request(r: zap.Request) !void {
try r.setHeader("Server", "zap.example"); r.setHeader("Server", "zap.example") catch unreachable;
try r.sendBody( try r.sendBody(
\\ <html><body> \\ <html><body>
\\ <h1>This is a simple Websocket chatroom example</h1> \\ <h1>This is a simple Websocket chatroom example</h1>
@ -174,7 +177,7 @@ fn on_request(r: zap.Request) !void {
); );
} }
fn on_upgrade(r: zap.Request, target_protocol: []const u8) !void { fn on_upgrade(r: zap.Request, target_protocol: []const u8) void {
// make sure we're talking the right protocol // make sure we're talking the right protocol
if (!std.mem.eql(u8, target_protocol, "websocket")) { if (!std.mem.eql(u8, target_protocol, "websocket")) {
std.log.warn("received illegal protocol: {s}", .{target_protocol}); std.log.warn("received illegal protocol: {s}", .{target_protocol});
@ -187,7 +190,10 @@ fn on_upgrade(r: zap.Request, target_protocol: []const u8) !void {
return; return;
}; };
try WebsocketHandler.upgrade(r.h, &context.settings); WebsocketHandler.upgrade(r.h, &context.settings) catch |err| {
std.log.err("Error in websocketUpgrade(): {any}", .{err});
return;
};
std.log.info("connection upgrade OK", .{}); std.log.info("connection upgrade OK", .{});
} }

View file

@ -1,16 +1,3 @@
//! zap.App takes the zap.Endpoint concept one step further: instead of having
//! only per-endpoint instance data (fields of your Endpoint struct), endpoints
//! in a zap.App easily share a global 'App Context'.
//!
//! In addition to the global App Context, all Endpoint request handlers also
//! receive an arena allocator for easy, care-free allocations. There is one
//! arena allocator per thread, and arenas are reset after each request.
//!
//! Just like regular / legacy zap.Endpoints, returning errors from request
//! handlers is OK. It's decided on a per-endpoint basis how errors are dealt
//! with, via the ErrorStrategy enum field.
//!
//! See `App.Create()`.
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator; const ArenaAllocator = std.heap.ArenaAllocator;
@ -29,20 +16,7 @@ pub const AppOpts = struct {
}; };
/// creates an App with custom app context /// creates an App with custom app context
/// pub fn Create(comptime Context: type) type {
/// About App Contexts:
///
/// ```zig
/// const MyContext = struct {
/// // You may (optionally) define the following global handlers:
/// pub fn unhandledRequest(_: *MyContext, _: Allocator, _: Request) anyerror!void {}
/// pub fn unhandledError(_: *MyContext, _: Request, _: anyerror) void {}
/// };
/// ```
pub fn Create(
/// Your user-defined "Global App Context" type
comptime Context: type,
) type {
return struct { return struct {
const App = @This(); const App = @This();
@ -61,21 +35,19 @@ pub fn Create(
/// the internal http listener /// the internal http listener
listener: HttpListener = undefined, listener: HttpListener = undefined,
/// function pointer to handler for otherwise unhandled requests. /// function pointer to handler for otherwise unhandled requests
/// Will automatically be set if your Context provides an /// Will automatically be set if your Context provides an unhandled
/// `unhandledRequest` function of type `fn(*Context, Allocator, /// function of type `fn(*Context, Allocator, Request)`
/// Request) !void`. ///
unhandled_request: ?*const fn (*Context, Allocator, Request) anyerror!void = null, unhandled: ?*const fn (*Context, Allocator, Request) anyerror!void = null,
/// function pointer to handler for unhandled errors.
/// Errors are unhandled if they are not logged but raised by the
/// ErrorStrategy. Will automatically be set if your Context
/// provides an `unhandledError` function of type `fn(*Context,
/// Allocator, Request, anyerror) void`.
unhandled_error: ?*const fn (*Context, Request, anyerror) void = null,
}; };
var _static: InstanceData = .{}; var _static: InstanceData = .{};
/// Internal, static request handler callback. Will be set to the optional,
/// user-defined request callback that only gets called if no endpoints match
/// a request.
var on_request: ?*const fn (Allocator, *Context, Request) anyerror!void = null;
pub const Endpoint = struct { pub const Endpoint = struct {
pub const Interface = struct { pub const Interface = struct {
call: *const fn (*Interface, Request) anyerror!void = undefined, call: *const fn (*Interface, Request) anyerror!void = undefined,
@ -110,15 +82,13 @@ pub fn Create(
} }
pub fn onRequest(self: *Bound, arena: Allocator, app_context: *Context, r: Request) !void { pub fn onRequest(self: *Bound, arena: Allocator, app_context: *Context, r: Request) !void {
// TODO: simplitfy this with @tagName?
const ret = switch (r.methodAsEnum()) { const ret = switch (r.methodAsEnum()) {
.GET => callHandlerIfExist("get", self.endpoint, arena, app_context, r), .GET => self.endpoint.*.get(arena, app_context, r),
.POST => callHandlerIfExist("post", self.endpoint, arena, app_context, r), .POST => self.endpoint.*.post(arena, app_context, r),
.PUT => callHandlerIfExist("put", self.endpoint, arena, app_context, r), .PUT => self.endpoint.*.put(arena, app_context, r),
.DELETE => callHandlerIfExist("delete", self.endpoint, arena, app_context, r), .DELETE => self.endpoint.*.delete(arena, app_context, r),
.PATCH => callHandlerIfExist("patch", self.endpoint, arena, app_context, r), .PATCH => self.endpoint.*.patch(arena, app_context, r),
.OPTIONS => callHandlerIfExist("options", self.endpoint, arena, app_context, r), .OPTIONS => self.endpoint.*.options(arena, app_context, r),
.HEAD => callHandlerIfExist("head", self.endpoint, arena, app_context, r),
else => error.UnsupportedHtmlRequestMethod, else => error.UnsupportedHtmlRequestMethod,
}; };
if (ret) { if (ret) {
@ -127,7 +97,7 @@ pub fn Create(
switch (self.endpoint.*.error_strategy) { switch (self.endpoint.*.error_strategy) {
.raise => return err, .raise => return err,
.log_to_response => return r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505), .log_to_response => return r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505),
.log_to_console => zap.log.err( .log_to_console => zap.debug(
"Error in {} {s} : {}", "Error in {} {s} : {}",
.{ Bound, r.method orelse "(no method)", err }, .{ Bound, r.method orelse "(no method)", err },
), ),
@ -175,7 +145,6 @@ pub fn Create(
"delete", "delete",
"patch", "patch",
"options", "options",
"head",
}; };
const params_to_check = [_]type{ const params_to_check = [_]type{
*T, *T,
@ -228,6 +197,8 @@ pub fn Create(
if (ret_info.error_union.payload != void) { if (ret_info.error_union.payload != void) {
@compileError("Expected return type of method `" ++ @typeName(T) ++ "." ++ method ++ "` to be !void, got: !" ++ @typeName(ret_info.error_union.payload)); @compileError("Expected return type of method `" ++ @typeName(T) ++ "." ++ method ++ "` to be !void, got: !" ++ @typeName(ret_info.error_union.payload));
} }
} else {
@compileError(@typeName(T) ++ " has no method named `" ++ method ++ "`");
} }
} }
} }
@ -256,8 +227,8 @@ pub fn Create(
/// Authenticates GET requests using the Authenticator. /// Authenticates GET requests using the Authenticator.
pub fn get(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void { pub fn get(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("get", self.ep, arena, context, request), .AuthOK => self.ep.*.get(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -265,8 +236,8 @@ pub fn Create(
/// Authenticates POST requests using the Authenticator. /// Authenticates POST requests using the Authenticator.
pub fn post(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void { pub fn post(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("post", self.ep, arena, context, request), .AuthOK => self.ep.*.post(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -274,8 +245,8 @@ pub fn Create(
/// Authenticates PUT requests using the Authenticator. /// Authenticates PUT requests using the Authenticator.
pub fn put(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void { pub fn put(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("put", self.ep, arena, context, request), .AuthOK => self.ep.*.put(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -283,8 +254,8 @@ pub fn Create(
/// Authenticates DELETE requests using the Authenticator. /// Authenticates DELETE requests using the Authenticator.
pub fn delete(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void { pub fn delete(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("delete", self.ep, arena, context, request), .AuthOK => self.ep.*.delete(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -292,8 +263,8 @@ pub fn Create(
/// Authenticates PATCH requests using the Authenticator. /// Authenticates PATCH requests using the Authenticator.
pub fn patch(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void { pub fn patch(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("patch", self.ep, arena, context, request), .AuthOK => self.ep.*.patch(arena, context, request),
.Handled => {}, .Handled => {},
}; };
} }
@ -301,17 +272,8 @@ pub fn Create(
/// Authenticates OPTIONS requests using the Authenticator. /// Authenticates OPTIONS requests using the Authenticator.
pub fn options(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void { pub fn options(self: *AuthenticatingEndpoint, arena: Allocator, context: *Context, request: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&request)) { try switch (self.authenticator.authenticateRequest(&request)) {
.AuthFailed => callHandlerIfExist("unauthorized", self.ep, arena, context, request), .AuthFailed => return self.ep.*.unauthorized(arena, context, request),
.AuthOK => callHandlerIfExist("options", self.ep, arena, context, request), .AuthOK => self.ep.*.put(arena, context, request),
.Handled => {},
};
}
/// 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),
.Handled => {}, .Handled => {},
}; };
} }
@ -331,7 +293,7 @@ pub fn Create(
tls: ?zap.Tls = null, tls: ?zap.Tls = null,
}; };
pub fn init(gpa_: Allocator, context_: *Context, opts_: AppOpts) !void { pub fn init(gpa_: Allocator, context_: *Context, opts_: AppOpts) !App {
if (_static.there_can_be_only_one) { if (_static.there_can_be_only_one) {
return error.OnlyOneAppAllowed; return error.OnlyOneAppAllowed;
} }
@ -340,28 +302,20 @@ pub fn Create(
_static.opts = opts_; _static.opts = opts_;
_static.there_can_be_only_one = true; _static.there_can_be_only_one = true;
// set unhandled_request callback if provided by Context // set unhandled callback if provided by Context
if (@hasDecl(Context, "unhandledRequest")) { if (@hasDecl(Context, "unhandled")) {
// try if we can use it // try if we can use it
const Unhandled = @TypeOf(@field(Context, "unhandledRequest")); const Unhandled = @TypeOf(@field(Context, "unhandled"));
const Expected = fn (_: *Context, _: Allocator, _: Request) anyerror!void; const Expected = fn (_: *Context, _: Allocator, _: Request) anyerror!void;
if (Unhandled != Expected) { if (Unhandled != Expected) {
@compileError("`unhandledRequest` method of " ++ @typeName(Context) ++ " has wrong type:\n" ++ @typeName(Unhandled) ++ "\nexpected:\n" ++ @typeName(Expected)); @compileError("`unhandled` method of " ++ @typeName(Context) ++ " has wrong type:\n" ++ @typeName(Unhandled) ++ "\nexpected:\n" ++ @typeName(Expected));
} }
_static.unhandled_request = Context.unhandledRequest; _static.unhandled = Context.unhandled;
}
if (@hasDecl(Context, "unhandledError")) {
// try if we can use it
const Unhandled = @TypeOf(@field(Context, "unhandledError"));
const Expected = fn (_: *Context, _: Request, _: anyerror) void;
if (Unhandled != Expected) {
@compileError("`unhandledError` method of " ++ @typeName(Context) ++ " has wrong type:\n" ++ @typeName(Unhandled) ++ "\nexpected:\n" ++ @typeName(Expected));
}
_static.unhandled_error = Context.unhandledError;
} }
return .{};
} }
pub fn deinit() void { pub fn deinit(_: *App) void {
// we created endpoint wrappers but only tracked their interfaces // we created endpoint wrappers but only tracked their interfaces
// hence, we need to destroy the wrappers through their interfaces // hence, we need to destroy the wrappers through their interfaces
if (false) { if (false) {
@ -382,26 +336,12 @@ pub fn Create(
var it = _static.track_arenas.valueIterator(); var it = _static.track_arenas.valueIterator();
while (it.next()) |arena| { while (it.next()) |arena| {
// std.debug.print("deiniting arena: {*}\n", .{arena});
arena.deinit(); arena.deinit();
} }
_static.track_arenas.deinit(_static.gpa); _static.track_arenas.deinit(_static.gpa);
} }
// This can be resolved at comptime so *perhaps it does affect optimiazation
pub fn callHandlerIfExist(comptime fn_name: []const u8, e: anytype, arena: Allocator, ctx: *Context, r: Request) anyerror!void {
const EndPoint = @TypeOf(e.*);
if (@hasDecl(EndPoint, fn_name)) {
return @field(EndPoint, fn_name)(e, arena, ctx, r);
}
zap.log.debug(
"Unhandled `{s}` {s} request ({s} not implemented in {s})",
.{ r.method orelse "<unknown>", r.path orelse "", fn_name, @typeName(Endpoint) },
);
r.setStatus(.method_not_allowed);
try r.sendBody("405 - method not allowed\r\n");
return;
}
pub fn get_arena() !*ArenaAllocator { pub fn get_arena() !*ArenaAllocator {
const thread_id = std.Thread.getCurrentId(); const thread_id = std.Thread.getCurrentId();
_static.track_arena_lock.lockShared(); _static.track_arena_lock.lockShared();
@ -424,7 +364,7 @@ pub fn Create(
/// If you try to register an endpoint whose path would shadow an /// If you try to register an endpoint whose path would shadow an
/// already registered one, you will receive an /// already registered one, you will receive an
/// EndpointPathShadowError. /// EndpointPathShadowError.
pub fn register(endpoint: anytype) !void { pub fn register(_: *App, endpoint: anytype) !void {
for (_static.endpoints.items) |other| { for (_static.endpoints.items) |other| {
if (std.mem.startsWith( if (std.mem.startsWith(
u8, u8,
@ -445,7 +385,7 @@ pub fn Create(
try _static.endpoints.append(_static.gpa, &bound.interface); try _static.endpoints.append(_static.gpa, &bound.interface);
} }
pub fn listen(l: ListenerSettings) !void { pub fn listen(_: *App, l: ListenerSettings) !void {
_static.listener = HttpListener.init(.{ _static.listener = HttpListener.init(.{
.interface = l.interface, .interface = l.interface,
.port = l.port, .port = l.port,
@ -464,34 +404,17 @@ pub fn Create(
if (r.path) |p| { if (r.path) |p| {
for (_static.endpoints.items) |interface| { for (_static.endpoints.items) |interface| {
if (std.mem.startsWith(u8, p, interface.path)) { if (std.mem.startsWith(u8, p, interface.path)) {
return interface.call(interface, r) catch |err| { return try interface.call(interface, r);
// if error is not dealt with in the interface, e.g.
// if error strategy is .raise:
if (_static.unhandled_error) |error_cb| {
error_cb(_static.context, r, err);
} else {
zap.log.err(
"App.Endpoint onRequest error {} in endpoint interface {}\n",
.{ err, interface },
);
}
};
} }
} }
} }
if (on_request) |foo| {
// this is basically the "not found" handler
if (_static.unhandled_request) |foo| {
var arena = try get_arena(); var arena = try get_arena();
foo(_static.context, arena.allocator(), r) catch |err| { foo(arena.allocator(), _static.context, r) catch |err| {
switch (_static.opts.default_error_strategy) { switch (_static.opts.default_error_strategy) {
.raise => if (_static.unhandled_error) |error_cb| { .raise => return err,
error_cb(_static.context, r, err);
} else {
zap.Logging.on_uncaught_error("App on_request", err);
},
.log_to_response => return r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505), .log_to_response => return r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505),
.log_to_console => zap.log.err("Error in {} {s} : {}", .{ App, r.method orelse "(no method)", err }), .log_to_console => zap.debug("Error in {} {s} : {}", .{ App, r.method orelse "(no method)", err }),
} }
}; };
} }

View file

@ -10,19 +10,17 @@
//! ```zig //! ```zig
//! /// The http request path / slug of the endpoint //! /// The http request path / slug of the endpoint
//! path: []const u8, //! path: []const u8,
//! error_strategy: zap.Endpoint.ErrorStrategy,
//! //!
//! /// Handlers by request method: //! /// Handlers by request method:
//! pub fn get(_: *Self, _: zap.Request) !void {} //! pub fn get(_: *Self, _: zap.Request) void {}
//! pub fn post(_: *Self, _: zap.Request) !void {} //! pub fn post(_: *Self, _: zap.Request) void {}
//! pub fn put(_: *Self, _: zap.Request) !void {} //! pub fn put(_: *Self, _: zap.Request) void {}
//! pub fn delete(_: *Self, _: zap.Request) !void {} //! pub fn delete(_: *Self, _: zap.Request) void {}
//! pub fn patch(_: *Self, _: zap.Request) !void {} //! pub fn patch(_: *Self, _: zap.Request) void {}
//! pub fn options(_: *Self, _: zap.Request) !void {} //! pub fn options(_: *Self, _: zap.Request) void {}
//! pub fn head(_: *Self, _: zap.Request) !void {}
//! //!
//! // optional, if auth stuff is used: //! // optional, if auth stuff is used:
//! pub fn unauthorized(_: *Self, _: zap.Request) !void {} //! pub fn unauthorized(_: *Self, _: zap.Request) void {}
//! ``` //! ```
//! //!
//! Example: //! Example:
@ -32,25 +30,22 @@
//! //!
//! ```zig //! ```zig
//! const StopEndpoint = struct { //! const StopEndpoint = struct {
//! path: []const u8,
//! error_strategy: zap.Endpoint.ErrorStrategy = .log_to_response,
//! //!
//! pub fn init(path: []const u8) StopEndpoint { //! pub fn init( path: []const u8,) StopEndpoint {
//! return .{ //! return .{
//! .path = path, //! .path = path,
//! }; //! };
//! } //! }
//! //!
//! pub fn get(_: *StopEndpoint, _: zap.Request) !void { //! pub fn post(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn put(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn delete(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn patch(_: *StopEndpoint, _: zap.Request) void {}
//! pub fn options(_: *StopEndpoint, _: zap.Request) void {}
//!
//! pub fn get(_: *StopEndpoint, _: zap.Request) void {
//! zap.stop(); //! zap.stop();
//! } //! }
//!
//! pub fn post(_: *StopEndpoint, _: zap.Request) !void {}
//! pub fn put(_: *StopEndpoint, _: zap.Request) !void {}
//! pub fn delete(_: *StopEndpoint, _: zap.Request) !void {}
//! pub fn patch(_: *StopEndpoint, _: zap.Request) !void {}
//! pub fn options(_: *StopEndpoint, _: zap.Request) !void {}
//! pub fn head(_: *StopEndpoint, _: zap.Request) !void {}
//! }; //! };
//! ``` //! ```
@ -62,11 +57,9 @@ const auth = @import("http_auth.zig");
pub const ErrorStrategy = enum { pub const ErrorStrategy = enum {
/// log errors to console /// log errors to console
log_to_console, log_to_console,
/// send an HTML response containing an error trace /// log errors to console AND generate a HTML response
log_to_response, log_to_response,
/// raise errors. /// raise errors -> TODO: clarify: where can they be caught? in App.run()
/// raised errors, if kept unhandled, will ultimately be logged by
/// zap.Logging.on_uncaught_error()
raise, raise,
}; };
@ -99,7 +92,6 @@ pub fn checkEndpointType(T: type) void {
"delete", "delete",
"patch", "patch",
"options", "options",
"head",
}; };
const params_to_check = [_]type{ const params_to_check = [_]type{
@ -195,7 +187,6 @@ pub const Binder = struct {
.DELETE => self.endpoint.*.delete(r), .DELETE => self.endpoint.*.delete(r),
.PATCH => self.endpoint.*.patch(r), .PATCH => self.endpoint.*.patch(r),
.OPTIONS => self.endpoint.*.options(r), .OPTIONS => self.endpoint.*.options(r),
.HEAD => self.endpoint.*.head(r),
else => error.UnsupportedHtmlRequestMethod, else => error.UnsupportedHtmlRequestMethod,
}; };
if (ret) { if (ret) {
@ -204,7 +195,7 @@ pub const Binder = struct {
switch (self.endpoint.*.error_strategy) { switch (self.endpoint.*.error_strategy) {
.raise => return err, .raise => return err,
.log_to_response => return r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505), .log_to_response => return r.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 505),
.log_to_console => zap.log.err("Error in {} {s} : {}", .{ Bound, r.method orelse "(no method)", err }), .log_to_console => zap.debug("Error in {} {s} : {}", .{ Bound, r.method orelse "(no method)", err }),
} }
} }
} }
@ -299,15 +290,6 @@ pub fn Authenticating(EndpointType: type, Authenticator: type) type {
.Handled => {}, .Handled => {},
}; };
} }
/// Authenticates HEAD requests using the Authenticator.
pub fn head(self: *AuthenticatingEndpoint, r: zap.Request) anyerror!void {
try switch (self.authenticator.authenticateRequest(&r)) {
.AuthFailed => return self.ep.*.unauthorized(r),
.AuthOK => self.ep.*.head(r),
.Handled => {},
};
}
}; };
} }
@ -326,33 +308,6 @@ pub const Listener = struct {
listener: HttpListener, listener: HttpListener,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
pub const Settings = struct {
port: usize,
interface: [*c]const u8 = null,
/// User-defined request callback that only gets called if no endpoints
/// match a request.
on_request: ?zap.HttpRequestFn,
on_response: ?zap.HttpRequestFn = null,
on_upgrade: ?zap.HttpUpgradeFn = null,
on_finish: ?zap.HttpFinishFn = null,
/// Callback, called if an error is raised and not caught by the
/// ErrorStrategy
on_error: ?*const fn (Request, anyerror) void = null,
// provide any pointer in there for "user data". it will be passed pack in
// on_finish()'s copy of the struct_http_settings_s
udata: ?*anyopaque = null,
public_folder: ?[]const u8 = null,
max_clients: ?isize = null,
max_body_size: ?usize = null,
timeout: ?u8 = null,
log: bool = false,
ws_timeout: u8 = 40,
ws_max_msg_size: usize = 262144,
tls: ?zap.Tls = null,
};
/// Internal static interface struct of member endpoints /// Internal static interface struct of member endpoints
var endpoints: std.ArrayListUnmanaged(*Binder.Interface) = .empty; var endpoints: std.ArrayListUnmanaged(*Binder.Interface) = .empty;
@ -361,46 +316,23 @@ pub const Listener = struct {
/// a request. /// a request.
var on_request: ?zap.HttpRequestFn = null; var on_request: ?zap.HttpRequestFn = null;
/// Callback, called if an error is raised and not caught by the ErrorStrategy
var on_error: ?*const fn (Request, anyerror) void = null;
/// Initialize a new endpoint listener. Note, if you pass an `on_request` /// Initialize a new endpoint listener. Note, if you pass an `on_request`
/// callback in the provided ListenerSettings, this request callback will be /// callback in the provided ListenerSettings, this request callback will be
/// called every time a request arrives that no endpoint matches. /// called every time a request arrives that no endpoint matches.
pub fn init(a: std.mem.Allocator, settings: Settings) Listener { pub fn init(a: std.mem.Allocator, l: ListenerSettings) Listener {
// reset the global in case init is called multiple times, as is the // reset the global in case init is called multiple times, as is the
// case in the authentication tests // case in the authentication tests
endpoints = .empty; endpoints = .empty;
var ls: zap.HttpListenerSettings = .{ // take copy of listener settings before modifying the callback field
.port = settings.port, var ls = l;
.interface = settings.interface,
// we set to our own handler
.on_request = onRequest,
.on_response = settings.on_response,
.on_upgrade = settings.on_upgrade,
.on_finish = settings.on_finish,
.udata = settings.udata,
.public_folder = settings.public_folder,
.max_clients = settings.max_clients,
.max_body_size = settings.max_body_size,
.timeout = settings.timeout,
.log = settings.log,
.ws_timeout = settings.ws_timeout,
.ws_max_msg_size = settings.ws_max_msg_size,
.tls = settings.tls,
};
// override the settings with our internal, actual callback function // override the settings with our internal, actual callback function
// so that "we" will be called on request // so that "we" will be called on request
ls.on_request = Listener.onRequest; ls.on_request = Listener.onRequest;
// store the settings-provided request callbacks for later use // store the settings-provided request callback for later use
on_request = settings.on_request; on_request = l.on_request;
on_error = settings.on_error;
return .{ return .{
.listener = HttpListener.init(ls), .listener = HttpListener.init(ls),
.allocator = a, .allocator = a,
@ -452,32 +384,13 @@ pub const Listener = struct {
if (r.path) |p| { if (r.path) |p| {
for (endpoints.items) |interface| { for (endpoints.items) |interface| {
if (std.mem.startsWith(u8, p, interface.path)) { if (std.mem.startsWith(u8, p, interface.path)) {
return interface.call(interface, r) catch |err| { return try interface.call(interface, r);
// if error is not dealt with in the entpoint, e.g.
// if error strategy is .raise:
if (on_error) |error_cb| {
error_cb(r, err);
} else {
zap.log.err(
"Endpoint onRequest error {} in endpoint interface {}\n",
.{ err, interface },
);
}
};
} }
} }
} }
// if set, call the user-provided default callback // if set, call the user-provided default callback
if (on_request) |foo| { if (on_request) |foo| {
foo(r) catch |err| { try foo(r);
// if error is not dealt with in the entpoint, e.g.
// if error strategy is .raise:
if (on_error) |error_cb| {
error_cb(r, err);
} else {
zap.Logging.on_uncaught_error("Endpoint on_request", err);
}
};
} }
} }
}; };

View file

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

View file

@ -1,18 +1,23 @@
//! Access to facil.io's logging facilities const std = @import("std");
//!
//! Zap uses Zig's standard logging facilities, which you can control like this: // TODO: rework logging in zap
//!
//! ```zig debugOn: bool,
//! pub const std_options: std.Options = .{
//! // general log level /// Access to facil.io's logging facilities
//! .log_level = .info, const Log = @This();
//! .log_scope_levels = &[_]std.log.ScopeLevel{
//! // log level specific to zap pub fn init(comptime debug: bool) Log {
//! .{ .scope = .zap, .level = .debug }, return .{
//! }, .debugOn = debug,
//! }; };
//! ``` }
const Logging = @This();
pub fn log(self: *const Log, comptime fmt: []const u8, args: anytype) void {
if (self.debugOn) {
std.debug.print("[zap] - " ++ fmt, args);
}
}
pub extern const fio_log_level_none: c_int; pub extern const fio_log_level_none: c_int;
pub extern const fio_log_level_fatal: c_int; pub extern const fio_log_level_fatal: c_int;
@ -29,9 +34,12 @@ pub extern fn fio_log_error(msg: [*c]const u8) void;
pub extern fn fio_log_fatal(msg: [*c]const u8) void; pub extern fn fio_log_fatal(msg: [*c]const u8) void;
pub extern fn fio_log_debug(msg: [*c]const u8) void; pub extern fn fio_log_debug(msg: [*c]const u8) void;
/// Error reporting of last resort // pub fn getDebugLogger(comptime debug: bool) type {
pub fn on_uncaught_error(comptime domain: []const u8, err: anyerror) void { // return struct {
const std = @import("std"); // pub fn log(comptime fmt: []const u8, args: anytype) void {
const log = std.log.scoped(.zap); // if (debug) {
log.err(domain ++ " : {}", .{err}); // std.debug.print("[zap] - " ++ fmt, args);
} // }
// }
// };
// }

View file

@ -109,7 +109,6 @@ pub fn EndpointHandler(comptime HandlerType: anytype, comptime EndpointType: any
.DELETE => try self.endpoint.*.delete(r), .DELETE => try self.endpoint.*.delete(r),
.PATCH => try self.endpoint.*.patch(r), .PATCH => try self.endpoint.*.patch(r),
.OPTIONS => try self.endpoint.*.options(r), .OPTIONS => try self.endpoint.*.options(r),
.HEAD => try self.endpoint.*.head(r),
else => {}, else => {},
} }
} }

View file

@ -194,13 +194,18 @@ fn fiobjectify(
.@"struct" => |S| { .@"struct" => |S| {
// create a new fio hashmap // create a new fio hashmap
const m = fio.fiobj_hash_new(); const m = fio.fiobj_hash_new();
// std.debug.print("new struct\n", .{});
inline for (S.fields) |Field| { inline for (S.fields) |Field| {
// don't include void fields // don't include void fields
if (Field.type == void) continue; if (Field.type == void) continue;
// std.debug.print(" new field: {s}\n", .{Field.name});
const fname = fio.fiobj_str_new(util.toCharPtr(Field.name), Field.name.len); const fname = fio.fiobj_str_new(util.toCharPtr(Field.name), Field.name.len);
// std.debug.print(" fiobj name : {any}\n", .{fname});
const v = @field(value, Field.name); const v = @field(value, Field.name);
// std.debug.print(" value: {any}\n", .{v});
const fvalue = fiobjectify(v); const fvalue = fiobjectify(v);
// std.debug.print(" fiobj value: {any}\n", .{fvalue});
_ = fio.fiobj_hash_set(m, fname, fvalue); _ = fio.fiobj_hash_set(m, fname, fvalue);
fio.fiobj_free_wrapped(fname); fio.fiobj_free_wrapped(fname);
} }
@ -220,6 +225,7 @@ fn fiobjectify(
}, },
// TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972) // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972)
.slice => { .slice => {
// std.debug.print("new slice\n", .{});
if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) { if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) {
return fio.fiobj_str_new(util.toCharPtr(value), value.len); return fio.fiobj_str_new(util.toCharPtr(value), value.len);
} }

View file

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Log = @import("log.zig");
const http = @import("http.zig"); const http = @import("http.zig");
const fio = @import("fio.zig"); const fio = @import("fio.zig");
@ -125,43 +126,36 @@ fn parseBinfilesFrom(a: Allocator, o: fio.FIOBJ) !HttpParam {
fio.fiobj_free_wrapped(key_name); fio.fiobj_free_wrapped(key_name);
fio.fiobj_free_wrapped(key_data); fio.fiobj_free_wrapped(key_data);
fio.fiobj_free_wrapped(key_type); fio.fiobj_free_wrapped(key_type);
} // files: they should have "data" and "filename" keys } // files: they should have "data", "type", and "filename" keys
if (fio.fiobj_hash_haskey(o, key_data) == 1 and fio.fiobj_hash_haskey(o, key_name) == 1) { if (fio.fiobj_hash_haskey(o, key_data) == 1 and fio.fiobj_hash_haskey(o, key_type) == 1 and fio.fiobj_hash_haskey(o, key_name) == 1) {
const filename = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_name)); const filename = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_name));
const mimetype = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_type));
const data = fio.fiobj_hash_get(o, key_data); const data = fio.fiobj_hash_get(o, key_data);
var mimetype: []const u8 = undefined;
if (fio.fiobj_hash_haskey(o, key_type) == 1) {
const mt = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_type));
mimetype = mt.data[0..mt.len];
} else {
mimetype = &"application/octet-stream".*;
}
var data_slice: ?[]const u8 = null; var data_slice: ?[]const u8 = null;
switch (fio.fiobj_type(data)) { switch (fio.fiobj_type(data)) {
fio.FIOBJ_T_DATA => { fio.FIOBJ_T_DATA => {
if (fio.is_invalid(data) == 1) { if (fio.is_invalid(data) == 1) {
data_slice = "(zap: invalid data)"; data_slice = "(zap: invalid data)";
zap.log.warn("HTTP param binary file is not a data object", .{}); std.log.warn("WARNING: HTTP param binary file is not a data object\n", .{});
} else { } else {
// the data // the data
const data_len = fio.fiobj_data_len(data); const data_len = fio.fiobj_data_len(data);
var data_buf = fio.fiobj_data_read(data, data_len); var data_buf = fio.fiobj_data_read(data, data_len);
if (data_len < 0) { if (data_len < 0) {
zap.log.warn("HTTP param binary file size negative: {d}", .{data_len}); std.log.warn("WARNING: HTTP param binary file size negative: {d}\n", .{data_len});
zap.log.warn("FIOBJ_TYPE of data is: {d}", .{fio.fiobj_type(data)}); std.log.warn("FIOBJ_TYPE of data is: {d}\n", .{fio.fiobj_type(data)});
} else { } else {
if (data_buf.len != data_len) { if (data_buf.len != data_len) {
zap.log.warn("HTTP param binary file size mismatch: should {d}, is: {d}", .{ data_len, data_buf.len }); std.log.warn("WARNING: HTTP param binary file size mismatch: should {d}, is: {d}\n", .{ data_len, data_buf.len });
} }
if (data_buf.len > 0) { if (data_buf.len > 0) {
data_slice = data_buf.data[0..data_buf.len]; data_slice = data_buf.data[0..data_buf.len];
} else { } else {
zap.log.warn("HTTP param binary file buffer size negative: {d}", .{data_buf.len}); std.log.warn("WARNING: HTTP param binary file buffer size negative: {d}\n", .{data_buf.len});
data_slice = "(zap: invalid data: negative BUFFER size)"; data_slice = "(zap: invalid data: negative BUFFER size)";
} }
} }
@ -171,7 +165,7 @@ fn parseBinfilesFrom(a: Allocator, o: fio.FIOBJ) !HttpParam {
const fiostr = fio.fiobj_obj2cstr(data); const fiostr = fio.fiobj_obj2cstr(data);
if (fiostr.len == 0) { if (fiostr.len == 0) {
data_slice = "(zap: empty string data)"; data_slice = "(zap: empty string data)";
zap.log.warn("WARNING: HTTP param binary file has empty string object\n", .{}); std.log.warn("WARNING: HTTP param binary file has empty string object\n", .{});
} else { } else {
data_slice = fiostr.data[0..fiostr.len]; data_slice = fiostr.data[0..fiostr.len];
} }
@ -191,15 +185,15 @@ fn parseBinfilesFrom(a: Allocator, o: fio.FIOBJ) !HttpParam {
const file_mimetype_obj = fio.fiobj_ary_entry(mt_ary, i); const file_mimetype_obj = fio.fiobj_ary_entry(mt_ary, i);
var has_error: bool = false; var has_error: bool = false;
if (fio.is_invalid(file_data_obj) == 1) { if (fio.is_invalid(file_data_obj) == 1) {
zap.log.debug("file data invalid in array", .{}); std.log.debug("file data invalid in array", .{});
has_error = true; has_error = true;
} }
if (fio.is_invalid(file_name_obj) == 1) { if (fio.is_invalid(file_name_obj) == 1) {
zap.log.debug("file name invalid in array", .{}); std.log.debug("file name invalid in array", .{});
has_error = true; has_error = true;
} }
if (fio.is_invalid(file_mimetype_obj) == 1) { if (fio.is_invalid(file_mimetype_obj) == 1) {
zap.log.debug("file mimetype invalid in array", .{}); std.log.debug("file mimetype invalid in array", .{});
has_error = true; has_error = true;
} }
if (has_error) { if (has_error) {
@ -228,7 +222,7 @@ fn parseBinfilesFrom(a: Allocator, o: fio.FIOBJ) !HttpParam {
return .{ .Hash_Binfile = .{ return .{ .Hash_Binfile = .{
.filename = filename.data[0..filename.len], .filename = filename.data[0..filename.len],
.mimetype = mimetype, .mimetype = mimetype.data[0..mimetype.len],
.data = data_slice, .data = data_slice,
} }; } };
} else { } else {
@ -364,6 +358,7 @@ pub fn sendBody(self: *const Request, body: []const u8) HttpError!void {
*anyopaque, *anyopaque,
@ptrFromInt(@intFromPtr(body.ptr)), @ptrFromInt(@intFromPtr(body.ptr)),
), body.len); ), body.len);
zap.debug("Request.sendBody(): ret = {}\n", .{ret});
if (ret == -1) return error.HttpSendBody; if (ret == -1) return error.HttpSendBody;
self.markAsFinished(true); self.markAsFinished(true);
} }
@ -386,7 +381,7 @@ pub fn setContentType(self: *const Request, c: ContentType) HttpError!void {
.JSON => "application/json", .JSON => "application/json",
else => "text/html", else => "text/html",
}; };
zap.log.debug("setting content-type to {s}", .{s}); zap.debug("setting content-type to {s}\n", .{s});
return self.setHeader("content-type", s); return self.setHeader("content-type", s);
} }
@ -398,6 +393,21 @@ pub fn redirectTo(self: *const Request, path: []const u8, code: ?http.StatusCode
self.markAsFinished(true); self.markAsFinished(true);
} }
/// shows how to use the logger
pub fn setContentTypeWithLogger(
self: *const Request,
c: ContentType,
logger: *const Log,
) HttpError!void {
const s = switch (c) {
.TEXT => "text/plain",
.JSON => "application/json",
else => "text/html",
};
logger.log("setting content-type to {s}\n", .{s});
return self.setHeader("content-type", s);
}
/// Tries to determine the content type by file extension of request path, and sets it. /// Tries to determine the content type by file extension of request path, and sets it.
pub fn setContentTypeFromPath(self: *const Request) !void { pub fn setContentTypeFromPath(self: *const Request) !void {
const t = fio.http_mimetype_find2(self.h.*.path); const t = fio.http_mimetype_find2(self.h.*.path);
@ -508,18 +518,21 @@ pub fn setHeader(self: *const Request, name: []const u8, value: []const u8) Http
.capa = name.len, .capa = name.len,
}; };
zap.debug("setHeader: hname = {s}\n", .{name});
const vname: fio.fio_str_info_s = .{ const vname: fio.fio_str_info_s = .{
.data = util.toCharPtr(value), .data = util.toCharPtr(value),
.len = value.len, .len = value.len,
.capa = value.len, .capa = value.len,
}; };
zap.debug("setHeader: vname = {s}\n", .{value});
const ret = fio.http_set_header2(self.h, hname, vname); const ret = fio.http_set_header2(self.h, hname, vname);
// 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", .{}); std.debug.print("***************** zap.zig:274\n", .{});
} }
zap.debug("setHeader: ret = {}\n", .{ret});
if (ret == 0) return; if (ret == 0) return;
return error.HttpSetHeader; return error.HttpSetHeader;
@ -687,7 +700,7 @@ pub fn setCookie(self: *const Request, args: CookieArgs) HttpError!void {
// TODO: still happening? // TODO: still happening?
const ret = fio.http_set_cookie(self.h, c); const ret = fio.http_set_cookie(self.h, c);
if (ret == -1) { if (ret == -1) {
zap.log.err("fio.http_set_cookie returned: {}", .{ret}); std.log.err("fio.http_set_cookie returned: {}\n", .{ret});
return error.SetCookie; return error.SetCookie;
} }
} }

View file

@ -170,7 +170,6 @@ pub const Endpoint = struct {
pub fn delete(_: *Endpoint, _: zap.Request) !void {} pub fn delete(_: *Endpoint, _: zap.Request) !void {}
pub fn patch(_: *Endpoint, _: zap.Request) !void {} pub fn patch(_: *Endpoint, _: zap.Request) !void {}
pub fn options(_: *Endpoint, _: zap.Request) !void {} pub fn options(_: *Endpoint, _: zap.Request) !void {}
pub fn head(_: *Endpoint, _: zap.Request) !void {}
}; };
// //
// end of http client code // end of http client code
@ -210,6 +209,10 @@ test "BearerAuthSingle authenticateRequest OK" {
try listener.register(&auth_ep); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("\n\n*******************************************\n", .{});
// std.debug.print("\n\nPlease run the following:\n", .{});
// std.debug.print("./zig-out/bin/http_client_runner\n", .{});
// std.debug.print("\n\n*******************************************\n", .{});
const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Bearer, .token = token }); const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Bearer, .token = token });
defer thread.join(); defer thread.join();
@ -263,6 +266,8 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" {
try listener.register(&auth_ep); try listener.register(&auth_ep);
try listener.listen(); try listener.listen();
// std.debug.print("Waiting for the following:\n", .{});
// std.debug.print("./zig-out/bin/http_client http://127.0.0.1:3000/test Bearer invalid\r", .{});
const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Bearer, .token = "invalid" }); const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Bearer, .token = "invalid" });
defer thread.join(); defer thread.join();
@ -274,6 +279,7 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" {
}); });
try std.testing.expectEqualStrings("UNAUTHORIZED", received_response); try std.testing.expectEqualStrings("UNAUTHORIZED", received_response);
std.debug.print("\nI made it\n", .{});
} }
test "BearerAuthMulti authenticateRequest OK" { test "BearerAuthMulti authenticateRequest OK" {
@ -310,6 +316,8 @@ test "BearerAuthMulti authenticateRequest OK" {
try listener.register(&auth_ep); try listener.register(&auth_ep);
try listener.listen(); try listener.listen();
// std.debug.print("Waiting for the following:\n", .{});
// std.debug.print("./zig-out/bin/http_client_runner http://127.0.0.1:3000/test Bearer " ++ token ++ "\r", .{});
const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Bearer, .token = token }); const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Bearer, .token = token });
defer thread.join(); defer thread.join();
@ -357,6 +365,8 @@ test "BearerAuthMulti authenticateRequest test-unauthorized" {
try listener.register(&auth_ep); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{});
// std.debug.print("./zig-out/bin/http_client_runner http://127.0.0.1:3000/test Bearer invalid\r", .{});
const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Bearer, .token = "invalid" }); const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Bearer, .token = "invalid" });
defer thread.join(); defer thread.join();
@ -409,6 +419,8 @@ test "BasicAuth Token68 authenticateRequest" {
try listener.register(&auth_ep); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{});
// std.debug.print("./zig-out/bin/http_client http://127.0.0.1:3000/test Basic " ++ token ++ "\r", .{});
const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Basic, .token = token }); const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Basic, .token = token });
defer thread.join(); defer thread.join();
@ -461,6 +473,8 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" {
try listener.register(&auth_ep); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{});
// std.debug.print("./zig-out/bin/http_client http://127.0.0.1:3000/test Basic " ++ "invalid\r", .{});
const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Basic, .token = "invalid" }); const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Basic, .token = "invalid" });
defer thread.join(); defer thread.join();
@ -523,6 +537,8 @@ test "BasicAuth UserPass authenticateRequest" {
try listener.register(&auth_ep); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{});
// std.debug.print("./zig-out/bin/http_client http://127.0.0.1:3000/test Basic {s}\r", .{encoded});
const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Basic, .token = encoded }); const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Basic, .token = encoded });
defer thread.join(); defer thread.join();
@ -588,6 +604,8 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" {
try listener.register(&auth_ep); try listener.register(&auth_ep);
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{});
// std.debug.print("./zig-out/bin/http_client http://127.0.0.1:3000/test Basic invalid\r", .{});
const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Basic, .token = "invalid" }); const thread = try makeRequestThread(a, "http://127.0.0.1:3000/test", .{ .auth = .Basic, .token = "invalid" });
defer thread.join(); defer thread.join();

View file

@ -1,14 +1,6 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); 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 },
},
};
fn makeRequest(a: std.mem.Allocator, url: []const u8) !void { fn makeRequest(a: std.mem.Allocator, url: []const u8) !void {
var http_client: std.http.Client = .{ .allocator = a }; var http_client: std.http.Client = .{ .allocator = a };
defer http_client.deinit(); defer http_client.deinit();
@ -72,6 +64,7 @@ test "http parameters" {
.max_body_size = 1 * 1024, .max_body_size = 1 * 1024,
}, },
); );
zap.enableDebugLog();
try listener.listen(); try listener.listen();
const thread = try makeRequestThread(allocator, "http://127.0.0.1:3001/?one=1&two=2&string=hello+world&float=6.28&bool=true"); const thread = try makeRequestThread(allocator, "http://127.0.0.1:3001/?one=1&two=2&string=hello+world&float=6.28&bool=true");

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;
}
}

View file

@ -1,14 +1,6 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); 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 },
},
};
var buffer: [1024]u8 = undefined; var buffer: [1024]u8 = undefined;
var read_len: ?usize = null; var read_len: ?usize = null;
@ -49,6 +41,7 @@ test "send file" {
.max_body_size = 1 * 1024, .max_body_size = 1 * 1024,
}, },
); );
zap.enableDebugLog();
try listener.listen(); try listener.listen();
const thread = try makeRequestThread(allocator, "http://127.0.0.1:3002/?file=src/tests/testfile.txt"); const thread = try makeRequestThread(allocator, "http://127.0.0.1:3002/?file=src/tests/testfile.txt");

View file

@ -21,15 +21,15 @@ pub fn Handler(comptime ContextType: type) type {
message: []const u8, message: []const u8,
/// indicator if message is text or binary /// indicator if message is text or binary
is_text: bool, is_text: bool,
) anyerror!void; ) void;
/// Callback (type) when websocket is closed. uuid is a connection identifier, /// Callback (type) when websocket is closed. uuid is a connection identifier,
/// it is -1 if a connection could not be established /// it is -1 if a connection could not be established
pub const WsOnCloseFn = *const fn (context: ?*ContextType, uuid: isize) anyerror!void; pub const WsOnCloseFn = *const fn (context: ?*ContextType, uuid: isize) void;
/// A websocket callback function type. provides the context passed in at /// A websocket callback function type. provides the context passed in at
/// websocketHttpUpgrade(). /// websocketHttpUpgrade().
pub const WsFn = *const fn (context: ?*ContextType, handle: WsHandle) anyerror!void; pub const WsFn = *const fn (context: ?*ContextType, handle: WsHandle) void;
/// Websocket connection handler creation settings. Provide the callbacks you need, /// Websocket connection handler creation settings. Provide the callbacks you need,
/// and an optional context. /// and an optional context.
@ -68,9 +68,7 @@ pub fn Handler(comptime ContextType: type) type {
const message = msg.data[0..msg.len]; const message = msg.data[0..msg.len];
if (user_provided_settings) |settings| { if (user_provided_settings) |settings| {
if (settings.on_message) |on_message| { if (settings.on_message) |on_message| {
on_message(settings.context, handle, message, is_text == 1) catch |err| { on_message(settings.context, handle, message, is_text == 1);
zap.Logging.on_uncaught_error("WebSocket Handler on_message", err);
};
} }
} }
} }
@ -79,9 +77,7 @@ pub fn Handler(comptime ContextType: type) type {
const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle)))); const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle))));
if (user_provided_settings) |settings| { if (user_provided_settings) |settings| {
if (settings.on_open) |on_open| { if (settings.on_open) |on_open| {
on_open(settings.context, handle) catch |err| { on_open(settings.context, handle);
zap.Logging.on_uncaught_error("WebSocket Handler on_open", err);
};
} }
} }
} }
@ -90,9 +86,7 @@ pub fn Handler(comptime ContextType: type) type {
const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle)))); const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle))));
if (user_provided_settings) |settings| { if (user_provided_settings) |settings| {
if (settings.on_ready) |on_ready| { if (settings.on_ready) |on_ready| {
on_ready(settings.context, handle) catch |err| { on_ready(settings.context, handle);
zap.Logging.on_uncaught_error("WebSocket Handler on_ready", err);
};
} }
} }
} }
@ -101,9 +95,7 @@ pub fn Handler(comptime ContextType: type) type {
const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle)))); const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(fio.websocket_udata_get(handle))));
if (user_provided_settings) |settings| { if (user_provided_settings) |settings| {
if (settings.on_shutdown) |on_shutdown| { if (settings.on_shutdown) |on_shutdown| {
on_shutdown(settings.context, handle) catch |err| { on_shutdown(settings.context, handle);
zap.Logging.on_uncaught_error("WebSocket Handler on_shutdown", err);
};
} }
} }
} }
@ -112,9 +104,7 @@ pub fn Handler(comptime ContextType: type) type {
const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(udata))); const user_provided_settings: ?*WebSocketSettings = @as(?*WebSocketSettings, @ptrCast(@alignCast(udata)));
if (user_provided_settings) |settings| { if (user_provided_settings) |settings| {
if (settings.on_close) |on_close| { if (settings.on_close) |on_close| {
on_close(settings.context, uuid) catch |err| { on_close(settings.context, uuid);
zap.Logging.on_uncaught_error("WebSocket Handler on_close", err);
};
} }
} }
} }
@ -165,10 +155,10 @@ pub fn Handler(comptime ContextType: type) type {
} }
/// Type for callback on subscription message. /// Type for callback on subscription message.
pub const SubscriptionOnMessageFn = *const fn (context: ?*ContextType, handle: WsHandle, channel: []const u8, message: []const u8) anyerror!void; pub const SubscriptionOnMessageFn = *const fn (context: ?*ContextType, handle: WsHandle, channel: []const u8, message: []const u8) void;
/// Type for callback on unsubscribe message. /// Type for callback on unsubscribe message.
pub const SubscriptionOnUnsubscribeFn = *const fn (context: ?*ContextType) anyerror!void; pub const SubscriptionOnUnsubscribeFn = *const fn (context: ?*ContextType) void;
/// Settings for subscribing to a channel. /// Settings for subscribing to a channel.
pub const SubscribeArgs = struct { pub const SubscribeArgs = struct {
@ -223,9 +213,7 @@ pub fn Handler(comptime ContextType: type) type {
if (udata) |p| { if (udata) |p| {
const args = @as(*SubscribeArgs, @ptrCast(@alignCast(p))); const args = @as(*SubscribeArgs, @ptrCast(@alignCast(p)));
if (args.on_message) |on_message| { if (args.on_message) |on_message| {
on_message(args.context, handle, channel.data[0..channel.len], message.data[0..message.len]) catch |err| { on_message(args.context, handle, channel.data[0..channel.len], message.data[0..message.len]);
zap.Logging.on_uncaught_error("WebSocket Subscription on_message", err);
};
} }
} }
} }
@ -233,9 +221,7 @@ pub fn Handler(comptime ContextType: type) type {
if (udata) |p| { if (udata) |p| {
const args = @as(*SubscribeArgs, @ptrCast(@alignCast(p))); const args = @as(*SubscribeArgs, @ptrCast(@alignCast(p)));
if (args.on_unsubscribe) |on_unsubscribe| { if (args.on_unsubscribe) |on_unsubscribe| {
on_unsubscribe(args.context) catch |err| { on_unsubscribe(args.context);
zap.Logging.on_uncaught_error("WebSocket Subscription on_unsubscribe", err);
};
} }
} }
} }

View file

@ -37,12 +37,13 @@ pub const Middleware = @import("middleware.zig");
/// Websocket API /// Websocket API
pub const WebSockets = @import("websockets.zig"); pub const WebSockets = @import("websockets.zig");
pub const Logging = @import("Logging.zig"); pub const Log = @import("log.zig");
pub const log = std.log.scoped(.zap);
pub const http = @import("http.zig"); pub const http = @import("http.zig");
pub const util = @import("util.zig"); pub const util = @import("util.zig");
// TODO: replace with comptime debug logger like in log.zig
var _debug: bool = false;
/// Start the IO reactor /// Start the IO reactor
/// ///
/// Will start listeners etc. /// Will start listeners etc.
@ -59,9 +60,23 @@ pub fn stop() void {
fio.fio_stop(); fio.fio_stop();
} }
/// Extremely simplistic zap debug function, to save a few keystrokes /// Extremely simplistic zap debug function.
/// TODO: re-wwrite logging or use std.log
pub fn debug(comptime fmt: []const u8, args: anytype) void { pub fn debug(comptime fmt: []const u8, args: anytype) void {
log.debug("[zap] - " ++ fmt, args); if (_debug) {
std.debug.print("[zap] - " ++ fmt, args);
}
}
/// Enable zap debug logging
pub fn enableDebugLog() void {
_debug = true;
}
/// start Zap with debug logging on
pub fn startWithLogging(args: fio.fio_start_args) void {
_debug = true;
fio.fio_start(args);
} }
/// Registers a new mimetype to be used for files ending with the given extension. /// Registers a new mimetype to be used for files ending with the given extension.
@ -123,14 +138,14 @@ pub const HttpRequestFn = *const fn (Request) anyerror!void;
/// websocket connection upgrade callback type /// websocket connection upgrade callback type
/// fn(request, targetstring) /// fn(request, targetstring)
pub const HttpUpgradeFn = *const fn (r: Request, target_protocol: []const u8) anyerror!void; pub const HttpUpgradeFn = *const fn (r: Request, target_protocol: []const u8) void;
/// http finish, called when zap finishes. You get your udata back in the /// http finish, called when zap finishes. You get your udata back in the
/// HttpFinishSetting struct. /// HttpFinishSetting struct.
pub const HttpFinishSettings = [*c]fio.struct_http_settings_s; pub const HttpFinishSettings = [*c]fio.struct_http_settings_s;
/// Http finish callback type /// Http finish callback type
pub const HttpFinishFn = *const fn (HttpFinishSettings) anyerror!void; pub const HttpFinishFn = *const fn (HttpFinishSettings) void;
/// Listener settings /// Listener settings
pub const HttpListenerSettings = struct { pub const HttpListenerSettings = struct {
@ -189,7 +204,8 @@ pub const HttpListener = struct {
std.debug.assert(l.settings.on_request != null); std.debug.assert(l.settings.on_request != null);
if (l.settings.on_request) |on_request| { if (l.settings.on_request) |on_request| {
on_request(req) catch |err| { on_request(req) catch |err| {
Logging.on_uncaught_error("HttpListener on_request", err); // TODO: log / handle the error in a better way
std.debug.print("zap on_request error: {}", .{err});
}; };
} }
} }
@ -213,7 +229,8 @@ pub const HttpListener = struct {
req._user_context = &user_context; req._user_context = &user_context;
l.settings.on_response.?(req) catch |err| { l.settings.on_response.?(req) catch |err| {
Logging.on_uncaught_error("HttpListener on_response", err); // TODO: log / handle the error in a better way
std.debug.print("zap on_response error: {}", .{err});
}; };
} }
} }
@ -236,18 +253,14 @@ pub const HttpListener = struct {
var user_context: Request.UserContext = .{}; var user_context: Request.UserContext = .{};
req._user_context = &user_context; req._user_context = &user_context;
l.settings.on_upgrade.?(req, zigtarget) catch |err| { l.settings.on_upgrade.?(req, zigtarget);
Logging.on_uncaught_error("HttpListener on_upgrade", err);
};
} }
} }
/// Used internally: the listener's facilio finish callback /// Used internally: the listener's facilio finish callback
pub fn theOneAndOnlyFinishCallBack(s: [*c]fio.struct_http_settings_s) callconv(.C) void { pub fn theOneAndOnlyFinishCallBack(s: [*c]fio.struct_http_settings_s) callconv(.C) void {
if (the_one_and_only_listener) |l| { if (the_one_and_only_listener) |l| {
l.settings.on_finish.?(s) catch |err| { l.settings.on_finish.?(s);
Logging.on_uncaught_error("HttpListener on_finish", err);
};
} }
} }
@ -377,6 +390,7 @@ pub const LowLevel = struct {
*anyopaque, *anyopaque,
@ptrFromInt(@intFromPtr(body.ptr)), @ptrFromInt(@intFromPtr(body.ptr)),
), body.len); ), body.len);
debug("sendBody(): ret = {}\n", .{ret});
if (ret != -1) return error.HttpSendBody; if (ret != -1) return error.HttpSendBody;
} }
}; };