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

Merge branch 'api_clean' into zig-0.12.0

This commit is contained in:
Rene Schallner 2024-01-09 12:12:46 +01:00
commit 652f0e2277
36 changed files with 664 additions and 569 deletions

View file

@ -113,9 +113,9 @@ port and docs dir: `zig build docserver && zig-out/bin/docserver --port=8989
less type-safe than `zap.Middleware`'s use of contexts. less type-safe than `zap.Middleware`'s use of contexts.
- [**Per Request Contexts**](./src/zap.zig#L102) : With the introduction of - [**Per Request Contexts**](./src/zap.zig#L102) : With the introduction of
`setUserContext()` and `getUserContext()`, you can, of course use those two in `setUserContext()` and `getUserContext()`, you can, of course use those two in
projects that don't use `zap.SimpleEndpoint` or `zap.Middleware`, too, if you projects that don't use `zap.Endpoint` or `zap.Middleware`, too, if you
really, really, absolutely don't find another way to solve your context really, really, absolutely don't find another way to solve your context
problem. **We recommend using a `zap.SimpleEndpoint`** inside of a struct that problem. **We recommend using a `zap.Endpoint`** inside of a struct that
can provide all the context you need **instead**. You get access to your can provide all the context you need **instead**. You get access to your
struct in the callbacks via the `@fieldParentPtr()` trick that is used struct in the callbacks via the `@fieldParentPtr()` trick that is used
extensively in Zap's examples, like the [endpoint extensively in Zap's examples, like the [endpoint
@ -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.SimpleRequest) 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});
} }
@ -388,7 +388,7 @@ fn on_request(r: zap.SimpleRequest) void {
} }
pub fn main() !void { pub fn main() !void {
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,
.log = true, .log = true,

View file

@ -12,7 +12,7 @@ For convenience, Authenticator types exist that can authenticate requests.
Zap also provides an `AuthenticatingEndpoint` endpoint-wrapper. Have a look at the [example](../examples/endpoint_auth) and the [tests](../src/tests/test_auth.zig). Zap also provides an `AuthenticatingEndpoint` endpoint-wrapper. Have a look at the [example](../examples/endpoint_auth) and the [tests](../src/tests/test_auth.zig).
The following describes the Authenticator types. All of them provide the The following describes the Authenticator types. All of them provide the
`authenticateRequest()` function, which takes a `zap.SimpleRequest` and returns `authenticateRequest()` function, which takes a `zap.Request` and returns
a bool value whether it could be authenticated or not. a bool value whether it could be authenticated or not.
Further down, we show how to use the Authenticators, and also the Further down, we show how to use the Authenticators, and also the
@ -60,7 +60,7 @@ var auth = try zap.BearerAuthSingle.init(allocator, token, null);
defer auth.deinit(); defer auth.deinit();
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
if(authenticator.authenticateRequest(r)) { if(authenticator.authenticateRequest(r)) {
r.sendBody( r.sendBody(
\\ <html><body> \\ <html><body>
@ -94,7 +94,7 @@ var auth = try zap.BearerAuthMulti(Set).init(allocator, &set, null);
defer auth.deinit(); defer auth.deinit();
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
if(authenticator.authenticateRequest(r)) { if(authenticator.authenticateRequest(r)) {
r.sendBody( r.sendBody(
\\ <html><body> \\ <html><body>
@ -132,7 +132,7 @@ var auth = try Authenticator.init(a, &map, null);
defer auth.deinit(); defer auth.deinit();
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
if(authenticator.authenticateRequest(r)) { if(authenticator.authenticateRequest(r)) {
r.sendBody( r.sendBody(
\\ <html><body> \\ <html><body>
@ -168,7 +168,7 @@ var auth = try Authenticator.init(allocator, &set, null);
defer auth.deinit(); defer auth.deinit();
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
if(authenticator.authenticateRequest(r)) { if(authenticator.authenticateRequest(r)) {
r.sendBody( r.sendBody(
\\ <html><body> \\ <html><body>
@ -206,13 +206,13 @@ const HTTP_RESPONSE: []const u8 =
; ;
// authenticated requests go here // authenticated requests go here
fn endpoint_http_get(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { fn endpoint_http_get(e: *zap.Endpoint, r: zap.Request) void {
_ = e; _ = e;
r.sendBody(HTTP_RESPONSE) catch return; r.sendBody(HTTP_RESPONSE) catch return;
} }
// just for fun, we also catch the unauthorized callback // just for fun, we also catch the unauthorized callback
fn endpoint_http_unauthorized(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { fn endpoint_http_unauthorized(e: *zap.Endpoint, r: zap.Request) void {
_ = e; _ = e;
r.setStatus(.unauthorized); r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED ACCESS") catch return; r.sendBody("UNAUTHORIZED ACCESS") catch return;
@ -220,7 +220,7 @@ fn endpoint_http_unauthorized(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void
pub fn main() !void { pub fn main() !void {
// setup listener // setup listener
var listener = zap.SimpleEndpointListener.init( var listener = zap.EndpointListener.init(
a, a,
.{ .{
.port = 3000, .port = 3000,
@ -233,7 +233,7 @@ pub fn main() !void {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = zap.SimpleEndpoint.init(.{ var ep = zap.Endpoint.init(.{
.path = "/test", .path = "/test",
.get = endpoint_http_get, .get = endpoint_http_get,
.unauthorized = endpoint_http_unauthorized, .unauthorized = endpoint_http_unauthorized,
@ -248,7 +248,7 @@ pub fn main() !void {
const BearerAuthEndpoint = zap.AuthenticatingEndpoint(Authenticator); const BearerAuthEndpoint = zap.AuthenticatingEndpoint(Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.addEndpoint(auth_ep.getEndpoint()); try listener.register(auth_ep.endpoint());
listener.listen() catch {}; listener.listen() catch {};
std.debug.print( std.debug.print(

View file

@ -58,14 +58,14 @@ Now that I think I've made my point 😊, you can, of course always do the
following: following:
```zig ```zig
fn on_request_with_errors(r: zap.SimpleHttpRequest) !void { fn on_request_with_errors(r: zap.HttpRequest) !void {
// do all the try stuff here // do all the try stuff here
} }
``` ```
```zig ```zig
// THIS IS WHAT YOU PASS TO THE LISTENER / ENDPONT / ... // THIS IS WHAT YOU PASS TO THE LISTENER / ENDPONT / ...
fn on_request(r: zap.SimpleHttpRequest) void { fn on_request(r: zap.HttpRequest) void {
on_request_with_errors(r) catch |err| { on_request_with_errors(r) catch |err| {
// log the error or use: // log the error or use:
return r.returnWithErrorStackTrace(err); return r.returnWithErrorStackTrace(err);

View file

@ -10,29 +10,12 @@ Here is how it is used in user-code:
```zig ```zig
// create a combined context struct // create a combined context struct
const Context = zap.Middleware.MixContexts(.{ const Context = struct {
.{ .name = "?user", .type = UserMiddleWare.User }, user: ?UserMiddleWare.User = null,
.{ .name = "?session", .type = SessionMiddleWare.Session }, session: ?SessionMiddleWare.Session = null,
}); };
``` ```
The result of this function call is a struct that has a `user` field of type
`?UserMiddleWare.User`, which is the `User` struct inside of its containing
struct - and a `session` field of type `?SessionMiddleWare.Session`.
So `MixContexts` accepts a **tuple** of structs that each contain a
`name` field and a `type` field. As a hack, we support the `?` in the name to
indicate we want the resulting struct field to be an optional.
A **tuple** means that we can "mix" as many structs as we like. Not just two
like in the example above.
`MixContexts` inspects the passed-in `type` fields and **composes a new struct
type at comptime**! Have a look at its [source code](../src/middleware.zig).
You'll be blown away if this kind of metaprogramming stuff isn't what you do
everyday. I was totally blown away by trying it out and seeing it that it
_actually_ worked.
Why do we create combined structs? Because all our Middleware handler functions Why do we create combined structs? Because all our Middleware handler functions
need to receive a per-request context. But each wants their own data: the User need to receive a per-request context. But each wants their own data: the User
middleware might want to access a User struct, the Session middleware might want middleware might want to access a User struct, the Session middleware might want
@ -62,10 +45,10 @@ Have a look at an excerpt of the example:
```zig ```zig
// create a combined context struct // create a combined context struct
const Context = zap.Middleware.MixContexts(.{ const Context = struct {
.{ .name = "?user", .type = UserMiddleWare.User }, user: ?UserMiddleWare.User = null,
.{ .name = "?session", .type = SessionMiddleWare.Session }, session: ?SessionMiddleWare.Session = null,
}); };
// we create a Handler type based on our Context // we create a Handler type based on our Context
const Handler = zap.Middleware.Handler(Context); const Handler = zap.Middleware.Handler(Context);

View file

@ -4,7 +4,7 @@ const zap = @import("zap");
const Handler = struct { const Handler = struct {
var alloc: std.mem.Allocator = undefined; var alloc: std.mem.Allocator = undefined;
pub fn on_request(r: zap.SimpleRequest) void { pub fn on_request(r: zap.Request) void {
// check for FORM parameters // check for FORM parameters
r.parseBody() catch |err| { r.parseBody() catch |err| {
std.log.err("Parse Body error: {any}. Expected if body is empty", .{err}); std.log.err("Parse Body error: {any}. Expected if body is empty", .{err});
@ -56,7 +56,7 @@ const Handler = struct {
else => { else => {
// might be a string param, we don't care // might be a string param, we don't care
// let's just get it as string // let's just get it as string
if (r.getParamStr(kv.key.str, Handler.alloc, false)) |maybe_str| { if (r.getParamStr(Handler.alloc, kv.key.str, false)) |maybe_str| {
const value: []const u8 = if (maybe_str) |s| s.str else "(no value)"; const value: []const u8 = if (maybe_str) |s| s.str else "(no value)";
std.log.debug(" {s} = {s}", .{ kv.key.str, value }); std.log.debug(" {s} = {s}", .{ kv.key.str, value });
} else |err| { } else |err| {
@ -68,12 +68,12 @@ const Handler = struct {
} }
// check if we received a terminate=true parameter // check if we received a terminate=true parameter
if (r.getParamStr("terminate", Handler.alloc, false)) |maybe_str| { if (r.getParamStr(Handler.alloc, "terminate", false)) |maybe_str| {
if (maybe_str) |*s| { if (maybe_str) |*s| {
defer s.deinit(); defer s.deinit();
std.log.info("?terminate={s}\n", .{s.str}); std.log.info("?terminate={s}\n", .{s.str});
if (std.mem.eql(u8, s.str, "true")) { if (std.mem.eql(u8, s.str, "true")) {
zap.fio_stop(); zap.stop();
} }
} }
} else |err| { } else |err| {
@ -92,7 +92,7 @@ pub fn main() !void {
Handler.alloc = allocator; Handler.alloc = allocator;
// setup listener // setup listener
var listener = zap.SimpleHttpListener.init( var listener = zap.HttpListener.init(
.{ .{
.port = 3000, .port = 3000,
.on_request = Handler.on_request, .on_request = Handler.on_request,

View file

@ -33,7 +33,7 @@ pub fn main() !void {
const Handler = struct { const Handler = struct {
var alloc: std.mem.Allocator = undefined; var alloc: std.mem.Allocator = undefined;
pub fn on_request(r: zap.SimpleRequest) void { pub fn on_request(r: zap.Request) void {
std.debug.print("\n=====================================================\n", .{}); std.debug.print("\n=====================================================\n", .{});
defer std.debug.print("=====================================================\n\n", .{}); defer std.debug.print("=====================================================\n\n", .{});
@ -61,7 +61,7 @@ pub fn main() !void {
// let's get cookie "ZIG_ZAP" by name // let's get cookie "ZIG_ZAP" by name
std.debug.print("\n", .{}); std.debug.print("\n", .{});
if (r.getCookieStr("ZIG_ZAP", alloc, false)) |maybe_str| { if (r.getCookieStr(alloc, "ZIG_ZAP", false)) |maybe_str| {
if (maybe_str) |*s| { if (maybe_str) |*s| {
defer s.deinit(); defer s.deinit();
@ -98,7 +98,7 @@ pub fn main() !void {
Handler.alloc = allocator; Handler.alloc = allocator;
// setup listener // setup listener
var listener = zap.SimpleHttpListener.init( var listener = zap.HttpListener.init(
.{ .{
.port = 3000, .port = 3000,
.on_request = Handler.on_request, .on_request = Handler.on_request,

View file

@ -1,10 +1,10 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
const Endpoint = @import("endpoint.zig"); const UserWeb = @import("userweb.zig");
const StopEndpoint = @import("stopendpoint.zig"); const StopEndpoint = @import("stopendpoint.zig");
// this is just to demo that we can catch arbitrary slugs // this is just to demo that we can catch arbitrary slugs
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
if (r.path) |the_path| { if (r.path) |the_path| {
std.debug.print("REQUESTED PATH: {s}\n", .{the_path}); std.debug.print("REQUESTED PATH: {s}\n", .{the_path});
} }
@ -21,7 +21,7 @@ pub fn main() !void {
// we scope everything that can allocate within this block for leak detection // we scope everything that can allocate within this block for leak detection
{ {
// setup listener // setup listener
var listener = zap.SimpleEndpointListener.init( var listener = zap.EndpointListener.init(
allocator, allocator,
.{ .{
.port = 3000, .port = 3000,
@ -34,19 +34,20 @@ pub fn main() !void {
); );
defer listener.deinit(); defer listener.deinit();
var endpoint = Endpoint.init(allocator, "/users"); // /users endpoint
defer endpoint.deinit(); var userWeb = UserWeb.init(allocator, "/users");
defer userWeb.deinit();
var stopEp = StopEndpoint.init("/stop"); var stopEp = StopEndpoint.init("/stop");
// add endpoint // register endpoints with the listener
try listener.addEndpoint(endpoint.getUserEndpoint()); try listener.register(userWeb.endpoint());
try listener.addEndpoint(stopEp.getEndpoint()); try listener.register(stopEp.endpoint());
// fake some users // fake some users
var uid: usize = undefined; var uid: usize = undefined;
uid = try endpoint.getUsers().addByName("renerocksai", null); uid = try userWeb.users().addByName("renerocksai", null);
uid = try endpoint.getUsers().addByName("renerocksai", "your mom"); uid = try userWeb.users().addByName("renerocksai", "your mom");
// listen // listen
try listener.listen(); try listener.listen();

View file

@ -5,24 +5,24 @@ const zap = @import("zap");
/// the main thread usually continues at the instructions after the call to zap.start(). /// the main thread usually continues at the instructions after the call to zap.start().
pub const Self = @This(); pub const Self = @This();
endpoint: zap.SimpleEndpoint = undefined, ep: zap.Endpoint = undefined,
pub fn init( pub fn init(
path: []const u8, path: []const u8,
) Self { ) Self {
return .{ return .{
.endpoint = zap.SimpleEndpoint.init(.{ .ep = zap.Endpoint.init(.{
.path = path, .path = path,
.get = get, .get = get,
}), }),
}; };
} }
pub fn getEndpoint(self: *Self) *zap.SimpleEndpoint { pub fn endpoint(self: *Self) *zap.Endpoint {
return &self.endpoint; return &self.ep;
} }
fn get(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { fn get(e: *zap.Endpoint, r: zap.Request) void {
_ = e; _ = e;
_ = r; _ = r;
zap.stop(); zap.stop();

View file

@ -8,8 +8,8 @@ const User = Users.User;
pub const Self = @This(); pub const Self = @This();
alloc: std.mem.Allocator = undefined, alloc: std.mem.Allocator = undefined,
endpoint: zap.SimpleEndpoint = undefined, ep: zap.Endpoint = undefined,
users: Users = undefined, _users: Users = undefined,
pub fn init( pub fn init(
a: std.mem.Allocator, a: std.mem.Allocator,
@ -17,8 +17,8 @@ pub fn init(
) Self { ) Self {
return .{ return .{
.alloc = a, .alloc = a,
.users = Users.init(a), ._users = Users.init(a),
.endpoint = zap.SimpleEndpoint.init(.{ .ep = zap.Endpoint.init(.{
.path = user_path, .path = user_path,
.get = getUser, .get = getUser,
.post = postUser, .post = postUser,
@ -31,30 +31,30 @@ pub fn init(
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.users.deinit(); self._users.deinit();
} }
pub fn getUsers(self: *Self) *Users { pub fn users(self: *Self) *Users {
return &self.users; return &self._users;
} }
pub fn getUserEndpoint(self: *Self) *zap.SimpleEndpoint { pub fn endpoint(self: *Self) *zap.Endpoint {
return &self.endpoint; return &self.ep;
} }
fn userIdFromPath(self: *Self, path: []const u8) ?usize { fn userIdFromPath(self: *Self, path: []const u8) ?usize {
if (path.len >= self.endpoint.settings.path.len + 2) { if (path.len >= self.ep.settings.path.len + 2) {
if (path[self.endpoint.settings.path.len] != '/') { if (path[self.ep.settings.path.len] != '/') {
return null; return null;
} }
const idstr = path[self.endpoint.settings.path.len + 1 ..]; const idstr = path[self.ep.settings.path.len + 1 ..];
return std.fmt.parseUnsigned(usize, idstr, 10) catch null; return std.fmt.parseUnsigned(usize, idstr, 10) catch null;
} }
return null; return null;
} }
fn getUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { fn getUser(e: *zap.Endpoint, r: zap.Request) void {
const self = @fieldParentPtr(Self, "endpoint", e); const self = @fieldParentPtr(Self, "ep", e);
if (r.path) |path| { if (r.path) |path| {
// /users // /users
if (path.len == e.settings.path.len) { if (path.len == e.settings.path.len) {
@ -62,7 +62,7 @@ fn getUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void {
} }
var jsonbuf: [256]u8 = undefined; var jsonbuf: [256]u8 = undefined;
if (self.userIdFromPath(path)) |id| { if (self.userIdFromPath(path)) |id| {
if (self.users.get(id)) |user| { if (self._users.get(id)) |user| {
if (zap.stringifyBuf(&jsonbuf, user, .{})) |json| { if (zap.stringifyBuf(&jsonbuf, user, .{})) |json| {
r.sendJson(json) catch return; r.sendJson(json) catch return;
} }
@ -71,8 +71,8 @@ fn getUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void {
} }
} }
fn listUsers(self: *Self, r: zap.SimpleRequest) void { fn listUsers(self: *Self, r: zap.Request) void {
if (self.users.toJSON()) |json| { if (self._users.toJSON()) |json| {
defer self.alloc.free(json); defer self.alloc.free(json);
r.sendJson(json) catch return; r.sendJson(json) catch return;
} else |err| { } else |err| {
@ -80,13 +80,13 @@ fn listUsers(self: *Self, r: zap.SimpleRequest) void {
} }
} }
fn postUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { fn postUser(e: *zap.Endpoint, r: zap.Request) void {
const self = @fieldParentPtr(Self, "endpoint", e); const self = @fieldParentPtr(Self, "ep", e);
if (r.body) |body| { if (r.body) |body| {
const maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null; const maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null;
if (maybe_user) |u| { if (maybe_user) |u| {
defer u.deinit(); defer u.deinit();
if (self.users.addByName(u.value.first_name, u.value.last_name)) |id| { if (self._users.addByName(u.value.first_name, u.value.last_name)) |id| {
var jsonbuf: [128]u8 = undefined; var jsonbuf: [128]u8 = undefined;
if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| { if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| {
r.sendJson(json) catch return; r.sendJson(json) catch return;
@ -99,17 +99,17 @@ fn postUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void {
} }
} }
fn putUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { fn putUser(e: *zap.Endpoint, r: zap.Request) void {
const self = @fieldParentPtr(Self, "endpoint", e); const self = @fieldParentPtr(Self, "ep", e);
if (r.path) |path| { if (r.path) |path| {
if (self.userIdFromPath(path)) |id| { if (self.userIdFromPath(path)) |id| {
if (self.users.get(id)) |_| { if (self._users.get(id)) |_| {
if (r.body) |body| { if (r.body) |body| {
const maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null; const maybe_user: ?std.json.Parsed(User) = std.json.parseFromSlice(User, self.alloc, body, .{}) catch null;
if (maybe_user) |u| { if (maybe_user) |u| {
defer u.deinit(); defer u.deinit();
var jsonbuf: [128]u8 = undefined; var jsonbuf: [128]u8 = undefined;
if (self.users.update(id, u.value.first_name, u.value.last_name)) { if (self._users.update(id, u.value.first_name, u.value.last_name)) {
if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| { if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| {
r.sendJson(json) catch return; r.sendJson(json) catch return;
} }
@ -125,12 +125,12 @@ fn putUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void {
} }
} }
fn deleteUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { fn deleteUser(e: *zap.Endpoint, r: zap.Request) void {
const self = @fieldParentPtr(Self, "endpoint", e); const self = @fieldParentPtr(Self, "ep", e);
if (r.path) |path| { if (r.path) |path| {
if (self.userIdFromPath(path)) |id| { if (self.userIdFromPath(path)) |id| {
var jsonbuf: [128]u8 = undefined; var jsonbuf: [128]u8 = undefined;
if (self.users.delete(id)) { if (self._users.delete(id)) {
if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| { if (zap.stringifyBuf(&jsonbuf, .{ .status = "OK", .id = id }, .{})) |json| {
r.sendJson(json) catch return; r.sendJson(json) catch return;
} }
@ -143,7 +143,7 @@ fn deleteUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void {
} }
} }
fn optionsUser(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { fn optionsUser(e: *zap.Endpoint, r: zap.Request) void {
_ = e; _ = e;
r.setHeader("Access-Control-Allow-Origin", "*") catch return; r.setHeader("Access-Control-Allow-Origin", "*") catch return;
r.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") catch return; r.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") catch return;

View file

@ -11,13 +11,13 @@ const HTTP_RESPONSE: []const u8 =
; ;
// authenticated requests go here // authenticated requests go here
fn endpoint_http_get(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { fn endpoint_http_get(e: *zap.Endpoint, r: zap.Request) void {
_ = e; _ = e;
r.sendBody(HTTP_RESPONSE) catch return; r.sendBody(HTTP_RESPONSE) catch return;
} }
// just for fun, we also catch the unauthorized callback // just for fun, we also catch the unauthorized callback
fn endpoint_http_unauthorized(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { fn endpoint_http_unauthorized(e: *zap.Endpoint, r: zap.Request) void {
_ = e; _ = e;
r.setStatus(.unauthorized); r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED ACCESS") catch return; r.sendBody("UNAUTHORIZED ACCESS") catch return;
@ -25,7 +25,7 @@ fn endpoint_http_unauthorized(e: *zap.SimpleEndpoint, r: zap.SimpleRequest) void
pub fn main() !void { pub fn main() !void {
// setup listener // setup listener
var listener = zap.SimpleEndpointListener.init( var listener = zap.EndpointListener.init(
a, a,
.{ .{
.port = 3000, .port = 3000,
@ -38,7 +38,7 @@ pub fn main() !void {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = zap.SimpleEndpoint.init(.{ var ep = zap.Endpoint.init(.{
.path = "/test", .path = "/test",
.get = endpoint_http_get, .get = endpoint_http_get,
.unauthorized = endpoint_http_unauthorized, .unauthorized = endpoint_http_unauthorized,
@ -53,7 +53,7 @@ pub fn main() !void {
const BearerAuthEndpoint = zap.AuthenticatingEndpoint(Authenticator); const BearerAuthEndpoint = zap.AuthenticatingEndpoint(Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.addEndpoint(auth_ep.getEndpoint()); try listener.register(auth_ep.endpoint());
listener.listen() catch {}; listener.listen() catch {};
std.debug.print( std.debug.print(

View file

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request_verbose(r: zap.SimpleRequest) void { fn on_request_verbose(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});
} }
@ -12,12 +12,12 @@ fn on_request_verbose(r: zap.SimpleRequest) void {
r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return; r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return;
} }
fn on_request_minimal(r: zap.SimpleRequest) void { fn on_request_minimal(r: zap.Request) void {
r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return; r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return;
} }
pub fn main() !void { pub fn main() !void {
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request_verbose, .on_request = on_request_verbose,
.log = true, .log = true,

View file

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
const m = r.method orelse ""; const m = r.method orelse "";
const p = r.path orelse "/"; const p = r.path orelse "/";
const qm = if (r.query) |_| "?" else ""; const qm = if (r.query) |_| "?" else "";
@ -35,7 +35,7 @@ fn on_request(r: zap.SimpleRequest) void {
} }
pub fn main() !void { pub fn main() !void {
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,
.log = false, .log = false,

View file

@ -6,7 +6,7 @@ const User = struct {
last_name: ?[]const u8 = null, last_name: ?[]const u8 = null,
}; };
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
if (!std.mem.eql(u8, r.method.?, "GET")) if (!std.mem.eql(u8, r.method.?, "GET"))
return; return;
@ -44,7 +44,7 @@ fn setupUserData(a: std.mem.Allocator) !void {
pub fn main() !void { pub fn main() !void {
const a = std.heap.page_allocator; const a = std.heap.page_allocator;
try setupUserData(a); try setupUserData(a);
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,
.log = false, .log = false,

View file

@ -32,7 +32,7 @@ pub fn main() !void {
const Handler = struct { const Handler = struct {
var alloc: std.mem.Allocator = undefined; var alloc: std.mem.Allocator = undefined;
pub fn on_request(r: zap.SimpleRequest) void { pub fn on_request(r: zap.Request) void {
std.debug.print("\n=====================================================\n", .{}); std.debug.print("\n=====================================================\n", .{});
defer std.debug.print("=====================================================\n\n", .{}); defer std.debug.print("=====================================================\n\n", .{});
@ -66,7 +66,7 @@ pub fn main() !void {
// let's get param "one" by name // let's get param "one" by name
std.debug.print("\n", .{}); std.debug.print("\n", .{});
if (r.getParamStr("one", alloc, false)) |maybe_str| { if (r.getParamStr(alloc, "one", false)) |maybe_str| {
if (maybe_str) |*s| { if (maybe_str) |*s| {
defer s.deinit(); defer s.deinit();
@ -82,11 +82,11 @@ pub fn main() !void {
} }
// check if we received a terminate=true parameter // check if we received a terminate=true parameter
if (r.getParamStr("terminate", alloc, false)) |maybe_str| { if (r.getParamStr(alloc, "terminate", false)) |maybe_str| {
if (maybe_str) |*s| { if (maybe_str) |*s| {
defer s.deinit(); defer s.deinit();
if (std.mem.eql(u8, s.str, "true")) { if (std.mem.eql(u8, s.str, "true")) {
zap.fio_stop(); zap.stop();
} }
} }
} else |err| { } else |err| {
@ -98,7 +98,7 @@ pub fn main() !void {
Handler.alloc = allocator; Handler.alloc = allocator;
// setup listener // setup listener
var listener = zap.SimpleHttpListener.init( var listener = zap.HttpListener.init(
.{ .{
.port = 3000, .port = 3000,
.on_request = Handler.on_request, .on_request = Handler.on_request,

View file

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request_verbose(r: zap.SimpleRequest) void { fn on_request_verbose(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});
} }
@ -12,7 +12,7 @@ fn on_request_verbose(r: zap.SimpleRequest) void {
r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return; r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return;
} }
fn on_request_minimal(r: zap.SimpleRequest) void { fn on_request_minimal(r: zap.Request) void {
r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return; r.sendBody("<html><body><h1>Hello from ZAP!!!</h1></body></html>") catch return;
} }
@ -51,7 +51,7 @@ pub fn main() !void {
}); });
defer tls.deinit(); defer tls.deinit();
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 4443, .port = 4443,
.on_request = on_request_verbose, .on_request = on_request_verbose,
.log = true, .log = true,

View file

@ -69,7 +69,7 @@ const UserMiddleWare = struct {
} }
// note that the first parameter is of type *Handler, not *Self !!! // note that the first parameter is of type *Handler, not *Self !!!
pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self = @fieldParentPtr(Self, "handler", handler); const self = @fieldParentPtr(Self, "handler", handler);
@ -113,7 +113,7 @@ const SessionMiddleWare = struct {
} }
// note that the first parameter is of type *Handler, not *Self !!! // note that the first parameter is of type *Handler, not *Self !!!
pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self = @fieldParentPtr(Self, "handler", handler); const self = @fieldParentPtr(Self, "handler", handler);
_ = self; _ = self;
@ -148,7 +148,7 @@ const HtmlMiddleWare = struct {
} }
// note that the first parameter is of type *Handler, not *Self !!! // note that the first parameter is of type *Handler, not *Self !!!
pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self = @fieldParentPtr(Self, "handler", handler); const self = @fieldParentPtr(Self, "handler", handler);

View file

@ -20,10 +20,11 @@ const SharedAllocator = struct {
}; };
// create a combined context struct // create a combined context struct
const Context = zap.Middleware.MixContexts(.{ // NOTE: context struct members need to be optionals which default to null!!!
.{ .name = "?user", .type = UserMiddleWare.User }, const Context = struct {
.{ .name = "?session", .type = SessionMiddleWare.Session }, user: ?UserMiddleWare.User = null,
}); session: ?SessionMiddleWare.Session = null,
};
// we create a Handler type based on our Context // we create a Handler type based on our Context
const Handler = zap.Middleware.Handler(Context); const Handler = zap.Middleware.Handler(Context);
@ -56,7 +57,7 @@ const UserMiddleWare = struct {
} }
// note that the first parameter is of type *Handler, not *Self !!! // note that the first parameter is of type *Handler, not *Self !!!
pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self = @fieldParentPtr(Self, "handler", handler); const self = @fieldParentPtr(Self, "handler", handler);
@ -102,7 +103,7 @@ const SessionMiddleWare = struct {
} }
// note that the first parameter is of type *Handler, not *Self !!! // note that the first parameter is of type *Handler, not *Self !!!
pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool { pub fn onRequest(handler: *Handler, r: zap.Request, context: *Context) bool {
// this is how we would get our self pointer // this is how we would get our self pointer
const self = @fieldParentPtr(Self, "handler", handler); const self = @fieldParentPtr(Self, "handler", handler);
_ = self; _ = self;
@ -137,24 +138,24 @@ const SessionMiddleWare = struct {
// parameter. // parameter.
// //
const HtmlEndpoint = struct { const HtmlEndpoint = struct {
endpoint: zap.SimpleEndpoint = undefined, ep: zap.Endpoint = undefined,
const Self = @This(); const Self = @This();
pub fn init() Self { pub fn init() Self {
return .{ return .{
.endpoint = zap.SimpleEndpoint.init(.{ .ep = zap.Endpoint.init(.{
.path = "/doesn'tmatter", .path = "/doesn't+matter",
.get = get, .get = get,
}), }),
}; };
} }
pub fn getEndpoint(self: *Self) *zap.SimpleEndpoint { pub fn endpoint(self: *Self) *zap.Endpoint {
return &self.endpoint; return &self.ep;
} }
pub fn get(ep: *zap.SimpleEndpoint, r: zap.SimpleRequest) void { pub fn get(ep: *zap.Endpoint, r: zap.Request) void {
const self = @fieldParentPtr(Self, "endpoint", ep); const self = @fieldParentPtr(Self, "ep", ep);
_ = self; _ = self;
var buf: [1024]u8 = undefined; var buf: [1024]u8 = undefined;
@ -207,7 +208,7 @@ pub fn main() !void {
// we wrap the endpoint with a middleware handler // we wrap the endpoint with a middleware handler
var htmlHandler = zap.Middleware.EndpointHandler(Handler, Context).init( var htmlHandler = zap.Middleware.EndpointHandler(Handler, Context).init(
htmlEndpoint.getEndpoint(), // 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. true, // break on finish. See EndpointHandler for this. Not applicable here.
); );

View file

@ -2,7 +2,7 @@ const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
const Mustache = @import("zap").Mustache; const Mustache = @import("zap").Mustache;
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
const template = const template =
\\ {{=<< >>=}} \\ {{=<< >>=}}
\\ * Users: \\ * Users:
@ -49,7 +49,7 @@ fn on_request(r: zap.SimpleRequest) void {
} }
pub fn main() !void { pub fn main() !void {
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,
.log = true, .log = true,

View file

@ -1,7 +1,9 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn dispatch_routes(r: zap.SimpleRequest) void { // NOTE: this is a super simplified example, just using a hashmap to map
// from HTTP path to request function.
fn dispatch_routes(r: zap.Request) void {
// dispatch // dispatch
if (r.path) |the_path| { if (r.path) |the_path| {
if (routes.get(the_path)) |foo| { if (routes.get(the_path)) |foo| {
@ -20,12 +22,12 @@ fn dispatch_routes(r: zap.SimpleRequest) void {
) catch return; ) catch return;
} }
fn static_site(r: zap.SimpleRequest) void { fn static_site(r: zap.Request) void {
r.sendBody("<html><body><h1>Hello from STATIC ZAP!</h1></body></html>") catch return; r.sendBody("<html><body><h1>Hello from STATIC ZAP!</h1></body></html>") catch return;
} }
var dynamic_counter: i32 = 0; var dynamic_counter: i32 = 0;
fn dynamic_site(r: zap.SimpleRequest) void { fn dynamic_site(r: zap.Request) void {
dynamic_counter += 1; dynamic_counter += 1;
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
const filled_buf = std.fmt.bufPrintZ( const filled_buf = std.fmt.bufPrintZ(
@ -37,16 +39,16 @@ fn dynamic_site(r: zap.SimpleRequest) void {
} }
fn setup_routes(a: std.mem.Allocator) !void { fn setup_routes(a: std.mem.Allocator) !void {
routes = std.StringHashMap(zap.SimpleHttpRequestFn).init(a); routes = std.StringHashMap(zap.HttpRequestFn).init(a);
try routes.put("/static", static_site); try routes.put("/static", static_site);
try routes.put("/dynamic", dynamic_site); try routes.put("/dynamic", dynamic_site);
} }
var routes: std.StringHashMap(zap.SimpleHttpRequestFn) = undefined; var routes: std.StringHashMap(zap.HttpRequestFn) = undefined;
pub fn main() !void { pub fn main() !void {
try setup_routes(std.heap.page_allocator); try setup_routes(std.heap.page_allocator);
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = dispatch_routes, .on_request = dispatch_routes,
.log = true, .log = true,

View file

@ -5,14 +5,14 @@ fn MAKE_MEGA_ERROR() !void {
return error.MEGA_ERROR; return error.MEGA_ERROR;
} }
fn MY_REQUEST_HANDLER(r: zap.SimpleRequest) void { fn MY_REQUEST_HANDLER(r: zap.Request) void {
MAKE_MEGA_ERROR() catch |err| { MAKE_MEGA_ERROR() catch |err| {
r.sendError(err, 505); r.sendError(err, 505);
}; };
} }
pub fn main() !void { pub fn main() !void {
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = MY_REQUEST_HANDLER, .on_request = MY_REQUEST_HANDLER,
.log = true, .log = true,

View file

@ -6,7 +6,7 @@ var read_len: ?usize = null;
const testfile = @embedFile("testfile.txt"); const testfile = @embedFile("testfile.txt");
pub fn on_request(r: zap.SimpleRequest) void { pub fn on_request(r: zap.Request) void {
// Sends a file if present in the filesystem orelse returns an error. // Sends a file if present in the filesystem orelse returns an error.
// //
// - efficiently sends a file using gzip compression // - efficiently sends a file using gzip compression
@ -27,7 +27,7 @@ pub fn on_request(r: zap.SimpleRequest) void {
pub fn main() !void { pub fn main() !void {
// setup listener // setup listener
var listener = zap.SimpleHttpListener.init( var listener = zap.HttpListener.init(
.{ .{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,

View file

@ -1,13 +1,13 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
r.setStatus(.not_found); r.setStatus(.not_found);
r.sendBody("<html><body><h1>404 - File not found</h1></body></html>") catch return; r.sendBody("<html><body><h1>404 - File not found</h1></body></html>") catch return;
} }
pub fn main() !void { pub fn main() !void {
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,
.public_folder = "examples/serve", .public_folder = "examples/serve",

View file

@ -20,17 +20,17 @@ const loginpage = @embedFile("html/login.html");
const img = @embedFile("./html/Ziggy_the_Ziguana.svg.png"); const img = @embedFile("./html/Ziggy_the_Ziguana.svg.png");
// global vars yeah! // global vars yeah!
// in bigger projects, we'd probably make use of zap.SimpleEndpoint or // in bigger projects, we'd probably make use of zap.Endpoint or
// zap.Middleware and "hide" stuff like authenticators in there // zap.Middleware and "hide" stuff like authenticators in there
var authenticator: Authenticator = undefined; var authenticator: Authenticator = undefined;
// the login page (embedded) // the login page (embedded)
fn on_login(r: zap.SimpleRequest) void { fn on_login(r: zap.Request) void {
r.sendBody(loginpage) catch return; r.sendBody(loginpage) catch return;
} }
// the "normal page" // the "normal page"
fn on_normal_page(r: zap.SimpleRequest) void { fn on_normal_page(r: zap.Request) void {
zap.debug("on_normal_page()\n", .{}); zap.debug("on_normal_page()\n", .{});
r.sendBody( r.sendBody(
\\ <html><body> \\ <html><body>
@ -42,7 +42,7 @@ fn on_normal_page(r: zap.SimpleRequest) void {
} }
// the logged-out page // the logged-out page
fn on_logout(r: zap.SimpleRequest) void { fn on_logout(r: zap.Request) void {
zap.debug("on_logout()\n", .{}); zap.debug("on_logout()\n", .{});
authenticator.logout(&r); authenticator.logout(&r);
// note, the link below doesn't matter as the authenticator will send us // note, the link below doesn't matter as the authenticator will send us
@ -56,7 +56,7 @@ fn on_logout(r: zap.SimpleRequest) void {
) catch return; ) catch return;
} }
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
switch (authenticator.authenticateRequest(&r)) { switch (authenticator.authenticateRequest(&r)) {
.Handled => { .Handled => {
// the authenticator handled the entire request for us. // the authenticator handled the entire request for us.
@ -124,7 +124,7 @@ pub fn main() !void {
// to detect leaks // to detect leaks
{ {
const allocator = gpa.allocator(); const allocator = gpa.allocator();
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request, .on_request = on_request,
.log = true, .log = true,

View file

@ -114,6 +114,7 @@ fn on_close_websocket(context: ?*Context, uuid: isize) void {
std.log.info("websocket closed: {s}", .{message}); std.log.info("websocket closed: {s}", .{message});
} }
} }
fn handle_websocket_message( fn handle_websocket_message(
context: ?*Context, context: ?*Context,
handle: WebSockets.WsHandle, handle: WebSockets.WsHandle,
@ -162,7 +163,7 @@ fn handle_websocket_message(
// //
// HTTP stuff // HTTP stuff
// //
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
r.setHeader("Server", "zap.example") catch unreachable; r.setHeader("Server", "zap.example") catch unreachable;
r.sendBody( r.sendBody(
\\ <html><body> \\ <html><body>
@ -171,7 +172,7 @@ fn on_request(r: zap.SimpleRequest) void {
) catch return; ) catch return;
} }
fn on_upgrade(r: zap.SimpleRequest, 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});
@ -207,7 +208,7 @@ pub fn main() !void {
defer GlobalContextManager.deinit(); defer GlobalContextManager.deinit();
// setup listener // setup listener
var listener = zap.SimpleHttpListener.init( var listener = zap.HttpListener.init(
.{ .{
.port = 3010, .port = 3010,
.on_request = on_request, .on_request = on_request,

View file

@ -2,29 +2,83 @@ const std = @import("std");
const zap = @import("zap.zig"); const zap = @import("zap.zig");
const auth = @import("http_auth.zig"); const auth = @import("http_auth.zig");
const Request = zap.SimpleRequest; // zap types
const ListenerSettings = zap.SimpleHttpListenerSettings; const Request = zap.Request;
const Listener = zap.SimpleHttpListener; const ListenerSettings = zap.HttpListenerSettings;
const Listener = zap.HttpListener;
pub const RequestFn = *const fn (self: *SimpleEndpoint, r: Request) void; /// Type of the request function callbacks.
pub const SimpleEndpointSettings = struct { pub const RequestFn = *const fn (self: *Endpoint, r: Request) void;
/// Settings to initialize an Endpoint
pub const EndpointSettings = struct {
/// path / slug of the endpoint
path: []const u8, path: []const u8,
/// callback to GET request handler
get: ?RequestFn = null, get: ?RequestFn = null,
/// callback to POST request handler
post: ?RequestFn = null, post: ?RequestFn = null,
/// callback to PUT request handler
put: ?RequestFn = null, put: ?RequestFn = null,
/// callback to DELETE request handler
delete: ?RequestFn = null, delete: ?RequestFn = null,
/// callback to PATCH request handler
patch: ?RequestFn = null, patch: ?RequestFn = null,
/// callback to OPTIONS request handler
options: ?RequestFn = null, options: ?RequestFn = null,
/// only applicable to AuthenticatingEndpoint /// Only applicable to AuthenticatingEndpoint: handler for unauthorized requests
unauthorized: ?RequestFn = null, unauthorized: ?RequestFn = null,
}; };
pub const SimpleEndpoint = struct { /// The simple Endpoint struct. Create one and pass in your callbacks. Then,
settings: SimpleEndpointSettings, /// pass it to a HttpListener's `register()` function to register with the
/// listener.
///
/// **NOTE**: A common endpoint pattern for zap is to create your own struct
/// that embeds an Endpoint, provides specific callbacks, and uses
/// `@fieldParentPtr` to get a reference to itself.
///
/// Example:
/// A simple endpoint listening on the /stop route that shuts down zap.
/// The main thread usually continues at the instructions after the call to zap.start().
///
/// ```zig
/// const StopEndpoint = struct {
/// ep: zap.Endpoint = undefined,
///
/// pub fn init(
/// path: []const u8,
/// ) StopEndpoint {
/// return .{
/// .ep = zap.Endpoint.init(.{
/// .path = path,
/// .get = get,
/// }),
/// };
/// }
///
/// // access the internal Endpoint
/// pub fn endpoint(self: *StopEndpoint) *zap.Endpoint {
/// return &self.ep;
/// }
///
/// fn get(e: *zap.Endpoint, r: zap.Request) void {
/// const self: *StopEndpoint = @fieldParentPtr(StopEndpoint, "ep", e);
/// _ = self;
/// _ = r;
/// zap.stop();
/// }
/// };
/// ```
pub const Endpoint = struct {
settings: EndpointSettings,
const Self = @This(); const Self = @This();
pub fn init(s: SimpleEndpointSettings) Self { /// Initialize the endpoint.
/// Set only the callbacks you need. Requests of HTTP methods without a
/// provided callback will be ignored.
pub fn init(s: EndpointSettings) Self {
return .{ return .{
.settings = .{ .settings = .{
.path = s.path, .path = s.path,
@ -39,12 +93,14 @@ pub const SimpleEndpoint = struct {
}; };
} }
fn nop(self: *SimpleEndpoint, r: Request) void { // no operation. Dummy handler function for ignoring unset request types.
fn nop(self: *Endpoint, r: Request) void {
_ = self; _ = self;
_ = r; _ = r;
} }
pub fn onRequest(self: *SimpleEndpoint, r: zap.SimpleRequest) void { /// The global request handler for this Endpoint, called by the listener.
pub fn onRequest(self: *Endpoint, r: zap.Request) void {
if (r.method) |m| { if (r.method) |m| {
if (std.mem.eql(u8, m, "GET")) if (std.mem.eql(u8, m, "GET"))
return self.settings.get.?(self, r); return self.settings.get.?(self, r);
@ -62,19 +118,23 @@ pub const SimpleEndpoint = struct {
} }
}; };
/// Wrap an endpoint with an authenticator /// Wrap an endpoint with an Authenticator -> new Endpoint of type Endpoint
/// is available via the `endpoint()` function.
pub fn AuthenticatingEndpoint(comptime Authenticator: type) type { pub fn AuthenticatingEndpoint(comptime Authenticator: type) type {
return struct { return struct {
authenticator: *Authenticator, authenticator: *Authenticator,
endpoint: *SimpleEndpoint, ep: *Endpoint,
auth_endpoint: SimpleEndpoint, auth_endpoint: Endpoint,
const Self = @This(); const Self = @This();
pub fn init(e: *SimpleEndpoint, authenticator: *Authenticator) Self { /// Init the authenticating endpoint. Pass in a pointer to the endpoint
/// you want to wrap, and the Authenticator that takes care of authenticating
/// requests.
pub fn init(e: *Endpoint, authenticator: *Authenticator) Self {
return .{ return .{
.authenticator = authenticator, .authenticator = authenticator,
.endpoint = e, .ep = e,
.auth_endpoint = SimpleEndpoint.init(.{ .auth_endpoint = Endpoint.init(.{
.path = e.settings.path, .path = e.settings.path,
// we override only the set ones. the other ones // we override only the set ones. the other ones
// are set to null anyway -> will be nopped out // are set to null anyway -> will be nopped out
@ -89,20 +149,21 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type {
}; };
} }
/// get the auth endpoint struct so we can be stored in the listener /// Get the auth endpoint struct of type Endpoint so it can be stored in the listener.
/// when the listener calls the auth_endpoint, onRequest will have /// When the listener calls the auth_endpoint, onRequest will have
/// access to all of us via fieldParentPtr /// access to all of this via fieldParentPtr
pub fn getEndpoint(self: *Self) *SimpleEndpoint { pub fn endpoint(self: *Self) *Endpoint {
return &self.auth_endpoint; return &self.auth_endpoint;
} }
/// here, the auth_endpoint will be passed in /// GET: here, the auth_endpoint will be passed in as endpoint.
pub fn get(e: *SimpleEndpoint, r: zap.SimpleRequest) void { /// Authenticates GET requests using the Authenticator.
pub fn get(e: *Endpoint, r: zap.Request) void {
const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e);
switch (authEp.authenticator.authenticateRequest(&r)) { switch (authEp.authenticator.authenticateRequest(&r)) {
.AuthFailed => { .AuthFailed => {
if (e.settings.unauthorized) |unauthorized| { if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.endpoint, r); unauthorized(authEp.ep, r);
return; return;
} else { } else {
r.setStatus(.unauthorized); r.setStatus(.unauthorized);
@ -110,18 +171,19 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type {
return; return;
} }
}, },
.AuthOK => authEp.endpoint.settings.get.?(authEp.endpoint, r), .AuthOK => authEp.ep.settings.get.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} }
} }
/// here, the auth_endpoint will be passed in /// POST: here, the auth_endpoint will be passed in as endpoint.
pub fn post(e: *SimpleEndpoint, r: zap.SimpleRequest) void { /// Authenticates POST requests using the Authenticator.
pub fn post(e: *Endpoint, r: zap.Request) void {
const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e);
switch (authEp.authenticator.authenticateRequest(&r)) { switch (authEp.authenticator.authenticateRequest(&r)) {
.AuthFailed => { .AuthFailed => {
if (e.settings.unauthorized) |unauthorized| { if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.endpoint, r); unauthorized(authEp.ep, r);
return; return;
} else { } else {
r.setStatus(.unauthorized); r.setStatus(.unauthorized);
@ -129,18 +191,19 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type {
return; return;
} }
}, },
.AuthOK => authEp.endpoint.settings.post.?(authEp.endpoint, r), .AuthOK => authEp.ep.settings.post.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} }
} }
/// here, the auth_endpoint will be passed in /// PUT: here, the auth_endpoint will be passed in as endpoint.
pub fn put(e: *SimpleEndpoint, r: zap.SimpleRequest) void { /// Authenticates PUT requests using the Authenticator.
pub fn put(e: *Endpoint, r: zap.Request) void {
const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e);
switch (authEp.authenticator.authenticateRequest(&r)) { switch (authEp.authenticator.authenticateRequest(&r)) {
.AuthFailed => { .AuthFailed => {
if (e.settings.unauthorized) |unauthorized| { if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.endpoint, r); unauthorized(authEp.ep, r);
return; return;
} else { } else {
r.setStatus(.unauthorized); r.setStatus(.unauthorized);
@ -148,18 +211,19 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type {
return; return;
} }
}, },
.AuthOK => authEp.endpoint.settings.put.?(authEp.endpoint, r), .AuthOK => authEp.ep.settings.put.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} }
} }
/// here, the auth_endpoint will be passed in /// DELETE: here, the auth_endpoint will be passed in as endpoint.
pub fn delete(e: *SimpleEndpoint, r: zap.SimpleRequest) void { /// Authenticates DELETE requests using the Authenticator.
pub fn delete(e: *Endpoint, r: zap.Request) void {
const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e);
switch (authEp.authenticator.authenticateRequest(&r)) { switch (authEp.authenticator.authenticateRequest(&r)) {
.AuthFailed => { .AuthFailed => {
if (e.settings.unauthorized) |unauthorized| { if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.endpoint, r); unauthorized(authEp.ep, r);
return; return;
} else { } else {
r.setStatus(.unauthorized); r.setStatus(.unauthorized);
@ -167,18 +231,19 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type {
return; return;
} }
}, },
.AuthOK => authEp.endpoint.settings.delete.?(authEp.endpoint, r), .AuthOK => authEp.ep.settings.delete.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} }
} }
/// here, the auth_endpoint will be passed in /// PATCH: here, the auth_endpoint will be passed in as endpoint.
pub fn patch(e: *SimpleEndpoint, r: zap.SimpleRequest) void { /// Authenticates PATCH requests using the Authenticator.
pub fn patch(e: *Endpoint, r: zap.Request) void {
const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e);
switch (authEp.authenticator.authenticateRequest(&r)) { switch (authEp.authenticator.authenticateRequest(&r)) {
.AuthFailed => { .AuthFailed => {
if (e.settings.unauthorized) |unauthorized| { if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.endpoint, r); unauthorized(authEp.ep, r);
return; return;
} else { } else {
r.setStatus(.unauthorized); r.setStatus(.unauthorized);
@ -186,18 +251,19 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type {
return; return;
} }
}, },
.AuthOK => authEp.endpoint.settings.patch.?(authEp.endpoint, r), .AuthOK => authEp.ep.settings.patch.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} }
} }
/// here, the auth_endpoint will be passed in /// OPTIONS: here, the auth_endpoint will be passed in as endpoint.
pub fn options(e: *SimpleEndpoint, r: zap.SimpleRequest) void { /// Authenticates OPTIONS requests using the Authenticator.
pub fn options(e: *Endpoint, r: zap.Request) void {
const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e); const authEp: *Self = @fieldParentPtr(Self, "auth_endpoint", e);
switch (authEp.authenticator.authenticateRequest(&r)) { switch (authEp.authenticator.authenticateRequest(&r)) {
.AuthFailed => { .AuthFailed => {
if (e.settings.unauthorized) |unauthorized| { if (e.settings.unauthorized) |unauthorized| {
unauthorized(authEp.endpoint, r); unauthorized(authEp.ep, r);
return; return;
} else { } else {
r.setStatus(.unauthorized); r.setStatus(.unauthorized);
@ -205,7 +271,7 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type {
return; return;
} }
}, },
.AuthOK => authEp.endpoint.settings.put.?(authEp.endpoint, r), .AuthOK => authEp.ep.settings.put.?(authEp.ep, r),
.Handled => {}, .Handled => {},
} }
} }
@ -213,26 +279,44 @@ pub fn AuthenticatingEndpoint(comptime Authenticator: type) type {
} }
pub const EndpointListenerError = error{ pub const EndpointListenerError = error{
/// Since we use .startsWith to check for matching paths, you cannot use
/// endpoint paths that overlap at the beginning. --> When trying to register
/// an endpoint whose path would shadow an already registered one, you will
/// receive this error.
EndpointPathShadowError, EndpointPathShadowError,
}; };
// NOTE: We switch on path.startsWith -> so use endpoints with distinctly /// The listener with ednpoint support
// starting names!! ///
pub const SimpleEndpointListener = struct { /// NOTE: It switches on path.startsWith -> so use endpoints with distinctly starting names!!
pub const EndpointListener = struct {
listener: Listener, listener: Listener,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
const Self = @This(); const Self = @This();
/// static struct member endpoints /// Internal static struct of member endpoints
var endpoints: std.ArrayList(*SimpleEndpoint) = undefined; var endpoints: std.ArrayList(*Endpoint) = undefined;
var on_request: ?zap.SimpleHttpRequestFn = null;
/// 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: ?zap.HttpRequestFn = null;
/// Initialize a new endpoint listener. Note, if you pass an `on_request`
/// callback in the provided ListenerSettings, this request callback will be
/// called every time a request arrives that no endpoint matches.
pub fn init(a: std.mem.Allocator, l: ListenerSettings) Self { pub fn init(a: std.mem.Allocator, l: ListenerSettings) Self {
endpoints = std.ArrayList(*SimpleEndpoint).init(a); endpoints = std.ArrayList(*Endpoint).init(a);
var ls = l; // take copy of listener settings // take copy of listener settings before modifying the callback field
var ls = l;
// override the settings with our internal, actul callback function
// so that "we" will be called on request
ls.on_request = onRequest; ls.on_request = onRequest;
// store the settings-provided request callback for later use
on_request = l.on_request; on_request = l.on_request;
return .{ return .{
.listener = Listener.init(ls), .listener = Listener.init(ls),
@ -240,16 +324,25 @@ pub const SimpleEndpointListener = struct {
}; };
} }
/// De-init the listener and free its resources.
/// Registered endpoints will not be de-initialized automatically; just removed
/// from the internal map.
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
_ = self; _ = self;
endpoints.deinit(); endpoints.deinit();
} }
/// Call this to start listening. After this, no more endpoints can be
/// registered.
pub fn listen(self: *Self) !void { pub fn listen(self: *Self) !void {
try self.listener.listen(); try self.listener.listen();
} }
pub fn addEndpoint(self: *Self, e: *SimpleEndpoint) !void { /// Register an endpoint with this listener.
/// NOTE: endpoint paths are matched with startsWith -> so use endpoints with distinctly starting names!!
/// If you try to register an endpoint whose path would shadow an already registered one, you will
/// receive an EndpointPathShadowError.
pub fn register(self: *Self, e: *Endpoint) !void {
_ = self; _ = self;
for (endpoints.items) |other| { for (endpoints.items) |other| {
if (std.mem.startsWith( if (std.mem.startsWith(
@ -276,6 +369,7 @@ pub const SimpleEndpointListener = struct {
} }
} }
} }
// if set, call the user-provided default callback
if (on_request) |foo| { if (on_request) |foo| {
foo(r); foo(r);
} }

View file

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap.zig"); const zap = @import("zap.zig");
/// Authentication Scheme enum: Basic or Bearer.
pub const AuthScheme = enum { pub const AuthScheme = enum {
Basic, Basic,
Bearer, Bearer,
@ -27,6 +28,7 @@ pub const AuthScheme = enum {
} }
}; };
/// Used internally: check for presence of the requested auth header.
pub fn checkAuthHeader(scheme: AuthScheme, auth_header: []const u8) bool { pub fn checkAuthHeader(scheme: AuthScheme, auth_header: []const u8) bool {
return switch (scheme) { return switch (scheme) {
.Basic => |b| std.mem.startsWith(u8, auth_header, b.str()) and auth_header.len > b.str().len, .Basic => |b| std.mem.startsWith(u8, auth_header, b.str()) and auth_header.len > b.str().len,
@ -34,13 +36,15 @@ pub fn checkAuthHeader(scheme: AuthScheme, auth_header: []const u8) bool {
}; };
} }
pub fn extractAuthHeader(scheme: AuthScheme, r: *const zap.SimpleRequest) ?[]const u8 { /// Used internally: return the requested auth header.
pub fn extractAuthHeader(scheme: AuthScheme, r: *const zap.Request) ?[]const u8 {
return switch (scheme) { return switch (scheme) {
.Basic => |b| r.getHeader(b.headerFieldStrFio()), .Basic => |b| r.getHeader(b.headerFieldStrFio()),
.Bearer => |b| r.getHeader(b.headerFieldStrFio()), .Bearer => |b| r.getHeader(b.headerFieldStrFio()),
}; };
} }
/// Decoding Strategy for Basic Authentication
const BasicAuthStrategy = enum { const BasicAuthStrategy = enum {
/// decode into user and pass, then check pass /// decode into user and pass, then check pass
UserPass, UserPass,
@ -48,20 +52,21 @@ const BasicAuthStrategy = enum {
Token68, Token68,
}; };
/// Authentication result
pub const AuthResult = enum { pub const AuthResult = enum {
/// authentication / authorization was successful /// authentication / authorization was successful
AuthOK, AuthOK,
/// authentication / authorization failed /// authentication / authorization failed
AuthFailed, AuthFailed,
/// the authenticator handled the request that didn't pass authentication / /// The authenticator handled the request that didn't pass authentication /
/// authorization . /// authorization.
/// this is used to implement authenticators that redirect to a login /// This is used to implement authenticators that redirect to a login
/// page. An AuthenticatingEndpoint will not do the default, which is trying /// page. An AuthenticatingEndpoint will not do the default, which is trying
/// to call the `unauthorized` callback or. /// to call the `unauthorized` callback if one exists orelse ignore the request.
Handled, Handled,
}; };
/// HTTP Basic Authentication RFC 7617 /// HTTP Basic Authentication RFC 7617.
/// "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" /// "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
/// user-pass strings: "$username:$password" -> base64 /// user-pass strings: "$username:$password" -> base64
/// ///
@ -73,10 +78,9 @@ pub const AuthResult = enum {
/// Errors: /// Errors:
/// WWW-Authenticate: Basic realm="this" /// WWW-Authenticate: Basic realm="this"
/// ///
/// T : any kind of map that implements get([]const u8) -> []const u8 /// Lookup : any kind of map that implements get([]const u8) -> []const u8
pub fn BasicAuth(comptime Lookup: type, comptime kind: BasicAuthStrategy) type { pub fn BasicAuth(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
return struct { return struct {
// kind: BasicAuthStrategy,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
realm: ?[]const u8, realm: ?[]const u8,
lookup: *Lookup, lookup: *Lookup,
@ -87,23 +91,24 @@ pub fn BasicAuth(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
/// different implementations can /// different implementations can
/// - either decode, lookup and compare passwords /// - either decode, lookup and compare passwords
/// - or just check for existence of the base64-encoded user:pass combination /// - or just check for existence of the base64-encoded user:pass combination
/// if realm is provided (not null), a copy is taken -> call deinit() to clean up /// if realm is provided (not null), a copy of it is taken -> call deinit() to clean up
pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !Self { pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !Self {
return .{ return .{
// .kind = kind,
.allocator = allocator, .allocator = allocator,
.lookup = lookup, .lookup = lookup,
.realm = if (realm) |the_realm| try allocator.dupe(u8, the_realm) else null, .realm = if (realm) |the_realm| try allocator.dupe(u8, the_realm) else null,
}; };
} }
/// Deinit the authenticator.
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
if (self.realm) |the_realm| { if (self.realm) |the_realm| {
self.allocator.free(the_realm); self.allocator.free(the_realm);
} }
} }
/// Use this to decode the auth_header into user:pass, lookup pass in lookup /// Use this to decode the auth_header into user:pass, lookup pass in lookup.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`.
pub fn authenticateUserPass(self: *Self, auth_header: []const u8) AuthResult { pub fn authenticateUserPass(self: *Self, auth_header: []const u8) AuthResult {
zap.debug("AuthenticateUserPass\n", .{}); zap.debug("AuthenticateUserPass\n", .{});
const encoded = auth_header[AuthScheme.Basic.str().len..]; const encoded = auth_header[AuthScheme.Basic.str().len..];
@ -165,22 +170,28 @@ pub fn BasicAuth(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
return .AuthFailed; return .AuthFailed;
} }
/// Use this to just look up if the base64-encoded auth_header exists in lookup /// Use this to just look up if the base64-encoded auth_header exists in lookup.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`.
pub fn authenticateToken68(self: *Self, auth_header: []const u8) AuthResult { pub fn authenticateToken68(self: *Self, auth_header: []const u8) AuthResult {
const token = auth_header[AuthScheme.Basic.str().len..]; const token = auth_header[AuthScheme.Basic.str().len..];
return if (self.lookup.*.contains(token)) .AuthOK else .AuthFailed; return if (self.lookup.*.contains(token)) .AuthOK else .AuthFailed;
} }
// dispatch based on kind /// dispatch based on kind (.UserPass / .Token689) and try to authenticate based on the header.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`.
pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult { pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult {
zap.debug("AUTHENTICATE\n", .{}); zap.debug("AUTHENTICATE\n", .{});
// switch (self.kind) {
switch (kind) { switch (kind) {
.UserPass => return self.authenticateUserPass(auth_header), .UserPass => return self.authenticateUserPass(auth_header),
.Token68 => return self.authenticateToken68(auth_header), .Token68 => return self.authenticateToken68(auth_header),
} }
} }
pub fn authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult {
/// The zap authentication request handler.
///
/// Tries to extract the authentication header and perform the authentication.
/// If no authentication header is found, an authorization header is tried.
pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult {
zap.debug("AUTHENTICATE REQUEST\n", .{}); zap.debug("AUTHENTICATE REQUEST\n", .{});
if (extractAuthHeader(.Basic, r)) |auth_header| { if (extractAuthHeader(.Basic, r)) |auth_header| {
zap.debug("Authentication Header found!\n", .{}); zap.debug("Authentication Header found!\n", .{});
@ -215,10 +226,9 @@ pub const BearerAuthSingle = struct {
const Self = @This(); const Self = @This();
/// Creates a Single-Token Bearer Authenticator /// Creates a Single-Token Bearer Authenticator.
/// takes a copy of the token /// Takes a copy of the token.
/// if realm is provided (not null), a copy is taken /// If realm is provided (not null), a copy is taken call deinit() to clean up.
/// call deinit() to clean up
pub fn init(allocator: std.mem.Allocator, token: []const u8, realm: ?[]const u8) !Self { pub fn init(allocator: std.mem.Allocator, token: []const u8, realm: ?[]const u8) !Self {
return .{ return .{
.allocator = allocator, .allocator = allocator,
@ -226,6 +236,9 @@ pub const BearerAuthSingle = struct {
.realm = if (realm) |the_realm| try allocator.dupe(u8, the_realm) else null, .realm = if (realm) |the_realm| try allocator.dupe(u8, the_realm) else null,
}; };
} }
/// Try to authenticate based on the header.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`.
pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult { pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult {
if (checkAuthHeader(.Bearer, auth_header) == false) { if (checkAuthHeader(.Bearer, auth_header) == false) {
return .AuthFailed; return .AuthFailed;
@ -234,13 +247,17 @@ pub const BearerAuthSingle = struct {
return if (std.mem.eql(u8, token, self.token)) .AuthOK else .AuthFailed; return if (std.mem.eql(u8, token, self.token)) .AuthOK else .AuthFailed;
} }
pub fn authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult { /// The zap authentication request handler.
///
/// Tries to extract the authentication header and perform the authentication.
pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult {
if (extractAuthHeader(.Bearer, r)) |auth_header| { if (extractAuthHeader(.Bearer, r)) |auth_header| {
return self.authenticate(auth_header); return self.authenticate(auth_header);
} }
return .AuthFailed; return .AuthFailed;
} }
/// Deinits the authenticator.
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
if (self.realm) |the_realm| { if (self.realm) |the_realm| {
self.allocator.free(the_realm); self.allocator.free(the_realm);
@ -267,9 +284,9 @@ pub fn BearerAuthMulti(comptime Lookup: type) type {
const Self = @This(); const Self = @This();
/// Creates a BasicAuth. `lookup` must implement `.get([]const u8) -> []const u8` /// Creates a Multi Token Bearer Authenticator. `lookup` must implement
/// to look up tokens /// `.get([]const u8) -> []const u8` to look up tokens.
/// if realm is provided (not null), a copy is taken -> call deinit() to clean up /// If realm is provided (not null), a copy of it is taken -> call deinit() to clean up.
pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !Self { pub fn init(allocator: std.mem.Allocator, lookup: *Lookup, realm: ?[]const u8) !Self {
return .{ return .{
.allocator = allocator, .allocator = allocator,
@ -278,12 +295,16 @@ pub fn BearerAuthMulti(comptime Lookup: type) type {
}; };
} }
/// Deinit the authenticator. Only required if a realm was provided at
/// init() time.
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
if (self.realm) |the_realm| { if (self.realm) |the_realm| {
self.allocator.free(the_realm); self.allocator.free(the_realm);
} }
} }
/// Try to authenticate based on the header.
/// Note: usually, you don't want to use this; you'd go for `authenticateRequest()`.
pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult { pub fn authenticate(self: *Self, auth_header: []const u8) AuthResult {
if (checkAuthHeader(.Bearer, auth_header) == false) { if (checkAuthHeader(.Bearer, auth_header) == false) {
return .AuthFailed; return .AuthFailed;
@ -292,7 +313,10 @@ pub fn BearerAuthMulti(comptime Lookup: type) type {
return if (self.lookup.*.contains(token)) .AuthOK else .AuthFailed; return if (self.lookup.*.contains(token)) .AuthOK else .AuthFailed;
} }
pub fn authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult { /// The zap authentication request handler.
///
/// Tries to extract the authentication header and perform the authentication.
pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult {
if (extractAuthHeader(.Bearer, r)) |auth_header| { if (extractAuthHeader(.Bearer, r)) |auth_header| {
return self.authenticate(auth_header); return self.authenticate(auth_header);
} }
@ -301,6 +325,7 @@ pub fn BearerAuthMulti(comptime Lookup: type) type {
}; };
} }
/// Settings to initialize a UserPassSessionAuth authenticator.
pub const UserPassSessionAuthArgs = struct { pub const UserPassSessionAuthArgs = struct {
/// username body parameter /// username body parameter
usernameParam: []const u8, usernameParam: []const u8,
@ -308,7 +333,7 @@ pub const UserPassSessionAuthArgs = struct {
passwordParam: []const u8, passwordParam: []const u8,
/// redirect to this page if auth fails /// redirect to this page if auth fails
loginPage: []const u8, loginPage: []const u8,
/// name of the cookie /// name of the auth cookie
cookieName: []const u8, cookieName: []const u8,
/// cookie max age in seconds; 0 -> session cookie /// cookie max age in seconds; 0 -> session cookie
cookieMaxAge: u8 = 0, cookieMaxAge: u8 = 0,
@ -330,8 +355,8 @@ pub const UserPassSessionAuthArgs = struct {
/// ///
/// Please note the implications of this simple approach: IF YOU REUSE "username" /// Please note the implications of this simple approach: IF YOU REUSE "username"
/// and "password" body params for anything else in your application, then the /// and "password" body params for anything else in your application, then the
/// mechanisms described above will kick in. For that reason: please know what you're /// mechanisms described above will still kick in. For that reason: please know what
/// doing. /// you're doing.
/// ///
/// See UserPassSessionAuthArgs: /// See UserPassSessionAuthArgs:
/// - username & password param names can be defined by you /// - username & password param names can be defined by you
@ -357,7 +382,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
lookup: *Lookup, lookup: *Lookup,
settings: UserPassSessionAuthArgs, settings: UserPassSessionAuthArgs,
// TODO: cookie store per user // TODO: cookie store per user?
sessionTokens: SessionTokenMap, sessionTokens: SessionTokenMap,
passwordLookupLock: std.Thread.Mutex = .{}, passwordLookupLock: std.Thread.Mutex = .{},
tokenLookupLock: std.Thread.Mutex = .{}, tokenLookupLock: std.Thread.Mutex = .{},
@ -368,6 +393,8 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
const Token = [Hash.digest_length * 2]u8; const Token = [Hash.digest_length * 2]u8;
/// Construct this authenticator. See above and related types for more
/// information.
pub fn init( pub fn init(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
lookup: *Lookup, lookup: *Lookup,
@ -390,6 +417,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
return ret; return ret;
} }
/// De-init this authenticator.
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.allocator.free(self.settings.usernameParam); self.allocator.free(self.settings.usernameParam);
self.allocator.free(self.settings.passwordParam); self.allocator.free(self.settings.passwordParam);
@ -405,8 +433,9 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
} }
/// Check for session token cookie, remove the token from the valid tokens /// Check for session token cookie, remove the token from the valid tokens
pub fn logout(self: *Self, r: *const zap.SimpleRequest) void { pub fn logout(self: *Self, r: *const zap.Request) void {
// we erase the list of valid tokens server-side // we erase the list of valid tokens server-side (later) and set the
// cookie to "invalid" on the client side.
if (r.setCookie(.{ if (r.setCookie(.{
.name = self.settings.cookieName, .name = self.settings.cookieName,
.value = "invalid", .value = "invalid",
@ -420,7 +449,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
r.parseCookies(false); r.parseCookies(false);
// check for session cookie // check for session cookie
if (r.getCookieStr(self.settings.cookieName, self.allocator, false)) |maybe_cookie| { if (r.getCookieStr(self.allocator, self.settings.cookieName, false)) |maybe_cookie| {
if (maybe_cookie) |cookie| { if (maybe_cookie) |cookie| {
defer cookie.deinit(); defer cookie.deinit();
self.tokenLookupLock.lock(); self.tokenLookupLock.lock();
@ -439,7 +468,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
} }
} }
fn _internal_authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult { fn _internal_authenticateRequest(self: *Self, r: *const zap.Request) AuthResult {
// if we're requesting the login page, let the request through // if we're requesting the login page, let the request through
if (r.path) |p| { if (r.path) |p| {
if (std.mem.startsWith(u8, p, self.settings.loginPage)) { if (std.mem.startsWith(u8, p, self.settings.loginPage)) {
@ -456,7 +485,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
r.parseCookies(false); r.parseCookies(false);
// check for session cookie // check for session cookie
if (r.getCookieStr(self.settings.cookieName, self.allocator, false)) |maybe_cookie| { if (r.getCookieStr(self.allocator, self.settings.cookieName, false)) |maybe_cookie| {
if (maybe_cookie) |cookie| { if (maybe_cookie) |cookie| {
defer cookie.deinit(); defer cookie.deinit();
// locked or unlocked token lookup // locked or unlocked token lookup
@ -478,10 +507,10 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
} }
// get params of username and password // get params of username and password
if (r.getParamStr(self.settings.usernameParam, self.allocator, false)) |maybe_username| { if (r.getParamStr(self.allocator, self.settings.usernameParam, false)) |maybe_username| {
if (maybe_username) |*username| { if (maybe_username) |*username| {
defer username.deinit(); defer username.deinit();
if (r.getParamStr(self.settings.passwordParam, self.allocator, false)) |maybe_pw| { if (r.getParamStr(self.allocator, self.settings.passwordParam, false)) |maybe_pw| {
if (maybe_pw) |*pw| { if (maybe_pw) |*pw| {
defer pw.deinit(); defer pw.deinit();
@ -530,7 +559,10 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
return .AuthFailed; return .AuthFailed;
} }
pub fn authenticateRequest(self: *Self, r: *const zap.SimpleRequest) AuthResult { /// The zap authentication request handler.
///
/// See above for how it works.
pub fn authenticateRequest(self: *Self, r: *const zap.Request) AuthResult {
switch (self._internal_authenticateRequest(r)) { switch (self._internal_authenticateRequest(r)) {
.AuthOK => { .AuthOK => {
// username and pass are ok -> created token, set header, caller can continue // username and pass are ok -> created token, set header, caller can continue
@ -550,7 +582,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
} }
} }
fn redirect(self: *Self, r: *const zap.SimpleRequest) !void { fn redirect(self: *Self, r: *const zap.Request) !void {
try r.redirectTo(self.settings.loginPage, self.settings.redirectCode); try r.redirectTo(self.settings.loginPage, self.settings.redirectCode);
} }

View file

@ -1,7 +1,10 @@
const std = @import("std"); const std = @import("std");
// TODO: rework logging in zap
debugOn: bool, debugOn: bool,
/// Access to facil.io's logging facilities
const Self = @This(); const Self = @This();
pub fn init(comptime debug: bool) Self { pub fn init(comptime debug: bool) Self {

View file

@ -1,43 +1,14 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap.zig"); const zap = @import("zap.zig");
pub const ContextDescriptor = struct { /// Your middleware components need to contain a handler.
name: []const u8, ///
type: type, /// A Handler is one element in the chain of request handlers that will be tried
}; /// by the listener when a request arrives. Handlers indicate to the previous
/// handler whether they processed a request by returning `true` from their
/// Provide a tuple of structs of type like ContextDescriptor /// `on_request` function, in which case a typical request handler would stop
/// a name starting with '?', such as "?user" will be treated as Optional with default `null`. /// trying to pass the request on to the next handler in the chain. See
pub fn MixContexts(comptime context_tuple: anytype) type { /// the `handle_other` function in this struct.
var fields: [context_tuple.len]std.builtin.Type.StructField = undefined;
for (context_tuple, 0..) |t, i| {
var fieldType: type = t.type;
var fieldName: []const u8 = t.name[0..];
var isOptional: bool = false;
if (fieldName[0] == '?') {
fieldType = @Type(.{ .Optional = .{ .child = fieldType } });
fieldName = fieldName[1..];
isOptional = true;
}
fields[i] = .{
.name = fieldName,
.type = fieldType,
.default_value = if (isOptional) &@as(fieldType, null) else null,
.is_comptime = false,
.alignment = 0,
};
}
return @Type(.{
.Struct = .{
.layout = .Auto,
.fields = fields[0..],
.decls = &[_]std.builtin.Type.Declaration{},
.is_tuple = false,
},
});
}
/// Your middleware components need to contain a handler
pub fn Handler(comptime ContextType: anytype) type { pub fn Handler(comptime ContextType: anytype) type {
return struct { return struct {
other_handler: ?*Self = null, other_handler: ?*Self = null,
@ -46,7 +17,7 @@ pub fn Handler(comptime ContextType: anytype) type {
// will be set // will be set
allocator: ?std.mem.Allocator = null, allocator: ?std.mem.Allocator = null,
pub const RequestFn = *const fn (*Self, zap.SimpleRequest, *ContextType) bool; pub const RequestFn = *const fn (*Self, zap.Request, *ContextType) bool;
const Self = @This(); const Self = @This();
pub fn init(on_request: RequestFn, other: ?*Self) Self { pub fn init(on_request: RequestFn, other: ?*Self) Self {
@ -56,10 +27,10 @@ pub fn Handler(comptime ContextType: anytype) type {
}; };
} }
// example for handling request // example for handling a request request
// which you can use in your components, e.g.: // which you can use in your components, e.g.:
// return self.handler.handleOther(r, context); // return self.handler.handleOther(r, context);
pub fn handleOther(self: *Self, r: zap.SimpleRequest, context: *ContextType) bool { pub fn handleOther(self: *Self, r: zap.Request, context: *ContextType) bool {
// in structs embedding a handler, we'd @fieldParentPtr the first // in structs embedding a handler, we'd @fieldParentPtr the first
// param to get to the real self // param to get to the real self
@ -80,16 +51,19 @@ pub fn Handler(comptime ContextType: anytype) type {
}; };
} }
/// A convenience handler for artibrary zap.SimpleEndpoint /// 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.SimpleEndpoint, endpoint: *zap.Endpoint,
breakOnFinish: bool, breakOnFinish: bool,
const Self = @This(); const Self = @This();
pub fn init(endpoint: *zap.SimpleEndpoint, other: ?*HandlerType, breakOnFinish: bool) Self { /// 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 {
return .{ return .{
.handler = HandlerType.init(onRequest, other), .handler = HandlerType.init(onRequest, other),
.endpoint = endpoint, .endpoint = endpoint,
@ -97,12 +71,17 @@ pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anyt
}; };
} }
// we need the handler as a common interface to chain stuff /// Provides the handler as a common interface to chain stuff
pub fn getHandler(self: *Self) *HandlerType { pub fn getHandler(self: *Self) *HandlerType {
return &self.handler; return &self.handler;
} }
pub fn onRequest(handler: *HandlerType, r: zap.SimpleRequest, context: *ContextType) bool { /// The Handler's request handling function. Gets called from the listener
/// with the request and a context instance. Calls the endpoint.
///
/// If `breakOnFinish` is `true`, the handler will stop handing requests down the chain if
/// the endpoint processed the request.
pub fn onRequest(handler: *HandlerType, r: zap.Request, context: *ContextType) bool {
var self = @fieldParentPtr(Self, "handler", handler); var self = @fieldParentPtr(Self, "handler", handler);
r.setUserContext(context); r.setUserContext(context);
self.endpoint.onRequest(r); self.endpoint.onRequest(r);
@ -117,15 +96,18 @@ pub fn EndpointHandler(comptime HandlerType: anytype, comptime ContextType: anyt
} }
pub const Error = error{ pub const Error = error{
/// The listener could not be created because the settings provided to its
/// init() function contained an `on_request` callback that was not null.
InitOnRequestIsNotNull, InitOnRequestIsNotNull,
}; };
pub const RequestAllocatorFn = *const fn () std.mem.Allocator; pub const RequestAllocatorFn = *const fn () std.mem.Allocator;
/// Special Listener that supports chaining request handlers.
pub fn Listener(comptime ContextType: anytype) type { pub fn Listener(comptime ContextType: anytype) type {
return struct { return struct {
listener: zap.SimpleHttpListener = undefined, listener: zap.HttpListener = undefined,
settings: zap.SimpleHttpListenerSettings, settings: zap.HttpListenerSettings,
// static initial handler // static initial handler
var handler: ?*Handler(ContextType) = undefined; var handler: ?*Handler(ContextType) = undefined;
@ -134,9 +116,10 @@ pub fn Listener(comptime ContextType: anytype) type {
const Self = @This(); const Self = @This();
/// initialize the middleware handler /// Construct and initialize a middleware handler.
/// the passed in settings must have on_request set to null /// The passed in settings must have on_request set to null! If that is
pub fn init(settings: zap.SimpleHttpListenerSettings, initial_handler: *Handler(ContextType), request_alloc: ?RequestAllocatorFn) Error!Self { /// not the case, an InitOnRequestIsNotNull error will be returned.
pub fn init(settings: zap.HttpListenerSettings, initial_handler: *Handler(ContextType), request_alloc: ?RequestAllocatorFn) Error!Self {
// override on_request with ourselves // override on_request with ourselves
if (settings.on_request != null) { if (settings.on_request != null) {
return Error.InitOnRequestIsNotNull; return Error.InitOnRequestIsNotNull;
@ -147,21 +130,26 @@ pub fn Listener(comptime ContextType: anytype) type {
var ret: Self = .{ var ret: Self = .{
.settings = settings, .settings = settings,
}; };
ret.settings.on_request = onRequest; ret.settings.on_request = onRequest;
ret.listener = zap.SimpleHttpListener.init(ret.settings); ret.listener = zap.HttpListener.init(ret.settings);
handler = initial_handler; handler = initial_handler;
return ret; return ret;
} }
/// Start listening.
pub fn listen(self: *Self) !void { pub fn listen(self: *Self) !void {
try self.listener.listen(); try self.listener.listen();
} }
// this is just a reference implementation /// The listener's request handler, stepping through the chain of Handlers
// but it's actually used obviously. Create your own listener if you /// by calling the initial one which takes it from there.
// want different behavior. ///
// Didn't want to make this a callback /// This is just a reference implementation that you can use by default.
pub fn onRequest(r: zap.SimpleRequest) void { /// Create your own listener if you want different behavior.
/// (Didn't want to make this a callback. Submit an issue if you really
/// think that's an issue).
pub fn onRequest(r: zap.Request) void {
// we are the 1st handler in the chain, so we create a context // we are the 1st handler in the chain, so we create a context
var context: ContextType = .{}; var context: ContextType = .{};
@ -182,60 +170,3 @@ pub fn Listener(comptime ContextType: anytype) type {
} }
}; };
} }
test "it" {
// just some made-up struct
const User = struct {
name: []const u8,
email: []const u8,
};
// just some made-up struct
const Session = struct {
sessionType: []const u8,
token: []const u8,
valid: bool,
};
const Mixed = MixContexts(
.{
.{ .name = "?user", .type = *User },
.{ .name = "?session", .type = *Session },
},
);
std.debug.print("{any}\n", .{Mixed});
inline for (@typeInfo(Mixed).Struct.fields, 0..) |f, i| {
std.debug.print("field {} : name = {s} : type = {any}\n", .{ i, f.name, f.type });
}
const mixed: Mixed = .{
// it's all optionals which we made default to null in MixContexts
};
std.debug.print("mixed = {any}\n", .{mixed});
const NonOpts = MixContexts(
.{
.{ .name = "user", .type = *User },
.{ .name = "session", .type = *Session },
},
);
var user: User = .{
.name = "renerocksai",
.email = "secret",
};
var session: Session = .{
.sessionType = "bearerToken",
.token = "ABCDEFG",
.valid = false,
};
// this will fail if we don't specify
const nonOpts: NonOpts = .{
.user = &user,
.session = &session,
};
std.debug.print("nonOpts = {any}\n", .{nonOpts});
}

View file

@ -106,21 +106,21 @@ const HTTP_RESPONSE: []const u8 =
; ;
var received_response: []const u8 = "null"; var received_response: []const u8 = "null";
fn endpoint_http_get(e: *Endpoints.SimpleEndpoint, r: zap.SimpleRequest) void { fn endpoint_http_get(e: *Endpoints.Endpoint, r: zap.Request) void {
_ = e; _ = e;
r.sendBody(HTTP_RESPONSE) catch return; r.sendBody(HTTP_RESPONSE) catch return;
received_response = HTTP_RESPONSE; received_response = HTTP_RESPONSE;
std.time.sleep(1 * std.time.ns_per_s); std.time.sleep(1 * std.time.ns_per_s);
zap.fio_stop(); zap.stop();
} }
fn endpoint_http_unauthorized(e: *Endpoints.SimpleEndpoint, r: zap.SimpleRequest) void { fn endpoint_http_unauthorized(e: *Endpoints.Endpoint, r: zap.Request) void {
_ = e; _ = e;
r.setStatus(.unauthorized); r.setStatus(.unauthorized);
r.sendBody("UNAUTHORIZED ACCESS") catch return; r.sendBody("UNAUTHORIZED ACCESS") catch return;
received_response = "UNAUTHORIZED"; received_response = "UNAUTHORIZED";
std.time.sleep(1 * std.time.ns_per_s); std.time.sleep(1 * std.time.ns_per_s);
zap.fio_stop(); zap.stop();
} }
// //
@ -165,7 +165,7 @@ fn makeRequest(a: std.mem.Allocator, url: []const u8, auth: ?ClientAuthReqHeader
std.debug.print("RESPONSE:\n{s}\n", .{buffer[0..len]}); std.debug.print("RESPONSE:\n{s}\n", .{buffer[0..len]});
} }
zap.fio_stop(); zap.stop();
} }
fn makeRequestThread(a: std.mem.Allocator, url: []const u8, auth: ?ClientAuthReqHeaderFields) !std.Thread { fn makeRequestThread(a: std.mem.Allocator, url: []const u8, auth: ?ClientAuthReqHeaderFields) !std.Thread {
@ -181,7 +181,7 @@ test "BearerAuthSingle authenticateRequest OK" {
const token = "ABCDEFG"; const token = "ABCDEFG";
// setup listener // setup listener
var listener = zap.SimpleEndpointListener.init( var listener = zap.EndpointListener.init(
a, a,
.{ .{
.port = 3000, .port = 3000,
@ -194,7 +194,7 @@ test "BearerAuthSingle authenticateRequest OK" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoints.SimpleEndpoint.init(.{ var ep = Endpoints.Endpoint.init(.{
.path = "/test", .path = "/test",
.get = endpoint_http_get, .get = endpoint_http_get,
.unauthorized = endpoint_http_unauthorized, .unauthorized = endpoint_http_unauthorized,
@ -209,7 +209,7 @@ test "BearerAuthSingle authenticateRequest OK" {
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.addEndpoint(auth_ep.getEndpoint()); try listener.register(auth_ep.endpoint());
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("\n\n*******************************************\n", .{}); // std.debug.print("\n\n*******************************************\n", .{});
@ -234,7 +234,7 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" {
const token = "ABCDEFG"; const token = "ABCDEFG";
// setup listener // setup listener
var listener = zap.SimpleEndpointListener.init( var listener = zap.EndpointListener.init(
a, a,
.{ .{
.port = 3000, .port = 3000,
@ -247,7 +247,7 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoints.SimpleEndpoint.init(.{ var ep = Endpoints.Endpoint.init(.{
.path = "/test", .path = "/test",
.get = endpoint_http_get, .get = endpoint_http_get,
.unauthorized = endpoint_http_unauthorized, .unauthorized = endpoint_http_unauthorized,
@ -268,7 +268,7 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" {
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.addEndpoint(auth_ep.getEndpoint()); try listener.register(auth_ep.endpoint());
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
@ -291,7 +291,7 @@ test "BearerAuthMulti authenticateRequest OK" {
const token = "ABCDEFG"; const token = "ABCDEFG";
// setup listener // setup listener
var listener = zap.SimpleEndpointListener.init( var listener = zap.EndpointListener.init(
a, a,
.{ .{
.port = 3000, .port = 3000,
@ -304,7 +304,7 @@ test "BearerAuthMulti authenticateRequest OK" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoints.SimpleEndpoint.init(.{ var ep = Endpoints.Endpoint.init(.{
.path = "/test", .path = "/test",
.get = endpoint_http_get, .get = endpoint_http_get,
.unauthorized = endpoint_http_unauthorized, .unauthorized = endpoint_http_unauthorized,
@ -319,7 +319,7 @@ test "BearerAuthMulti authenticateRequest OK" {
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.addEndpoint(auth_ep.getEndpoint()); try listener.register(auth_ep.endpoint());
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
@ -342,7 +342,7 @@ test "BearerAuthMulti authenticateRequest test-unauthorized" {
const token = "invalid"; const token = "invalid";
// setup listener // setup listener
var listener = zap.SimpleEndpointListener.init( var listener = zap.EndpointListener.init(
a, a,
.{ .{
.port = 3000, .port = 3000,
@ -355,7 +355,7 @@ test "BearerAuthMulti authenticateRequest test-unauthorized" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoints.SimpleEndpoint.init(.{ var ep = Endpoints.Endpoint.init(.{
.path = "/test", .path = "/test",
.get = endpoint_http_get, .get = endpoint_http_get,
.unauthorized = endpoint_http_unauthorized, .unauthorized = endpoint_http_unauthorized,
@ -370,7 +370,7 @@ test "BearerAuthMulti authenticateRequest test-unauthorized" {
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.addEndpoint(auth_ep.getEndpoint()); try listener.register(auth_ep.endpoint());
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
@ -393,7 +393,7 @@ test "BasicAuth Token68 authenticateRequest" {
const token = "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; const token = "QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
// setup listener // setup listener
var listener = zap.SimpleEndpointListener.init( var listener = zap.EndpointListener.init(
a, a,
.{ .{
.port = 3000, .port = 3000,
@ -406,7 +406,7 @@ test "BasicAuth Token68 authenticateRequest" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoints.SimpleEndpoint.init(.{ var ep = Endpoints.Endpoint.init(.{
.path = "/test", .path = "/test",
.get = endpoint_http_get, .get = endpoint_http_get,
.unauthorized = endpoint_http_unauthorized, .unauthorized = endpoint_http_unauthorized,
@ -426,7 +426,7 @@ test "BasicAuth Token68 authenticateRequest" {
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.addEndpoint(auth_ep.getEndpoint()); try listener.register(auth_ep.endpoint());
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
@ -449,7 +449,7 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" {
const token = "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; const token = "QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
// setup listener // setup listener
var listener = zap.SimpleEndpointListener.init( var listener = zap.EndpointListener.init(
a, a,
.{ .{
.port = 3000, .port = 3000,
@ -462,7 +462,7 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoints.SimpleEndpoint.init(.{ var ep = Endpoints.Endpoint.init(.{
.path = "/test", .path = "/test",
.get = endpoint_http_get, .get = endpoint_http_get,
.unauthorized = endpoint_http_unauthorized, .unauthorized = endpoint_http_unauthorized,
@ -482,7 +482,7 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" {
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.addEndpoint(auth_ep.getEndpoint()); try listener.register(auth_ep.endpoint());
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
@ -504,7 +504,7 @@ test "BasicAuth UserPass authenticateRequest" {
const a = std.testing.allocator; const a = std.testing.allocator;
// setup listener // setup listener
var listener = zap.SimpleEndpointListener.init( var listener = zap.EndpointListener.init(
a, a,
.{ .{
.port = 3000, .port = 3000,
@ -517,7 +517,7 @@ test "BasicAuth UserPass authenticateRequest" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoints.SimpleEndpoint.init(.{ var ep = Endpoints.Endpoint.init(.{
.path = "/test", .path = "/test",
.get = endpoint_http_get, .get = endpoint_http_get,
.unauthorized = endpoint_http_unauthorized, .unauthorized = endpoint_http_unauthorized,
@ -548,7 +548,7 @@ test "BasicAuth UserPass authenticateRequest" {
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.addEndpoint(auth_ep.getEndpoint()); try listener.register(auth_ep.endpoint());
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});
@ -570,7 +570,7 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" {
const a = std.testing.allocator; const a = std.testing.allocator;
// setup listener // setup listener
var listener = zap.SimpleEndpointListener.init( var listener = zap.EndpointListener.init(
a, a,
.{ .{
.port = 3000, .port = 3000,
@ -583,7 +583,7 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" {
defer listener.deinit(); defer listener.deinit();
// create mini endpoint // create mini endpoint
var ep = Endpoints.SimpleEndpoint.init(.{ var ep = Endpoints.Endpoint.init(.{
.path = "/test", .path = "/test",
.get = endpoint_http_get, .get = endpoint_http_get,
.unauthorized = endpoint_http_unauthorized, .unauthorized = endpoint_http_unauthorized,
@ -615,7 +615,7 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" {
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator); const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator); var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
try listener.addEndpoint(auth_ep.getEndpoint()); try listener.register(auth_ep.endpoint());
listener.listen() catch {}; listener.listen() catch {};
// std.debug.print("Waiting for the following:\n", .{}); // std.debug.print("Waiting for the following:\n", .{});

View file

@ -15,7 +15,7 @@ fn makeRequest(a: std.mem.Allocator, url: []const u8) !void {
try req.send(.{}); try req.send(.{});
try req.wait(); try req.wait();
zap.fio_stop(); zap.stop();
} }
fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread { fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread {
@ -34,7 +34,7 @@ test "http parameters" {
var params: ?zap.HttpParamKVList = null; var params: ?zap.HttpParamKVList = null;
var paramOneStr: ?zap.FreeOrNot = null; var paramOneStr: ?zap.FreeOrNot = null;
pub fn on_request(r: zap.SimpleRequest) void { pub fn on_request(r: zap.Request) void {
ran = true; ran = true;
r.parseQuery(); r.parseQuery();
param_count = r.getParamCount(); param_count = r.getParamCount();
@ -45,7 +45,7 @@ test "http parameters" {
// true -> make copies of temp strings // true -> make copies of temp strings
params = r.parametersToOwnedList(alloc, true) catch unreachable; params = r.parametersToOwnedList(alloc, true) catch unreachable;
var maybe_str = r.getParamStr("one", alloc, true) catch unreachable; var maybe_str = r.getParamStr(alloc, "one", true) catch unreachable;
if (maybe_str) |*s| { if (maybe_str) |*s| {
paramOneStr = s.*; paramOneStr = s.*;
} }
@ -55,7 +55,7 @@ test "http parameters" {
Handler.alloc = allocator; Handler.alloc = allocator;
// setup listener // setup listener
var listener = zap.SimpleHttpListener.init( var listener = zap.HttpListener.init(
.{ .{
.port = 3001, .port = 3001,
.on_request = Handler.on_request, .on_request = Handler.on_request,

View file

@ -22,13 +22,13 @@ fn makeRequest(a: std.mem.Allocator, url: []const u8) !void {
try req.wait(); try req.wait();
read_len = try req.readAll(&buffer); read_len = try req.readAll(&buffer);
zap.fio_stop(); zap.stop();
} }
fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread { fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread {
return try std.Thread.spawn(.{}, makeRequest, .{ a, url }); return try std.Thread.spawn(.{}, makeRequest, .{ a, url });
} }
pub fn on_request(r: zap.SimpleRequest) void { pub fn on_request(r: zap.Request) void {
r.sendFile("src/tests/testfile.txt") catch unreachable; r.sendFile("src/tests/testfile.txt") catch unreachable;
} }
@ -36,7 +36,7 @@ test "send file" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
// setup listener // setup listener
var listener = zap.SimpleHttpListener.init( var listener = zap.HttpListener.init(
.{ .{
.port = 3002, .port = 3002,
.on_request = on_request, .on_request = on_request,

View file

@ -1,9 +1,10 @@
const std = @import("std"); const std = @import("std");
const fio = @import("fio.zig"); const fio = @import("fio.zig");
/// Used internally: convert a FIO object into its string representation.
/// note: since this is called from within request functions, we don't make /// note: since this is called from within request functions, we don't make
/// copies. Also, we return temp memory from fio. -> don't hold on to it outside /// copies. Also, we return temp memory from fio. -> don't hold on to it outside
/// of a request function /// of a request function. FIO temp memory strings do not need to be freed.
pub fn fio2str(o: fio.FIOBJ) ?[]const u8 { pub fn fio2str(o: fio.FIOBJ) ?[]const u8 {
if (o == 0) return null; if (o == 0) return null;
const x: fio.fio_str_info_s = fio.fiobj_obj2cstr(o); const x: fio.fio_str_info_s = fio.fiobj_obj2cstr(o);
@ -12,6 +13,12 @@ pub fn fio2str(o: fio.FIOBJ) ?[]const u8 {
return x.data[0..x.len]; return x.data[0..x.len];
} }
/// A "string" type used internally that carries a flag whether its buffer needs
/// to be freed or not - and honors it in `deinit()`. That way, it's always
/// safe to call deinit().
/// For instance, slices taken directly from the zap.Request need not be freed.
/// But the ad-hoc created string representation of a float parameter must be
/// freed after use.
pub const FreeOrNot = struct { pub const FreeOrNot = struct {
str: []const u8, str: []const u8,
freeme: bool, freeme: bool,
@ -24,7 +31,10 @@ pub const FreeOrNot = struct {
} }
}; };
pub fn fio2strAllocOrNot(o: fio.FIOBJ, a: std.mem.Allocator, always_alloc: bool) !FreeOrNot { /// Used internally: convert a FIO object into its string representation.
/// Depending on the type of the object, a buffer will be created. Hence a
/// FreeOrNot type is used as the return type.
pub fn fio2strAllocOrNot(a: std.mem.Allocator, o: fio.FIOBJ, always_alloc: bool) !FreeOrNot {
if (o == 0) return .{ .str = "null", .freeme = false }; if (o == 0) return .{ .str = "null", .freeme = false };
if (o == fio.FIOBJ_INVALID) return .{ .str = "invalid", .freeme = false }; if (o == fio.FIOBJ_INVALID) return .{ .str = "invalid", .freeme = false };
return switch (fio.fiobj_type(o)) { return switch (fio.fiobj_type(o)) {
@ -38,6 +48,8 @@ pub fn fio2strAllocOrNot(o: fio.FIOBJ, a: std.mem.Allocator, always_alloc: bool)
else => .{ .str = "unknown_type", .freeme = false }, else => .{ .str = "unknown_type", .freeme = false },
}; };
} }
/// Used internally: convert a zig slice into a FIO string.
pub fn str2fio(s: []const u8) fio.fio_str_info_s { pub fn str2fio(s: []const u8) fio.fio_str_info_s {
return .{ return .{
.data = toCharPtr(s), .data = toCharPtr(s),
@ -46,6 +58,7 @@ pub fn str2fio(s: []const u8) fio.fio_str_info_s {
}; };
} }
/// Used internally: convert a zig slice into a C pointer
pub fn toCharPtr(s: []const u8) [*c]u8 { pub fn toCharPtr(s: []const u8) [*c]u8 {
return @as([*c]u8, @ptrFromInt(@intFromPtr(s.ptr))); return @as([*c]u8, @ptrFromInt(@intFromPtr(s.ptr)));
} }
@ -54,7 +67,8 @@ pub fn toCharPtr(s: []const u8) [*c]u8 {
// JSON helpers // JSON helpers
// //
/// provide your own buf, NOT mutex-protected! /// Concenience: format an arbitrary value into a JSON string buffer.
/// Provide your own buf; this function is NOT mutex-protected!
pub fn stringifyBuf( pub fn stringifyBuf(
buffer: []u8, buffer: []u8,
value: anytype, value: anytype,
@ -68,47 +82,3 @@ pub fn stringifyBuf(
return null; return null;
} }
} }
// deprecated:
// 1MB JSON buffer
// var jsonbuf: [1024 * 1024]u8 = undefined;
// var mutex: std.Thread.Mutex = .{};
// use default 1MB buffer, mutex-protected
// pub fn stringify(
// value: anytype,
// options: std.json.StringifyOptions,
// ) ?[]const u8 {
// mutex.lock();
// defer mutex.unlock();
// var fba = std.heap.FixedBufferAllocator.init(&jsonbuf);
// var string = std.ArrayList(u8).init(fba.allocator());
// if (std.json.stringify(value, options, string.writer())) {
// return string.items;
// } else |_| { // error
// return null;
// }
// }
// use default 1MB buffer, mutex-protected
// pub fn stringifyArrayList(
// comptime T: anytype,
// list: *std.ArrayList(T),
// options: std.json.StringifyOptions,
// ) !?[]const u8 {
// mutex.lock();
// defer mutex.unlock();
// var fba = std.heap.FixedBufferAllocator.init(&jsonbuf);
// var string = std.ArrayList(u8).init(fba.allocator());
// var writer = string.writer();
// try writer.writeByte('[');
// var first: bool = true;
// for (list.items) |user| {
// if (!first) try writer.writeByte(',');
// first = false;
// try std.json.stringify(user, options, string.writer());
// }
// try writer.writeByte(']');
// return string.items;
// }

View file

@ -3,10 +3,15 @@ const zap = @import("zap.zig");
const fio = @import("fio.zig"); const fio = @import("fio.zig");
const util = @import("util.zig"); const util = @import("util.zig");
/// The Handle type used for WebSocket connections. Do not mess with this.
pub const WsHandle = ?*fio.ws_s; pub const WsHandle = ?*fio.ws_s;
/// WebSocket Handler. Pass in a Context type and it will give you a struct that
/// contains all the types and functions you need. See the websocket example
/// for more details.
pub fn Handler(comptime ContextType: type) type { pub fn Handler(comptime ContextType: type) type {
return struct { return struct {
/// OnMessage Callback on a websocket /// OnMessage Callback on a websocket, type.
pub const WsOnMessageFn = *const fn ( pub const WsOnMessageFn = *const fn (
/// user-provided context, passed in from websocketHttpUpgrade() /// user-provided context, passed in from websocketHttpUpgrade()
context: ?*ContextType, context: ?*ContextType,
@ -18,14 +23,16 @@ pub fn Handler(comptime ContextType: type) type {
is_text: bool, is_text: bool,
) void; ) void;
/// Callback 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) void; pub const WsOnCloseFn = *const fn (context: ?*ContextType, uuid: isize) void;
/// A websocket callback function. 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) void; pub const WsFn = *const fn (context: ?*ContextType, handle: WsHandle) void;
/// Websocket connection handler creation settings. Provide the callbacks you need,
/// and an optional context.
pub const WebSocketSettings = struct { pub const WebSocketSettings = struct {
/// on_message(context, handle, message, is_text) /// on_message(context, handle, message, is_text)
on_message: ?WsOnMessageFn = null, on_message: ?WsOnMessageFn = null,
@ -102,12 +109,13 @@ pub fn Handler(comptime ContextType: type) type {
} }
} }
const WebSocketError = error{ pub const WebSocketError = error{
WriteError, WriteError,
UpgradeError, UpgradeError,
SubscribeError, SubscribeError,
}; };
/// Write to the websocket identified by the handle.
pub inline fn write(handle: WsHandle, message: []const u8, is_text: bool) WebSocketError!void { pub inline fn write(handle: WsHandle, message: []const u8, is_text: bool) WebSocketError!void {
if (fio.websocket_write( if (fio.websocket_write(
handle, handle,
@ -118,21 +126,26 @@ pub fn Handler(comptime ContextType: type) type {
} }
} }
/// The context pointer is stored in facilio's udata pointer. Use
/// this function to turn that pointer into a pointer to your
/// ContextType.
pub fn udataToContext(udata: *anyopaque) *ContextType { pub fn udataToContext(udata: *anyopaque) *ContextType {
return @as(*ContextType, @ptrCast(@alignCast(udata))); return @as(*ContextType, @ptrCast(@alignCast(udata)));
} }
/// Close the websocket connection.
pub inline fn close(handle: WsHandle) void { pub inline fn close(handle: WsHandle) void {
fio.websocket_close(handle); fio.websocket_close(handle);
} }
/// Settings for publishing a message in a channel.
const PublishArgs = struct { const PublishArgs = struct {
channel: []const u8, channel: []const u8,
message: []const u8, message: []const u8,
is_json: bool = false, is_json: bool = false,
}; };
/// publish a message in a channel /// Publish a message in a channel.
pub inline fn publish(args: PublishArgs) void { pub inline fn publish(args: PublishArgs) void {
fio.fio_publish(.{ fio.fio_publish(.{
.channel = util.str2fio(args.channel), .channel = util.str2fio(args.channel),
@ -141,12 +154,19 @@ pub fn Handler(comptime ContextType: type) type {
}); });
} }
/// Type for callback on subscription message.
pub const SubscriptionOnMessageFn = *const fn (context: ?*ContextType, handle: WsHandle, channel: []const u8, message: []const u8) void; pub const SubscriptionOnMessageFn = *const fn (context: ?*ContextType, handle: WsHandle, channel: []const u8, message: []const u8) void;
/// Type for callback on unsubscribe message.
pub const SubscriptionOnUnsubscribeFn = *const fn (context: ?*ContextType) void; pub const SubscriptionOnUnsubscribeFn = *const fn (context: ?*ContextType) void;
/// Settings for subscribing to a channel.
pub const SubscribeArgs = struct { pub const SubscribeArgs = struct {
/// channel name
channel: []const u8, channel: []const u8,
/// on message callback
on_message: ?SubscriptionOnMessageFn = null, on_message: ?SubscriptionOnMessageFn = null,
/// on unsubscribe callback
on_unsubscribe: ?SubscriptionOnUnsubscribeFn = null, on_unsubscribe: ?SubscriptionOnUnsubscribeFn = null,
/// this is not wrapped nicely yet /// this is not wrapped nicely yet
match: fio.fio_match_fn = null, match: fio.fio_match_fn = null,
@ -162,9 +182,11 @@ pub fn Handler(comptime ContextType: type) type {
/// above ~32Kb might be assumed to be binary rather than tested. force_binary has /// above ~32Kb might be assumed to be binary rather than tested. force_binary has
/// precedence over force_text. /// precedence over force_text.
force_text: bool = false, force_text: bool = false,
/// your provided arbitrary context
context: ?*ContextType = null, context: ?*ContextType = null,
}; };
/// Subscribe to a channel.
/// Returns a subscription ID on success and 0 on failure. /// Returns a subscription ID on success and 0 on failure.
/// we copy the pointer so make sure the struct stays valid. /// we copy the pointer so make sure the struct stays valid.
/// we need it to look up the ziggified callbacks. /// we need it to look up the ziggified callbacks.

View file

@ -2,18 +2,28 @@
// or maybe let's just make it zap directly... // or maybe let's just make it zap directly...
const std = @import("std"); const std = @import("std");
const fio = @import("fio.zig");
/// The facilio C API. No need to use this.
pub const fio = @import("fio.zig");
/// Server-Side TLS function wrapper /// Server-Side TLS function wrapper
pub const Tls = @import("tls.zig"); pub const Tls = @import("tls.zig");
pub usingnamespace @import("fio.zig"); // pub usingnamespace @import("fio.zig");
pub usingnamespace @import("endpoint.zig"); pub usingnamespace @import("endpoint.zig");
pub usingnamespace @import("util.zig"); pub usingnamespace @import("util.zig");
pub usingnamespace @import("http.zig"); pub usingnamespace @import("http.zig");
pub usingnamespace @import("mustache.zig"); pub usingnamespace @import("mustache.zig");
pub usingnamespace @import("http_auth.zig"); pub usingnamespace @import("http_auth.zig");
/// Middleware support.
/// Contains a special Listener and a Handler struct that support chaining
/// requests handlers, with an optional stop once a handler indicates it
/// processed the request. Also sports an EndpointHandler for using regular zap
/// Endpoints as Handlers.
pub const Middleware = @import("middleware.zig"); pub const Middleware = @import("middleware.zig");
/// Websocket API
pub const WebSockets = @import("websockets.zig"); pub const WebSockets = @import("websockets.zig");
pub const Log = @import("log.zig"); pub const Log = @import("log.zig");
@ -40,16 +50,20 @@ pub fn stop() void {
fio.fio_stop(); fio.fio_stop();
} }
/// 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 {
if (_debug) { if (_debug) {
std.debug.print("[zap] - " ++ fmt, args); std.debug.print("[zap] - " ++ fmt, args);
} }
} }
/// Enable zap debug logging
pub fn enableDebugLog() void { pub fn enableDebugLog() void {
_debug = true; _debug = true;
} }
/// start Zap with debug logging on
pub fn startWithLogging(args: fio.fio_start_args) void { pub fn startWithLogging(args: fio.fio_start_args) void {
debug = true; debug = true;
fio.fio_start(args); fio.fio_start(args);
@ -70,13 +84,17 @@ pub const HttpError = error{
SendFile, SendFile,
}; };
/// Http Content Type enum.
/// Needs some love.
pub const ContentType = enum { pub const ContentType = enum {
TEXT, TEXT,
HTML, HTML,
JSON, JSON,
// TODO: more content types
}; };
pub const SimpleRequest = struct { /// HttpRequest passed to request callback functions.
pub const Request = struct {
path: ?[]const u8, path: ?[]const u8,
query: ?[]const u8, query: ?[]const u8,
body: ?[]const u8, body: ?[]const u8,
@ -90,7 +108,7 @@ pub const SimpleRequest = struct {
/// NEVER touch this field!!!! /// NEVER touch this field!!!!
/// use markAsFinished() and isFinished() instead /// use markAsFinished() and isFinished() instead
/// this is a hack: the listener will put a pointer to this into the udata /// this is a hack: the listener will put a pointer to this into the udata
/// field of `h`. So copies of the SimpleRequest will all have way to the /// field of `h`. So copies of the Request will all have way to the
/// same instance of this field. /// same instance of this field.
_is_finished_request_global: bool, _is_finished_request_global: bool,
/// NEVER touch this field!!!! /// NEVER touch this field!!!!
@ -103,22 +121,27 @@ pub const SimpleRequest = struct {
const Self = @This(); const Self = @This();
/// mark the current request as finished. Important for middleware-style
/// request handler chaining. Called when sending a body, redirecting, etc.
pub fn markAsFinished(self: *const Self, finished: bool) void { pub fn markAsFinished(self: *const Self, finished: bool) void {
// we might be a copy // we might be a copy
self._is_finished.* = finished; self._is_finished.* = finished;
} }
/// tell whether request processing has finished. (e.g. response sent,
/// redirected, ...)
pub fn isFinished(self: *const Self) bool { pub fn isFinished(self: *const Self) bool {
// we might be a copy // we might be a copy
return self._is_finished.*; return self._is_finished.*;
} }
/// if you absolutely must, you can set any context here /// if you absolutely must, you can set any context on the request here
// (note, this line is linked to from the readme) // (note, this line is linked to from the readme) -- TODO: sync
pub fn setUserContext(self: *const Self, context: *anyopaque) void { pub fn setUserContext(self: *const Self, context: *anyopaque) void {
self._user_context.*.user_context = context; self._user_context.*.user_context = context;
} }
/// get the associated user context of the request.
pub fn getUserContext(self: *const Self, comptime Context: type) ?*Context { pub fn getUserContext(self: *const Self, comptime Context: type) ?*Context {
if (self._user_context.*.user_context) |ptr| { if (self._user_context.*.user_context) |ptr| {
return @as(*Context, @ptrCast(@alignCast(ptr))); return @as(*Context, @ptrCast(@alignCast(ptr)));
@ -127,6 +150,7 @@ pub const SimpleRequest = struct {
} }
} }
/// Tries to send an error stack trace.
pub fn sendError(self: *const Self, err: anyerror, errorcode_num: usize) void { pub fn sendError(self: *const Self, err: anyerror, errorcode_num: usize) void {
// TODO: query accept headers // TODO: query accept headers
if (self._internal_sendError(err, errorcode_num)) { if (self._internal_sendError(err, errorcode_num)) {
@ -135,6 +159,8 @@ pub const SimpleRequest = struct {
self.sendBody(@errorName(err)) catch return; self.sendBody(@errorName(err)) catch return;
} }
} }
/// Used internally. Probably does not need to be public.
pub fn _internal_sendError(self: *const Self, err: anyerror, errorcode_num: usize) !void { pub fn _internal_sendError(self: *const Self, err: anyerror, errorcode_num: usize) !void {
// TODO: query accept headers // TODO: query accept headers
// TODO: let's hope 20k is enough. Maybe just really allocate here // TODO: let's hope 20k is enough. Maybe just really allocate here
@ -151,16 +177,18 @@ pub const SimpleRequest = struct {
try self.sendBody(string.items); try self.sendBody(string.items);
} }
/// Send body.
pub fn sendBody(self: *const Self, body: []const u8) HttpError!void { pub fn sendBody(self: *const Self, body: []const u8) HttpError!void {
const ret = fio.http_send_body(self.h, @as( const ret = fio.http_send_body(self.h, @as(
*anyopaque, *anyopaque,
@ptrFromInt(@intFromPtr(body.ptr)), @ptrFromInt(@intFromPtr(body.ptr)),
), body.len); ), body.len);
debug("SimpleRequest.sendBody(): ret = {}\n", .{ret}); debug("Request.sendBody(): ret = {}\n", .{ret});
if (ret == -1) return error.HttpSendBody; if (ret == -1) return error.HttpSendBody;
self.markAsFinished(true); self.markAsFinished(true);
} }
/// Set content type and send json buffer.
pub fn sendJson(self: *const Self, json: []const u8) HttpError!void { pub fn sendJson(self: *const Self, json: []const u8) HttpError!void {
if (self.setContentType(.JSON)) { if (self.setContentType(.JSON)) {
if (fio.http_send_body(self.h, @as( if (fio.http_send_body(self.h, @as(
@ -171,6 +199,7 @@ pub const SimpleRequest = struct {
} else |err| return err; } else |err| return err;
} }
/// Set content type.
pub fn setContentType(self: *const Self, c: ContentType) HttpError!void { pub fn setContentType(self: *const Self, c: ContentType) HttpError!void {
const s = switch (c) { const s = switch (c) {
.TEXT => "text/plain", .TEXT => "text/plain",
@ -181,7 +210,7 @@ pub const SimpleRequest = struct {
return self.setHeader("content-type", s); return self.setHeader("content-type", s);
} }
// redirect to path with status code 302 by default /// redirect to path with status code 302 by default
pub fn redirectTo(self: *const Self, path: []const u8, code: ?http.StatusCode) HttpError!void { pub fn redirectTo(self: *const Self, path: []const u8, code: ?http.StatusCode) HttpError!void {
self.setStatus(if (code) |status| status else .found); self.setStatus(if (code) |status| status else .found);
try self.setHeader("Location", path); try self.setHeader("Location", path);
@ -242,6 +271,7 @@ pub const SimpleRequest = struct {
return util.fio2str(fio.fiobj_hash_get(self.h.*.headers, hname)); return util.fio2str(fio.fiobj_hash_get(self.h.*.headers, hname));
} }
/// Set header.
pub fn setHeader(self: *const Self, name: []const u8, value: []const u8) HttpError!void { pub fn setHeader(self: *const Self, name: []const u8, value: []const u8) HttpError!void {
const hname: fio.fio_str_info_s = .{ const hname: fio.fio_str_info_s = .{
.data = util.toCharPtr(name), .data = util.toCharPtr(name),
@ -261,7 +291,7 @@ pub const SimpleRequest = struct {
// 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) {
std.debug.print("***************** zap.zig:145\n", .{}); std.debug.print("***************** zap.zig:274\n", .{});
} }
debug("setHeader: ret = {}\n", .{ret}); debug("setHeader: ret = {}\n", .{ret});
@ -269,10 +299,12 @@ pub const SimpleRequest = struct {
return error.HttpSetHeader; return error.HttpSetHeader;
} }
/// Set status by numeric value.
pub fn setStatusNumeric(self: *const Self, status: usize) void { pub fn setStatusNumeric(self: *const Self, status: usize) void {
self.h.*.status = status; self.h.*.status = status;
} }
/// Set status by enum.
pub fn setStatus(self: *const Self, status: http.StatusCode) void { pub fn setStatus(self: *const Self, status: http.StatusCode) void {
self.h.*.status = @as(usize, @intCast(@intFromEnum(status))); self.h.*.status = @as(usize, @intCast(@intFromEnum(status)));
} }
@ -315,11 +347,12 @@ pub const SimpleRequest = struct {
fio.http_parse_query(self.h); fio.http_parse_query(self.h);
} }
/// Parse received cookie headers
pub fn parseCookies(self: *const Self, url_encoded: bool) void { pub fn parseCookies(self: *const Self, url_encoded: bool) void {
fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0); fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0);
} }
// Set a response cookie /// Set a response cookie
pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void { pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void {
const c: fio.http_cookie_args_s = .{ const c: fio.http_cookie_args_s = .{
.name = util.toCharPtr(args.name), .name = util.toCharPtr(args.name),
@ -340,6 +373,7 @@ pub const SimpleRequest = struct {
// if(fio.http_set_cookie(...) == -1) // if(fio.http_set_cookie(...) == -1)
// instead of capturing it in `ret` first and then checking it, // instead of capturing it in `ret` first and then checking it,
// all ReleaseXXX builds return an error! // all ReleaseXXX builds return an error!
// 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) {
std.log.err("fio.http_set_cookie returned: {}\n", .{ret}); std.log.err("fio.http_set_cookie returned: {}\n", .{ret});
@ -347,8 +381,8 @@ pub const SimpleRequest = struct {
} }
} }
/// Returns named cookie. Works like getParamStr() /// Returns named cookie. Works like getParamStr().
pub fn getCookieStr(self: *const Self, name: []const u8, a: std.mem.Allocator, always_alloc: bool) !?util.FreeOrNot { pub fn getCookieStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot {
if (self.h.*.cookies == 0) return null; if (self.h.*.cookies == 0) return null;
const key = fio.fiobj_str_new(name.ptr, name.len); const key = fio.fiobj_str_new(name.ptr, name.len);
defer fio.fiobj_free_wrapped(key); defer fio.fiobj_free_wrapped(key);
@ -356,10 +390,10 @@ pub const SimpleRequest = struct {
if (value == fio.FIOBJ_INVALID) { if (value == fio.FIOBJ_INVALID) {
return null; return null;
} }
return try util.fio2strAllocOrNot(value, a, always_alloc); return try util.fio2strAllocOrNot(a, value, always_alloc);
} }
/// Returns the number of parameters after parsing. /// Returns the number of cookies after parsing.
/// ///
/// Parse with parseCookies() /// Parse with parseCookies()
pub fn getCookiesCount(self: *const Self) isize { pub fn getCookiesCount(self: *const Self) isize {
@ -440,11 +474,11 @@ pub const SimpleRequest = struct {
// this is thread-safe, guaranteed by fio // this is thread-safe, guaranteed by fio
const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop(); const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
ctx.params.append(.{ ctx.params.append(.{
.key = util.fio2strAllocOrNot(fiobj_key, ctx.allocator, ctx.always_alloc) catch |err| { .key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.always_alloc) catch |err| {
ctx.last_error = err; ctx.last_error = err;
return -1; return -1;
}, },
.value = util.fio2strAllocOrNot(fiobj_value, ctx.allocator, ctx.always_alloc) catch |err| { .value = util.fio2strAllocOrNot(ctx.allocator, fiobj_value, ctx.always_alloc) catch |err| {
ctx.last_error = err; ctx.last_error = err;
return -1; return -1;
}, },
@ -493,11 +527,11 @@ pub const SimpleRequest = struct {
// this is thread-safe, guaranteed by fio // this is thread-safe, guaranteed by fio
const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop(); const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
ctx.params.append(.{ ctx.params.append(.{
.key = util.fio2strAllocOrNot(fiobj_key, ctx.allocator, ctx.dupe_strings) catch |err| { .key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.dupe_strings) catch |err| {
ctx.last_error = err; ctx.last_error = err;
return -1; return -1;
}, },
.value = Fiobj2HttpParam(fiobj_value, ctx.allocator, ctx.dupe_strings) catch |err| { .value = Fiobj2HttpParam(ctx.allocator, fiobj_value, ctx.dupe_strings) catch |err| {
ctx.last_error = err; ctx.last_error = err;
return -1; return -1;
}, },
@ -524,7 +558,7 @@ pub const SimpleRequest = struct {
/// ///
/// Requires parseBody() and/or parseQuery() have been called. /// Requires parseBody() and/or parseQuery() have been called.
/// The returned string needs to be deinited with .deinit() /// The returned string needs to be deinited with .deinit()
pub fn getParamStr(self: *const Self, name: []const u8, a: std.mem.Allocator, always_alloc: bool) !?util.FreeOrNot { pub fn getParamStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot {
if (self.h.*.params == 0) return null; if (self.h.*.params == 0) return null;
const key = fio.fiobj_str_new(name.ptr, name.len); const key = fio.fiobj_str_new(name.ptr, name.len);
defer fio.fiobj_free_wrapped(key); defer fio.fiobj_free_wrapped(key);
@ -532,7 +566,7 @@ pub const SimpleRequest = struct {
if (value == fio.FIOBJ_INVALID) { if (value == fio.FIOBJ_INVALID) {
return null; return null;
} }
return try util.fio2strAllocOrNot(value, a, always_alloc); return try util.fio2strAllocOrNot(a, value, always_alloc);
} }
}; };
@ -546,6 +580,7 @@ pub const HttpParamStrKV = struct {
} }
}; };
/// List of key value pairs of Http param strings.
pub const HttpParamStrKVList = struct { pub const HttpParamStrKVList = struct {
items: []HttpParamStrKV, items: []HttpParamStrKV,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
@ -557,6 +592,7 @@ pub const HttpParamStrKVList = struct {
} }
}; };
/// List of key value pairs of Http params (might be of different types).
pub const HttpParamKVList = struct { pub const HttpParamKVList = struct {
items: []HttpParamKV, items: []HttpParamKV,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
@ -568,6 +604,7 @@ pub const HttpParamKVList = struct {
} }
}; };
/// Enum for HttpParam tagged union
pub const HttpParamValueType = enum { pub const HttpParamValueType = enum {
// Null, // Null,
Bool, Bool,
@ -579,6 +616,7 @@ pub const HttpParamValueType = enum {
Array_Binfile, Array_Binfile,
}; };
/// Tagged union holding a typed Http param
pub const HttpParam = union(HttpParamValueType) { pub const HttpParam = union(HttpParamValueType) {
Bool: bool, Bool: bool,
Int: isize, Int: isize,
@ -593,6 +631,7 @@ pub const HttpParam = union(HttpParamValueType) {
Array_Binfile: std.ArrayList(HttpParamBinaryFile), Array_Binfile: std.ArrayList(HttpParamBinaryFile),
}; };
/// Key value pair of one typed Http param
pub const HttpParamKV = struct { pub const HttpParamKV = struct {
key: util.FreeOrNot, key: util.FreeOrNot,
value: ?HttpParam, value: ?HttpParam,
@ -607,6 +646,7 @@ pub const HttpParamKV = struct {
} }
}; };
/// Struct representing an uploaded file.
pub const HttpParamBinaryFile = struct { pub const HttpParamBinaryFile = struct {
/// file contents /// file contents
data: ?[]const u8 = null, data: ?[]const u8 = null,
@ -615,6 +655,7 @@ pub const HttpParamBinaryFile = struct {
/// filename /// filename
filename: ?[]const u8 = null, filename: ?[]const u8 = null,
/// format function for printing file upload data
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) std.os.WriteError!void { pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) std.os.WriteError!void {
const d = value.data orelse "\\0"; const d = value.data orelse "\\0";
const m = value.mimetype orelse "null"; const m = value.mimetype orelse "null";
@ -735,14 +776,15 @@ fn parseBinfilesFrom(a: std.mem.Allocator, o: fio.FIOBJ) !HttpParam {
} }
} }
pub fn Fiobj2HttpParam(o: fio.FIOBJ, a: std.mem.Allocator, dupe_string: bool) !?HttpParam { /// Parse FIO object into a typed Http param. Supports file uploads.
pub fn Fiobj2HttpParam(a: std.mem.Allocator, o: fio.FIOBJ, dupe_string: bool) !?HttpParam {
return switch (fio.fiobj_type(o)) { return switch (fio.fiobj_type(o)) {
fio.FIOBJ_T_NULL => null, fio.FIOBJ_T_NULL => null,
fio.FIOBJ_T_TRUE => .{ .Bool = true }, fio.FIOBJ_T_TRUE => .{ .Bool = true },
fio.FIOBJ_T_FALSE => .{ .Bool = false }, fio.FIOBJ_T_FALSE => .{ .Bool = false },
fio.FIOBJ_T_NUMBER => .{ .Int = fio.fiobj_obj2num(o) }, fio.FIOBJ_T_NUMBER => .{ .Int = fio.fiobj_obj2num(o) },
fio.FIOBJ_T_FLOAT => .{ .Float = fio.fiobj_obj2float(o) }, fio.FIOBJ_T_FLOAT => .{ .Float = fio.fiobj_obj2float(o) },
fio.FIOBJ_T_STRING => .{ .String = try util.fio2strAllocOrNot(o, a, dupe_string) }, fio.FIOBJ_T_STRING => .{ .String = try util.fio2strAllocOrNot(a, o, dupe_string) },
fio.FIOBJ_T_ARRAY => { fio.FIOBJ_T_ARRAY => {
return .{ .Unsupported = null }; return .{ .Unsupported = null };
}, },
@ -754,6 +796,7 @@ pub fn Fiobj2HttpParam(o: fio.FIOBJ, a: std.mem.Allocator, dupe_string: bool) !?
}; };
} }
/// Args for setting a cookie
pub const CookieArgs = struct { pub const CookieArgs = struct {
name: []const u8, name: []const u8,
value: []const u8, value: []const u8,
@ -765,25 +808,31 @@ pub const CookieArgs = struct {
http_only: bool = true, http_only: bool = true,
}; };
pub const HttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void; /// Used internally: facilio Http request callback function type
pub const SimpleHttpRequestFn = *const fn (SimpleRequest) void; pub const FioHttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void;
/// websocket connection upgrade /// Zap Http request callback function type.
pub const HttpRequestFn = *const fn (Request) void;
/// websocket connection upgrade callback type
/// fn(request, targetstring) /// fn(request, targetstring)
pub const SimpleHttpUpgradeFn = *const fn (r: SimpleRequest, target_protocol: []const u8) 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
/// struct. /// HttpFinishSetting struct.
pub const SimpleHttpFinishSettings = [*c]fio.struct_http_settings_s; pub const HttpFinishSettings = [*c]fio.struct_http_settings_s;
pub const SimpleHttpFinishFn = *const fn (SimpleHttpFinishSettings) void;
pub const SimpleHttpListenerSettings = struct { /// Http finish callback type
pub const HttpFinishFn = *const fn (HttpFinishSettings) void;
/// Listener settings
pub const HttpListenerSettings = struct {
port: usize, port: usize,
interface: [*c]const u8 = null, interface: [*c]const u8 = null,
on_request: ?SimpleHttpRequestFn, on_request: ?HttpRequestFn,
on_response: ?SimpleHttpRequestFn = null, on_response: ?HttpRequestFn = null,
on_upgrade: ?SimpleHttpUpgradeFn = null, on_upgrade: ?HttpUpgradeFn = null,
on_finish: ?SimpleHttpFinishFn = null, on_finish: ?HttpFinishFn = null,
// provide any pointer in there for "user data". it will be passed pack in // provide any pointer in there for "user data". it will be passed pack in
// on_finish()'s copy of the struct_http_settings_s // on_finish()'s copy of the struct_http_settings_s
udata: ?*anyopaque = null, udata: ?*anyopaque = null,
@ -797,26 +846,26 @@ pub const SimpleHttpListenerSettings = struct {
tls: ?Tls = null, tls: ?Tls = null,
}; };
pub const SimpleHttpListener = struct { /// Http listener
settings: SimpleHttpListenerSettings, pub const HttpListener = struct {
settings: HttpListenerSettings,
const Self = @This(); const Self = @This();
var the_one_and_only_listener: ?*SimpleHttpListener = null; var the_one_and_only_listener: ?*HttpListener = null;
pub fn init(settings: SimpleHttpListenerSettings) Self { /// Create a listener
pub fn init(settings: HttpListenerSettings) Self {
std.debug.assert(settings.on_request != null); std.debug.assert(settings.on_request != null);
return .{ return .{
.settings = settings, .settings = settings,
}; };
} }
// on_upgrade: ?*const fn ([*c]fio.http_s, [*c]u8, usize) callconv(.C) void = null, // we could make it dynamic by passing a HttpListener via udata
// on_finish: ?*const fn ([*c]fio.struct_http_settings_s) callconv(.C) void = null, /// Used internally: the listener's facilio request callback
// we could make it dynamic by passing a SimpleHttpListener via udata
pub fn theOneAndOnlyRequestCallBack(r: [*c]fio.http_s) callconv(.C) void { pub fn theOneAndOnlyRequestCallBack(r: [*c]fio.http_s) callconv(.C) void {
if (the_one_and_only_listener) |l| { if (the_one_and_only_listener) |l| {
var req: SimpleRequest = .{ var req: Request = .{
.path = util.fio2str(r.*.path), .path = util.fio2str(r.*.path),
.query = util.fio2str(r.*.query), .query = util.fio2str(r.*.query),
.body = util.fio2str(r.*.body), .body = util.fio2str(r.*.body),
@ -827,7 +876,7 @@ pub const SimpleHttpListener = struct {
}; };
req._is_finished = &req._is_finished_request_global; req._is_finished = &req._is_finished_request_global;
var user_context: SimpleRequest.UserContext = .{}; var user_context: Request.UserContext = .{};
req._user_context = &user_context; req._user_context = &user_context;
req.markAsFinished(false); req.markAsFinished(false);
@ -839,9 +888,10 @@ pub const SimpleHttpListener = struct {
} }
} }
/// Used internally: the listener's facilio response callback
pub fn theOneAndOnlyResponseCallBack(r: [*c]fio.http_s) callconv(.C) void { pub fn theOneAndOnlyResponseCallBack(r: [*c]fio.http_s) callconv(.C) void {
if (the_one_and_only_listener) |l| { if (the_one_and_only_listener) |l| {
var req: SimpleRequest = .{ var req: Request = .{
.path = util.fio2str(r.*.path), .path = util.fio2str(r.*.path),
.query = util.fio2str(r.*.query), .query = util.fio2str(r.*.query),
.body = util.fio2str(r.*.body), .body = util.fio2str(r.*.body),
@ -852,16 +902,17 @@ pub const SimpleHttpListener = struct {
}; };
req._is_finished = &req._is_finished_request_global; req._is_finished = &req._is_finished_request_global;
var user_context: SimpleRequest.UserContext = .{}; var user_context: Request.UserContext = .{};
req._user_context = &user_context; req._user_context = &user_context;
l.settings.on_response.?(req); l.settings.on_response.?(req);
} }
} }
/// Used internally: the listener's facilio upgrade callback
pub fn theOneAndOnlyUpgradeCallBack(r: [*c]fio.http_s, target: [*c]u8, target_len: usize) callconv(.C) void { pub fn theOneAndOnlyUpgradeCallBack(r: [*c]fio.http_s, target: [*c]u8, target_len: usize) callconv(.C) void {
if (the_one_and_only_listener) |l| { if (the_one_and_only_listener) |l| {
var req: SimpleRequest = .{ var req: Request = .{
.path = util.fio2str(r.*.path), .path = util.fio2str(r.*.path),
.query = util.fio2str(r.*.query), .query = util.fio2str(r.*.query),
.body = util.fio2str(r.*.body), .body = util.fio2str(r.*.body),
@ -873,25 +924,27 @@ pub const SimpleHttpListener = struct {
const zigtarget: []u8 = target[0..target_len]; const zigtarget: []u8 = target[0..target_len];
req._is_finished = &req._is_finished_request_global; req._is_finished = &req._is_finished_request_global;
var user_context: SimpleRequest.UserContext = .{}; var user_context: Request.UserContext = .{};
req._user_context = &user_context; req._user_context = &user_context;
l.settings.on_upgrade.?(req, zigtarget); l.settings.on_upgrade.?(req, zigtarget);
} }
} }
/// 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); l.settings.on_finish.?(s);
} }
} }
/// Start listening
pub fn listen(self: *Self) !void { pub fn listen(self: *Self) !void {
var pfolder: [*c]const u8 = null; var pfolder: [*c]const u8 = null;
var pfolder_len: usize = 0; var pfolder_len: usize = 0;
if (self.settings.public_folder) |pf| { if (self.settings.public_folder) |pf| {
debug("SimpleHttpListener.listen(): public folder is {s}\n", .{pf}); debug("HttpListener.listen(): public folder is {s}\n", .{pf});
pfolder_len = pf.len; pfolder_len = pf.len;
pfolder = pf.ptr; pfolder = pf.ptr;
} }
@ -921,15 +974,12 @@ pub const SimpleHttpListener = struct {
// TODO: BUG: without this print/sleep statement, -Drelease* loop forever // TODO: BUG: without this print/sleep statement, -Drelease* loop forever
// in debug2 and debug3 of hello example // in debug2 and debug3 of hello example
// std.debug.print("X\n", .{}); // std.debug.print("X\n", .{});
std.time.sleep(500 * 1000 * 1000); // TODO: still happening?
std.time.sleep(500 * std.time.ns_per_ms);
var portbuf: [100]u8 = undefined; var portbuf: [100]u8 = undefined;
const printed_port = try std.fmt.bufPrintZ(&portbuf, "{d}", .{self.settings.port}); const printed_port = try std.fmt.bufPrintZ(&portbuf, "{d}", .{self.settings.port});
// pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintError![:0]u8 {
// const result = try bufPrint(buf, fmt ++ "\x00", args);
// return result[0 .. result.len - 1 :0];
// }
const ret = fio.http_listen(printed_port.ptr, self.settings.interface, x); const ret = fio.http_listen(printed_port.ptr, self.settings.interface, x);
if (ret == -1) { if (ret == -1) {
return error.ListenError; return error.ListenError;
@ -937,7 +987,7 @@ pub const SimpleHttpListener = struct {
// set ourselves up to handle requests: // set ourselves up to handle requests:
// TODO: do we mind the race condition? // TODO: do we mind the race condition?
// the SimpleHttpRequestFn will check if this is null and not process // the HttpRequestFn will check if this is null and not process
// the request if it isn't set. hence, if started under full load, the // the request if it isn't set. hence, if started under full load, the
// first request(s) might not be serviced, as long as it takes from // first request(s) might not be serviced, as long as it takes from
// fio.http_listen() to here // fio.http_listen() to here
@ -945,73 +995,78 @@ pub const SimpleHttpListener = struct {
} }
}; };
// /// Low-level API
// lower level listening pub const LowLevel = struct {
// /// lower level listening, if you don't want to use a listener but rather use
pub const ListenSettings = struct { /// the listen() function.
on_request: ?*const fn ([*c]fio.http_s) callconv(.C) void = null, pub const ListenSettings = struct {
on_upgrade: ?*const fn ([*c]fio.http_s, [*c]u8, usize) callconv(.C) void = null, on_request: ?FioHttpRequestFn = null,
on_response: ?*const fn ([*c]fio.http_s) callconv(.C) void = null, on_upgrade: ?FioHttpRequestFn = null,
on_finish: ?*const fn ([*c]fio.struct_http_settings_s) callconv(.C) void = null, on_response: ?FioHttpRequestFn = null,
public_folder: ?[]const u8 = null, on_finish: ?FioHttpRequestFn = null,
max_header_size: usize = 32 * 1024, public_folder: ?[]const u8 = null,
max_body_size: usize = 50 * 1024 * 1024, max_header_size: usize = 32 * 1024,
max_clients: isize = 100, max_body_size: usize = 50 * 1024 * 1024,
keepalive_timeout_s: u8 = 5, max_clients: isize = 100,
log: bool = false, keepalive_timeout_s: u8 = 5,
log: bool = false,
const Self = @This(); const Self = @This();
pub fn init() Self { /// Create settings with defaults
return .{}; pub fn init() Self {
return .{};
}
};
/// Low level listen function
pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSettings) ListenError!void {
var pfolder: [*c]const u8 = null;
var pfolder_len: usize = 0;
if (settings.public_folder) |pf| {
pfolder_len = pf.len;
pfolder = pf.ptr;
}
const x: fio.http_settings_s = .{
.on_request = settings.on_request,
.on_upgrade = settings.on_upgrade,
.on_response = settings.on_response,
.on_finish = settings.on_finish,
.udata = null,
.public_folder = pfolder,
.public_folder_length = pfolder_len,
.max_header_size = settings.max_header_size,
.max_body_size = settings.max_body_size,
.max_clients = settings.max_clients,
.tls = null,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
.ws_max_msg_size = settings.ws_max_msg_size,
.timeout = settings.keepalive_timeout_s,
.ws_timeout = 0,
.log = if (settings.log) 1 else 0,
.is_client = 0,
};
// TODO: BUG: without this print/sleep statement, -Drelease* loop forever
// in debug2 and debug3 of hello example
// std.debug.print("X\n", .{});
// TODO: still happening?
std.time.sleep(500 * std.time.ns_per_ms);
if (fio.http_listen(port, interface, x) == -1) {
return error.ListenError;
}
}
/// lower level sendBody
pub fn sendBody(request: [*c]fio.http_s, body: []const u8) HttpError!void {
const ret = fio.http_send_body(request, @as(
*anyopaque,
@ptrFromInt(@intFromPtr(body.ptr)),
), body.len);
debug("sendBody(): ret = {}\n", .{ret});
if (ret != -1) return error.HttpSendBody;
} }
}; };
pub fn listen(port: [*c]const u8, interface: [*c]const u8, settings: ListenSettings) ListenError!void {
var pfolder: [*c]const u8 = null;
var pfolder_len: usize = 0;
if (settings.public_folder) |pf| {
pfolder_len = pf.len;
pfolder = pf.ptr;
}
const x: fio.http_settings_s = .{
.on_request = settings.on_request,
.on_upgrade = settings.on_upgrade,
.on_response = settings.on_response,
.on_finish = settings.on_finish,
.udata = null,
.public_folder = pfolder,
.public_folder_length = pfolder_len,
.max_header_size = settings.max_header_size,
.max_body_size = settings.max_body_size,
.max_clients = settings.max_clients,
.tls = null,
.reserved1 = 0,
.reserved2 = 0,
.reserved3 = 0,
.ws_max_msg_size = settings.ws_max_msg_size,
.timeout = settings.keepalive_timeout_s,
.ws_timeout = 0,
.log = if (settings.log) 1 else 0,
.is_client = 0,
};
// TODO: BUG: without this print/sleep statement, -Drelease* loop forever
// in debug2 and debug3 of hello example
// std.debug.print("X\n", .{});
std.time.sleep(500 * 1000 * 1000);
if (fio.http_listen(port, interface, x) == -1) {
return error.ListenError;
}
}
// lower level sendBody
pub fn sendBody(request: [*c]fio.http_s, body: []const u8) HttpError!void {
const ret = fio.http_send_body(request, @as(
*anyopaque,
@ptrFromInt(@intFromPtr(body.ptr)),
), body.len);
debug("sendBody(): ret = {}\n", .{ret});
if (ret != -1) return error.HttpSendBody;
}

View file

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request(r: zap.SimpleRequest) void { fn on_request(r: zap.Request) void {
r.setStatus(.not_found); r.setStatus(.not_found);
r.sendBody("<html><body><h1>404 - File not found</h1></body></html>") catch return; r.sendBody("<html><body><h1>404 - File not found</h1></body></html>") catch return;
} }
@ -26,7 +26,7 @@ pub fn main() !void {
} }
} }
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = port, .port = port,
.on_request = on_request, .on_request = on_request,
.public_folder = docs_dir, .public_folder = docs_dir,

View file

@ -1,12 +1,12 @@
const std = @import("std"); const std = @import("std");
const zap = @import("zap"); const zap = @import("zap");
fn on_request_minimal(r: zap.SimpleRequest) void { fn on_request_minimal(r: zap.Request) void {
r.sendBody("Hello from ZAP!!!") catch return; r.sendBody("Hello from ZAP!!!") catch return;
} }
pub fn main() !void { pub fn main() !void {
var listener = zap.SimpleHttpListener.init(.{ var listener = zap.HttpListener.init(.{
.port = 3000, .port = 3000,
.on_request = on_request_minimal, .on_request = on_request_minimal,
.log = false, .log = false,