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 = 8802;
6
7// This example demonstrates using a custom Handler. It shows how to have
8// global state (here we show a counter, but it could be a more complex struct
9// including things such as a DB pool) and how to define not found and error
10// handlers.
11
12pub fn main(init: std.process.Init) !void {
13 const allocator = init.gpa;
14
15 // We specify our "Handler" and, as the last parameter to init, pass an
16 // instance of it.
17 var handler = Handler{};
18 var server = try httpz.Server(*Handler).init(init.io, allocator, .{ .address = .localhost(PORT) }, &handler);
19
20 defer server.deinit();
21
22 // ensures a clean shutdown, finishing off any existing requests
23 // see 09_shutdown.zig for how to to break server.listen with an interrupt
24 defer server.stop();
25
26 var router = try server.router(.{});
27
28 // Register routes.
29
30 router.get("/", index, .{});
31 router.get("/hits", hits, .{});
32 router.get("/error", @"error", .{});
33
34 std.debug.print("listening http://localhost:{d}/\n", .{PORT});
35
36 // Starts the server, this is blocking.
37 try server.listen();
38}
39
40const Handler = struct {
41 _hits: usize = 0,
42
43 // If the handler defines a special "notFound" function, it'll be called
44 // when a request is made and no route matches.
45 pub fn notFound(_: *Handler, _: *httpz.Request, res: *httpz.Response) !void {
46 res.status = 404;
47 res.body = "NOPE!";
48 }
49
50 // If the handler defines the special "uncaughtError" function, it'll be
51 // called when an action returns an error.
52 // Note that this function takes an additional parameter (the error) and
53 // returns a `void` rather than a `!void`.
54 pub fn uncaughtError(_: *Handler, req: *httpz.Request, res: *httpz.Response, err: anyerror) void {
55 std.debug.print("uncaught http error at {s}: {}\n", .{ req.url.path, err });
56
57 // Alternative to res.content_type = .TYPE
58 // useful for dynamic content types, or content types not defined in
59 // httpz.ContentType
60 res.headers.add("content-type", "text/html; charset=utf-8");
61
62 res.status = 505;
63 res.body = "<!DOCTYPE html>(╯°□°)╯︵ ┻━┻";
64 }
65};
66
67fn index(_: *Handler, _: *httpz.Request, res: *httpz.Response) !void {
68 res.body =
69 \\<!DOCTYPE html>
70 \\ <p>Except in very simple cases, you'll want to use a custom Handler.
71 \\ <p>A custom Handler is how you share app-specific data with your actions (like a DB pool)
72 \\ and define a custom not found and error function.
73 \\ <p>Other examples show more advanced things you can do with a custom Handler.
74 \\ <ul>
75 \\ <li><a href="/hits">Shared global hit counter</a>
76 \\ <li><a href="/not_found">Custom not found handler</a>
77 \\ <li><a href="/error">Custom error handler</a>
78 ;
79}
80
81pub fn hits(h: *Handler, _: *httpz.Request, res: *httpz.Response) !void {
82 const count = @atomicRmw(usize, &h._hits, .Add, 1, .monotonic);
83
84 // @atomicRmw returns the previous version so we need to +1 it
85 // to display the count includin this hit
86 return res.json(.{ .hits = count + 1 }, .{});
87}
88
89fn @"error"(_: *Handler, _: *httpz.Request, _: *httpz.Response) !void {
90 return error.ActionError;
91}