diff --git a/README.md b/README.md index d86b7fc..334ad5d 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ master though. ## Here's what works I recommend checking out **Endpoint-based examples for more realistic -use cases**. Most of the examples are super stripped down to only include +use cases**. Most of the examples are super stripped down to only include what's necessary to show a feature. **NOTE: To see API docs, run `zig build run-docserver`.** To specify a custom @@ -132,10 +132,10 @@ port and docs dir: `zig build docserver && zig-out/bin/docserver --port=8989 call `r.sendError(err, status_code)` when you catch an error and a stack trace will be returned to the client / browser. - [**HTTPS**](examples/https/https.zig): Shows how easy it is to use facil.io's - openssl support. Must be compiled with `-Dopenssl=true` or the environment + openssl support. Must be compiled with `-Dopenssl=true` or the environment variable `ZAP_USE_OPENSSL` set to `true` and requires openssl dev dependencies (headers, lib) to be installed on the system. - - run it like this: `ZAP_USE_OPENSSL=true zig build run-https` + - run it like this: `ZAP_USE_OPENSSL=true zig build run-https` OR like this: `zig build -Dopenssl=true run-https` - it will tell you how to generate certificates - [**simple_router**](examples/simple_router/simple_router.zig): See how you @@ -181,7 +181,7 @@ simplistic testing scenario. So, being somewhere in the ballpark of basic GO performance, zig zap seems to be -... of reasonable performance 😎. +... of reasonable performance 😎. I can rest my case that developing ZAP was a good idea because it's faster than both alternatives: a) staying with Python, and b) creating a GO + Zig hybrid. @@ -257,7 +257,7 @@ $ git init ## (optional) **Note**: Nix/NixOS users are lucky; you can use the existing `flake.nix` and run `nix develop` to get a development shell providing zig and all dependencies to build and run the GO, python, and rust examples for the -`wrk` performance tests. For the mere building of zap projects, +`wrk` performance tests. For the mere building of zap projects, `nix develop .#build` will only fetch zig 0.11.0. TODO: upgrade to latest zig. With an existing Zig project, adding Zap to it is easy: @@ -274,9 +274,9 @@ To add zap to `build.zig.zon`: .version = "0.0.1", .dependencies = .{ - // zap v0.7.0 + // zap v0.8.0 .zap = .{ - .url = "https://github.com/zigzap/zap/archive/refs/tags/v0.7.0.tar.gz", + .url = "https://github.com/zigzap/zap/archive/v0.8.0.tar.gz", .hash = "12209936c3333b53b53edcf453b1670babb9ae8c2197b1ca627c01e72670e20c1a21", }, }, @@ -416,3 +416,4 @@ pub fn main() !void { } ``` + diff --git a/examples/middleware_with_endpoint/middleware_with_endpoint.zig b/examples/middleware_with_endpoint/middleware_with_endpoint.zig index 543600c..3e94450 100644 --- a/examples/middleware_with_endpoint/middleware_with_endpoint.zig +++ b/examples/middleware_with_endpoint/middleware_with_endpoint.zig @@ -123,19 +123,17 @@ const SessionMiddleWare = struct { // // !!!! ENDPOINT !!! // -// We define an endpoint as we usually would. -// NO ROUTING IS PERFORMED -// as we are just going to wrap it in a bunch of Middleware components -// and therefore NOT using an endpoint listener that would the routing for us +// We define an endpoint as we usually would; however, +// NO ROUTING IS PERFORMED BY DEFAULT! // -// Hence, the endpoint should check r.path in its on_request to check wether -// it is adressed or not. +// This can be changed using the EndpointHandlerOptions passed into the +// EndpointHandler.init() method. // // N.B. the EndpointHandler checks if the endpoint turned the request into // "finished" state, e.g. by sending anything. If the endpoint didn't finish the // request, the EndpointHandler will pass the request on to the next handler in -// the chain if there is one. See also the EndpointHandler's `breakOnFinish` -// parameter. +// the chain if there is one. See also the EndpointHandlerOptions's +// `breakOnFinish` parameter. // const HtmlEndpoint = struct { ep: zap.Endpoint = undefined, @@ -210,7 +208,7 @@ pub fn main() !void { var htmlHandler = zap.Middleware.EndpointHandler(Handler, Context).init( htmlEndpoint.endpoint(), // the endpoint null, // no other handler (we are the last in the chain) - true, // break on finish. See EndpointHandler for this. Not applicable here. + .{}, // We can set custom EndpointHandlerOptions here ); // we wrap it in the session Middleware component diff --git a/src/fio.zig b/src/fio.zig index 31e9496..7696989 100644 --- a/src/fio.zig +++ b/src/fio.zig @@ -11,8 +11,16 @@ pub const fio_url_s = extern struct { target: fio_str_info_s, }; pub extern fn fio_url_parse(url: [*c]const u8, length: usize) fio_url_s; + +/// Negative thread / worker values indicate a fraction of the number of CPU cores. i.e., -2 will normally indicate "half" (1/2) the number of cores. +/// +/// If one value is set to zero, it will be the absolute value of the other value. i.e.: if .threads == -2 and .workers == 0, than facil.io will run 2 worker processes with (cores/2) threads per process. pub const struct_fio_start_args = extern struct { + /// The number of threads to run in the thread pool. threads: i16, + /// The number of worker processes to run (in addition to a root process) + /// + /// This invokes facil.io's cluster mode, where a crashed worker will be automatically re-spawned and "hot restart" is enabled (using the USR1 signal). workers: i16, }; pub const fio_start_args = struct_fio_start_args; diff --git a/src/middleware.zig b/src/middleware.zig index 91b3da3..c094fee 100644 --- a/src/middleware.zig +++ b/src/middleware.zig @@ -51,23 +51,38 @@ pub fn Handler(comptime ContextType: anytype) type { }; } +/// Options used to change the behavior of an `EndpointHandler` +pub const EndpointHandlerOptions = struct { + /// If `true`, the handler will stop handing requests down the chain if the + /// endpoint processed the request. + breakOnFinish: bool = true, + + /// If `true`, the handler will only execute against requests that match + /// the endpoint's `path` setting. + checkPath: bool = false, +}; + /// A convenience handler for artibrary zap.Endpoint pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anytype) type { return struct { handler: HandlerType, endpoint: *zap.Endpoint, - breakOnFinish: bool, + options: EndpointHandlerOptions, const Self = @This(); /// Create an endpointhandler from an endpoint and pass in the next (other) handler in the chain. - /// If `breakOnFinish` is `true`, the handler will stop handing requests down the chain if - /// the endpoint processed the request. - pub fn init(endpoint: *zap.Endpoint, other: ?*HandlerType, breakOnFinish: bool) Self { + /// + /// By default no routing is performed on requests. This behavior can be changed by setting + /// `checkPath` in the provided options. + /// + /// If the `breakOnFinish` option is `true`, the handler will stop handing requests down the chain + /// if the endpoint processed the request. + pub fn init(endpoint: *zap.Endpoint, other: ?*HandlerType, options: EndpointHandlerOptions) Self { return .{ .handler = HandlerType.init(onRequest, other), .endpoint = endpoint, - .breakOnFinish = breakOnFinish, + .options = options, }; } @@ -84,10 +99,14 @@ pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anyt pub fn onRequest(handler: *HandlerType, r: zap.Request, context: *ContextType) bool { const self: *Self = @fieldParentPtr("handler", handler); r.setUserContext(context); - self.endpoint.onRequest(r); + if (!self.options.checkPath or + std.mem.startsWith(u8, r.path orelse "", self.endpoint.settings.path)) + { + self.endpoint.onRequest(r); + } // if the request was handled by the endpoint, we may break the chain here - if (r.isFinished() and self.breakOnFinish) { + if (r.isFinished() and self.options.breakOnFinish) { return true; } return self.handler.handleOther(r, context); diff --git a/src/mustache.zig b/src/mustache.zig index 4b00338..5b75d88 100644 --- a/src/mustache.zig +++ b/src/mustache.zig @@ -15,7 +15,7 @@ extern fn fiobj_mustache_build2(dest: fio.FIOBJ, mustache: ?*mustache_s, data: f extern fn fiobj_mustache_free(mustache: ?*mustache_s) void; /// Load arguments used when creating a new Mustache instance. -pub const MustacheLoadArgs = struct { +pub const LoadArgs = struct { /// Filename. This enables partial templates on filesystem. filename: ?[]const u8 = null, @@ -51,7 +51,7 @@ pub const Error = error{ /// Create a new `Mustache` instance; `deinit()` should be called to free /// the object after usage. -pub fn init(load_args: MustacheLoadArgs) Error!Self { +pub fn init(load_args: LoadArgs) Error!Self { var err: mustache_error_en = undefined; const args: MustacheLoadArgsFio = .{ @@ -113,7 +113,7 @@ pub fn deinit(self: *Self) void { // pub extern fn fiobj_mustache_build2(dest: FIOBJ, mustache: ?*mustache_s, data: FIOBJ) FIOBJ; /// The result from calling `build`. -const MustacheBuildResult = struct { +pub const BuildResult = struct { fiobj_result: fio.FIOBJ = 0, /// Holds the context converted into a fiobj. @@ -121,13 +121,13 @@ const MustacheBuildResult = struct { fiobj_context: fio.FIOBJ = 0, /// Free the data backing a `MustacheBuildResult` instance. - pub fn deinit(m: *const MustacheBuildResult) void { + pub fn deinit(m: *const BuildResult) void { fio.fiobj_free_wrapped(m.fiobj_result); fio.fiobj_free_wrapped(m.fiobj_context); } /// Retrieve a string representation of the built template. - pub fn str(m: *const MustacheBuildResult) ?[]const u8 { + pub fn str(m: *const BuildResult) ?[]const u8 { return util.fio2str(m.fiobj_result); } }; @@ -137,13 +137,13 @@ const MustacheBuildResult = struct { // TODO: The build may be slow because it needs to convert zig types to facil.io // types. However, this needs to be investigated into. // See `fiobjectify` for more information. -pub fn build(self: *Self, data: anytype) MustacheBuildResult { +pub fn build(self: *Self, data: anytype) BuildResult { const T = @TypeOf(data); if (@typeInfo(T) != .Struct) { @compileError("No struct: '" ++ @typeName(T) ++ "'"); } - var result: MustacheBuildResult = .{}; + var result: BuildResult = .{}; result.fiobj_context = fiobjectify(data); result.fiobj_result = fiobj_mustache_build(self.handle, result.fiobj_context); diff --git a/src/request.zig b/src/request.zig index 1b7c66b..e0d7668 100644 --- a/src/request.zig +++ b/src/request.zig @@ -431,8 +431,10 @@ pub fn setContentTypeFromFilename(self: *const Self, filename: []const u8) !void } } -/// Returns the header value of given key name. Returned mem is temp. -/// Do not free it. +/// Returns the header value of given key name. +/// NOTE that header-names are lowerased automatically while parsing the request. +/// so please only use lowercase keys! +/// Returned mem is temp. Do not free it. pub fn getHeader(self: *const Self, name: []const u8) ?[]const u8 { const hname = fio.fiobj_str_new(util.toCharPtr(name), name.len); defer fio.fiobj_free_wrapped(hname);