forked from
karlseguin.tngl.sh/http.zig
An HTTP/1.1 server for zig
1const std = @import("std");
2const httpz = @import("httpz");
3const Allocator = std.mem.Allocator;
4
5const PORT = 8804;
6
7// This example is very similar to 03_dispatch.zig, but shows how the action
8// state can be a different type than the handler.
9
10pub fn main(init: std.process.Init) !void {
11 const allocator = init.gpa;
12
13 var handler = Handler{};
14 var server = try httpz.Server(*Handler).init(init.io, allocator, .{ .address = .localhost(PORT) }, &handler);
15
16 defer server.deinit();
17
18 // ensures a clean shutdown, finishing off any existing requests
19 // see 09_shutdown.zig for how to to break server.listen with an interrupt
20 defer server.stop();
21
22 var router = try server.router(.{});
23
24 const restricted_route = &RouteData{ .restricted = true };
25
26 // We can register arbitrary data to a route, which we can retrieve
27 // via req.route_data. This is stored as a `*const anyopaque`.
28 router.get("/", index, .{});
29 router.get("/admin", admin, .{ .data = restricted_route });
30
31 std.debug.print("listening http://localhost:{d}/\n", .{PORT});
32
33 // Starts the server, this is blocking.
34 try server.listen();
35}
36
37const Handler = struct {
38 // In example_3, our action type was: httpz.Action(*Handler).
39 // In this example, we've changed it to: httpz.Action(*Env)
40 // This allows our handler to be a general app-wide "state" while our actions
41 // received a request-specific context
42 pub fn dispatch(self: *Handler, action: httpz.Action(*Env), req: *httpz.Request, res: *httpz.Response) !void {
43 const user = (try req.query()).get("auth");
44
45 // RouteData can be anything, but since it's stored as a *const anyopaque
46 // you'll need to restore the type/alignment.
47
48 // (You could also use a per-route handler, or middleware, to achieve
49 // the same thing. Using route data is a bit ugly due to the type erasure
50 // but it can be convenient!).
51 if (req.route_data) |rd| {
52 const route_data: *const RouteData = @ptrCast(@alignCast(rd));
53 if (route_data.restricted and (user == null or user.?.len == 0)) {
54 res.status = 401;
55 res.body = "permission denied";
56 return;
57 }
58 }
59
60 var env = Env{
61 .user = user, // todo: this is not very good security!
62 .handler = self,
63 };
64
65 try action(&env, req, res);
66 }
67};
68
69const RouteData = struct {
70 restricted: bool,
71};
72
73const Env = struct {
74 handler: *Handler,
75 user: ?[]const u8,
76};
77
78fn index(_: *Env, _: *httpz.Request, res: *httpz.Response) !void {
79 res.content_type = .HTML;
80 res.body =
81 \\<!DOCTYPE html>
82 \\ <p>The <code>Handler.dispatch</code> method takes an <code>httpz.Action(*Env)</code>.
83 \\ <p>This allows the handler method to create a request-specific value to pass into actions.
84 \\ <p>For example, dispatch might load a User (using a request header value maybe) and make it available to the action.
85 \\ <p>Goto <a href="/admin?auth=superuser">admin</a> to simulate a (very insecure) authentication.
86 ;
87}
88
89// because of our dispatch method, this can only be called when env.user != null
90fn admin(env: *Env, _: *httpz.Request, res: *httpz.Response) !void {
91 res.body = try std.fmt.allocPrint(res.arena, "Welcome to the admin portal, {s}", .{env.user.?});
92}