|
| 1 | +//! HttpArena: zix-ws |
| 2 | +//! zix version: 0.4.x |
| 3 | +//! |
| 4 | +//! zix HttpArena WebSocket entry point. |
| 5 | +//! |
| 6 | +//! Intent: demonstrate the engine-owned WebSocket path of zix.Http1 (EPOLL |
| 7 | +//! dispatch model) against the HttpArena echo and echo-pipeline suites. |
| 8 | +//! |
| 9 | +//! Design choices: |
| 10 | +//! - GET /ws upgrades, then zix.Http1.WebSocket.serve drives the echo loop |
| 11 | +//! inside the engine: frames are echoed on readiness and a pipelined burst is |
| 12 | +//! coalesced into a single write. |
| 13 | +//! - No response cache: echo is per-connection, not a broadcast fanout, so there |
| 14 | +//! is nothing to precompute or share across connections. |
| 15 | + |
| 16 | +const std = @import("std"); |
| 17 | +const zix = @import("zix"); |
| 18 | + |
| 19 | +// --------------------------------------------------------- // |
| 20 | + |
| 21 | +const PORT: u16 = 8080; |
| 22 | +const LISTEN_IP: []const u8 = "::"; |
| 23 | +const DISPATCH_MODEL: zix.Http1.DispatchModel = .URING; |
| 24 | +const KERNEL_BACKLOG: u31 = 16 * 1024; |
| 25 | +const MAX_RECV_BUF: usize = 4 * 1024; |
| 26 | +const WS_RECV_BUF: usize = 32 * 1024; |
| 27 | +const MAX_HEADERS: u8 = 16; |
| 28 | +const WORKERS: usize = 0; |
| 29 | + |
| 30 | +// --------------------------------------------------------- // |
| 31 | + |
| 32 | +fn badRequest(fd: std.posix.fd_t) void { |
| 33 | + zix.Http1.writeSimple(fd, 400, "text/plain", "bad request") catch {}; |
| 34 | +} |
| 35 | + |
| 36 | +fn notFound(fd: std.posix.fd_t) void { |
| 37 | + zix.Http1.writeSimple(fd, 404, "text/plain", "Not Found") catch {}; |
| 38 | +} |
| 39 | + |
| 40 | +// --------------------------------------------------------- // |
| 41 | + |
| 42 | +// Echo every text/binary frame back. Ping/close are handled by the engine. |
| 43 | +fn wsOnFrame(fd: std.posix.fd_t, opcode: u8, payload: []const u8) void { |
| 44 | + zix.Http1.WebSocket.send(fd, @enumFromInt(opcode), payload) catch {}; |
| 45 | +} |
| 46 | + |
| 47 | +// GET /ws : WebSocket upgrade then engine-owned echo. |
| 48 | +fn wsHandler(head: *const zix.Http1.ParsedHead, body: []const u8, fd: std.posix.fd_t) void { |
| 49 | + _ = body; |
| 50 | + |
| 51 | + const upgrade_val = zix.Http1.getHeader(head, "upgrade") orelse ""; |
| 52 | + const ws_key = zix.Http1.getHeader(head, "sec-websocket-key"); |
| 53 | + |
| 54 | + if (!std.ascii.eqlIgnoreCase(upgrade_val, "websocket") or ws_key == null) { |
| 55 | + return badRequest(fd); |
| 56 | + } |
| 57 | + |
| 58 | + zix.Http1.WebSocket.serve(fd, ws_key.?, wsOnFrame) catch { |
| 59 | + zix.Http1.writeSimple(fd, 500, "text/plain", "handshake failed") catch {}; |
| 60 | + return; |
| 61 | + }; |
| 62 | +} |
| 63 | + |
| 64 | +// --------------------------------------------------------- // |
| 65 | + |
| 66 | +fn dispatch(head: *const zix.Http1.ParsedHead, body: []const u8, fd: std.posix.fd_t) void { |
| 67 | + if (std.mem.eql(u8, head.path, "/ws")) return wsHandler(head, body, fd); |
| 68 | + |
| 69 | + notFound(fd); |
| 70 | +} |
| 71 | + |
| 72 | +// --------------------------------------------------------- // |
| 73 | + |
| 74 | +pub fn main(process: std.process.Init) !void { |
| 75 | + // Elevate scheduling priority (setpriority -19). Fails silently when the |
| 76 | + // process lacks CAP_SYS_NICE, so no special capability is required for correctness. |
| 77 | + _ = std.os.linux.syscall3(.setpriority, 0, 0, @as(usize, @bitCast(@as(isize, -19)))); |
| 78 | + |
| 79 | + var server = zix.Http1.Server.init(dispatch, .{ |
| 80 | + .io = process.io, |
| 81 | + .ip = LISTEN_IP, |
| 82 | + .port = PORT, |
| 83 | + .dispatch_model = DISPATCH_MODEL, |
| 84 | + .kernel_backlog = KERNEL_BACKLOG, |
| 85 | + .max_recv_buf = MAX_RECV_BUF, |
| 86 | + .ws_recv_buf = WS_RECV_BUF, |
| 87 | + .max_headers = MAX_HEADERS, |
| 88 | + .workers = WORKERS, |
| 89 | + .send_date_header = false, |
| 90 | + }); |
| 91 | + defer server.deinit(); |
| 92 | + |
| 93 | + try server.run(); |
| 94 | +} |
0 commit comments