A websocket implementation for zig
69 kB
1969 lines
1const std = @import("std");
2const builtin = @import("builtin");
3const proto = @import("../proto.zig");
4const buffer = @import("../buffer.zig");
5
6const net = std.net;
7const posix = std.posix;
8const Thread = std.Thread;
9const Allocator = std.mem.Allocator;
10const FixedBufferAllocator = std.heap.FixedBufferAllocator;
11
12const log = std.log.scoped(.websocket);
13
14const OpCode = proto.OpCode;
15const Reader = proto.Reader;
16const Message = proto.Message;
17pub const Handshake = @import("handshake.zig").Handshake;
18const FallbackAllocator = @import("fallback_allocator.zig").FallbackAllocator;
19
20const DEFAULT_MAX_CONN = 16_384;
21const DEFAULT_BUFFER_SIZE = 2048;
22const DEFAULT_MAX_MESSAGE_SIZE = 65_536;
23
24const EMPTY_PONG = ([2]u8{ @intFromEnum(OpCode.pong), 0 })[0..];
25// CLOSE, 2 length, code
26const CLOSE_NORMAL = ([_]u8{ @intFromEnum(OpCode.close), 2, 3, 232 })[0..]; // code: 1000
27const CLOSE_PROTOCOL_ERROR = ([_]u8{ @intFromEnum(OpCode.close), 2, 3, 234 })[0..]; //code: 1002
28
29const force_blocking: bool = blk: {
30 const build = @import("build");
31 if (@hasDecl(build, "websocket_blocking")) {
32 break :blk build.websocket_blocking;
33 }
34 break :blk false;
35};
36
37pub fn blockingMode() bool {
38 if (force_blocking) {
39 return true;
40 }
41 return switch (builtin.os.tag) {
42 .linux, .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => false,
43 else => true,
44 };
45}
46
47pub const Config = struct {
48 port: u16 = 9882,
49 address: []const u8 = "127.0.0.1",
50 unix_path: ?[]const u8 = null,
51
52 worker_count: ?u8 = null,
53
54 max_conn: ?usize = null,
55 max_message_size: ?usize = null,
56
57 handshake: Config.Handshake = .{},
58 thread_pool: ThreadPool = .{},
59 buffers: Config.Buffers = .{},
60
61 pub const ThreadPool = struct {
62 count: ?u16 = null,
63 backlog: ?u32 = null,
64 buffer_size: ?usize = null,
65 };
66
67 const Handshake = struct {
68 timeout: u32 = 10,
69 max_size: ?u16 = null,
70 max_headers: ?u16 = null,
71 count: ?u16 = null,
72 };
73
74 const Buffers = struct {
75 small_size: ?usize = null,
76 small_pool: ?usize = null,
77 large_size: ?usize = null,
78 large_pool: ?u16 = null,
79 };
80
81 fn workerCount(self: *const Config) usize {
82 if (comptime blockingMode()) {
83 return 1;
84 }
85 return self.worker_count orelse 1;
86 }
87};
88
89pub fn Server(comptime H: type) type {
90 return struct {
91 config: Config,
92 allocator: Allocator,
93
94 _state: WorkerState,
95 _signals: []posix.fd_t,
96 _mut: Thread.Mutex,
97 _cond: Thread.Condition,
98
99 const Self = @This();
100
101 pub fn init(allocator: Allocator, config: Config) !Self {
102 if (blockingMode()) {
103 if (config.buffers.small_pool) |p| {
104 if (p > 1) {
105 log.warn("blockingMode() cannot utilize a small buffer pool, using per-connection buffer instead", .{});
106 }
107 }
108 }
109
110 const signals = try allocator.alloc(posix.fd_t, config.workerCount());
111 errdefer allocator.free(signals);
112
113 var state = try WorkerState.init(allocator, config);
114 errdefer state.deinit();
115
116 return .{
117 ._mut = .{},
118 ._cond = .{},
119 ._state = state,
120 ._signals = signals,
121 .config = config,
122 .allocator = allocator,
123 };
124 }
125
126 pub fn deinit(self: *Self) void {
127 self._state.deinit();
128 self.allocator.free(self._signals);
129 }
130
131 pub fn listenInNewThread(self: *Self, ctx: anytype) !Thread {
132 self._mut.lock();
133 defer self._mut.unlock();
134 const thrd = try Thread.spawn(.{}, Self.listen, .{ self, ctx });
135
136 // we don't return until listen() signals us that the server is up
137 self._cond.wait(&self._mut);
138 return thrd;
139 }
140
141 pub fn listen(self: *Self, ctx: anytype) !void {
142 self._mut.lock();
143 errdefer self._mut.unlock();
144
145 const config = &self.config;
146
147 var no_delay = true;
148 const address = blk: {
149 if (config.unix_path) |unix_path| {
150 if (comptime builtin.os.tag == .windows) {
151 return error.UnixPathNotSupported;
152 }
153 no_delay = false;
154 std.fs.deleteFileAbsolute(unix_path) catch {};
155 break :blk try net.Address.initUnix(unix_path);
156 } else {
157 const listen_port = config.port;
158 const listen_address = config.address;
159 break :blk try net.Address.parseIp(listen_address, listen_port);
160 }
161 };
162
163 const socket = blk: {
164 var sock_flags: u32 = posix.SOCK.STREAM | posix.SOCK.CLOEXEC;
165 if (blockingMode() == false) sock_flags |= posix.SOCK.NONBLOCK;
166
167 const socket_proto = if (address.any.family == posix.AF.UNIX) @as(u32, 0) else posix.IPPROTO.TCP;
168 break :blk try posix.socket(address.any.family, sock_flags, socket_proto);
169 };
170
171 if (no_delay) {
172 // TODO: Broken on darwin:
173 // https://github.com/ziglang/zig/issues/17260
174 // if (@hasDecl(os.TCP, "NODELAY")) {
175 // try os.setsockopt(socket.sockfd.?, os.IPPROTO.TCP, os.TCP.NODELAY, &std.mem.toBytes(@as(c_int, 1)));
176 // }
177 try posix.setsockopt(socket, posix.IPPROTO.TCP, 1, &std.mem.toBytes(@as(c_int, 1)));
178 }
179
180 if (@hasDecl(posix.SO, "REUSEPORT_LB")) {
181 try posix.setsockopt(socket, posix.SOL.SOCKET, posix.SO.REUSEPORT_LB, &std.mem.toBytes(@as(c_int, 1)));
182 } else if (@hasDecl(posix.SO, "REUSEPORT")) {
183 try posix.setsockopt(socket, posix.SOL.SOCKET, posix.SO.REUSEPORT, &std.mem.toBytes(@as(c_int, 1)));
184 } else {
185 try posix.setsockopt(socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1)));
186 }
187
188 {
189 const socklen = address.getOsSockLen();
190 try posix.bind(socket, &address.any, socklen);
191 try posix.listen(socket, 1024); // kernel backlog
192 }
193
194 const C = @TypeOf(ctx);
195
196 if (comptime blockingMode()) {
197 errdefer posix.close(socket);
198 var w = try Blocking(H).init(self.allocator, &self._state);
199 defer w.deinit();
200
201 const thrd = try std.Thread.spawn(.{}, Blocking(H).run, .{ &w, socket, ctx });
202 log.info("starting blocking worker to listen on {}", .{address});
203
204 // incase listenInNewThread was used and is waiting for us to start
205 self._cond.signal();
206
207 // this is what we'll shutdown when stop() is called
208 self._signals[0] = socket;
209 self._mut.unlock();
210 thrd.join();
211 } else {
212 defer posix.close(socket);
213 const W = NonBlocking(H, C);
214
215 const allocator = self.allocator;
216
217 var signals = self._signals;
218 const worker_count = signals.len;
219 const threads = try allocator.alloc(Thread, worker_count);
220 const workers = try allocator.alloc(W, worker_count);
221
222 var started: usize = 0;
223
224 errdefer for (0..started) |i| {
225 // on success, these will be closed by a call to stop();
226 posix.close(signals[i]);
227 };
228
229 defer {
230 for (0..started) |i| {
231 workers[i].deinit();
232 }
233 allocator.free(threads);
234 allocator.free(workers);
235 }
236
237 for (0..worker_count) |i| {
238 const pipe = try posix.pipe2(.{ .NONBLOCK = true });
239 errdefer posix.close(pipe[1]);
240
241 workers[i] = try W.init(self.allocator, &self._state, ctx);
242 errdefer workers[i].deinit();
243
244 threads[i] = try Thread.spawn(.{}, W.run, .{ &workers[i], socket, pipe[0] });
245
246 signals[i] = pipe[1];
247 started += 1;
248 }
249
250 log.info("starting nonblocking worker to listen on {}", .{address});
251
252 // in case startInNewThread is waiting
253 self._cond.signal();
254
255 self._mut.unlock();
256
257 for (threads) |thrd| {
258 thrd.join();
259 }
260 }
261 }
262
263 pub fn stop(self: *Self) void {
264 self._mut.lock();
265 defer self._mut.unlock();
266 for (self._signals) |s| {
267 if (blockingMode()) {
268 // necessary to unblock accept on linux
269 // (which might not be that necessary since, on Linux,
270 // NonBlocking should be used)
271 posix.shutdown(s, .recv) catch {};
272 }
273 posix.close(s);
274 }
275 }
276 };
277}
278
279// This is our Blocking worker. It's very different than NonBlocking and much simpler.
280pub fn Blocking(comptime H: type) type {
281 return struct {
282 allocator: Allocator,
283 handshake_timeout: Timeout,
284 connection_buffer_size: usize,
285 conn_manager: ConnManager(H, false),
286 handshake_pool: *Handshake.Pool,
287 buffer_provider: *buffer.Provider,
288
289 const Timeout = struct {
290 sec: u32,
291 timeval: [@sizeOf(std.posix.timeval)]u8,
292
293 // if sec is null, it means we want to cancel the timeout.
294 fn init(sec: u32) Timeout {
295 return .{
296 .sec = sec,
297 .timeval = std.mem.toBytes(posix.timeval{ .sec = @intCast(sec), .usec = 0 }),
298 };
299 }
300
301 pub const none = std.mem.toBytes(posix.timeval{ .sec = 0, .usec = 0 });
302 };
303
304 const Self = @This();
305
306 pub fn init(allocator: Allocator, state: *WorkerState) !Self {
307 const config = &state.config;
308
309 var conn_manager = try ConnManager(H, false).init(allocator);
310 errdefer conn_manager.deinit();
311
312 return .{
313 .conn_manager = conn_manager,
314 .allocator = allocator,
315 .handshake_pool = state.handshake_pool,
316 .buffer_provider = &state.buffer_provider,
317 .handshake_timeout = Timeout.init(config.handshake.timeout),
318 .connection_buffer_size = config.buffers.small_size orelse DEFAULT_BUFFER_SIZE,
319 };
320 }
321
322 pub fn deinit(self: *Self) void {
323 self.conn_manager.deinit();
324 }
325
326 pub fn run(self: *Self, listener: posix.socket_t, ctx: anytype) void {
327 defer self.shutdown();
328 while (true) {
329 var address: net.Address = undefined;
330 var address_len: posix.socklen_t = @sizeOf(net.Address);
331 const socket = posix.accept(listener, &address.any, &address_len, posix.SOCK.CLOEXEC) catch |err| {
332 if (err == error.ConnectionAborted or err == error.SocketNotListening) {
333 log.info("received shutdown signal", .{});
334 return;
335 }
336 log.err("failed to accept socket: {}", .{err});
337 continue;
338 };
339 log.debug("({}) connected", .{address});
340
341 const thread = std.Thread.spawn(.{}, Self.handleConnection, .{ self, socket, address, ctx }) catch |err| {
342 posix.close(socket);
343 log.err("({}) failed to spawn connection thread: {}", .{ address, err });
344 continue;
345 };
346 thread.detach();
347 }
348 }
349
350 // Called in a thread started above in listen.
351 // Wrapper around _handleConnection so that we can handle erros
352 fn handleConnection(self: *Self, socket: posix.socket_t, address: net.Address, ctx: anytype) void {
353 self._handleConnection(socket, address, ctx) catch |err| {
354 log.err("({}) uncaught error in connection handler: {}", .{ address, err });
355 };
356 }
357
358 fn _handleConnection(self: *Self, socket: posix.socket_t, address: net.Address, ctx: anytype) !void {
359 const conn_manager = &self.conn_manager;
360 const hc = try conn_manager.create(socket, address, timestamp());
361
362 {
363 // Do our handshake
364 errdefer self.cleanupConn(hc);
365 const timeout = self.handshake_timeout;
366 const deadline = timestamp() + timeout.sec;
367 try posix.setsockopt(socket, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &timeout.timeval);
368
369 while (true) {
370 if (handleHandshake(H, self, hc, ctx) == false) {
371 self.cleanupConn(hc);
372 return;
373 }
374 if (hc.handler != null) {
375 // if we have a handler, the our handshake completed
376 break;
377 }
378 if (timestamp() > deadline) {
379 self.cleanupConn(hc);
380 return;
381 }
382 }
383 }
384
385 return self.readLoop(hc);
386 }
387
388 // The readloop is extracted from _handleConnection so that it can be called
389 // directly when integrating with an http server
390 pub fn readLoop(self: *Self, hc: *HandlerConn(H)) !void {
391 defer self.cleanupConn(hc);
392
393 try posix.setsockopt(hc.socket, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &Timeout.none);
394
395 // In BlockingMode, we always assign a reader for the duration of the connection
396 // In scenarios where client rarely send data, this is going to use up an unecessary amount
397 // of memory, but unlike nonblocking mode, we can't initialize the buffer just-in-time.
398 const reader_buf = try self.allocator.alloc(u8, self.connection_buffer_size);
399 defer self.allocator.free(reader_buf);
400
401 hc.reader = Reader.init(reader_buf, self.buffer_provider);
402 while (true) {
403 if (handleClientData(H, hc, self.allocator, undefined) == false) {
404 break;
405 }
406 }
407 }
408
409 fn cleanupConn(self: *Self, hc: *HandlerConn(H)) void {
410 hc.conn.closeSocket();
411 self.conn_manager.cleanup(hc);
412 }
413
414 fn shutdown(self: *Self) void {
415 var conn_manager = &self.conn_manager;
416 conn_manager.shutdown(self);
417
418 // wait up to 1 second for every connection to cleanly shutdown
419 var i: usize = 0;
420 while (i < 10) : (i += 1) {
421 if (conn_manager.count() == 0) {
422 return;
423 }
424 std.time.sleep(std.time.ns_per_ms * 100);
425 }
426 }
427
428 // called for each hc when shutting down
429 fn shutdownCleanup(_: *Self, hc: *HandlerConn(H)) void {
430 posix.shutdown(hc.socket, .recv) catch {};
431 }
432 };
433}
434
435fn NonBlocking(comptime H: type, comptime C: type) type {
436 return struct {
437 ctx: C,
438
439 max_conn: usize,
440
441 // KQueue or Epoll, depending on the platform
442 loop: Loop,
443 thread_pool: *ThreadPool,
444
445 handshake_timeout: u32,
446 handshake_pool: *Handshake.Pool,
447 base: NonBlockingBase(H, true),
448
449 const Self = @This();
450 const ThreadPool = @import("thread_pool.zig").ThreadPool(Self.dataAvailable);
451
452 pub fn init(allocator: Allocator, state: *WorkerState, ctx: C) !Self {
453 var base = try NonBlockingBase(H, true).init(allocator, state);
454 errdefer base.deinit();
455
456 const loop = try Loop.init();
457 errdefer loop.deinit();
458
459 const config = &state.config;
460 var thread_pool = try ThreadPool.init(allocator, .{
461 .count = config.thread_pool.count orelse 4,
462 .backlog = config.thread_pool.backlog orelse 500,
463 .buffer_size = config.thread_pool.buffer_size orelse if (needsAllocator(H)) 32_768 else 0,
464 });
465 errdefer thread_pool.deinit();
466
467 return .{
468 .ctx = ctx,
469 .loop = loop,
470 .base = base,
471 .thread_pool = thread_pool,
472 .handshake_pool = state.handshake_pool,
473 .handshake_timeout = state.config.handshake.timeout,
474 .max_conn = config.max_conn orelse DEFAULT_MAX_CONN,
475 };
476 }
477
478 pub fn deinit(self: *Self) void {
479 self.loop.deinit();
480 self.base.deinit();
481 self.thread_pool.deinit();
482 }
483
484 fn run(self: *Self, listener: posix.socket_t, signal: posix.fd_t) void {
485 self.loop.monitorAccept(listener) catch |err| {
486 log.err("failed to add monitor to listening socket: {}", .{err});
487 return;
488 };
489
490 self.loop.monitorSignal(signal) catch |err| {
491 log.err("failed to add monitor to signal pipe: {}", .{err});
492 return;
493 };
494
495 const thread_pool = self.thread_pool;
496 const conn_manager = &self.base.conn_manager;
497 const handshake_timeout = self.handshake_timeout;
498
499 var now = timestamp();
500 var oldest_pending_start_time: ?u32 = null;
501 while (true) {
502 const handshake_cutoff = now - handshake_timeout;
503 const timeout = self.prepareToWait(handshake_cutoff) orelse blk: {
504 if (oldest_pending_start_time) |started| {
505 break :blk if (started < handshake_cutoff) 1 else @as(i32, @intCast(started - handshake_cutoff));
506 }
507 break :blk null;
508 };
509
510 var it = self.loop.wait(timeout) catch |err| {
511 log.err("failed to wait on events: {}", .{err});
512 std.time.sleep(std.time.ns_per_s);
513 continue;
514 };
515
516 now = timestamp();
517 oldest_pending_start_time = null;
518 while (it.next()) |data| {
519 if (data == 0) {
520 self.accept(listener, now) catch |err| {
521 log.err("accept error: {}", .{err});
522 std.time.sleep(std.time.ns_per_ms);
523 };
524 continue;
525 }
526
527 if (data == 1) {
528 self.base.shutdown();
529 return;
530 }
531
532 const hc: *HandlerConn(H) = @ptrFromInt(data);
533 if (hc.state == .handshake) {
534 // we need to get this out of the pending list, so that it doesn't
535 // cause a timeout while we're processing it.
536 // But we stll need to care about its timeout.
537 if (oldest_pending_start_time) |s| {
538 oldest_pending_start_time = @min(hc.conn.started, s);
539 } else {
540 oldest_pending_start_time = hc.conn.started;
541 }
542 conn_manager.activate(hc);
543 }
544 thread_pool.spawn(.{ self, hc });
545 }
546 }
547 }
548
549 // Enforces timeouts, and returns when the next timeout should be checked.
550 fn prepareToWait(self: *Self, cutoff: u32) ?i32 {
551 const cm = &self.base.conn_manager;
552
553 // always ordered from oldest to newest, so once we find a conneciton
554 // that isn't timed out, we can stop
555 cm.lock.lock();
556 defer cm.lock.unlock();
557
558 var next_conn = cm.pending.head;
559 while (next_conn) |hc| {
560 const conn = &hc.conn;
561 const started = conn.started;
562 if (started > cutoff) {
563 // This is the first connection which hasn't timed out
564 // return the time until it times out.
565 return @intCast(started - cutoff);
566 }
567
568 next_conn = hc.next;
569
570 // this connection has timed out. Don't use self.cleanup since there's
571 // a bunch of stuff we can assume here..like there's no handler or reader
572 conn.closeSocket();
573 log.debug("({}) handshake timeout", .{conn.address});
574 if (hc.handshake) |h| {
575 h.release();
576 }
577 cm.pending.remove(hc);
578 cm.pool.destroy(hc);
579 }
580 return null;
581 }
582
583 fn accept(self: *Self, listener: posix.fd_t, now: u32) !void {
584 const max_conn = self.max_conn;
585 const conn_manager = &self.base.conn_manager;
586
587 while (conn_manager.count() < max_conn) {
588 var address: net.Address = undefined;
589 var address_len: posix.socklen_t = @sizeOf(net.Address);
590
591 const socket = posix.accept(listener, &address.any, &address_len, posix.SOCK.CLOEXEC) catch |err| {
592 // When available, we use SO_REUSEPORT_LB or SO_REUSEPORT, so WouldBlock
593 // should not be possible in those cases, but if it isn't available
594 // this error should be ignored as it means another thread picked it up.
595 return if (err == error.WouldBlock) {} else err;
596 };
597
598 log.debug("({}) connected", .{address});
599
600 {
601 errdefer posix.close(socket);
602 // socket is _probably_ in NONBLOCKING mode (it inherits
603 // the flag from the listening socket).
604 const flags = try posix.fcntl(socket, posix.F.GETFL, 0);
605 const nonblocking = @as(u32, @bitCast(posix.O{ .NONBLOCK = true }));
606 if (flags & nonblocking == nonblocking) {
607 // Yup, it's in nonblocking mode. Disable that flag to
608 // put it in blocking mode.
609 _ = try posix.fcntl(socket, posix.F.SETFL, flags & ~nonblocking);
610 }
611 }
612 const hc = try self.base.newConn(socket, address, now);
613 self.loop.monitorRead(hc, false) catch |err| {
614 self.base.cleanupConn(hc);
615 return err;
616 };
617 }
618 }
619
620 // Called in a thread-pool thread/
621 // !! Access to self has to be synchronized !!
622 // There can only be 1 dataAvailable executing per HC at any given time.
623 // Access to HC *should not* need to be synchronized. Access to hc.conn
624 // needs to be synchronized once &hc.conn is passed to the handler during
625 // handshake (Conn is self-synchronized).
626 // Shutdown throws a wrench in our synchronization model, since we could
627 // be shutting down while we're processing data...but hopefully the way we
628 // shutdown (by waiting for all the thread pools threads to end) solves this.
629 // Else, we'll need to throw a bunch of locking around HC just to handle shutdown.
630 fn dataAvailable(self: *Self, hc: *HandlerConn(H), thread_buf: []u8) void {
631 var success = false;
632 if (hc.handler == null) {
633 success = self.dataForHandshake(hc) catch |err| blk: {
634 log.err("({}) error processing handshake: {}", .{ hc.conn.address, err });
635 break :blk false;
636 };
637 } else {
638 success = self.base.dataAvailable(hc, thread_buf);
639 }
640
641 var conn = &hc.conn;
642 var closed: bool = undefined;
643 if (success == false) {
644 conn.closeSocket();
645 closed = true;
646 } else {
647 closed = conn.isClosed();
648 }
649
650 if (closed) {
651 self.base.cleanupConn(hc);
652 } else {
653 self.loop.monitorRead(hc, true) catch |err| {
654 log.debug("({}) failed to add read event monitor: {}", .{ conn.address, err });
655 conn.closeSocket();
656 self.base.cleanupConn(hc);
657 };
658 }
659 }
660
661 fn dataForHandshake(self: *Self, hc: *HandlerConn(H)) !bool {
662 if (handleHandshake(H, self, hc, self.ctx) == false) {
663 return false;
664 }
665
666 if (hc.handler == null) {
667 // still don't have a handshake
668 self.base.conn_manager.inactive(hc);
669 }
670
671 return true;
672 }
673 };
674}
675
676fn NonBlockingBase(comptime H: type, comptime MANAGE_HS: bool) type {
677 return struct {
678 allocator: Allocator,
679
680 buffer_provider: *buffer.Provider,
681
682 // App can configure the use of a "small" buffer pool. This is the difference
683 // between assigning a buffer to the connection just-in-time per message, or always
684 small_buffer_pool: ?buffer.Pool,
685 connection_buffer_size: usize,
686
687 conn_manager: ConnManager(H, MANAGE_HS),
688
689 const Self = @This();
690
691 fn init(allocator: Allocator, state: *WorkerState) !Self {
692 const config = &state.config;
693
694 var conn_manager = try ConnManager(H, MANAGE_HS).init(allocator);
695 errdefer conn_manager.deinit();
696
697 const connection_buffer_size = config.buffers.small_size orelse DEFAULT_BUFFER_SIZE;
698 var small_buffer_pool: ?buffer.Pool = null;
699 if (config.buffers.small_pool) |pool_count| {
700 small_buffer_pool = try buffer.Pool.init(allocator, pool_count, connection_buffer_size);
701 }
702
703 errdefer if (small_buffer_pool) |sbp| {
704 sbp.deinit();
705 };
706
707 return .{
708 .allocator = allocator,
709 .conn_manager = conn_manager,
710 .small_buffer_pool = small_buffer_pool,
711 .buffer_provider = &state.buffer_provider,
712 .connection_buffer_size = connection_buffer_size,
713 };
714 }
715
716 fn deinit(self: *Self) void {
717 self.conn_manager.deinit();
718 if (self.small_buffer_pool) |*sbp| {
719 sbp.deinit();
720 }
721 }
722
723 fn newConn(self: *Self, socket: posix.socket_t, address: net.Address, time: u32) !*HandlerConn(H) {
724 return self.conn_manager.create(socket, address, time);
725 }
726
727 pub fn dataAvailable(self: *Self, hc: *HandlerConn(H), thread_buf: []u8) bool {
728 return self._dataAvailable(hc, thread_buf) catch |err| {
729 log.err("({}) error processing client message: {}", .{ hc.conn.address, err });
730 return false;
731 };
732 }
733
734 fn _dataAvailable(self: *Self, hc: *HandlerConn(H), thread_buf: []u8) !bool {
735 if (hc.reader == null) {
736 const reader_buf = if (self.small_buffer_pool) |*sbp| try sbp.acquireOrCreate() else try self.allocator.alloc(u8, self.connection_buffer_size);
737 hc.reader = Reader.init(reader_buf, self.buffer_provider);
738 }
739 const reader = &hc.reader.?;
740
741 var fba = FixedBufferAllocator.init(thread_buf);
742 const ok = handleClientData(H, hc, self.allocator, &fba);
743
744 if (self.small_buffer_pool) |*sbp| {
745 if (reader.isEmpty()) {
746 sbp.release(reader.static);
747 hc.reader = null;
748 }
749 }
750
751 return ok;
752 }
753
754 fn cleanupConn(self: *Self, hc: *HandlerConn(H)) void {
755 if (hc.reader) |*reader| {
756 if (self.small_buffer_pool) |*sbp| {
757 sbp.release(reader.static);
758 } else {
759 self.allocator.free(reader.static);
760 }
761 }
762 self.conn_manager.cleanup(hc);
763 }
764
765 fn shutdown(self: *Self) void {
766 log.info("received shutdown signal", .{});
767 self.conn_manager.shutdown(self);
768 }
769
770 // called for each hc when shutting down
771 fn shutdownCleanup(self: *Self, hc: *HandlerConn(H)) void {
772 if (hc.reader) |*reader| {
773 if (self.small_buffer_pool == null) {
774 self.allocator.free(reader.static);
775 }
776 }
777 }
778 };
779}
780
781const Loop = switch (@import("builtin").os.tag) {
782 .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => KQueue,
783 .linux => EPoll,
784 else => unreachable,
785};
786
787const KQueue = struct {
788 q: i32,
789 change_count: usize,
790 change_buffer: [16]Kevent,
791 event_list: [64]Kevent,
792
793 const Kevent = posix.Kevent;
794
795 fn init() !KQueue {
796 return .{
797 .q = try posix.kqueue(),
798 .change_count = 0,
799 .change_buffer = undefined,
800 .event_list = undefined,
801 };
802 }
803
804 fn deinit(self: KQueue) void {
805 posix.close(self.q);
806 }
807
808 fn monitorAccept(self: *KQueue, fd: c_int) !void {
809 try self.change(fd, 0, posix.system.EVFILT.READ, posix.system.EV.ADD);
810 }
811
812 fn monitorSignal(self: *KQueue, fd: c_int) !void {
813 try self.change(fd, 1, posix.system.EVFILT.READ, posix.system.EV.ADD);
814 }
815
816 // Normally, we add the socket in the worker thread with rearm == false.
817 // Because this is a DISPATCH, it'll only fire once until we rearm it which
818 // we do by re-enabling it with rearm == true.
819 // However, notice that the rearm path also has the EV.ADD flag. From the above
820 // description, this should not be necessary.
821 // But monitorRead where rearm == true is also used by our generic ServerLoop when
822 // taking over a connection (say from httpz). Hence, we need the EV.ADD flag too.
823 fn monitorRead(self: *KQueue, hc: anytype, comptime rearm: bool) !void {
824 if (rearm == false) {
825 return self.change(hc.socket, @intFromPtr(hc), posix.system.EVFILT.READ, posix.system.EV.ADD | posix.system.EV.ENABLE | posix.system.EV.DISPATCH);
826 }
827 const event = Kevent{
828 .ident = @intCast(hc.socket),
829 .filter = posix.system.EVFILT.READ,
830 .flags = posix.system.EV.ADD | posix.system.EV.ENABLE | posix.system.EV.DISPATCH,
831 .fflags = 0,
832 .data = 0,
833 .udata = @intFromPtr(hc),
834 };
835 _ = try posix.kevent(self.q, &.{event}, &[_]Kevent{}, null);
836 }
837
838 fn change(self: *KQueue, fd: posix.fd_t, data: usize, filter: i16, flags: u16) !void {
839 var change_count = self.change_count;
840 var change_buffer = &self.change_buffer;
841
842 if (change_count == change_buffer.len) {
843 // calling this with an empty event_list will return immediate
844 _ = try posix.kevent(self.q, change_buffer, &[_]Kevent{}, null);
845 change_count = 0;
846 }
847 change_buffer[change_count] = .{
848 .ident = @intCast(fd),
849 .filter = filter,
850 .flags = flags,
851 .fflags = 0,
852 .data = 0,
853 .udata = data,
854 };
855 self.change_count = change_count + 1;
856 }
857
858 fn wait(self: *KQueue, timeout_sec: ?i32) !Iterator {
859 const event_list = &self.event_list;
860 const timeout: ?posix.timespec = if (timeout_sec) |ts| posix.timespec{ .sec = ts, .nsec = 0 } else null;
861 const event_count = try posix.kevent(self.q, self.change_buffer[0..self.change_count], event_list, if (timeout) |ts| &ts else null);
862 self.change_count = 0;
863
864 return .{
865 .index = 0,
866 .events = event_list[0..event_count],
867 };
868 }
869
870 const Iterator = struct {
871 index: usize,
872 events: []Kevent,
873
874 fn next(self: *Iterator) ?usize {
875 const index = self.index;
876 const events = self.events;
877 if (index == events.len) {
878 return null;
879 }
880 self.index = index + 1;
881 return self.events[index].udata;
882 }
883 };
884};
885
886const EPoll = struct {
887 q: i32,
888 event_list: [64]EpollEvent,
889
890 const linux = std.os.linux;
891 const EpollEvent = linux.epoll_event;
892
893 fn init() !EPoll {
894 return .{
895 .event_list = undefined,
896 .q = try posix.epoll_create1(0),
897 };
898 }
899
900 fn deinit(self: EPoll) void {
901 posix.close(self.q);
902 }
903
904 fn monitorAccept(self: *EPoll, fd: c_int) !void {
905 var event = linux.epoll_event{ .events = linux.EPOLL.IN, .data = .{ .ptr = 0 } };
906 return std.posix.epoll_ctl(self.q, linux.EPOLL.CTL_ADD, fd, &event);
907 }
908
909 fn monitorSignal(self: *EPoll, fd: c_int) !void {
910 var event = linux.epoll_event{ .events = linux.EPOLL.IN, .data = .{ .ptr = 1 } };
911 return std.posix.epoll_ctl(self.q, linux.EPOLL.CTL_ADD, fd, &event);
912 }
913
914 fn monitorRead(self: *EPoll, hc: anytype, comptime rearm: bool) !void {
915 const op = if (rearm) linux.EPOLL.CTL_MOD else linux.EPOLL.CTL_ADD;
916 var event = linux.epoll_event{ .events = linux.EPOLL.IN | linux.EPOLL.ONESHOT, .data = .{ .ptr = @intFromPtr(hc) } };
917 return posix.epoll_ctl(self.q, op, hc.socket, &event);
918 }
919
920 fn wait(self: *EPoll, timeout_sec: ?i32) !Iterator {
921 const event_list = &self.event_list;
922 var timeout: i32 = -1;
923 if (timeout_sec) |sec| {
924 if (sec > 2147483) {
925 // max supported timeout by epoll_wait.
926 timeout = 2147483647;
927 } else {
928 timeout = sec * 1000;
929 }
930 }
931
932 const event_count = posix.epoll_wait(self.q, event_list, timeout);
933 return .{
934 .index = 0,
935 .events = event_list[0..event_count],
936 };
937 }
938
939 const Iterator = struct {
940 index: usize,
941 events: []EpollEvent,
942
943 fn next(self: *Iterator) ?usize {
944 const index = self.index;
945 const events = self.events;
946 if (index == events.len) {
947 return null;
948 }
949 self.index = index + 1;
950 return self.events[index].data.ptr;
951 }
952 };
953};
954
955// We want to extract as much common logic as possible from the Blocking and
956// NonBlockign workers. The code from this point on is meant to be used with
957// both workers, independently from the blocking/nonblocking nonsense.
958
959// Abstraction ontop of NonBlocking and Blocking. Exists solely for integration
960// with httpz (or any other http library I guess.). Serves a similar purpose
961// as Server, but doesn't accept/listen.
962pub fn Worker(comptime H: type) type {
963 return struct {
964 worker: W,
965
966 const Self = @This();
967 const W = if (blockingMode()) Blocking(H) else NonBlockingBase(H, false);
968
969 pub fn init(allocator: Allocator, state: *WorkerState) !Self {
970 return .{
971 .worker = try W.init(allocator, state),
972 };
973 }
974
975 pub fn deinit(self: *Self) void {
976 self.worker.deinit();
977 }
978
979 pub fn createConn(self: *Self, socket: posix.socket_t, address: net.Address, now: u32) !*HandlerConn(H) {
980 return self.worker.conn_manager.create(socket, address, now);
981 }
982
983 pub fn cleanupConn(self: *Self, hc: *HandlerConn(H)) void {
984 self.worker.cleanupConn(hc);
985 }
986
987 pub fn shutdown(self: *Self) void {
988 self.worker.shutdown();
989 }
990 };
991}
992
993// These are things that both the Blocking and NonBlocking workers need. Just cleaner
994// to have a single place for it. This could used to be directly in Server(H), with
995// Server(H) having handshake_pool and buffer_provider fields, but it was extracted
996// into its own struct for integration with webservers (i.e. httpz). The goal is
997// that a webserver can have websocket support without starting a full Server.
998pub const WorkerState = struct {
999 config: Config,
1000 handshake_pool: *Handshake.Pool,
1001 buffer_provider: buffer.Provider,
1002
1003 pub fn init(allocator: Allocator, config: Config) !WorkerState {
1004 const handshake_pool_count = config.handshake.count orelse 32;
1005 const handshake_max_size = config.handshake.max_size orelse 1024;
1006 const handshake_max_headers = config.handshake.max_headers orelse 10;
1007
1008 var handshake_pool = try Handshake.Pool.init(allocator, handshake_pool_count, handshake_max_size, handshake_max_headers);
1009 errdefer handshake_pool.deinit();
1010
1011 const max_message_size = config.max_message_size orelse DEFAULT_MAX_MESSAGE_SIZE;
1012 const large_buffer_pool = config.buffers.large_pool orelse 8;
1013 const large_buffer_size = config.buffers.large_size orelse @min((config.buffers.small_size orelse DEFAULT_BUFFER_SIZE) * 2, max_message_size);
1014
1015 var buffer_provider = try buffer.Provider.init(allocator, .{
1016 .max = max_message_size,
1017 .size = large_buffer_size,
1018 .count = large_buffer_pool,
1019 });
1020 errdefer buffer_provider.deinit();
1021
1022 return .{
1023 .config = config,
1024 .handshake_pool = handshake_pool,
1025 .buffer_provider = buffer_provider,
1026 };
1027 }
1028
1029 pub fn deinit(self: *WorkerState) void {
1030 self.handshake_pool.deinit();
1031 self.buffer_provider.deinit();
1032 }
1033};
1034
1035// In the Blocking worker, all the state could be stored on the spawn'd threads
1036// stack. The only reason we use a HandlerConn(H) in there is to be able to re-use
1037// code with the NonBlocking worker.
1038//
1039// For the NonBlocking worker, HandlerConn(H) is critical as it contains all the
1040// state for a connection. It lives on the heap, a pointer is registered into
1041// the event loop, and passed back when data is ready to read.
1042// * If handler is null, it means we haven't done our handshake yet.
1043// * If handler is null AND handshake is null, it means we havent' received
1044// any data yet (we delay creating the handshake state until we at least have
1045// some data ready).
1046pub fn HandlerConn(comptime H: type) type {
1047 return struct {
1048 state: State,
1049 conn: Conn,
1050 handler: ?H,
1051 reader: ?Reader,
1052 socket: posix.socket_t, // denormalization from conn.stream.handle
1053 handshake: ?*Handshake.State,
1054 next: ?*HandlerConn(H) = null,
1055 prev: ?*HandlerConn(H) = null,
1056
1057 const State = enum {
1058 handshake,
1059 active,
1060 };
1061 };
1062}
1063
1064pub fn ConnManager(comptime H: type, comptime MANAGE_HS: bool) type {
1065 return struct {
1066 lock: Thread.Mutex,
1067 active: List(HandlerConn(H)),
1068 pending: List(HandlerConn(H)),
1069 pool: std.heap.MemoryPool(HandlerConn(H)),
1070
1071 const Self = @This();
1072
1073 pub fn init(allocator: Allocator) !Self {
1074 var pool = std.heap.MemoryPool(HandlerConn(H)).init(allocator);
1075 errdefer pool.deinit();
1076
1077 return .{
1078 .lock = .{},
1079 .pool = pool,
1080 .active = .{},
1081 .pending = .{},
1082 };
1083 }
1084
1085 pub fn deinit(self: *Self) void {
1086 self.pool.deinit();
1087 }
1088
1089 pub fn count(self: *Self) usize {
1090 self.lock.lock();
1091 defer self.lock.unlock();
1092 if (MANAGE_HS == false) {
1093 return self.active.len;
1094 }
1095 return self.active.len + self.pending.len;
1096 }
1097
1098 pub fn create(self: *Self, socket: posix.socket_t, address: net.Address, now: u32) !*HandlerConn(H) {
1099 errdefer posix.close(socket);
1100
1101 self.lock.lock();
1102 defer self.lock.unlock();
1103
1104 const hc = try self.pool.create();
1105 hc.* = .{
1106 .state = if (MANAGE_HS) .handshake else .active,
1107 .socket = socket,
1108 .handler = null,
1109 .handshake = null,
1110 .reader = null,
1111 .conn = .{
1112 ._closed = false,
1113 .started = now,
1114 .address = address,
1115 .stream = .{ .handle = socket },
1116 },
1117 };
1118
1119 if (comptime MANAGE_HS) {
1120 // Still waiting for a handshake. Only care about this with the full
1121 // NonBlocking worker
1122 self.pending.insert(hc);
1123 } else {
1124 self.active.insert(hc);
1125 }
1126 return hc;
1127 }
1128
1129 pub fn activate(self: *Self, hc: *HandlerConn(H)) void {
1130 std.debug.assert(MANAGE_HS == true);
1131
1132 // our caller made sute this was the case
1133 std.debug.assert(hc.state == .handshake);
1134 self.lock.lock();
1135 defer self.lock.unlock();
1136 self.pending.remove(hc);
1137 self.active.insert(hc);
1138 hc.state = .active;
1139 }
1140
1141 pub fn inactive(self: *Self, hc: *HandlerConn(H)) void {
1142 std.debug.assert(MANAGE_HS == false);
1143
1144 // this should only be called when we need more data to complete the handshake
1145 // which should only happen on an active connection
1146 std.debug.assert(hc.state == .active);
1147 hc.state = .handshake;
1148
1149 self.lock.lock();
1150 defer self.lock.unlock();
1151 self.active.remove(hc);
1152 self.pending.insert(hc);
1153 }
1154
1155 pub fn cleanup(self: *Self, hc: *HandlerConn(H)) void {
1156 if (hc.handshake) |h| {
1157 h.release();
1158 }
1159
1160 if (hc.reader) |*r| {
1161 r.deinit();
1162 }
1163
1164 if (hc.handler) |*h| {
1165 if (comptime std.meta.hasFn(H, "close")) {
1166 h.close();
1167 }
1168 hc.handler = null;
1169 }
1170
1171 self.lock.lock();
1172 if (hc.state == .active) {
1173 self.active.remove(hc);
1174 } else {
1175 self.pending.remove(hc);
1176 }
1177
1178 self.pool.destroy(hc);
1179 self.lock.unlock();
1180 }
1181
1182 pub fn shutdown(self: *Self, worker: anytype) void {
1183 self.lock.lock();
1184 defer self.lock.unlock();
1185
1186 shutdownList(self.active.head, worker);
1187 shutdownList(self.pending.head, worker);
1188 }
1189
1190 // This is sloppy and leaves things in an unrecoverable state. To keep
1191 // things clean, we should call self.cleanup(hc) on each entry in the list
1192 // but that does a bunch of things we don't need if we know that we're
1193 // shutting down - like returning data to the pools, nd popping items
1194 // out of the list.
1195 fn shutdownList(head: ?*HandlerConn(H), worker: anytype) void {
1196 var next_node = head;
1197 while (next_node) |hc| {
1198 if (comptime std.meta.hasFn(H, "close")) {
1199 if (hc.handler) |*h| {
1200 h.close();
1201 hc.handler = null;
1202 }
1203 }
1204
1205 worker.shutdownCleanup(hc);
1206 const conn = &hc.conn;
1207 conn.closeSocket();
1208 next_node = hc.next;
1209 }
1210 }
1211 };
1212}
1213
1214// This is what actually gets exposed to the app
1215pub const Conn = struct {
1216 _closed: bool,
1217 started: u32,
1218 stream: net.Stream,
1219 address: net.Address,
1220 lock: Thread.Mutex = .{},
1221
1222 pub fn isClosed(self: *Conn) bool {
1223 // don't use lock to protect _closed. `isClosed` is called from
1224 // the worker thread and we don't want that potentially blocked while
1225 // a write is going on.
1226 return @atomicLoad(bool, &self._closed, .monotonic);
1227 }
1228
1229 pub fn writeBin(self: *Conn, data: []const u8) !void {
1230 return self.writeFrame(.binary, data);
1231 }
1232
1233 pub fn writeText(self: *Conn, data: []const u8) !void {
1234 return self.writeFrame(.text, data);
1235 }
1236
1237 pub fn write(self: *Conn, data: []const u8) !void {
1238 return self.writeFrame(.text, data);
1239 }
1240
1241 pub fn writePing(self: *Conn, data: []u8) !void {
1242 return self.writeFrame(.ping, data);
1243 }
1244
1245 pub fn writePong(self: *Conn, data: []u8) !void {
1246 return self.writeFrame(.pong, data);
1247 }
1248
1249 const CloseOpts = struct {
1250 code: u16 = 1000,
1251 reason: []const u8 = "",
1252 };
1253
1254 pub fn close(self: *Conn, opts: CloseOpts) !void {
1255 if (self.isClosed()) {
1256 return;
1257 }
1258 defer self.closeSocket();
1259
1260 const reason = opts.reason;
1261 if (reason.len == 0) {
1262 var buf: [2]u8 = undefined;
1263 std.mem.writeInt(u16, &buf, opts.code, .big);
1264 return self.writeFrame(.close, &buf);
1265 }
1266
1267 if (reason.len > 123) {
1268 return error.ReasonTooLong;
1269 }
1270
1271 var buf: [4]u8 = undefined;
1272 buf[0] = @intFromEnum(OpCode.close);
1273 buf[1] = @intCast(reason.len + 2);
1274 std.mem.writeInt(u16, buf[2..], opts.code, .big);
1275
1276 var vec = [2]std.posix.iovec_const{
1277 .{ .len = buf.len, .base = &buf },
1278 .{ .len = reason.len, .base = reason.ptr },
1279 };
1280
1281 try writeAllIOVec(self, &vec);
1282 }
1283
1284 pub fn writeFrame(self: *Conn, op_code: OpCode, data: []const u8) !void {
1285 const l: u64 = data.len;
1286 const stream = self.stream;
1287
1288 // maximum possible prefix length. op_code + length_type + 8byte length
1289 var buf: [10]u8 = undefined;
1290 var header: []const u8 = undefined;
1291 buf[0] = @intFromEnum(op_code);
1292
1293 if (l <= 125) {
1294 buf[1] = @intCast(l);
1295 header = buf[0..2];
1296 } else if (l < 65536) {
1297 buf[1] = 126;
1298 buf[2] = @intCast((l >> 8) & 0xFF);
1299 buf[3] = @intCast(l & 0xFF);
1300 header = buf[0..4];
1301 } else {
1302 buf[1] = 127;
1303 buf[2] = @intCast((l >> 56) & 0xFF);
1304 buf[3] = @intCast((l >> 48) & 0xFF);
1305 buf[4] = @intCast((l >> 40) & 0xFF);
1306 buf[5] = @intCast((l >> 32) & 0xFF);
1307 buf[6] = @intCast((l >> 24) & 0xFF);
1308 buf[7] = @intCast((l >> 16) & 0xFF);
1309 buf[8] = @intCast((l >> 8) & 0xFF);
1310 buf[9] = @intCast(l & 0xFF);
1311 header = buf[0..];
1312 }
1313
1314 if (l == 0) {
1315 // no body, just write the header
1316 self.lock.lock();
1317 defer self.lock.unlock();
1318 return stream.writeAll(header);
1319 }
1320
1321 var vec = [2]std.posix.iovec_const{
1322 .{ .len = header.len, .base = header.ptr },
1323 .{ .len = data.len, .base = data.ptr },
1324 };
1325
1326 return self.writeAllIOVec(&vec);
1327 }
1328
1329 pub fn writeFramed(self: *Conn, data: []const u8) !void {
1330 self.lock.lock();
1331 defer self.lock.unlock();
1332 try self.stream.writeAll(data);
1333 }
1334
1335 fn writeAllIOVec(self: *Conn, vec: []std.posix.iovec_const) !void {
1336 const socket = self.stream.handle;
1337
1338 self.lock.lock();
1339 defer self.lock.unlock();
1340
1341 var i: usize = 0;
1342 while (true) {
1343 var n = try std.posix.writev(socket, vec[i..]);
1344 while (n >= vec[i].len) {
1345 n -= vec[i].len;
1346 i += 1;
1347 if (i >= vec.len) return;
1348 }
1349 vec[i].base += n;
1350 vec[i].len -= n;
1351 }
1352 }
1353
1354 pub fn writeBuffer(self: *Conn, allocator: Allocator, op_code: OpCode) Writer {
1355 return .{
1356 .conn = self,
1357 .op_code = op_code,
1358 .buf = std.ArrayList(u8).init(allocator),
1359 };
1360 }
1361
1362 fn closeSocket(self: *Conn) void {
1363 if (@atomicRmw(bool, &self._closed, .Xchg, true, .monotonic) == false) {
1364 posix.close(self.stream.handle);
1365 }
1366 }
1367
1368 pub const Writer = struct {
1369 conn: *Conn,
1370 op_code: OpCode,
1371 buf: std.ArrayList(u8),
1372
1373 pub const Error = Allocator.Error;
1374 pub const IOWriter = std.io.Writer(*Writer, error{OutOfMemory}, Writer.write);
1375
1376 pub fn deinit(self: *Writer) void {
1377 self.buf.deinit();
1378 }
1379
1380 pub fn writer(self: *Writer) IOWriter {
1381 return .{.context = self};
1382 }
1383
1384 pub fn write(self: *Writer, data: []const u8) Allocator.Error!usize {
1385 try self.buf.appendSlice(data);
1386 return data.len;
1387 }
1388
1389 pub fn flush(self: *Writer) !void {
1390 try self.conn.writeFrame(self.op_code, self.buf.items);
1391 }
1392 };
1393};
1394
1395fn handleHandshake(comptime H: type, worker: anytype, hc: *HandlerConn(H), ctx: anytype) bool {
1396 return _handleHandshake(H, worker, hc, ctx) catch |err| {
1397 log.warn("({}) uncaugh error processing handshake: {}", .{ hc.conn.address, err });
1398 return false;
1399 };
1400}
1401
1402fn _handleHandshake(comptime H: type, worker: anytype, hc: *HandlerConn(H), ctx: anytype) !bool {
1403 std.debug.assert(hc.handler == null);
1404
1405 var state = hc.handshake orelse blk: {
1406 const s = try worker.handshake_pool.acquire();
1407 hc.handshake = s;
1408 break :blk s;
1409 };
1410
1411 var buf = state.buf;
1412 var conn = &hc.conn;
1413 const len = state.len;
1414
1415 if (len == buf.len) {
1416 log.warn("({}) handshake request exceeded maximum configured size ({d})", .{ conn.address, buf.len });
1417 return false;
1418 }
1419
1420 const n = posix.read(hc.socket, buf[len..]) catch |err| {
1421 switch (err) {
1422 error.BrokenPipe, error.ConnectionResetByPeer => log.debug("({}) handshake connection closed: {}", .{ conn.address, err }),
1423 error.WouldBlock => {
1424 std.debug.assert(blockingMode());
1425 log.debug("({}) handshake timeout", .{conn.address});
1426 },
1427 else => log.warn("({}) handshake error reading from socket: {}", .{ conn.address, err }),
1428 }
1429 return false;
1430 };
1431
1432 if (n == 0) {
1433 log.debug("({}) handshake connection closed", .{conn.address});
1434 return false;
1435 }
1436
1437 state.len = len + n;
1438 const handshake = Handshake.parse(state) catch |err| {
1439 log.debug("({}) error parsing handshake: {}", .{ conn.address, err });
1440 respondToHandshakeError(conn, err);
1441 return false;
1442 } orelse {
1443 // we need more data
1444 return true;
1445 };
1446
1447 state.release();
1448 hc.handshake = null;
1449
1450 // After this, the app has access to &hc.conn, so any access to the
1451 // conn has to be synchronized (which the conn does internally).
1452
1453 const handler = H.init(handshake, conn, ctx) catch |err| {
1454 if (comptime std.meta.hasFn(H, "handshakeErrorResponse")) {
1455 preHandOffWrite(H.handshakeErrorResponse(err));
1456 } else {
1457 respondToHandshakeError(conn, err);
1458 }
1459 log.debug("({}) " ++ @typeName(H) ++ ".init rejected request {}", .{ conn.address, err });
1460 return false;
1461 };
1462
1463 hc.handler = handler;
1464 try conn.writeFramed(&Handshake.createReply(handshake.key));
1465
1466 if (comptime std.meta.hasFn(H, "afterInit")) {
1467 const params = @typeInfo(@TypeOf(H.afterInit)).Fn.params;
1468 const res = if (params.len == 1) handler.afterInit() else handler.afterInit(ctx);
1469 res catch |err| {
1470 log.debug("({}) " ++ @typeName(H) ++ ".afterInit error: {}", .{ conn.address, err });
1471 return false;
1472 };
1473 }
1474
1475 log.debug("({}) connection successfully upgraded", .{conn.address});
1476 return true;
1477}
1478
1479fn handleClientData(comptime H: type, hc: *HandlerConn(H), allocator: Allocator, fba: *FixedBufferAllocator) bool {
1480 std.debug.assert(hc.handshake == null);
1481 return _handleClientData(H, hc, allocator, fba) catch |err| {
1482 log.warn("({}) uncaugh error handling incoming data: {}", .{ hc.conn.address, err });
1483 return false;
1484 };
1485}
1486
1487fn _handleClientData(comptime H: type, hc: *HandlerConn(H), allocator: Allocator, fba: *FixedBufferAllocator) !bool {
1488 var conn = &hc.conn;
1489 var reader = &hc.reader.?;
1490 reader.fill(conn.stream) catch |err| {
1491 switch (err) {
1492 error.BrokenPipe, error.Closed, error.ConnectionResetByPeer => log.debug("({}) connection closed: {}", .{ conn.address, err }),
1493 else => log.warn("({}) error reading from connection: {}", .{ conn.address, err }),
1494 }
1495 return false;
1496 };
1497
1498 const handler = &hc.handler.?;
1499 while (true) {
1500 const has_more, const message = reader.read() catch |err| {
1501 switch (err) {
1502 error.LargeControl => conn.writeFramed(CLOSE_PROTOCOL_ERROR) catch {},
1503 error.ReservedFlags => conn.writeFramed(CLOSE_PROTOCOL_ERROR) catch {},
1504 else => {},
1505 }
1506 log.debug("({}) invalid websocket packet: {}", .{ conn.address, err });
1507 return false;
1508 } orelse {
1509 // everything is fine, we just need more data
1510 return true;
1511 };
1512
1513 const message_type = message.type;
1514 defer reader.done(message_type);
1515
1516 log.debug("({}) received {s} message", .{ hc.conn.address, @tagName(message_type) });
1517 switch (message_type) {
1518 .text, .binary => {
1519 const params = @typeInfo(@TypeOf(H.clientMessage)).Fn.params;
1520 const needs_allocator = comptime needsAllocator(H);
1521
1522 var arena: std.heap.ArenaAllocator = undefined;
1523 var fallback_allocator: FallbackAllocator = undefined;
1524 var aa: Allocator = undefined;
1525
1526 if (comptime needs_allocator) {
1527 arena = std.heap.ArenaAllocator.init(allocator);
1528 if (comptime blockingMode()) {
1529 aa = arena.allocator();
1530 } else {
1531 fallback_allocator = FallbackAllocator{
1532 .fba = fba,
1533 .fallback = arena.allocator(),
1534 .fixed = fba.allocator(),
1535 };
1536 aa = fallback_allocator.allocator();
1537 }
1538 }
1539
1540 defer if (comptime needs_allocator) {
1541 arena.deinit();
1542 };
1543
1544 switch (comptime params.len) {
1545 2 => handler.clientMessage(message.data) catch return false,
1546 3 => if (needs_allocator) {
1547 handler.clientMessage(aa, message.data) catch return false;
1548 } else {
1549 handler.clientMessage(message.data, if (message_type == .text) .text else .binary) catch return false;
1550 },
1551 4 => handler.clientMessage(aa, message.data, if (message_type == .text) .text else .binary) catch return false,
1552 else => @compileError(@typeName(H) ++ ".clientMessage has invalid parameter count"),
1553 }
1554 },
1555 .pong => if (comptime std.meta.hasFn(H, "clientPong")) {
1556 try handler.clientPong();
1557 },
1558 .ping => {
1559 const data = message.data;
1560 if (comptime std.meta.hasFn(H, "clientPing")) {
1561 try handler.clientPing(data);
1562 } else if (data.len == 0) {
1563 try hc.conn.writeFramed(EMPTY_PONG);
1564 } else {
1565 try hc.conn.writeFrame(.pong, data);
1566 }
1567 },
1568 .close => {
1569 const data = message.data;
1570 if (comptime std.meta.hasFn(H, "clientClose")) {
1571 try handler.clientClose(data);
1572 return false;
1573 }
1574
1575 const l = data.len;
1576 if (l == 0) {
1577 try conn.close(.{});
1578 return false;
1579 }
1580
1581 if (l == 1) {
1582 // close with a payload always has to have at least a 2-byte payload,
1583 // since a 2-byte code is required
1584 try conn.writeFramed(CLOSE_PROTOCOL_ERROR);
1585 return false;
1586 }
1587
1588 const code = @as(u16, @intCast(data[1])) | (@as(u16, @intCast(data[0])) << 8);
1589 if (code < 1000 or code == 1004 or code == 1005 or code == 1006 or (code > 1013 and code < 3000)) {
1590 try conn.writeFramed(CLOSE_PROTOCOL_ERROR);
1591 return false;
1592 }
1593
1594 if (l == 2) {
1595 try conn.writeFramed(CLOSE_NORMAL);
1596 return false;
1597 }
1598
1599 const payload = data[2..];
1600 if (!std.unicode.utf8ValidateSlice(payload)) {
1601 // if we have a payload, it must be UTF8 (why?!)
1602 try conn.writeFramed(CLOSE_PROTOCOL_ERROR);
1603 } else {
1604 try conn.close(.{});
1605 }
1606 return false;
1607 },
1608 }
1609
1610 if (conn.isClosed()) {
1611 return false;
1612 }
1613
1614 if (has_more == false) {
1615 // we don't have more data ready to be processed in our buffer
1616 // back to our caller for more data
1617 return true;
1618 }
1619 }
1620}
1621
1622fn needsAllocator(comptime H: type) bool {
1623 const params = @typeInfo(@TypeOf(H.clientMessage)).Fn.params;
1624 return comptime params[1].type == Allocator;
1625}
1626
1627fn respondToHandshakeError(conn: *Conn, err: anyerror) void {
1628 const response = switch (err) {
1629 error.Close => return,
1630 error.RequestTooLarge => buildError(400, "too large"),
1631 error.Timeout, error.WouldBlock => buildError(400, "timeout"),
1632 error.InvalidProtocol => buildError(400, "invalid protocol"),
1633 error.InvalidRequestLine => buildError(400, "invalid requestline"),
1634 error.InvalidHeader => buildError(400, "invalid header"),
1635 error.InvalidUpgrade => buildError(400, "invalid upgrade"),
1636 error.InvalidVersion => buildError(400, "invalid version"),
1637 error.InvalidConnection => buildError(400, "invalid connection"),
1638 error.MissingHeaders => buildError(400, "missingheaders"),
1639 error.Empty => buildError(400, "invalid request"),
1640 else => buildError(400, "unknown"),
1641 };
1642 preHandOffWrite(conn, response);
1643}
1644
1645fn buildError(comptime status: u16, comptime err: []const u8) []const u8 {
1646 return std.fmt.comptimePrint("HTTP/1.1 {d} \r\nConnection: Close\r\nError: {s}\r\nContent-Length: 0\r\n\r\n", .{ status, err });
1647}
1648
1649fn preHandOffWrite(conn: *Conn, response: []const u8) void {
1650 // "preHandOff" means we haven't given the application handler a reference
1651 // to *Conn yet. In theory, this means we don't need to worry about thread-safety
1652 // However, it is possible for the worker to be stopped while we're doing this
1653 // which causes issues unless we lock
1654 conn.lock.lock();
1655 defer conn.lock.unlock();
1656
1657 if (conn.isClosed()) {
1658 return;
1659 }
1660
1661 const socket = conn.stream.handle;
1662 const timeout = std.mem.toBytes(posix.timeval{ .sec = 5, .usec = 0 });
1663 posix.setsockopt(socket, posix.SOL.SOCKET, posix.SO.SNDTIMEO, &timeout) catch return;
1664
1665 var pos: usize = 0;
1666 while (pos < response.len) {
1667 const n = posix.write(socket, response[pos..]) catch return;
1668 if (n == 0) {
1669 // closed
1670 return;
1671 }
1672 pos += n;
1673 }
1674}
1675
1676fn timestamp() u32 {
1677 if (comptime @hasDecl(std.c, "CLOCK") == false) {
1678 return @intCast(std.time.timestamp());
1679 }
1680 var ts: posix.timespec = undefined;
1681 posix.clock_gettime(posix.CLOCK.REALTIME, &ts) catch unreachable;
1682 return @intCast(ts.sec);
1683}
1684
1685// intrusive doubly-linked list with count, not thread safe
1686fn List(comptime T: type) type {
1687 return struct {
1688 len: usize = 0,
1689 head: ?*T = null,
1690 tail: ?*T = null,
1691
1692 const Self = @This();
1693
1694 pub fn insert(self: *Self, node: *T) void {
1695 if (self.tail) |tail| {
1696 tail.next = node;
1697 node.prev = tail;
1698 self.tail = node;
1699 } else {
1700 self.head = node;
1701 self.tail = node;
1702 }
1703 self.len += 1;
1704 node.next = null;
1705 }
1706
1707 pub fn remove(self: *Self, node: *T) void {
1708 if (node.prev) |prev| {
1709 prev.next = node.next;
1710 } else {
1711 self.head = node.next;
1712 }
1713
1714 if (node.next) |next| {
1715 next.prev = node.prev;
1716 } else {
1717 self.tail = node.prev;
1718 }
1719 node.prev = null;
1720 node.next = null;
1721 self.len -= 1;
1722 }
1723 };
1724}
1725
1726const t = @import("../t.zig");
1727
1728var test_thread: Thread = undefined;
1729var test_server: Server(TestHandler) = undefined;
1730var global_test_allocator = std.heap.GeneralPurposeAllocator(.{}){};
1731
1732test "tests:beforeAll" {
1733 test_server = try Server(TestHandler).init(global_test_allocator.allocator(), .{
1734 .port = 9292,
1735 .address = "127.0.0.1",
1736 });
1737 test_thread = try test_server.listenInNewThread({});
1738}
1739
1740test "tests:afterAll" {
1741 test_server.stop();
1742 test_thread.join();
1743 test_server.deinit();
1744 try t.expectEqual(false, global_test_allocator.detectLeaks());
1745}
1746
1747test "Server: invalid handshake" {
1748 const stream = try testStream(false);
1749 defer stream.close();
1750
1751 try stream.writeAll("GET / HTTP/1.1\r\n\r\n");
1752 var buf: [1024]u8 = undefined;
1753 var pos: usize = 0;
1754 while (pos < buf.len) {
1755 const n = try stream.read(buf[0..]);
1756 if (n == 0) {
1757 break;
1758 }
1759 pos += n;
1760 } else {
1761 unreachable;
1762 }
1763
1764 try t.expectString("HTTP/1.1 400 \r\nConnection: Close\r\nError: missingheaders\r\nContent-Length: 0\r\n\r\n", buf[0..pos]);
1765}
1766
1767test "Server: read and write" {
1768 const stream = try testStream(true);
1769 defer stream.close();
1770
1771 try stream.writeAll(&proto.frame(.text, "over"));
1772 var buf: [12]u8 = undefined;
1773 _ = try stream.readAtLeast(&buf, 6);
1774 try t.expectSlice(u8, &.{ 129, 4, '9', '0', '0', '0' }, buf[0..6]);
1775}
1776
1777test "Server: clientMessage allocator" {
1778 const stream = try testStream(true);
1779 defer stream.close();
1780
1781 try stream.writeAll(&proto.frame(.text, "dyn"));
1782 var buf: [12]u8 = undefined;
1783 _ = try stream.readAtLeast(&buf, 12);
1784 try t.expectSlice(u8, &.{ 129, 10, 'o', 'v', 'e', 'r', ' ', '9', '0', '0', '0', '!' }, buf[0..12]);
1785}
1786
1787test "Server: clientMessage writer" {
1788 const stream = try testStream(true);
1789 defer stream.close();
1790
1791 try stream.writeAll(&proto.frame(.text, "writer"));
1792 var buf: [9]u8 = undefined;
1793 _ = try stream.readAtLeast(&buf, 9);
1794 try t.expectSlice(u8, &.{ 129, 7, '9', '0', '0', '0', '!', '!', '!' }, buf[0..9]);
1795}
1796
1797// Same as above, but client doesn't shutdown the connection
1798// When afterAll is runs and things are shutdown, this should still be properly cleaned up
1799test "Server: dirty clientMessage allocator" {
1800 const stream = try testStream(true);
1801
1802 try stream.writeAll(&proto.frame(.text, "dyn"));
1803 var buf: [12]u8 = undefined;
1804 _ = try stream.readAtLeast(&buf, 12);
1805 try t.expectSlice(u8, &.{ 129, 10, 'o', 'v', 'e', 'r', ' ', '9', '0', '0', '0', '!' }, buf[0..12]);
1806}
1807
1808test "Conn: close" {
1809 {
1810 // plain close
1811 const stream = try testStream(true);
1812 defer stream.close();
1813 try stream.writeAll(&proto.frame(.text, "close1"));
1814 var buf: [4]u8 = undefined;
1815 _ = try stream.readAtLeast(&buf, 4);
1816 try t.expectSlice(u8, &.{ 136, 2, 3, 232 }, buf[0..4]);
1817 }
1818
1819 {
1820 // close with code
1821 const stream = try testStream(true);
1822 defer stream.close();
1823 try stream.writeAll(&proto.frame(.text, "close2"));
1824 var buf: [4]u8 = undefined;
1825 _ = try stream.readAtLeast(&buf, 4);
1826 try t.expectSlice(u8, &.{ 136, 2, 0, 0x7b }, buf[0..4]);
1827 }
1828
1829 {
1830 // close with reason
1831 const stream = try testStream(true);
1832 defer stream.close();
1833 try stream.writeAll(&proto.frame(.text, "close3"));
1834 var buf: [7]u8 = undefined;
1835 _ = try stream.readAtLeast(&buf, 7);
1836 try t.expectSlice(u8, &.{ 136, 5, 0, 0xea, 'b', 'y', 'e' }, buf[0..7]);
1837 }
1838}
1839
1840fn testStream(handshake: bool) !net.Stream {
1841 const timeout = std.mem.toBytes(std.posix.timeval{ .sec = 0, .usec = 20_000 });
1842 const address = try std.net.Address.parseIp("127.0.0.1", 9292);
1843 const stream = try std.net.tcpConnectToAddress(address);
1844 try std.posix.setsockopt(stream.handle, std.posix.SOL.SOCKET, std.posix.SO.RCVTIMEO, &timeout);
1845 try std.posix.setsockopt(stream.handle, std.posix.SOL.SOCKET, std.posix.SO.SNDTIMEO, &timeout);
1846
1847 if (handshake == false) {
1848 return stream;
1849 }
1850
1851 try stream.writeAll("GET / HTTP/1.1\r\ncontent-length: 0\r\nupgrade: websocket\r\nsec-websocket-version: 13\r\nconnection: upgrade\r\nsec-websocket-key: my-key\r\n\r\n");
1852 var buf: [1024]u8 = undefined;
1853 var pos: usize = 0;
1854 while (pos < buf.len) {
1855 const n = try stream.read(buf[0..]);
1856 if (n == 0) break;
1857
1858 pos += n;
1859 if (std.mem.endsWith(u8, buf[0..pos], "\r\n\r\n")) {
1860 break;
1861 }
1862 } else {
1863 unreachable;
1864 }
1865
1866 try t.expectString("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: upgrade\r\nSec-Websocket-Accept: L8KGBs4w2MNLLzhfzlVoM0scCIE=\r\n\r\n", buf[0..pos]);
1867
1868 return stream;
1869}
1870
1871const TestHandler = struct {
1872 conn: *Conn,
1873
1874 pub fn init(_: Handshake, conn: *Conn, _: void) !TestHandler {
1875 return .{
1876 .conn = conn,
1877 };
1878 }
1879 pub fn clientMessage(self: *TestHandler, allocator: Allocator, data: []const u8,) !void {
1880 if (std.mem.eql(u8, data, "over")) {
1881 return self.conn.writeText("9000");
1882 }
1883 if (std.mem.eql(u8, data, "dyn")) {
1884 return self.conn.writeText(try std.fmt.allocPrint(allocator, "over {d}!", .{9000}));
1885 }
1886 if (std.mem.eql(u8, data, "writer")) {
1887 var wb = self.conn.writeBuffer(allocator, .text);
1888 try std.fmt.format(wb.writer(), "{d}!!!", .{9000});
1889 return wb.flush();
1890 }
1891 if (std.mem.eql(u8, data, "close1")) {
1892 return self.conn.close(.{});
1893 }
1894 if (std.mem.eql(u8, data, "close2")) {
1895 return self.conn.close(.{.code = 123});
1896 }
1897 if (std.mem.eql(u8, data, "close3")) {
1898 return self.conn.close(.{.code = 234, .reason = "bye"});
1899 }
1900 }
1901};
1902
1903test "List" {
1904 var list = List(TestNode){};
1905 try expectList(&.{}, list);
1906
1907 var n1 = TestNode{ .id = 1 };
1908 list.insert(&n1);
1909 try expectList(&.{1}, list);
1910
1911 list.remove(&n1);
1912 try expectList(&.{}, list);
1913
1914 var n2 = TestNode{ .id = 2 };
1915 list.insert(&n2);
1916 list.insert(&n1);
1917 try expectList(&.{ 2, 1 }, list);
1918
1919 var n3 = TestNode{ .id = 3 };
1920 list.insert(&n3);
1921 try expectList(&.{ 2, 1, 3 }, list);
1922
1923 list.remove(&n1);
1924 try expectList(&.{ 2, 3 }, list);
1925
1926 list.insert(&n1);
1927 try expectList(&.{ 2, 3, 1 }, list);
1928
1929 list.remove(&n2);
1930 try expectList(&.{ 3, 1 }, list);
1931
1932 list.remove(&n1);
1933 try expectList(&.{3}, list);
1934
1935 list.remove(&n3);
1936 try expectList(&.{}, list);
1937}
1938
1939const TestNode = struct {
1940 id: i32,
1941 next: ?*TestNode = null,
1942 prev: ?*TestNode = null,
1943};
1944
1945fn expectList(expected: []const i32, list: List(TestNode)) !void {
1946 if (expected.len == 0) {
1947 try t.expectEqual(null, list.head);
1948 try t.expectEqual(null, list.tail);
1949 return;
1950 }
1951
1952 var i: usize = 0;
1953 var next = list.head;
1954 while (next) |node| {
1955 try t.expectEqual(expected[i], node.id);
1956 i += 1;
1957 next = node.next;
1958 }
1959 try t.expectEqual(expected.len, i);
1960
1961 i = expected.len;
1962 var prev = list.tail;
1963 while (prev) |node| {
1964 i -= 1;
1965 try t.expectEqual(expected[i], node.id);
1966 prev = node.prev;
1967 }
1968 try t.expectEqual(0, i);
1969}