forked from zigzap/zap
-
Notifications
You must be signed in to change notification settings - Fork 0
/
middleware.zig
230 lines (186 loc) · 6.95 KB
/
middleware.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
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 = struct {
user: ?UserMiddleWare.User = null,
session: ?SessionMiddleWare.Session = null,
};
// 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.Request, 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 = "[email protected]",
};
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 session 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.Request, 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 and sends a response
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.Request, 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;
std.debug.assert(r.isFinished() == false);
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;
std.debug.assert(r.isFinished() == true);
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,
});
}