mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 23:24:09 +00:00
MIDDLEWARE support
This commit is contained in:
parent
7e6bda1b8a
commit
d656452667
8 changed files with 538 additions and 4 deletions
|
@ -54,6 +54,10 @@ Here's what works:
|
||||||
A convenience authenticator that redirects un-authenticated requests to a
|
A convenience authenticator that redirects un-authenticated requests to a
|
||||||
login page and sends cookies containing session tokens based on
|
login page and sends cookies containing session tokens based on
|
||||||
username/password pairs transmitted via POST request.
|
username/password pairs transmitted via POST request.
|
||||||
|
- **[MIDDLEWARE support](examples/middleware/middleware.zig)**: chain together
|
||||||
|
request handlers in middleware style. Provide custom context structs, totally
|
||||||
|
type-safe, using **[ZIG-CEPTION](doc/zig-ception.md)**. If you come from GO
|
||||||
|
this might appeal to you.
|
||||||
|
|
||||||
|
|
||||||
I'll continue wrapping more of facil.io's functionality and adding stuff to zap
|
I'll continue wrapping more of facil.io's functionality and adding stuff to zap
|
||||||
|
|
|
@ -55,6 +55,7 @@ pub fn build(b: *std.build.Builder) !void {
|
||||||
.{ .name = "websockets", .src = "examples/websockets/websockets.zig" },
|
.{ .name = "websockets", .src = "examples/websockets/websockets.zig" },
|
||||||
.{ .name = "userpass_session", .src = "examples/userpass_session_auth/userpass_session_auth.zig" },
|
.{ .name = "userpass_session", .src = "examples/userpass_session_auth/userpass_session_auth.zig" },
|
||||||
.{ .name = "sendfile", .src = "examples/sendfile/sendfile.zig" },
|
.{ .name = "sendfile", .src = "examples/sendfile/sendfile.zig" },
|
||||||
|
.{ .name = "middleware", .src = "examples/middleware/middleware.zig" },
|
||||||
}) |excfg| {
|
}) |excfg| {
|
||||||
const ex_name = excfg.name;
|
const ex_name = excfg.name;
|
||||||
const ex_src = excfg.src;
|
const ex_src = excfg.src;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.{
|
.{
|
||||||
.name = "zap",
|
.name = "zap",
|
||||||
.version = "0.0.20",
|
.version = "0.0.21",
|
||||||
|
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.@"facil.io" = .{
|
.@"facil.io" = .{
|
||||||
|
|
93
doc/zig-ception.md
Normal file
93
doc/zig-ception.md
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# ZIG-CEPTION!
|
||||||
|
|
||||||
|
In ZAP, we have great zig-ception moment in the [middleware
|
||||||
|
example](../examples/middleware/middleware.zig). But first we need to introduce
|
||||||
|
one key function of `zap.Middleware`: **combining structs at comptime!**
|
||||||
|
|
||||||
|
## Combining structs at runtime
|
||||||
|
|
||||||
|
Here is how it is used in user-code:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
// create a combined context struct
|
||||||
|
const Context = zap.Middleware.MixContexts(.{
|
||||||
|
.{ .name = "?user", .type = UserMiddleWare.User },
|
||||||
|
.{ .name = "?session", .type = SessionMiddleWare.Session },
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
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 to 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 above example.
|
||||||
|
|
||||||
|
`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
|
||||||
|
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
|
||||||
|
a Session struct, and so on. So, which struct should we use in the prototype of
|
||||||
|
the "on_request" callback function? We could just use an `anyopaque` pointer.
|
||||||
|
That would solve the generic function prototype problem. But then everyone
|
||||||
|
implementing such a handler would need to cast this pointer back into - what?
|
||||||
|
Into the same type that the caller of the handler used. It gets really messy
|
||||||
|
when we continue this train of thought.
|
||||||
|
|
||||||
|
So, in ZAP, I opted for one Context type for all request handlers. Since ZAP is
|
||||||
|
a library, it cannot know what your preferred Context struct is. What it should
|
||||||
|
consist of. Therefore, it lets you combine all the structs your and maybe your
|
||||||
|
3rd parties's middleware components require - at comptime! And derive the
|
||||||
|
callback function prototype from that. If you look at the [middleware
|
||||||
|
example](../examples/middleware/middleware.zig), you'll notice, it's really
|
||||||
|
smooth to use.
|
||||||
|
|
||||||
|
**NOTE:** In your contexts, please also use OPTIONALS. They are set null at
|
||||||
|
context creation time. And will aid you in not shooting yourself in the foot
|
||||||
|
when accessing context fields that haven't been initialized - which may happen
|
||||||
|
when the order of your chain of components isn't perfect yet. 😉
|
||||||
|
|
||||||
|
## The zig-ception moment
|
||||||
|
|
||||||
|
Have a look at an excerpt of the example:
|
||||||
|
|
||||||
|
```zig
|
||||||
|
|
||||||
|
// we create a Handler type based on our Context
|
||||||
|
const Handler = zap.Middleware.Handler(Context);
|
||||||
|
|
||||||
|
//
|
||||||
|
// ZIG-CEPTION!!!
|
||||||
|
//
|
||||||
|
// Note how amazing zig is:
|
||||||
|
// - we create the "mixed" context based on the both middleware structs
|
||||||
|
// - we create the handler based on this context
|
||||||
|
// - we create the middleware structs based on the handler
|
||||||
|
// - which needs the context
|
||||||
|
// - which needs the middleware structs
|
||||||
|
// - ZIG-CEPTION!
|
||||||
|
|
||||||
|
// Example user middleware: puts user info into the context
|
||||||
|
const UserMiddleWare = struct {
|
||||||
|
handler: Handler,
|
||||||
|
|
||||||
|
// .. the UserMiddleWare depends on the handler
|
||||||
|
// which depends on the Context
|
||||||
|
// which depends on this UserMiddleWare struct
|
||||||
|
// ZIG-CEPTION!!!
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🤯
|
||||||
|
|
||||||
|
The comments in the code say it all.
|
||||||
|
|
||||||
|
**Isn't ZIG AMAZING?**
|
228
examples/middleware/middleware.zig
Normal file
228
examples/middleware/middleware.zig
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const zap = @import("zap");
|
||||||
|
|
||||||
|
// just a way to share our allocator via callback
|
||||||
|
const SharedAllocator = struct {
|
||||||
|
// static
|
||||||
|
var allocator: std.mem.Allocator = undefined;
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
// just a convenience function
|
||||||
|
pub fn init(a: std.mem.Allocator) void {
|
||||||
|
allocator = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static function we can pass to the listener later
|
||||||
|
pub fn getAllocator() std.mem.Allocator {
|
||||||
|
return allocator;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// create a combined context struct
|
||||||
|
const Context = zap.Middleware.MixContexts(.{
|
||||||
|
.{ .name = "?user", .type = UserMiddleWare.User },
|
||||||
|
.{ .name = "?session", .type = SessionMiddleWare.Session },
|
||||||
|
});
|
||||||
|
|
||||||
|
// we create a Handler type based on our Context
|
||||||
|
const Handler = zap.Middleware.Handler(Context);
|
||||||
|
|
||||||
|
//
|
||||||
|
// ZIG-CEPTION!!!
|
||||||
|
//
|
||||||
|
// Note how amazing zig is:
|
||||||
|
// - we create the "mixed" context based on the both middleware structs
|
||||||
|
// - we create the handler based on this context
|
||||||
|
// - we create the middleware structs based on the handler
|
||||||
|
// - which needs the context
|
||||||
|
// - which needs the middleware structs
|
||||||
|
// - ZIG-CEPTION!
|
||||||
|
|
||||||
|
// Example user middleware: puts user info into the context
|
||||||
|
const UserMiddleWare = struct {
|
||||||
|
handler: Handler,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
// Just some arbitrary struct we want in the per-request context
|
||||||
|
// note: it MUST have all default values!!!
|
||||||
|
// note: it MUST have all default values!!!
|
||||||
|
// note: it MUST have all default values!!!
|
||||||
|
// note: it MUST have all default values!!!
|
||||||
|
// This is so that it can be constructed via .{}
|
||||||
|
// as we can't expect the listener to know how to initialize our context structs
|
||||||
|
const User = struct {
|
||||||
|
name: []const u8 = undefined,
|
||||||
|
email: []const u8 = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(other: ?*Handler) Self {
|
||||||
|
return .{
|
||||||
|
.handler = Handler.init(onRequest, other),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need the handler as a common interface to chain stuff
|
||||||
|
pub fn getHandler(self: *Self) *Handler {
|
||||||
|
return &self.handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// note that the first parameter is of type *Handler, not *Self !!!
|
||||||
|
pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool {
|
||||||
|
|
||||||
|
// this is how we would get our self pointer
|
||||||
|
var self = @fieldParentPtr(Self, "handler", handler);
|
||||||
|
_ = self;
|
||||||
|
|
||||||
|
// do our work: fill in the user field of the context
|
||||||
|
context.user = User{
|
||||||
|
.name = "renerocksai",
|
||||||
|
.email = "supa@secret.org",
|
||||||
|
};
|
||||||
|
|
||||||
|
std.debug.print("\n\nUser Middleware: set user in context {any}\n\n", .{context.user});
|
||||||
|
|
||||||
|
// continue in the chain
|
||||||
|
return handler.handleOther(r, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example session middleware: puts user info into the context
|
||||||
|
const SessionMiddleWare = struct {
|
||||||
|
handler: Handler,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
// Just some arbitrary struct we want in the per-request context
|
||||||
|
// note: it MUST have all default values!!!
|
||||||
|
const Session = struct {
|
||||||
|
info: []const u8 = undefined,
|
||||||
|
token: []const u8 = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(other: ?*Handler) Self {
|
||||||
|
return .{
|
||||||
|
.handler = Handler.init(onRequest, other),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need the handler as a common interface to chain stuff
|
||||||
|
pub fn getHandler(self: *Self) *Handler {
|
||||||
|
return &self.handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// note that the first parameter is of type *Handler, not *Self !!!
|
||||||
|
pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool {
|
||||||
|
// this is how we would get our self pointer
|
||||||
|
var self = @fieldParentPtr(Self, "handler", handler);
|
||||||
|
_ = self;
|
||||||
|
|
||||||
|
context.session = Session{
|
||||||
|
.info = "secret session",
|
||||||
|
.token = "rot47-asdlkfjsaklfdj",
|
||||||
|
};
|
||||||
|
|
||||||
|
std.debug.print("\n\nSessionMiddleware: set session in context {any}\n\n", .{context.session});
|
||||||
|
|
||||||
|
// continue in the chain
|
||||||
|
return handler.handleOther(r, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Example html middleware: handles the request
|
||||||
|
const HtmlMiddleWare = struct {
|
||||||
|
handler: Handler,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn init(other: ?*Handler) Self {
|
||||||
|
return .{
|
||||||
|
.handler = Handler.init(onRequest, other),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need the handler as a common interface to chain stuff
|
||||||
|
pub fn getHandler(self: *Self) *Handler {
|
||||||
|
return &self.handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// note that the first parameter is of type *Handler, not *Self !!!
|
||||||
|
pub fn onRequest(handler: *Handler, r: zap.SimpleRequest, context: *Context) bool {
|
||||||
|
|
||||||
|
// this is how we would get our self pointer
|
||||||
|
var self = @fieldParentPtr(Self, "handler", handler);
|
||||||
|
_ = self;
|
||||||
|
|
||||||
|
std.debug.print("\n\nHtmlMiddleware: handling request with context: {any}\n\n", .{context});
|
||||||
|
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var userFound: bool = false;
|
||||||
|
var sessionFound: bool = false;
|
||||||
|
if (context.user) |user| {
|
||||||
|
userFound = true;
|
||||||
|
if (context.session) |session| {
|
||||||
|
sessionFound = true;
|
||||||
|
|
||||||
|
const message = std.fmt.bufPrint(&buf, "User: {s} / {s}, Session: {s} / {s}", .{
|
||||||
|
user.name,
|
||||||
|
user.email,
|
||||||
|
session.info,
|
||||||
|
session.token,
|
||||||
|
}) catch unreachable;
|
||||||
|
r.setContentType(.TEXT) catch unreachable;
|
||||||
|
r.sendBody(message) catch unreachable;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = std.fmt.bufPrint(&buf, "User info found: {}, session info found: {}", .{ userFound, sessionFound }) catch unreachable;
|
||||||
|
|
||||||
|
r.setContentType(.TEXT) catch unreachable;
|
||||||
|
r.sendBody(message) catch unreachable;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{
|
||||||
|
.thread_safe = true,
|
||||||
|
}){};
|
||||||
|
var allocator = gpa.allocator();
|
||||||
|
SharedAllocator.init(allocator);
|
||||||
|
|
||||||
|
// we create our HTML middleware component that handles the request
|
||||||
|
var htmlHandler = HtmlMiddleWare.init(null);
|
||||||
|
|
||||||
|
// we wrap it in the session Middleware component
|
||||||
|
var sessionHandler = SessionMiddleWare.init(htmlHandler.getHandler());
|
||||||
|
|
||||||
|
// we wrap that in the user Middleware component
|
||||||
|
var userHandler = UserMiddleWare.init(sessionHandler.getHandler());
|
||||||
|
|
||||||
|
// we create a listener with our combined context
|
||||||
|
// and pass it the initial handler: the user handler
|
||||||
|
var listener = try zap.Middleware.Listener(Context).init(
|
||||||
|
.{
|
||||||
|
.on_request = null, // must be null
|
||||||
|
.port = 3000,
|
||||||
|
.log = true,
|
||||||
|
.max_clients = 100000,
|
||||||
|
},
|
||||||
|
userHandler.getHandler(),
|
||||||
|
SharedAllocator.getAllocator,
|
||||||
|
);
|
||||||
|
zap.enableDebugLog();
|
||||||
|
listener.listen() catch |err| {
|
||||||
|
std.debug.print("\nLISTEN ERROR: {any}\n", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.debug.print("Visit me on http://127.0.0.1:3000\n", .{});
|
||||||
|
|
||||||
|
// start worker threads
|
||||||
|
zap.start(.{
|
||||||
|
.threads = 2,
|
||||||
|
.workers = 1,
|
||||||
|
});
|
||||||
|
}
|
201
src/middleware.zig
Normal file
201
src/middleware.zig
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const zap = @import("zap.zig");
|
||||||
|
|
||||||
|
pub const ContextDescriptor = struct {
|
||||||
|
name: []const u8,
|
||||||
|
type: type,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Provide a tuple of structs of type like ContextDescriptor
|
||||||
|
/// a name starting with '?', such as "?user" will be treated as Optional with default `null`.
|
||||||
|
pub fn MixContexts(comptime context_tuple: anytype) type {
|
||||||
|
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) &null else null,
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return @Type(.{
|
||||||
|
.Struct = .{
|
||||||
|
.layout = .Auto,
|
||||||
|
.fields = fields[0..],
|
||||||
|
.decls = &[_]std.builtin.Type.Declaration{},
|
||||||
|
.is_tuple = false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ContextType must implement .init(zap.SimpleRequest)
|
||||||
|
pub fn Handler(comptime ContextType: anytype) type {
|
||||||
|
return struct {
|
||||||
|
other_handler: ?*Self = null,
|
||||||
|
on_request: ?RequestFn = null,
|
||||||
|
|
||||||
|
// will be set
|
||||||
|
allocator: ?std.mem.Allocator = null,
|
||||||
|
|
||||||
|
pub const RequestFn = *const fn (*Self, zap.SimpleRequest, *ContextType) bool;
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn init(on_request: RequestFn, other: ?*Self) Self {
|
||||||
|
return .{
|
||||||
|
.other_handler = other,
|
||||||
|
.on_request = on_request,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// example for handling request
|
||||||
|
pub fn handleOther(self: *Self, r: zap.SimpleRequest, context: *ContextType) bool {
|
||||||
|
// in structs embedding a handler, we'd @fieldParentPtr the first
|
||||||
|
// param to get to the real self
|
||||||
|
|
||||||
|
// First, do our pre-other stuff
|
||||||
|
// ..
|
||||||
|
|
||||||
|
// then call the wrapped thing
|
||||||
|
var other_handler_finished = false;
|
||||||
|
if (self.other_handler) |other_handler| {
|
||||||
|
if (other_handler.on_request) |on_request| {
|
||||||
|
other_handler_finished = on_request(other_handler, r, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now do our post stuff
|
||||||
|
return other_handler_finished;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
InitOnRequestIsNotNull,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RequestAllocatorFn = *const fn () std.mem.Allocator;
|
||||||
|
|
||||||
|
pub fn Listener(comptime ContextType: anytype) type {
|
||||||
|
return struct {
|
||||||
|
listener: zap.SimpleHttpListener = undefined,
|
||||||
|
settings: zap.SimpleHttpListenerSettings,
|
||||||
|
|
||||||
|
// static initial handler
|
||||||
|
var handler: ?*Handler(ContextType) = undefined;
|
||||||
|
// static allocator getter
|
||||||
|
var requestAllocator: ?RequestAllocatorFn = null;
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
/// initialize the middleware handler
|
||||||
|
/// the passed in settings must have on_request set to null
|
||||||
|
///
|
||||||
|
pub fn init(settings: zap.SimpleHttpListenerSettings, initial_handler: *Handler(ContextType), request_alloc: ?RequestAllocatorFn) Error!Self {
|
||||||
|
// override on_request with ourselves
|
||||||
|
if (settings.on_request != null) {
|
||||||
|
return Error.InitOnRequestIsNotNull;
|
||||||
|
}
|
||||||
|
requestAllocator = request_alloc;
|
||||||
|
std.debug.assert(requestAllocator != null);
|
||||||
|
|
||||||
|
var ret: Self = .{
|
||||||
|
.settings = settings,
|
||||||
|
};
|
||||||
|
ret.settings.on_request = onRequest;
|
||||||
|
ret.listener = zap.SimpleHttpListener.init(ret.settings);
|
||||||
|
handler = initial_handler;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listen(self: *Self) !void {
|
||||||
|
try self.listener.listen();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is just a reference implementation
|
||||||
|
pub fn onRequest(r: zap.SimpleRequest) void {
|
||||||
|
// we are the 1st handler in the chain, so we create a context
|
||||||
|
var context: ContextType = .{};
|
||||||
|
|
||||||
|
// handlers might need an allocator
|
||||||
|
// we CAN provide an allocator getter
|
||||||
|
var allocator: ?std.mem.Allocator = null;
|
||||||
|
if (requestAllocator) |foo| {
|
||||||
|
allocator = foo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler) |initial_handler| {
|
||||||
|
initial_handler.allocator = allocator;
|
||||||
|
if (initial_handler.on_request) |on_request| {
|
||||||
|
// we don't care about the return value at the top level
|
||||||
|
_ = on_request(initial_handler, r, &context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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
|
||||||
|
var nonOpts: NonOpts = .{
|
||||||
|
.user = &user,
|
||||||
|
.session = &session,
|
||||||
|
};
|
||||||
|
std.debug.print("nonOpts = {any}\n", .{nonOpts});
|
||||||
|
}
|
11
src/zap.zig
11
src/zap.zig
|
@ -10,6 +10,7 @@ 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");
|
||||||
|
pub const Middleware = @import("middleware.zig");
|
||||||
pub const WebSockets = @import("websockets.zig");
|
pub const WebSockets = @import("websockets.zig");
|
||||||
|
|
||||||
pub const Log = @import("log.zig");
|
pub const Log = @import("log.zig");
|
||||||
|
@ -556,6 +557,7 @@ pub const SimpleHttpListener = struct {
|
||||||
var the_one_and_only_listener: ?*SimpleHttpListener = null;
|
var the_one_and_only_listener: ?*SimpleHttpListener = null;
|
||||||
|
|
||||||
pub fn init(settings: SimpleHttpListenerSettings) Self {
|
pub fn init(settings: SimpleHttpListenerSettings) Self {
|
||||||
|
std.debug.assert(settings.on_request != null);
|
||||||
return .{
|
return .{
|
||||||
.settings = settings,
|
.settings = settings,
|
||||||
};
|
};
|
||||||
|
@ -574,7 +576,11 @@ pub const SimpleHttpListener = struct {
|
||||||
.method = util.fio2str(r.*.method),
|
.method = util.fio2str(r.*.method),
|
||||||
.h = r,
|
.h = r,
|
||||||
};
|
};
|
||||||
l.settings.on_request.?(req);
|
std.debug.assert(l.settings.on_request != null);
|
||||||
|
if (l.settings.on_request) |on_request| {
|
||||||
|
// l.settings.on_request.?(req);
|
||||||
|
on_request(req);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -655,7 +661,8 @@ pub const SimpleHttpListener = struct {
|
||||||
// const result = try bufPrint(buf, fmt ++ "\x00", args);
|
// const result = try bufPrint(buf, fmt ++ "\x00", args);
|
||||||
// return result[0 .. result.len - 1 :0];
|
// return result[0 .. result.len - 1 :0];
|
||||||
// }
|
// }
|
||||||
if (fio.http_listen(printed_port.ptr, self.settings.interface, x) == -1) {
|
var ret = fio.http_listen(printed_port.ptr, self.settings.interface, x);
|
||||||
|
if (ret == -1) {
|
||||||
return error.ListenError;
|
return error.ListenError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,4 +13,4 @@ cookies
|
||||||
websockets
|
websockets
|
||||||
userpass_session
|
userpass_session
|
||||||
sendfile
|
sendfile
|
||||||
|
middleware
|
||||||
|
|
Loading…
Add table
Reference in a new issue