forked from
karlseguin.tngl.sh/http.zig
An HTTP/1.1 server for zig
1const std = @import("std");
2const httpz = @import("httpz");
3
4const websocket = httpz.websocket;
5
6const Allocator = std.mem.Allocator;
7
8const PORT = 8808;
9
10// websocket.zig is verbose, let's limit it to err messages
11pub const std_options = std.Options{ .log_scope_levels = &[_]std.log.ScopeLevel{
12 .{ .scope = .websocket, .level = .err },
13} };
14
15// This example show how to upgrade a request to websocket.
16pub fn main(init: std.process.Init) !void {
17 const allocator = init.gpa;
18
19 // For websocket support, you _must_ define a Handler, and your Handler _must_
20 // have a WebsocketHandler declaration
21 var server = try httpz.Server(Handler).init(init.io, allocator, .{ .address = .localhost(PORT) }, Handler{});
22
23 defer server.deinit();
24
25 // ensures a clean shutdown, finishing off any existing requests
26 // see 09_shutdown.zig for how to to break server.listen with an interrupt
27 defer server.stop();
28
29 var router = try server.router(.{});
30
31 router.get("/", index, .{});
32
33 router.get("/ws", ws, .{});
34
35 std.debug.print("listening http://localhost:{d}/\n", .{PORT});
36
37 // Starts the server, this is blocking.
38 try server.listen();
39}
40
41const Handler = struct {
42 // or you could define the full structure here
43 pub const WebsocketHandler = Client;
44};
45
46const Client = struct {
47 user_id: u32,
48 conn: *websocket.Conn,
49
50 const Context = struct {
51 user_id: u32,
52 };
53
54 // context is any abitrary data that you want, you'll pass it to upgradeWebsocket
55 pub fn init(conn: *websocket.Conn, ctx: *const Context) !Client {
56 return .{
57 .conn = conn,
58 .user_id = ctx.user_id,
59 };
60 }
61
62 // at this point, it's safe to write to conn
63 pub fn afterInit(self: *Client) !void {
64 return self.conn.write("welcome!");
65 }
66
67 pub fn clientMessage(self: *Client, data: []const u8) !void {
68 // echo back to client
69 return self.conn.write(data);
70 }
71};
72
73fn index(_: Handler, _: *httpz.Request, res: *httpz.Response) !void {
74 res.content_type = .HTML;
75 res.body =
76 \\<!DOCTYPE html>
77 \\ <p>httpz integrates with my own <a href="https://github.com/karlseguin/websocket.zig/">websocket.zig</a>.
78 \\ <p>A websocket connection should already be established.</p>
79 \\ <p>Copy and paste the following in your browser console to have the server echo back:</p>
80 \\ <pre>ws.send("hello from the client!");</pre>
81 \\ <script>
82 \\ const ws = new WebSocket("ws://localhost:8808/ws");
83 \\ ws.addEventListener("message", (event) => { console.log("from server: ", event.data) });
84 \\ </script>
85 ;
86}
87
88fn ws(_: Handler, req: *httpz.Request, res: *httpz.Response) !void {
89 // Could do authentication or anything else before upgrading the connection
90 // The context is any arbitrary data you want to pass to Client.init.
91 const ctx = Client.Context{ .user_id = 9001 };
92
93 // The first parameter, Client, ***MUST*** be the same as Handler.WebSocketHandler
94 // I'm sorry about the awkwardness of that.
95 // It's undefined behavior if they don't match, and it _will_ behave weirdly/crash.
96 if (try httpz.upgradeWebsocket(Client, req, res, &ctx) == false) {
97 res.status = 500;
98 res.body = "invalid websocket";
99 }
100 // unsafe to use req or res at this point!
101}