1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 15:14:08 +00:00

Merge branch 'master' into zig-master

This commit is contained in:
Rene Schallner 2024-10-14 22:12:23 +02:00
commit 54c2ed64c6
No known key found for this signature in database
6 changed files with 60 additions and 32 deletions

View file

@ -58,7 +58,7 @@ master though.
## Here's what works ## Here's what works
I recommend checking out **Endpoint-based examples for more realistic 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. what's necessary to show a feature.
**NOTE: To see API docs, run `zig build run-docserver`.** To specify a custom **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 call `r.sendError(err, status_code)` when you catch an error and a stack trace
will be returned to the client / browser. will be returned to the client / browser.
- [**HTTPS**](examples/https/https.zig): Shows how easy it is to use facil.io's - [**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 variable `ZAP_USE_OPENSSL` set to `true` and requires openssl dev dependencies
(headers, lib) to be installed on the system. (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` OR like this: `zig build -Dopenssl=true run-https`
- it will tell you how to generate certificates - it will tell you how to generate certificates
- [**simple_router**](examples/simple_router/simple_router.zig): See how you - [**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 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 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. 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 **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 `nix develop` to get a development shell providing zig and all
dependencies to build and run the GO, python, and rust examples for the 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. `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: 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", .version = "0.0.1",
.dependencies = .{ .dependencies = .{
// zap v0.7.0 // zap v0.8.0
.zap = .{ .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", .hash = "12209936c3333b53b53edcf453b1670babb9ae8c2197b1ca627c01e72670e20c1a21",
}, },
}, },
@ -416,3 +416,4 @@ pub fn main() !void {
} }
``` ```

View file

@ -123,19 +123,17 @@ const SessionMiddleWare = struct {
// //
// !!!! ENDPOINT !!! // !!!! ENDPOINT !!!
// //
// We define an endpoint as we usually would. // We define an endpoint as we usually would; however,
// NO ROUTING IS PERFORMED // NO ROUTING IS PERFORMED BY DEFAULT!
// 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
// //
// Hence, the endpoint should check r.path in its on_request to check wether // This can be changed using the EndpointHandlerOptions passed into the
// it is adressed or not. // EndpointHandler.init() method.
// //
// N.B. the EndpointHandler checks if the endpoint turned the request into // 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 // "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 // 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` // the chain if there is one. See also the EndpointHandlerOptions's
// parameter. // `breakOnFinish` parameter.
// //
const HtmlEndpoint = struct { const HtmlEndpoint = struct {
ep: zap.Endpoint = undefined, ep: zap.Endpoint = undefined,
@ -210,7 +208,7 @@ pub fn main() !void {
var htmlHandler = zap.Middleware.EndpointHandler(Handler, Context).init( var htmlHandler = zap.Middleware.EndpointHandler(Handler, Context).init(
htmlEndpoint.endpoint(), // the endpoint htmlEndpoint.endpoint(), // the endpoint
null, // no other handler (we are the last in the chain) 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 // we wrap it in the session Middleware component

View file

@ -11,8 +11,16 @@ pub const fio_url_s = extern struct {
target: fio_str_info_s, target: fio_str_info_s,
}; };
pub extern fn fio_url_parse(url: [*c]const u8, length: usize) fio_url_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 { pub const struct_fio_start_args = extern struct {
/// The number of threads to run in the thread pool.
threads: i16, 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, workers: i16,
}; };
pub const fio_start_args = struct_fio_start_args; pub const fio_start_args = struct_fio_start_args;

View file

@ -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 /// A convenience handler for artibrary zap.Endpoint
pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anytype) type { pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anytype) type {
return struct { return struct {
handler: HandlerType, handler: HandlerType,
endpoint: *zap.Endpoint, endpoint: *zap.Endpoint,
breakOnFinish: bool, options: EndpointHandlerOptions,
const Self = @This(); const Self = @This();
/// Create an endpointhandler from an endpoint and pass in the next (other) handler in the chain. /// 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. /// By default no routing is performed on requests. This behavior can be changed by setting
pub fn init(endpoint: *zap.Endpoint, other: ?*HandlerType, breakOnFinish: bool) Self { /// `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 .{ return .{
.handler = HandlerType.init(onRequest, other), .handler = HandlerType.init(onRequest, other),
.endpoint = endpoint, .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 { pub fn onRequest(handler: *HandlerType, r: zap.Request, context: *ContextType) bool {
const self: *Self = @fieldParentPtr("handler", handler); const self: *Self = @fieldParentPtr("handler", handler);
r.setUserContext(context); 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 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 true;
} }
return self.handler.handleOther(r, context); return self.handler.handleOther(r, context);

View file

@ -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; extern fn fiobj_mustache_free(mustache: ?*mustache_s) void;
/// Load arguments used when creating a new Mustache instance. /// 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. This enables partial templates on filesystem.
filename: ?[]const u8 = null, filename: ?[]const u8 = null,
@ -51,7 +51,7 @@ pub const Error = error{
/// Create a new `Mustache` instance; `deinit()` should be called to free /// Create a new `Mustache` instance; `deinit()` should be called to free
/// the object after usage. /// 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; var err: mustache_error_en = undefined;
const args: MustacheLoadArgsFio = .{ 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; // pub extern fn fiobj_mustache_build2(dest: FIOBJ, mustache: ?*mustache_s, data: FIOBJ) FIOBJ;
/// The result from calling `build`. /// The result from calling `build`.
const MustacheBuildResult = struct { pub const BuildResult = struct {
fiobj_result: fio.FIOBJ = 0, fiobj_result: fio.FIOBJ = 0,
/// Holds the context converted into a fiobj. /// Holds the context converted into a fiobj.
@ -121,13 +121,13 @@ const MustacheBuildResult = struct {
fiobj_context: fio.FIOBJ = 0, fiobj_context: fio.FIOBJ = 0,
/// Free the data backing a `MustacheBuildResult` instance. /// 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_result);
fio.fiobj_free_wrapped(m.fiobj_context); fio.fiobj_free_wrapped(m.fiobj_context);
} }
/// Retrieve a string representation of the built template. /// 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); 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 // TODO: The build may be slow because it needs to convert zig types to facil.io
// types. However, this needs to be investigated into. // types. However, this needs to be investigated into.
// See `fiobjectify` for more information. // 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); const T = @TypeOf(data);
if (@typeInfo(T) != .Struct) { if (@typeInfo(T) != .Struct) {
@compileError("No struct: '" ++ @typeName(T) ++ "'"); @compileError("No struct: '" ++ @typeName(T) ++ "'");
} }
var result: MustacheBuildResult = .{}; var result: BuildResult = .{};
result.fiobj_context = fiobjectify(data); result.fiobj_context = fiobjectify(data);
result.fiobj_result = fiobj_mustache_build(self.handle, result.fiobj_context); result.fiobj_result = fiobj_mustache_build(self.handle, result.fiobj_context);

View file

@ -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. /// Returns the header value of given key name.
/// Do not free it. /// 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 { pub fn getHeader(self: *const Self, name: []const u8) ?[]const u8 {
const hname = fio.fiobj_str_new(util.toCharPtr(name), name.len); const hname = fio.fiobj_str_new(util.toCharPtr(name), name.len);
defer fio.fiobj_free_wrapped(hname); defer fio.fiobj_free_wrapped(hname);