diff --git a/README.md b/README.md index 6c6380e..c8a4a1e 100644 --- a/README.md +++ b/README.md @@ -25,42 +25,63 @@ juicy, and alpha._ Here's what works: -- **Super easy build process**: zap's `build.zig` now uses the up-and-coming - zig package manager for its C-dependencies, no git submodules anymore. +- **Super easy build process**: zap's `build.zig` now uses the up-and-coming zig + package manager for its C-dependencies, no git submodules anymore. - _tested on Linux and macOS (arm, M1)_ -- **[hello](examples/hello/hello.zig)**: welcomes you with some static - HTML -- **[routes](examples/routes/routes.zig)**: a super easy example - dispatching on the HTTP path -- **[serve](examples/serve/serve.zig)**: the traditional static web - server with optional dynamic request handling +- **[hello](examples/hello/hello.zig)**: welcomes you with some static HTML +- **[routes](examples/routes/routes.zig)**: a super easy example dispatching on + the HTTP path +- **[serve](examples/serve/serve.zig)**: the traditional static web server with + optional dynamic request handling - **[sendfile](examples/sendfile/sendfile.zig)**: simple example of how to send a file, honoring compression headers, etc. - **[hello_json](examples/hello_json/hello_json.zig)**: serves you json dependent on HTTP path -- **[endpoint](examples/endpoint/)**: a simple JSON REST API example featuring - a `/users` endpoint for PUTting/DELETE-ing/GET-ting/POST-ing and listing - users, together with a static HTML and JavaScript frontend to play with. +- **[endpoint](examples/endpoint/)**: a simple JSON REST API example featuring a + `/users` endpoint for PUTting/DELETE-ing/GET-ting/POST-ing and listing users, + together with a static HTML and JavaScript frontend to play with. - **[mustache](examples/mustache/mustache.zig)**: a simple example using [mustache](https://mustache.github.io/) templating. -- **[endpoint authentication](examples/endpoint_auth/endpoint_auth.zig)**: a simple authenticated - endpoint. Read more about authentication [here](./doc/authentication.md). -- **[http parameters](examples/http_params/http_params.zig)**: a simple example sending - itself query parameters of all supported types. -- **[cookies](examples/cookies/cookies.zig)**: a simple example sending - itself a cookie and responding with a session cookie. -- **[websockets](examples/websockets/)**: a simple websockets chat for the browser. -- **[Username/Password Session Authentication](./examples/userpass_session_auth/)**: - A convenience authenticator that redirects un-authenticated requests to a - login page and sends cookies containing session tokens based on - username/password pairs transmitted via POST request. +- **[endpoint authentication](examples/endpoint_auth/endpoint_auth.zig)**: a + simple authenticated endpoint. Read more about authentication + [here](./doc/authentication.md). +- **[http parameters](examples/http_params/http_params.zig)**: a simple example + sending itself query parameters of all supported types. +- **[cookies](examples/cookies/cookies.zig)**: a simple example sending itself a + cookie and responding with a session cookie. +- **[websockets](examples/websockets/)**: a simple websockets chat for the + browser. +- **[Username/Password Session + Authentication](./examples/userpass_session_auth/)**: A convenience + authenticator that redirects un-authenticated requests to a login page and + sends cookies containing session tokens based on username/password pairs + transmitted via POST request. - **[MIDDLEWARE support](examples/middleware/middleware.zig)**: chain together request handlers in middleware style. Provide custom context structs, totally type-safe, using **[ZIG-CEPTION](doc/zig-ception.md)**. If you come from GO this might appeal to you. -- **[MIDDLEWARE with endpoint support](examples/middleware_with_endpoint/middleware_with_endpoint.zig)**: Same as the example above, but this time we use an endpoint at the end of the chain, by wrapping it via `zap.Middleware.EndpointHandler`. Mixing endpoints in your middleware chain allows for usage of Zap's authenticated endpoints and your custom endpoints. Since Endpoints use a simpler API, you have to use `r.setUserContext()` and `r.getUserContext()` with the request if you want to access the middleware context from a wrapped endpoint. Since this mechanism uses an `*anyopaque` pointer underneath (to not break the Endpoint API), it is less type-safe than `zap.Middleware`'s use of contexts. -- **Per Request Contexts** : With the introduction of `setContext()` and `getContext()`, you can, of course use those two in projects that don't use `zap.SimpleEndpoint` or `zap.Middleware`, too, if you really, really, absolutely don't find another way to solve your context problem. **We recommend using a `zap.SimpleEndpoint`** inside of a struct that can provide all the context you need **instead**. You get access to your struct in the callbacks via the `@fieldParentPtr()` trick that is used extensively in Zap's examples, like the [endpoint example](examples/endpoint/endpoint.zig). - +- **[MIDDLEWARE with endpoint + support](examples/middleware_with_endpoint/middleware_with_endpoint.zig)**: + Same as the example above, but this time we use an endpoint at the end of the + chain, by wrapping it via `zap.Middleware.EndpointHandler`. Mixing endpoints + in your middleware chain allows for usage of Zap's authenticated endpoints and + your custom endpoints. Since Endpoints use a simpler API, you have to use + `r.setUserContext()` and `r.getUserContext()` with the request if you want to + access the middleware context from a wrapped endpoint. Since this mechanism + uses an `*anyopaque` pointer underneath (to not break the Endpoint API), it is + less type-safe than `zap.Middleware`'s use of contexts. +- [**Per Request Contexts**](./src/zap.zig#L102) : With the introduction of + `setUserContext()` and `getUserContext()`, you can, of course use those two in + projects that don't use `zap.SimpleEndpoint` or `zap.Middleware`, too, if you + really, really, absolutely don't find another way to solve your context + problem. **We recommend using a `zap.SimpleEndpoint`** inside of a struct that + can provide all the context you need **instead**. You get access to your + struct in the callbacks via the `@fieldParentPtr()` trick that is used + extensively in Zap's examples, like the [endpoint + example](examples/endpoint/endpoint.zig). +- [**Error Trace Responses**](./examples/senderror/senderror.zig): You can now + call `r.sendError(err, status_code)` when you catch an error and a stack trace + will be returned to the client / browser. I'll continue wrapping more of facil.io's functionality and adding stuff to zap to a point where I can use it as the JSON REST API backend for real research diff --git a/build.zig b/build.zig index 7e7e033..362685a 100644 --- a/build.zig +++ b/build.zig @@ -57,6 +57,7 @@ pub fn build(b: *std.build.Builder) !void { .{ .name = "sendfile", .src = "examples/sendfile/sendfile.zig" }, .{ .name = "middleware", .src = "examples/middleware/middleware.zig" }, .{ .name = "middleware_with_endpoint", .src = "examples/middleware_with_endpoint/middleware_with_endpoint.zig" }, + .{ .name = "senderror", .src = "examples/senderror/senderror.zig" }, }) |excfg| { const ex_name = excfg.name; const ex_src = excfg.src; diff --git a/examples/senderror/senderror.zig b/examples/senderror/senderror.zig new file mode 100644 index 0000000..32addd9 --- /dev/null +++ b/examples/senderror/senderror.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const zap = @import("zap"); + +fn MAKE_MEGA_ERROR() !void { + return error.MEGA_ERROR; +} + +fn MY_REQUEST_HANDLER(r: zap.SimpleRequest) void { + MAKE_MEGA_ERROR() catch |err| { + r.sendError(err, 505); + }; +} + +pub fn main() !void { + var listener = zap.SimpleHttpListener.init(.{ + .port = 3000, + .on_request = MY_REQUEST_HANDLER, + .log = true, + }); + try listener.listen(); + + std.debug.print("Listening on 0.0.0.0:3000\n", .{}); + + zap.start(.{ + .threads = 2, + .workers = 2, + }); +} diff --git a/src/zap.zig b/src/zap.zig index d109020..68a6d6b 100644 --- a/src/zap.zig +++ b/src/zap.zig @@ -99,6 +99,8 @@ pub const SimpleRequest = struct { return self._is_finished.*; } + /// if you absolutely must, you can set any context here + // (note, this line is linked to from the readme) pub fn setUserContext(self: *const Self, context: *anyopaque) void { self._user_context.*.user_context = context; } @@ -111,6 +113,30 @@ pub const SimpleRequest = struct { } } + pub fn sendError(self: *const Self, err: anyerror, errorcode_num: usize) void { + // TODO: query accept headers + if (self._internal_sendError(err, errorcode_num)) { + return; + } else |_| { + self.sendBody(@errorName(err)) catch return; + } + } + pub fn _internal_sendError(self: *const Self, err: anyerror, errorcode_num: usize) !void { + // TODO: query accept headers + // TODO: let's hope 20k is enough. Maybe just really allocate here + self.h.*.status = errorcode_num; + var buf: [20 * 1024]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&buf); + var string = std.ArrayList(u8).init(fba.allocator()); + var writer = string.writer(); + try writer.print("ERROR: {any}\n\n", .{err}); + + var debugInfo = try std.debug.getSelfDebugInfo(); + var ttyConfig: std.debug.TTY.Config = .no_color; + try std.debug.writeCurrentStackTrace(writer, debugInfo, ttyConfig, null); + try self.sendBody(string.items); + } + pub fn sendBody(self: *const Self, body: []const u8) HttpError!void { const ret = fio.http_send_body(self.h, @intToPtr( *anyopaque, diff --git a/targets.txt b/targets.txt index 6330cf8..fbfd0ac 100644 --- a/targets.txt +++ b/targets.txt @@ -16,3 +16,4 @@ userpass_session sendfile middleware middleware_with_endpoint +senderror