A websocket implementation for zig
0

Configure Feed

Select the types of activity you want to include in your feed.

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