An HTTP/1.1 server for zig
0

Configure Feed

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

http.zig / src / httpz.zig
70 kB 1881 lines
1const std = @import("std"); 2const builtin = @import("builtin"); 3 4pub const testing = @import("testing.zig"); 5pub const websocket = @import("websocket"); 6 7pub const routing = @import("router.zig"); 8pub const request = @import("request.zig"); 9pub const response = @import("response.zig"); 10pub const key_value = @import("key_value.zig"); 11pub const middleware = @import("middleware/middleware.zig"); 12 13pub const Router = routing.Router; 14pub const Request = request.Request; 15pub const Response = response.Response; 16pub const Url = @import("url.zig").Url; 17pub const Config = @import("config.zig").Config; 18 19const Thread = std.Thread; 20const net = std.net; 21const posix = std.posix; 22const Allocator = std.mem.Allocator; 23const FixedBufferAllocator = std.heap.FixedBufferAllocator; 24 25const log = std.log.scoped(.httpz); 26 27const worker = @import("worker.zig"); 28const HTTPConn = worker.HTTPConn; 29 30const build = @import("build"); 31const force_blocking: bool = if (@hasDecl(build, "httpz_blocking")) build.httpz_blocking else false; 32 33const MAX_REQUEST_COUNT = std.math.maxInt(usize); 34 35pub fn writeMetrics(writer: anytype) !void { 36 return @import("metrics.zig").write(writer); 37} 38 39pub const Protocol = enum { 40 HTTP10, 41 HTTP11, 42}; 43 44pub const Method = enum { 45 GET, 46 HEAD, 47 POST, 48 PUT, 49 PATCH, 50 DELETE, 51 OPTIONS, 52 CONNECT, 53 OTHER, 54}; 55 56pub const ContentType = enum { 57 BINARY, 58 CSS, 59 CSV, 60 EOT, 61 EVENTS, 62 GIF, 63 GZ, 64 HTML, 65 ICO, 66 JPG, 67 JS, 68 JSON, 69 OTF, 70 PDF, 71 PNG, 72 SVG, 73 TAR, 74 TEXT, 75 TTF, 76 WASM, 77 WEBP, 78 WOFF, 79 WOFF2, 80 XML, 81 UNKNOWN, 82 83 const asUint = @import("url.zig").asUint; 84 85 pub fn forExtension(ext: []const u8) ContentType { 86 if (ext.len == 0) return .UNKNOWN; 87 const temp = if (ext[0] == '.') ext[1..] else ext; 88 if (temp.len > 5) return .UNKNOWN; 89 90 var normalized: [5]u8 = undefined; 91 for (temp, 0..) |c, i| { 92 normalized[i] = std.ascii.toLower(c); 93 } 94 95 switch (temp.len) { 96 2 => { 97 switch (@as(u16, @bitCast(normalized[0..2].*))) { 98 asUint("js") => return .JS, 99 asUint("gz") => return .GZ, 100 else => return .UNKNOWN, 101 } 102 }, 103 3 => { 104 switch (@as(u24, @bitCast(normalized[0..3].*))) { 105 asUint("css") => return .CSS, 106 asUint("csv") => return .CSV, 107 asUint("eot") => return .EOT, 108 asUint("gif") => return .GIF, 109 asUint("htm") => return .HTML, 110 asUint("ico") => return .ICO, 111 asUint("jpg") => return .JPG, 112 asUint("otf") => return .OTF, 113 asUint("pdf") => return .PDF, 114 asUint("png") => return .PNG, 115 asUint("svg") => return .SVG, 116 asUint("tar") => return .TAR, 117 asUint("ttf") => return .TTF, 118 asUint("xml") => return .XML, 119 else => return .UNKNOWN, 120 } 121 }, 122 4 => { 123 switch (@as(u32, @bitCast(normalized[0..4].*))) { 124 asUint("jpeg") => return .JPG, 125 asUint("json") => return .JSON, 126 asUint("html") => return .HTML, 127 asUint("text") => return .TEXT, 128 asUint("wasm") => return .WASM, 129 asUint("woff") => return .WOFF, 130 asUint("webp") => return .WEBP, 131 else => return .UNKNOWN, 132 } 133 }, 134 5 => { 135 switch (@as(u40, @bitCast(normalized[0..5].*))) { 136 asUint("woff2") => return .WOFF2, 137 else => return .UNKNOWN, 138 } 139 }, 140 else => return .UNKNOWN, 141 } 142 return .UNKNOWN; 143 } 144 145 pub fn forFile(file_name: []const u8) ContentType { 146 return forExtension(std.fs.path.extension(file_name)); 147 } 148}; 149 150// When we initialize our Server(handler: type) with a non-void handler, 151// the ActionContext will either be defined by the handler or it'll be the 152// handler itself. So, for this type, "ActionContext" can be either 153// the Handler or ActionContext from the Server. 154pub fn Action(comptime ActionContext: type) type { 155 if (ActionContext == void) { 156 return *const fn (*Request, *Response) anyerror!void; 157 } 158 return *const fn (ActionContext, *Request, *Response) anyerror!void; 159} 160 161pub fn Dispatcher(comptime Handler: type, comptime ActionArg: type) type { 162 if (Handler == void) { 163 return *const fn (Action(void), *Request, *Response) anyerror!void; 164 } 165 return *const fn (Handler, ActionArg, *Request, *Response) anyerror!void; 166} 167 168pub fn DispatchableAction(comptime Handler: type, comptime ActionArg: type) type { 169 return struct { 170 data: ?*const anyopaque, 171 handler: Handler, 172 action: ActionArg, 173 dispatcher: Dispatcher(Handler, ActionArg), 174 middlewares: []const Middleware(Handler) = &.{}, 175 }; 176} 177 178pub fn Middleware(comptime H: type) type { 179 return struct { 180 ptr: *anyopaque, 181 deinitFn: *const fn (ptr: *anyopaque) void, 182 executeFn: *const fn (ptr: *anyopaque, req: *Request, res: *Response, executor: *Server(H).Executor) anyerror!void, 183 184 const Self = @This(); 185 186 pub fn init(ptr: anytype) Self { 187 const T = @TypeOf(ptr); 188 const ptr_info = @typeInfo(T); 189 190 const gen = struct { 191 pub fn deinit(pointer: *anyopaque) void { 192 const self: T = @ptrCast(@alignCast(pointer)); 193 if (std.meta.hasMethod(T, "deinit")) { 194 return ptr_info.pointer.child.deinit(self); 195 } 196 } 197 198 pub fn execute(pointer: *anyopaque, req: *Request, res: *Response, executor: *Server(H).Executor) anyerror!void { 199 const self: T = @ptrCast(@alignCast(pointer)); 200 return ptr_info.pointer.child.execute(self, req, res, executor); 201 } 202 }; 203 204 return .{ 205 .ptr = ptr, 206 .deinitFn = gen.deinit, 207 .executeFn = gen.execute, 208 }; 209 } 210 211 pub fn deinit(self: Self) void { 212 self.deinitFn(self.ptr); 213 } 214 215 pub fn execute(self: Self, req: *Request, res: *Response, executor: *Server(H).Executor) !void { 216 return self.executeFn(self.ptr, req, res, executor); 217 } 218 }; 219} 220 221// When no WebsocketHandler is specified, we give it a dummy handler just to get 222// the code to compile. 223pub const DummyWebsocketHandler = struct { 224 pub fn clientMessage(_: DummyWebsocketHandler, _: []const u8) !void {} 225}; 226 227pub const MiddlewareConfig = struct { 228 arena: Allocator, 229 allocator: Allocator, 230}; 231 232pub fn Server(comptime H: type) type { 233 const Handler = switch (@typeInfo(H)) { 234 .@"struct" => H, 235 .pointer => |ptr| ptr.child, 236 .void => void, 237 else => @compileError("Server handler must be a struct, got: " ++ @tagName(@typeInfo(H))), 238 }; 239 240 const ActionArg = if (comptime std.meta.hasFn(Handler, "dispatch")) @typeInfo(@TypeOf(Handler.dispatch)).@"fn".params[1].type.? else Action(H); 241 242 const has_websocket = Handler != void and @hasDecl(Handler, "WebsocketHandler"); 243 const WebsocketHandler = if (has_websocket) Handler.WebsocketHandler else DummyWebsocketHandler; 244 245 const RouterConfig = struct { 246 middlewares: []const Middleware(H) = &.{}, 247 }; 248 249 const MiddlewareItem = struct { 250 middleware: Middleware(H), 251 node: std.SinglyLinkedList.Node = .{}, 252 }; 253 254 return struct { 255 handler: H, 256 config: Config, 257 arena: Allocator, 258 allocator: Allocator, 259 _router: Router(H, ActionArg), 260 _mut: Thread.Mutex, 261 _workers: []Worker, 262 _cond: Thread.Condition, 263 _listener: ?posix.socket_t, 264 _max_request_per_connection: usize, 265 _middlewares: []const Middleware(H), 266 _websocket_state: websocket.server.WorkerState, 267 _middleware_registry: std.SinglyLinkedList, 268 269 const Self = @This(); 270 const Worker = if (blockingMode()) worker.Blocking(*Self, WebsocketHandler) else worker.NonBlocking(*Self, WebsocketHandler); 271 272 pub fn init(allocator: Allocator, config: Config, handler: H) !Self { 273 // Be mindful about where we pass this arena. Most things are able to 274 // do dynamic allocation, and need to be able to free when they're 275 // done with their memory. Only use this for stuff that's created on 276 // startup and won't dynamically need to grow/shrink. 277 const arena = try allocator.create(std.heap.ArenaAllocator); 278 errdefer allocator.destroy(arena); 279 arena.* = std.heap.ArenaAllocator.init(allocator); 280 errdefer arena.deinit(); 281 282 const default_dispatcher = if (comptime Handler == void) defaultDispatcher else defaultDispatcherWithHandler; 283 284 // do not pass arena.allocator to WorkerState, it needs to be able to 285 // allocate and free at will. 286 const ws_config = config.websocket; 287 var websocket_state = try websocket.server.WorkerState.init(allocator, .{ 288 .max_message_size = ws_config.max_message_size, 289 .buffers = .{ 290 .small_size = if (has_websocket) ws_config.small_buffer_size else 0, 291 .small_pool = if (has_websocket) ws_config.small_buffer_pool else 0, 292 .large_size = if (has_websocket) ws_config.large_buffer_size else 0, 293 .large_pool = if (has_websocket) ws_config.large_buffer_pool else 0, 294 }, 295 // disable handshake memory allocation since httpz is handling 296 // the handshake request directly 297 .handshake = .{ 298 .count = 0, 299 .max_size = 0, 300 .max_headers = 0, 301 }, 302 .compression = if (ws_config.compression) .{ 303 .write_threshold = ws_config.compression_write_treshold, 304 .retain_write_buffer = ws_config.compression_retain_writer, 305 } else null, 306 }); 307 errdefer websocket_state.deinit(); 308 309 const workers = try arena.allocator().alloc(Worker, config.workerCount()); 310 311 return .{ 312 .config = config, 313 .handler = handler, 314 .allocator = allocator, 315 .arena = arena.allocator(), 316 ._mut = .{}, 317 ._cond = .{}, 318 ._workers = workers, 319 ._listener = null, 320 ._middlewares = &.{}, 321 ._middleware_registry = .{}, 322 ._websocket_state = websocket_state, 323 ._router = try Router(H, ActionArg).init(arena.allocator(), default_dispatcher, handler), 324 ._max_request_per_connection = config.timeout.request_count orelse MAX_REQUEST_COUNT, 325 }; 326 } 327 328 pub fn deinit(self: *Self) void { 329 self._websocket_state.deinit(); 330 331 var node = self._middleware_registry.first; 332 while (node) |n| { 333 const item: *MiddlewareItem = @fieldParentPtr("node", n); 334 item.middleware.deinit(); 335 node = n.next; 336 } 337 338 const arena: *std.heap.ArenaAllocator = @ptrCast(@alignCast(self.arena.ptr)); 339 arena.deinit(); 340 self.allocator.destroy(arena); 341 } 342 343 pub fn listen(self: *Self) !void { 344 // incase "stop" is waiting 345 defer self._cond.signal(); 346 self._mut.lock(); 347 348 const config = self.config; 349 350 var no_delay = true; 351 const address = blk: { 352 if (config.unix_path) |unix_path| { 353 if (comptime std.net.has_unix_sockets == false) { 354 return error.UnixPathNotSupported; 355 } 356 no_delay = false; 357 std.fs.deleteFileAbsolute(unix_path) catch {}; 358 break :blk try net.Address.initUnix(unix_path); 359 } else { 360 const listen_port = config.port orelse 5882; 361 const listen_address = config.address orelse "127.0.0.1"; 362 break :blk try net.Address.parseIp(listen_address, listen_port); 363 } 364 }; 365 366 const listener = blk: { 367 var sock_flags: u32 = posix.SOCK.STREAM | posix.SOCK.CLOEXEC; 368 if (blockingMode() == false) sock_flags |= posix.SOCK.NONBLOCK; 369 370 const proto = if (address.any.family == posix.AF.UNIX) @as(u32, 0) else posix.IPPROTO.TCP; 371 break :blk try posix.socket(address.any.family, sock_flags, proto); 372 }; 373 374 if (no_delay) { 375 // TODO: Broken on darwin: 376 // https://github.com/ziglang/zig/issues/17260 377 // if (@hasDecl(os.TCP, "NODELAY")) { 378 // try os.setsockopt(socket.sockfd.?, os.IPPROTO.TCP, os.TCP.NODELAY, &std.mem.toBytes(@as(c_int, 1))); 379 // } 380 try posix.setsockopt(listener, posix.IPPROTO.TCP, 1, &std.mem.toBytes(@as(c_int, 1))); 381 } 382 383 try posix.setsockopt(listener, posix.SOL.SOCKET, posix.SO.REUSEADDR, &std.mem.toBytes(@as(c_int, 1))); 384 385 if (config.unix_path == null and self._workers.len > 1) { 386 if (@hasDecl(posix.SO, "REUSEPORT_LB")) { 387 try posix.setsockopt(listener, posix.SOL.SOCKET, posix.SO.REUSEPORT_LB, &std.mem.toBytes(@as(c_int, 1))); 388 } else if (@hasDecl(posix.SO, "REUSEPORT")) { 389 try posix.setsockopt(listener, posix.SOL.SOCKET, posix.SO.REUSEPORT, &std.mem.toBytes(@as(c_int, 1))); 390 } 391 } 392 393 { 394 const socklen = address.getOsSockLen(); 395 try posix.bind(listener, &address.any, socklen); 396 try posix.listen(listener, 1024); // kernel backlog 397 } 398 399 self._listener = listener; 400 401 var workers = self._workers; 402 const allocator = self.allocator; 403 404 if (comptime blockingMode()) { 405 workers[0] = try worker.Blocking(*Self, WebsocketHandler).init(allocator, self, &config); 406 defer workers[0].deinit(); 407 408 const thrd = try Thread.spawn(.{}, worker.Blocking(*Self, WebsocketHandler).listen, .{ &workers[0], listener }); 409 410 // incase listenInNewThread was used and is waiting for us to start 411 self._cond.signal(); 412 self._mut.unlock(); 413 414 // This will unblock when server.stop() is called and the listening 415 // socket is closed. 416 thrd.join(); 417 } else { 418 var started: usize = 0; 419 defer for (0..started) |i| { 420 workers[i].deinit(); 421 }; 422 423 errdefer for (0..started) |i| { 424 workers[i].stop(); 425 }; 426 427 var ready_sem = std.Thread.Semaphore{}; 428 const threads = try self.arena.alloc(Thread, workers.len); 429 for (0..workers.len) |i| { 430 workers[i] = try Worker.init(allocator, self, &config); 431 errdefer { 432 workers[i].stop(); 433 workers[i].deinit(); 434 } 435 threads[i] = try Thread.spawn(.{}, Worker.run, .{ &workers[i], listener, &ready_sem }); 436 started += 1; 437 } 438 439 for (0..workers.len) |_| { 440 ready_sem.wait(); 441 } 442 443 // incase listenInNewThread was used and is waiting for us to start 444 self._cond.signal(); 445 self._mut.unlock(); 446 447 for (threads) |thrd| { 448 thrd.join(); 449 } 450 } 451 } 452 453 pub fn listenInNewThread(self: *Self) !std.Thread { 454 self._mut.lock(); 455 defer self._mut.unlock(); 456 const thrd = try std.Thread.spawn(.{}, listen, .{self}); 457 458 // we don't return until listen() signals us that the server is up 459 self._cond.wait(&self._mut); 460 461 return thrd; 462 } 463 464 pub fn stop(self: *Self) void { 465 self._mut.lock(); 466 defer self._mut.unlock(); 467 468 for (self._workers) |*w| { 469 w.stop(); 470 } 471 472 if (self._listener) |l| { 473 if (comptime blockingMode()) { 474 // necessary to unblock accept on linux 475 // (which might not be that necessary since, on Linux, 476 // NonBlocking should be used) 477 posix.shutdown(l, .recv) catch {}; 478 } 479 posix.close(l); 480 } 481 } 482 483 pub fn router(self: *Self, config: RouterConfig) !*Router(H, ActionArg) { 484 // we store this in self for us when no route is found (these will 485 // still be executed). 486 487 const owned = try self.arena.dupe(Middleware(H), config.middlewares); 488 self._middlewares = owned; 489 490 // we store this in router to append to add/append to created routes 491 self._router.middlewares = owned; 492 493 return &self._router; 494 } 495 496 fn defaultDispatcher(action: ActionArg, req: *Request, res: *Response) !void { 497 return action(req, res); 498 } 499 500 fn defaultDispatcherWithHandler(handler: H, action: ActionArg, req: *Request, res: *Response) !void { 501 if (comptime std.meta.hasFn(Handler, "dispatch")) { 502 return handler.dispatch(action, req, res); 503 } 504 return action(handler, req, res); 505 } 506 507 // This is always called from within a threadpool thread. For nonblocking, 508 // notifyingHandler (above) was the threadpool's main entry and it called this. 509 // For blocking, the threadpool was directed to the worker's handleConnection 510 // which eventually called this. 511 // thread_buf is a thread-specific configurable-sized buffer that we're 512 // free to use as we want. This is, by far, the most efficient memory 513 // we can use because it's allocated on server start and re-used on 514 // each request (which is safe, because, in blocking or nonblocking, once 515 // a request reaches this point, processing is blocking from the point 516 // of view of the server). 517 // We'll use thread_buf as part of a FallBackAllocator with the conn 518 // arena for our request ONLY. We cannot use thread_buf for the response 519 // because the response data must outlive the execution of this function 520 // (and thus, in nonblocking, outlives this threadpool's execution unit). 521 pub fn handleRequest(self: *Self, conn: *HTTPConn, thread_buf: []u8) void { 522 const aa = conn.req_arena.allocator(); 523 524 var fba = FixedBufferAllocator.init(thread_buf); 525 var fb = FallbackAllocator{ 526 .fba = &fba, 527 .fallback = aa, 528 .fixed = fba.allocator(), 529 }; 530 531 const allocator = fb.allocator(); 532 var req = Request.init(allocator, conn); 533 var res = Response.init(allocator, conn); 534 535 defer std.debug.assert(res.written == true); 536 537 if (comptime std.meta.hasFn(Handler, "handle")) { 538 if (comptime @typeInfo(@TypeOf(Handler.handle)).@"fn".return_type != void) { 539 @compileError(@typeName(Handler) ++ ".handle must return 'void'"); 540 } 541 self.handler.handle(&req, &res); 542 } else { 543 const dispatchable_action = self._router.route(req.method, req.method_string, req.url.path, req.params); 544 545 var executor = Executor{ 546 .index = 0, 547 .req = &req, 548 .res = &res, 549 .handler = self.handler, 550 .middlewares = undefined, 551 .dispatchable_action = dispatchable_action, 552 }; 553 554 if (dispatchable_action) |da| { 555 req.route_data = da.data; 556 executor.middlewares = da.middlewares; 557 } else { 558 req.route_data = null; 559 executor.middlewares = self._middlewares; 560 } 561 562 executor.next() catch |err| { 563 if (comptime std.meta.hasFn(Handler, "uncaughtError")) { 564 self.handler.uncaughtError(&req, &res, err); 565 } else { 566 res.status = 500; 567 res.body = "Internal Server Error"; 568 std.log.warn("httpz: unhandled exception for request: {s}\nErr: {}", .{ req.url.raw, err }); 569 } 570 }; 571 } 572 573 if (conn.handover == .unknown) { 574 // close is the default 575 conn.handover = if (req.canKeepAlive() and conn.request_count < self._max_request_per_connection) .keepalive else .close; 576 } 577 578 res.write() catch { 579 conn.handover = .close; 580 }; 581 582 if (req.unread_body > 0 and conn.handover == .keepalive) { 583 drain(&req) catch { 584 conn.handover = .close; 585 }; 586 } 587 } 588 589 pub fn middleware(self: *Self, comptime M: type, config: M.Config) !Middleware(H) { 590 const arena = self.arena; 591 592 const node = try arena.create(MiddlewareItem); 593 errdefer arena.destroy(node); 594 595 const m = try arena.create(M); 596 errdefer arena.destroy(m); 597 switch (comptime @typeInfo(@TypeOf(M.init)).@"fn".params.len) { 598 1 => m.* = try M.init(config), 599 2 => m.* = try M.init(config, MiddlewareConfig{ 600 .arena = arena, 601 .allocator = self.allocator, 602 }), 603 else => @compileError(@typeName(M) ++ ".init should accept 1 or 2 parameters"), 604 } 605 606 const iface = Middleware(H).init(m); 607 node.*.middleware = iface; 608 self._middleware_registry.prepend(&node.node); 609 610 return iface; 611 } 612 613 pub const Executor = struct { 614 index: usize, 615 req: *Request, 616 res: *Response, 617 handler: H, 618 // pull this out of da since we'll access it a lot (not really, but w/e) 619 middlewares: []const Middleware(H), 620 dispatchable_action: ?*const DispatchableAction(H, ActionArg), 621 622 pub fn next(self: *Executor) !void { 623 const index = self.index; 624 const middlewares = self.middlewares; 625 626 if (index < middlewares.len) { 627 self.index = index + 1; 628 return middlewares[index].execute(self.req, self.res, self); 629 } 630 631 // done executing our middlewares, now we either execute the 632 // dispatcher or not found. 633 if (self.dispatchable_action) |da| { 634 if (comptime H == void) { 635 return da.dispatcher(da.action, self.req, self.res); 636 } 637 return da.dispatcher(da.handler, da.action, self.req, self.res); 638 } 639 640 if (comptime std.meta.hasFn(Handler, "notFound")) { 641 return self.handler.notFound(self.req, self.res); 642 } 643 self.res.status = 404; 644 self.res.body = "Not Found"; 645 return; 646 } 647 }; 648 }; 649} 650 651pub fn blockingMode() bool { 652 if (force_blocking) { 653 return true; 654 } 655 return switch (builtin.os.tag) { 656 .linux, .macos, .ios, .tvos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd => false, 657 else => true, 658 }; 659} 660 661pub fn upgradeWebsocket(comptime H: type, req: *Request, res: *Response, ctx: anytype) !bool { 662 const upgrade = req.header("upgrade") orelse return false; 663 if (std.ascii.eqlIgnoreCase(upgrade, "websocket") == false) { 664 return false; 665 } 666 667 const version = req.header("sec-websocket-version") orelse return false; 668 if (std.ascii.eqlIgnoreCase(version, "13") == false) { 669 return false; 670 } 671 672 // firefox will send multiple values for this header 673 const connection = req.header("connection") orelse return false; 674 if (std.ascii.indexOfIgnoreCase(connection, "upgrade") == null) { 675 return false; 676 } 677 678 const key = req.header("sec-websocket-key") orelse return false; 679 680 const http_conn = res.conn; 681 const ws_worker: *websocket.server.Worker(H) = @ptrCast(@alignCast(http_conn.ws_worker)); 682 683 var hc = try ws_worker.createConn(http_conn.stream.handle, http_conn.address, worker.timestamp(0)); 684 errdefer ws_worker.cleanupConn(hc); 685 686 hc.handler = try H.init(&hc.conn, ctx); 687 688 var compression = false; 689 if (ws_worker.canCompress()) { 690 if (req.header("sec-websocket-extensions")) |ext| { 691 compression = try websocket.Handshake.parseExtension(ext) != null; 692 } 693 } 694 695 var reply_buf: [512]u8 = undefined; 696 const reply = try websocket.Handshake.createReply(key, null, compression, &reply_buf); 697 try http_conn.stream.writeAll(reply); 698 if (comptime std.meta.hasFn(H, "afterInit")) { 699 const params = @typeInfo(@TypeOf(H.afterInit)).@"fn".params; 700 try if (comptime params.len == 1) hc.handler.?.afterInit() else hc.handler.?.afterInit(ctx); 701 } 702 try ws_worker.setupConnection(hc); 703 res.written = true; 704 http_conn.handover = .{ .websocket = hc }; 705 return true; 706} 707 708// std.heap.StackFallbackAllocator is very specific. It's really _stack_ as it 709// requires a comptime size. Also, it uses non-public calls from the FixedBufferAllocator. 710// There should be a more generic FallbackAllocator that just takes 2 allocators... 711// which is what this is. 712const FallbackAllocator = struct { 713 fixed: Allocator, 714 fallback: Allocator, 715 fba: *FixedBufferAllocator, 716 717 pub fn allocator(self: *FallbackAllocator) Allocator { 718 return .{ 719 .ptr = self, 720 .vtable = &.{ 721 .alloc = alloc, 722 .resize = resize, 723 .free = free, 724 .remap = remap, 725 }, 726 }; 727 } 728 729 fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ra: usize) ?[*]u8 { 730 const self: *FallbackAllocator = @ptrCast(@alignCast(ctx)); 731 return self.fixed.rawAlloc(len, alignment, ra) orelse self.fallback.rawAlloc(len, alignment, ra); 732 } 733 734 fn resize(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ra: usize) bool { 735 const self: *FallbackAllocator = @ptrCast(@alignCast(ctx)); 736 if (self.fba.ownsPtr(buf.ptr)) { 737 if (self.fixed.rawResize(buf, alignment, new_len, ra)) { 738 return true; 739 } 740 } 741 return self.fallback.rawResize(buf, alignment, new_len, ra); 742 } 743 744 fn free(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, ra: usize) void { 745 _ = ctx; 746 _ = buf; 747 _ = alignment; 748 _ = ra; 749 // hack. 750 // Always noop since, in our specific usage, we know fallback is an arena. 751 } 752 753 fn remap(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 { 754 if (resize(ctx, memory, alignment, new_len, ret_addr)) { 755 return memory.ptr; 756 } 757 return null; 758 } 759}; 760 761// Called when we have unread bytes on the request and want to keepalive the 762// connection. Only happens when lazy_read_size is configured and the client 763// didn't read the [whole] body 764// There should already be a receive timeout on the socket since the only 765// way for this to be 766fn drain(req: *Request) !void { 767 var r = try req.reader(2000); 768 var buf: [4096]u8 = undefined; 769 while (true) { 770 if (try r.read(&buf) == 0) { 771 return; 772 } 773 } 774} 775 776const t = @import("t.zig"); 777var global_test_allocator = std.heap.GeneralPurposeAllocator(.{}){}; 778 779var test_handler_dispatch = TestHandlerDispatch{ .state = 10 }; 780var test_handler_disaptch_context = TestHandlerDispatchContext{ .state = 20 }; 781var test_handler_default_dispatch1 = TestHandlerDefaultDispatch{ .state = 3 }; 782var test_handler_default_dispatch2 = TestHandlerDefaultDispatch{ .state = 99 }; 783var test_handler_default_dispatch3 = TestHandlerDefaultDispatch{ .state = 20 }; 784 785var default_server: Server(void) = undefined; 786var dispatch_default_server: Server(*TestHandlerDefaultDispatch) = undefined; 787var dispatch_server: Server(*TestHandlerDispatch) = undefined; 788var dispatch_action_context_server: Server(*TestHandlerDispatchContext) = undefined; 789var reuse_server: Server(void) = undefined; 790var handle_server: Server(TestHandlerHandle) = undefined; 791var websocket_server: Server(TestWebsocketHandler) = undefined; 792 793var test_server_threads: [7]Thread = undefined; 794 795test "tests:beforeAll" { 796 // this will leak since the server will run until the process exits. If we use 797 // our testing allocator, it'll report the leak. 798 const ga = global_test_allocator.allocator(); 799 800 { 801 default_server = try Server(void).init(ga, .{ .port = 5992, .request = .{ 802 .lazy_read_size = 4_096, 803 .max_body_size = 1_048_576, 804 } }, {}); 805 806 // only need to do this because we're using listenInNewThread instead 807 // of blocking here. So the array to hold the middleware needs to outlive 808 // this function. 809 var cors = try default_server.arena.alloc(Middleware(void), 1); 810 cors[0] = try default_server.middleware(middleware.Cors, .{ 811 .max_age = "300", 812 .methods = "GET,POST", 813 .origin = "httpz.local", 814 .headers = "content-type", 815 }); 816 817 var middlewares = try default_server.arena.alloc(Middleware(void), 2); 818 middlewares[0] = try default_server.middleware(TestMiddleware, .{ .id = 100 }); 819 middlewares[1] = cors[0]; 820 821 var router = try default_server.router(.{}); 822 // router.get("/test/ws", testWS); 823 router.get("/fail", TestDummyHandler.fail, .{}); 824 router.get("/test/json", TestDummyHandler.jsonRes, .{}); 825 router.get("/test/method", TestDummyHandler.method, .{}); 826 router.put("/test/method", TestDummyHandler.method, .{}); 827 router.method("TEA", "/test/method", TestDummyHandler.method, .{}); 828 router.method("PING", "/test/method", TestDummyHandler.method, .{}); 829 router.get("/test/query", TestDummyHandler.reqQuery, .{}); 830 router.get("/test/stream", TestDummyHandler.eventStream, .{}); 831 router.get("/test/streamsync", TestDummyHandler.eventStreamSync, .{}); 832 router.get("/test/req_reader", TestDummyHandler.reqReader, .{}); 833 router.get("/test/chunked", TestDummyHandler.chunked, .{}); 834 router.get("/test/route_data", TestDummyHandler.routeData, .{ .data = &TestDummyHandler.RouteData{ .power = 12345 } }); 835 router.all("/test/cors", TestDummyHandler.jsonRes, .{ .middlewares = cors }); 836 router.all("/test/middlewares", TestDummyHandler.middlewares, .{ .middlewares = middlewares }); 837 router.all("/test/dispatcher", TestDummyHandler.dispatchedAction, .{ .dispatcher = TestDummyHandler.routeSpecificDispacthcer }); 838 test_server_threads[0] = try default_server.listenInNewThread(); 839 } 840 841 { 842 dispatch_default_server = try Server(*TestHandlerDefaultDispatch).init(ga, .{ .port = 5993 }, &test_handler_default_dispatch1); 843 var router = try dispatch_default_server.router(.{}); 844 router.get("/", TestHandlerDefaultDispatch.echo, .{}); 845 router.get("/write/*", TestHandlerDefaultDispatch.echoWrite, .{}); 846 router.get("/fail", TestHandlerDefaultDispatch.fail, .{}); 847 router.post("/login", TestHandlerDefaultDispatch.echo, .{}); 848 router.get("/test/body/cl", TestHandlerDefaultDispatch.clBody, .{}); 849 router.get("/test/headers", TestHandlerDefaultDispatch.headers, .{}); 850 router.all("/api/:version/users/:UserId", TestHandlerDefaultDispatch.params, .{}); 851 852 var admin_routes = router.group("/admin/", .{ .dispatcher = TestHandlerDefaultDispatch.dispatch2, .handler = &test_handler_default_dispatch2 }); 853 admin_routes.get("/users", TestHandlerDefaultDispatch.echo, .{}); 854 admin_routes.put("/users/:id", TestHandlerDefaultDispatch.echo, .{}); 855 856 var debug_routes = router.group("/debug", .{ .dispatcher = TestHandlerDefaultDispatch.dispatch3, .handler = &test_handler_default_dispatch3 }); 857 debug_routes.head("/ping", TestHandlerDefaultDispatch.echo, .{}); 858 debug_routes.options("/stats", TestHandlerDefaultDispatch.echo, .{}); 859 860 test_server_threads[1] = try dispatch_default_server.listenInNewThread(); 861 } 862 863 { 864 dispatch_server = try Server(*TestHandlerDispatch).init(ga, .{ .port = 5994 }, &test_handler_dispatch); 865 var router = try dispatch_server.router(.{}); 866 router.get("/", TestHandlerDispatch.root, .{}); 867 test_server_threads[2] = try dispatch_server.listenInNewThread(); 868 } 869 870 { 871 dispatch_action_context_server = try Server(*TestHandlerDispatchContext).init(ga, .{ .port = 5995 }, &test_handler_disaptch_context); 872 var router = try dispatch_action_context_server.router(.{}); 873 router.get("/", TestHandlerDispatchContext.root, .{}); 874 test_server_threads[3] = try dispatch_action_context_server.listenInNewThread(); 875 } 876 877 { 878 // with only 1 worker, and a min/max conn of 1, each request should 879 // hit our reset path. 880 reuse_server = try Server(void).init(ga, .{ .port = 5996, .workers = .{ .count = 1, .min_conn = 1, .max_conn = 1 } }, {}); 881 var router = try reuse_server.router(.{}); 882 router.get("/test/writer", TestDummyHandler.reuseWriter, .{}); 883 test_server_threads[4] = try reuse_server.listenInNewThread(); 884 } 885 886 { 887 handle_server = try Server(TestHandlerHandle).init(ga, .{ .port = 5997 }, TestHandlerHandle{}); 888 test_server_threads[5] = try handle_server.listenInNewThread(); 889 } 890 891 { 892 websocket_server = try Server(TestWebsocketHandler).init(ga, .{ .port = 5998 }, TestWebsocketHandler{}); 893 var router = try websocket_server.router(.{}); 894 router.get("/ws", TestWebsocketHandler.upgrade, .{}); 895 test_server_threads[6] = try websocket_server.listenInNewThread(); 896 } 897 898 std.testing.refAllDecls(@This()); 899} 900 901test "tests:afterAll" { 902 default_server.stop(); 903 dispatch_default_server.stop(); 904 dispatch_server.stop(); 905 dispatch_action_context_server.stop(); 906 reuse_server.stop(); 907 handle_server.stop(); 908 websocket_server.stop(); 909 910 for (test_server_threads) |thread| { 911 thread.join(); 912 } 913 914 default_server.deinit(); 915 dispatch_default_server.deinit(); 916 dispatch_server.deinit(); 917 dispatch_action_context_server.deinit(); 918 reuse_server.deinit(); 919 handle_server.deinit(); 920 websocket_server.deinit(); 921 922 try t.expectEqual(false, global_test_allocator.detectLeaks()); 923} 924 925test "httpz: quick shutdown" { 926 var server = try Server(void).init(t.allocator, .{ .port = 6992 }, {}); 927 const thrd = try server.listenInNewThread(); 928 server.stop(); 929 thrd.join(); 930 server.deinit(); 931} 932 933test "httpz: invalid request" { 934 const stream = testStream(5992); 935 defer stream.close(); 936 try stream.writeAll("TEA HTTP/1.1\r\n\r\n"); 937 938 var buf: [100]u8 = undefined; 939 try t.expectString("HTTP/1.1 400 \r\nConnection: Close\r\nContent-Length: 15\r\n\r\nInvalid Request", testReadAll(stream, &buf)); 940} 941 942test "httpz: invalid request path" { 943 const stream = testStream(5992); 944 defer stream.close(); 945 try stream.writeAll("TEA /hello\rn\nWorld:test HTTP/1.1\r\n\r\n"); 946 947 var buf: [100]u8 = undefined; 948 try t.expectString("HTTP/1.1 400 \r\nConnection: Close\r\nContent-Length: 15\r\n\r\nInvalid Request", testReadAll(stream, &buf)); 949} 950 951test "httpz: invalid header name" { 952 const stream = testStream(5992); 953 defer stream.close(); 954 try stream.writeAll("GET / HTTP/1.1\r\nOver: 9000\r\nHel\tlo:World\r\n\r\n"); 955 956 var buf: [100]u8 = undefined; 957 try t.expectString("HTTP/1.1 400 \r\nConnection: Close\r\nContent-Length: 15\r\n\r\nInvalid Request", testReadAll(stream, &buf)); 958} 959 960test "httpz: invalid content length value (1)" { 961 const stream = testStream(5992); 962 defer stream.close(); 963 try stream.writeAll("GET / HTTP/1.1\r\nContent-Length: HaHA\r\n\r\n"); 964 965 var buf: [100]u8 = undefined; 966 try t.expectString("HTTP/1.1 400 \r\nConnection: Close\r\nContent-Length: 15\r\n\r\nInvalid Request", testReadAll(stream, &buf)); 967} 968 969test "httpz: invalid content length value (2)" { 970 const stream = testStream(5992); 971 defer stream.close(); 972 try stream.writeAll("GET / HTTP/1.1\r\nContent-Length: 1.0\r\n\r\n"); 973 974 var buf: [100]u8 = undefined; 975 try t.expectString("HTTP/1.1 400 \r\nConnection: Close\r\nContent-Length: 15\r\n\r\nInvalid Request", testReadAll(stream, &buf)); 976} 977 978test "httpz: body too big" { 979 const stream = testStream(5993); 980 defer stream.close(); 981 try stream.writeAll("POST / HTTP/1.1\r\nContent-Length: 999999999999999999\r\n\r\n"); 982 983 var buf: [100]u8 = undefined; 984 try t.expectString("HTTP/1.1 413 \r\nConnection: Close\r\nContent-Length: 23\r\n\r\nRequest body is too big", testReadAll(stream, &buf)); 985} 986 987test "httpz: overflow content length" { 988 const stream = testStream(5992); 989 defer stream.close(); 990 try stream.writeAll("GET / HTTP/1.1\r\nContent-Length: 999999999999999999999999999\r\n\r\n"); 991 992 var buf: [100]u8 = undefined; 993 try t.expectString("HTTP/1.1 400 \r\nConnection: Close\r\nContent-Length: 15\r\n\r\nInvalid Request", testReadAll(stream, &buf)); 994} 995 996test "httpz: no route" { 997 const stream = testStream(5992); 998 defer stream.close(); 999 try stream.writeAll("GET / HTTP/1.1\r\n\r\n"); 1000 1001 var buf: [100]u8 = undefined; 1002 try t.expectString("HTTP/1.1 404 \r\nContent-Length: 9\r\n\r\nNot Found", testReadAll(stream, &buf)); 1003} 1004 1005test "httpz: no route with custom notFound handler" { 1006 const stream = testStream(5993); 1007 defer stream.close(); 1008 try stream.writeAll("GET /not_found HTTP/1.1\r\n\r\n"); 1009 1010 var buf: [100]u8 = undefined; 1011 try t.expectString("HTTP/1.1 404 \r\nstate: 3\r\nContent-Length: 10\r\n\r\nwhere lah?", testReadAll(stream, &buf)); 1012} 1013 1014test "httpz: unhandled exception" { 1015 std.testing.log_level = .err; 1016 defer std.testing.log_level = .warn; 1017 1018 const stream = testStream(5992); 1019 defer stream.close(); 1020 try stream.writeAll("GET /fail HTTP/1.1\r\n\r\n"); 1021 1022 var buf: [150]u8 = undefined; 1023 try t.expectString("HTTP/1.1 500 \r\nContent-Length: 21\r\n\r\nInternal Server Error", testReadAll(stream, &buf)); 1024} 1025 1026test "httpz: unhandled exception with custom error handler" { 1027 std.testing.log_level = .err; 1028 defer std.testing.log_level = .warn; 1029 1030 const stream = testStream(5993); 1031 defer stream.close(); 1032 try stream.writeAll("GET /fail HTTP/1.1\r\n\r\n"); 1033 1034 var buf: [150]u8 = undefined; 1035 try t.expectString("HTTP/1.1 500 \r\nstate: 3\r\nerr: TestUnhandledError\r\nContent-Length: 29\r\n\r\n#/why/arent/tags/hierarchical", testReadAll(stream, &buf)); 1036} 1037 1038test "httpz: custom methods" { 1039 const stream = testStream(5992); 1040 defer stream.close(); 1041 1042 { 1043 try stream.writeAll("GET /test/method HTTP/1.1\r\n\r\n"); 1044 var res = testReadParsed(stream); 1045 defer res.deinit(); 1046 try res.expectJson(.{ .method = "GET", .string = "" }); 1047 } 1048 1049 { 1050 try stream.writeAll("PUT /test/method HTTP/1.1\r\n\r\n"); 1051 var res = testReadParsed(stream); 1052 defer res.deinit(); 1053 try res.expectJson(.{ .method = "PUT", .string = "" }); 1054 } 1055 1056 { 1057 try stream.writeAll("TEA /test/method HTTP/1.1\r\n\r\n"); 1058 var res = testReadParsed(stream); 1059 defer res.deinit(); 1060 try res.expectJson(.{ .method = "OTHER", .string = "TEA" }); 1061 } 1062 1063 { 1064 try stream.writeAll("PING /test/method HTTP/1.1\r\n\r\n"); 1065 var res = testReadParsed(stream); 1066 defer res.deinit(); 1067 try res.expectJson(.{ .method = "OTHER", .string = "PING" }); 1068 } 1069 1070 { 1071 try stream.writeAll("TEA /test/other HTTP/1.1\r\n\r\n"); 1072 var buf: [100]u8 = undefined; 1073 try t.expectString("HTTP/1.1 404 \r\nContent-Length: 9\r\n\r\nNot Found", testReadAll(stream, &buf)); 1074 } 1075} 1076 1077test "httpz: route params" { 1078 const stream = testStream(5993); 1079 defer stream.close(); 1080 try stream.writeAll("GET /api/v2/users/9001 HTTP/1.1\r\n\r\n"); 1081 1082 var buf: [100]u8 = undefined; 1083 try t.expectString("HTTP/1.1 200 \r\nContent-Length: 20\r\n\r\nversion=v2,user=9001", testReadAll(stream, &buf)); 1084} 1085 1086test "httpz: request and response headers" { 1087 const stream = testStream(5993); 1088 defer stream.close(); 1089 try stream.writeAll("GET /test/headers HTTP/1.1\r\nHeader-Name: Header-Value\r\n\r\n"); 1090 1091 var buf: [100]u8 = undefined; 1092 try t.expectString("HTTP/1.1 200 \r\nstate: 3\r\nEcho: Header-Value\r\nother: test-value\r\nContent-Length: 0\r\n\r\n", testReadAll(stream, &buf)); 1093} 1094 1095test "httpz: content-length body" { 1096 const stream = testStream(5993); 1097 defer stream.close(); 1098 try stream.writeAll("GET /test/body/cl HTTP/1.1\r\nHeader-Name: Header-Value\r\nContent-Length: 4\r\n\r\nabcz"); 1099 1100 var buf: [100]u8 = undefined; 1101 try t.expectString("HTTP/1.1 200 \r\nEcho-Body: abcz\r\nContent-Length: 0\r\n\r\n", testReadAll(stream, &buf)); 1102} 1103 1104test "httpz: json response" { 1105 const stream = testStream(5992); 1106 defer stream.close(); 1107 try stream.writeAll("GET /test/json HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 1108 1109 var buf: [200]u8 = undefined; 1110 try t.expectString("HTTP/1.1 201 \r\nContent-Type: application/json; charset=UTF-8\r\nContent-Length: 26\r\n\r\n{\"over\":9000,\"teg\":\"soup\"}", testReadAll(stream, &buf)); 1111} 1112 1113test "httpz: query" { 1114 const stream = testStream(5992); 1115 defer stream.close(); 1116 try stream.writeAll("GET /test/query?fav=keemun%20te%61%21 HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 1117 1118 var buf: [200]u8 = undefined; 1119 try t.expectString("HTTP/1.1 200 \r\nContent-Length: 11\r\n\r\nkeemun tea!", testReadAll(stream, &buf)); 1120} 1121 1122test "httpz: chunked" { 1123 const stream = testStream(5992); 1124 defer stream.close(); 1125 try stream.writeAll("GET /test/chunked HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 1126 1127 var buf: [1000]u8 = undefined; 1128 try t.expectString("HTTP/1.1 200 \r\nOver: 9000!\r\nTransfer-Encoding: chunked\r\n\r\n7\r\nChunk 1\r\n11\r\nand another chunk\r\n0\r\n\r\n", testReadAll(stream, &buf)); 1129} 1130 1131test "httpz: route-specific dispatcher" { 1132 const stream = testStream(5992); 1133 defer stream.close(); 1134 try stream.writeAll("HEAD /test/dispatcher HTTP/1.1\r\n\r\n"); 1135 1136 var buf: [200]u8 = undefined; 1137 try t.expectString("HTTP/1.1 200 \r\ndispatcher: test-dispatcher-1\r\nContent-Length: 6\r\n\r\naction", testReadAll(stream, &buf)); 1138} 1139 1140test "httpz: middlewares" { 1141 const stream = testStream(5992); 1142 defer stream.close(); 1143 1144 { 1145 try stream.writeAll("GET /test/middlewares HTTP/1.1\r\n\r\n"); 1146 var res = testReadParsed(stream); 1147 defer res.deinit(); 1148 1149 try res.expectJson(.{ .v1 = "tm1-100", .v2 = "tm2-100" }); 1150 try t.expectString("httpz.local", res.headers.get("Access-Control-Allow-Origin").?); 1151 } 1152} 1153 1154test "httpz: CORS" { 1155 const stream = testStream(5992); 1156 defer stream.close(); 1157 1158 { 1159 try stream.writeAll("GET /echo HTTP/1.1\r\n\r\n"); 1160 var res = testReadParsed(stream); 1161 defer res.deinit(); 1162 try t.expectEqual(null, res.headers.get("Access-Control-Max-Age")); 1163 try t.expectEqual(null, res.headers.get("Access-Control-Allow-Methods")); 1164 try t.expectEqual(null, res.headers.get("Access-Control-Allow-Headers")); 1165 try t.expectEqual(null, res.headers.get("Access-Control-Allow-Origin")); 1166 } 1167 1168 { 1169 // cors endpoint but not cors options 1170 try stream.writeAll("OPTIONS /test/cors HTTP/1.1\r\nSec-Fetch-Mode: navigate\r\n\r\n"); 1171 var res = testReadParsed(stream); 1172 defer res.deinit(); 1173 1174 try t.expectEqual(null, res.headers.get("Access-Control-Max-Age")); 1175 try t.expectEqual(null, res.headers.get("Access-Control-Allow-Methods")); 1176 try t.expectEqual(null, res.headers.get("Access-Control-Allow-Headers")); 1177 try t.expectString("httpz.local", res.headers.get("Access-Control-Allow-Origin").?); 1178 } 1179 1180 { 1181 // cors request 1182 try stream.writeAll("OPTIONS /test/cors HTTP/1.1\r\nSec-Fetch-Mode: cors\r\n\r\n"); 1183 var res = testReadParsed(stream); 1184 defer res.deinit(); 1185 1186 try t.expectString("300", res.headers.get("Access-Control-Max-Age").?); 1187 try t.expectString("GET,POST", res.headers.get("Access-Control-Allow-Methods").?); 1188 try t.expectString("content-type", res.headers.get("Access-Control-Allow-Headers").?); 1189 try t.expectString("httpz.local", res.headers.get("Access-Control-Allow-Origin").?); 1190 } 1191 1192 { 1193 // cors request, non-options 1194 try stream.writeAll("GET /test/cors HTTP/1.1\r\nSec-Fetch-Mode: cors\r\n\r\n"); 1195 var res = testReadParsed(stream); 1196 defer res.deinit(); 1197 1198 try t.expectEqual(null, res.headers.get("Access-Control-Max-Age")); 1199 try t.expectEqual(null, res.headers.get("Access-Control-Allow-Methods")); 1200 try t.expectEqual(null, res.headers.get("Access-Control-Allow-Headers")); 1201 try t.expectString("httpz.local", res.headers.get("Access-Control-Allow-Origin").?); 1202 } 1203} 1204 1205test "httpz: router groups" { 1206 const stream = testStream(5993); 1207 defer stream.close(); 1208 1209 { 1210 try stream.writeAll("GET / HTTP/1.1\r\n\r\n"); 1211 var res = testReadParsed(stream); 1212 defer res.deinit(); 1213 1214 try res.expectJson(.{ .state = 3, .method = "GET", .path = "/" }); 1215 try t.expectEqual(true, res.headers.get("dispatcher") == null); 1216 } 1217 1218 { 1219 try stream.writeAll("GET /admin/users HTTP/1.1\r\n\r\n"); 1220 var res = testReadParsed(stream); 1221 defer res.deinit(); 1222 1223 try res.expectJson(.{ .state = 99, .method = "GET", .path = "/admin/users" }); 1224 try t.expectString("test-dispatcher-2", res.headers.get("dispatcher").?); 1225 } 1226 1227 { 1228 try stream.writeAll("PUT /admin/users/:id HTTP/1.1\r\n\r\n"); 1229 var res = testReadParsed(stream); 1230 defer res.deinit(); 1231 1232 try res.expectJson(.{ .state = 99, .method = "PUT", .path = "/admin/users/:id" }); 1233 try t.expectString("test-dispatcher-2", res.headers.get("dispatcher").?); 1234 } 1235 1236 { 1237 try stream.writeAll("HEAD /debug/ping HTTP/1.1\r\n\r\n"); 1238 var res = testReadParsed(stream); 1239 defer res.deinit(); 1240 1241 try res.expectJson(.{ .state = 20, .method = "HEAD", .path = "/debug/ping" }); 1242 try t.expectString("test-dispatcher-3", res.headers.get("dispatcher").?); 1243 } 1244 1245 { 1246 try stream.writeAll("OPTIONS /debug/stats HTTP/1.1\r\n\r\n"); 1247 var res = testReadParsed(stream); 1248 defer res.deinit(); 1249 1250 try res.expectJson(.{ .state = 20, .method = "OPTIONS", .path = "/debug/stats" }); 1251 try t.expectString("test-dispatcher-3", res.headers.get("dispatcher").?); 1252 } 1253 1254 { 1255 try stream.writeAll("POST /login HTTP/1.1\r\n\r\n"); 1256 var res = testReadParsed(stream); 1257 defer res.deinit(); 1258 1259 try res.expectJson(.{ .state = 3, .method = "POST", .path = "/login" }); 1260 try t.expectEqual(true, res.headers.get("dispatcher") == null); 1261 } 1262} 1263 1264test "httpz: event stream" { 1265 const stream = testStream(5992); 1266 defer stream.close(); 1267 try stream.writeAll("GET /test/stream HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 1268 1269 var res = testReadParsed(stream); 1270 defer res.deinit(); 1271 1272 try t.expectEqual(818, res.status); 1273 try t.expectEqual(true, res.headers.get("Content-Length") == null); 1274 try t.expectString("text/event-stream; charset=UTF-8", res.headers.get("Content-Type").?); 1275 try t.expectString("no-cache", res.headers.get("Cache-Control").?); 1276 try t.expectString("keep-alive", res.headers.get("Connection").?); 1277 try t.expectString("helloa message", res.body); 1278} 1279 1280test "httpz: event stream sync" { 1281 const stream = testStream(5992); 1282 defer stream.close(); 1283 try stream.writeAll("GET /test/streamsync HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 1284 1285 var res = testReadParsed(stream); 1286 defer res.deinit(); 1287 1288 try t.expectEqual(818, res.status); 1289 try t.expectEqual(true, res.headers.get("Content-Length") == null); 1290 try t.expectString("text/event-stream; charset=UTF-8", res.headers.get("Content-Type").?); 1291 try t.expectString("no-cache", res.headers.get("Cache-Control").?); 1292 try t.expectString("keep-alive", res.headers.get("Connection").?); 1293 try t.expectString("helloa sync message", res.body); 1294} 1295 1296test "httpz: keepalive" { 1297 const stream = testStream(5993); 1298 defer stream.close(); 1299 try stream.writeAll("GET /api/v2/users/9001 HTTP/1.1\r\n\r\n"); 1300 1301 var buf: [100]u8 = undefined; 1302 try t.expectString("HTTP/1.1 200 \r\nContent-Length: 20\r\n\r\nversion=v2,user=9001", testReadAll(stream, &buf)); 1303 1304 try stream.writeAll("GET /api/v2/users/123 HTTP/1.1\r\n\r\n"); 1305 try t.expectString("HTTP/1.1 200 \r\nContent-Length: 19\r\n\r\nversion=v2,user=123", testReadAll(stream, &buf)); 1306} 1307 1308test "httpz: route data" { 1309 const stream = testStream(5992); 1310 defer stream.close(); 1311 try stream.writeAll("GET /test/route_data HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 1312 1313 var res = testReadParsed(stream); 1314 defer res.deinit(); 1315 try res.expectJson(.{ .power = 12345 }); 1316} 1317 1318test "httpz: keepalive with explicit write" { 1319 const stream = testStream(5993); 1320 defer stream.close(); 1321 try stream.writeAll("GET /write/9001 HTTP/1.1\r\n\r\n"); 1322 1323 var buf: [1000]u8 = undefined; 1324 try t.expectString("HTTP/1.1 200 \r\nContent-Length: 47\r\n\r\n{\"state\":3,\"method\":\"GET\",\"path\":\"/write/9001\"}", testReadAll(stream, &buf)); 1325 1326 try stream.writeAll("GET /write/123 HTTP/1.1\r\n\r\n"); 1327 try t.expectString("HTTP/1.1 200 \r\nContent-Length: 46\r\n\r\n{\"state\":3,\"method\":\"GET\",\"path\":\"/write/123\"}", testReadAll(stream, &buf)); 1328} 1329 1330test "httpz: request in chunks" { 1331 const stream = testStream(5993); 1332 defer stream.close(); 1333 try stream.writeAll("GET /api/v2/use"); 1334 std.Thread.sleep(std.time.ns_per_ms * 10); 1335 try stream.writeAll("rs/11 HTTP/1.1\r\n\r\n"); 1336 1337 var buf: [100]u8 = undefined; 1338 try t.expectString("HTTP/1.1 200 \r\nContent-Length: 18\r\n\r\nversion=v2,user=11", testReadAll(stream, &buf)); 1339} 1340 1341test "httpz: writer re-use" { 1342 defer t.reset(); 1343 1344 const stream = testStream(5996); 1345 defer stream.close(); 1346 1347 var expected: [10]TestUser = undefined; 1348 1349 var buf: [100]u8 = undefined; 1350 for (0..10) |i| { 1351 expected[i] = .{ 1352 .id = try std.fmt.allocPrint(t.arena.allocator(), "id-{d}", .{i}), 1353 .power = i, 1354 }; 1355 try stream.writeAll(try std.fmt.bufPrint(&buf, "GET /test/writer?count={d} HTTP/1.1\r\nContent-Length: 0\r\n\r\n", .{i + 1})); 1356 1357 var res = testReadParsed(stream); 1358 defer res.deinit(); 1359 1360 try res.expectJson(.{ .data = expected[0 .. i + 1] }); 1361 } 1362} 1363 1364test "httpz: custom dispatch without action context" { 1365 const stream = testStream(5994); 1366 defer stream.close(); 1367 try stream.writeAll("GET / HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 1368 1369 var buf: [200]u8 = undefined; 1370 try t.expectString("HTTP/1.1 200 \r\nContent-Type: application/json; charset=UTF-8\r\ndstate: 10\r\ndispatch: TestHandlerDispatch\r\nContent-Length: 12\r\n\r\n{\"state\":10}", testReadAll(stream, &buf)); 1371} 1372 1373test "httpz: custom dispatch with action context" { 1374 const stream = testStream(5995); 1375 defer stream.close(); 1376 try stream.writeAll("GET /?name=teg HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 1377 1378 var buf: [200]u8 = undefined; 1379 try t.expectString("HTTP/1.1 200 \r\nContent-Type: application/json; charset=UTF-8\r\ndstate: 20\r\ndispatch: TestHandlerDispatchContext\r\nContent-Length: 12\r\n\r\n{\"other\":30}", testReadAll(stream, &buf)); 1380} 1381 1382test "httpz: custom handle" { 1383 const stream = testStream(5997); 1384 defer stream.close(); 1385 try stream.writeAll("GET /whatever?name=teg HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 1386 1387 var buf: [100]u8 = undefined; 1388 try t.expectString("HTTP/1.1 200 \r\nContent-Length: 9\r\n\r\nhello teg", testReadAll(stream, &buf)); 1389} 1390 1391test "httpz: request body reader" { 1392 { 1393 // no body 1394 const stream = testStream(5992); 1395 defer stream.close(); 1396 try stream.writeAll("GET /test/req_reader HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 1397 1398 var res = testReadParsed(stream); 1399 defer res.deinit(); 1400 try res.expectJson(.{ .length = 0 }); 1401 } 1402 1403 { 1404 // small body 1405 const stream = testStream(5992); 1406 defer stream.close(); 1407 try stream.writeAll("GET /test/req_reader HTTP/1.1\r\nContent-Length: 4\r\n\r\n123z"); 1408 1409 var res = testReadParsed(stream); 1410 defer res.deinit(); 1411 try res.expectJson(.{ .length = 4 }); 1412 } 1413 1414 var r = t.getRandom(); 1415 const random = r.random(); 1416 1417 // a bit of fuzzing 1418 for (0..10) |_| { 1419 const stream = testStream(5992); 1420 defer stream.close(); 1421 var req: []const u8 = "GET /test/req_reader HTTP/1.1\r\nContent-Length: 20000\r\n\r\n" ++ ("a" ** 20_000); 1422 while (req.len > 0) { 1423 const len = random.uintAtMost(usize, req.len - 1) + 1; 1424 const n = stream.write(req[0..len]) catch |err| switch (err) { 1425 error.WouldBlock => 0, 1426 else => return err, 1427 }; 1428 std.Thread.sleep(std.time.ns_per_ms * 2); 1429 req = req[n..]; 1430 } 1431 1432 var res = testReadParsed(stream); 1433 defer res.deinit(); 1434 try res.expectJson(.{ .length = 20_000 }); 1435 } 1436} 1437 1438test "websocket: invalid request" { 1439 const stream = testStream(5998); 1440 defer stream.close(); 1441 try stream.writeAll("GET /ws HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); 1442 1443 var res = testReadParsed(stream); 1444 defer res.deinit(); 1445 try t.expectString("invalid websocket", res.body); 1446} 1447 1448test "websocket: upgrade" { 1449 const stream = testStream(5998); 1450 defer stream.close(); 1451 try stream.writeAll("GET /ws HTTP/1.1\r\nContent-Length: 0\r\n"); 1452 try stream.writeAll("upgrade: WEBsocket\r\n"); 1453 try stream.writeAll("Sec-Websocket-verSIon: 13\r\n"); 1454 try stream.writeAll("ConnectioN: abc,upgrade,123\r\n"); 1455 try stream.writeAll("SEC-WEBSOCKET-KeY: a-secret-key\r\n\r\n"); 1456 1457 var res = testReadHeader(stream); 1458 defer res.deinit(); 1459 try t.expectEqual(101, res.status); 1460 try t.expectString("websocket", res.headers.get("Upgrade").?); 1461 try t.expectString("upgrade", res.headers.get("Connection").?); 1462 try t.expectString("55eM2SNGu+68v5XXrr982mhPFkU=", res.headers.get("Sec-Websocket-Accept").?); 1463 1464 try stream.writeAll(&websocket.frameText("over 9000!")); 1465 try stream.writeAll(&websocket.frameText("close")); 1466 1467 var pos: usize = 0; 1468 var buf: [100]u8 = undefined; 1469 var wait_count: usize = 0; 1470 while (pos < 16) { 1471 const n = stream.read(buf[pos..]) catch |err| switch (err) { 1472 error.WouldBlock => { 1473 if (wait_count == 100) { 1474 break; 1475 } 1476 wait_count += 1; 1477 std.Thread.sleep(std.time.ns_per_ms); 1478 continue; 1479 }, 1480 else => return err, 1481 }; 1482 if (n == 0) { 1483 break; 1484 } 1485 pos += n; 1486 } 1487 try t.expectEqual(16, pos); 1488 try t.expectEqual(129, buf[0]); 1489 try t.expectEqual(10, buf[1]); 1490 try t.expectString("over 9000!", buf[2..12]); 1491 try t.expectString(&.{ 136, 2, 3, 232 }, buf[12..16]); 1492} 1493 1494test "ContentType: forX" { 1495 inline for (@typeInfo(ContentType).@"enum".fields) |field| { 1496 if (comptime std.mem.eql(u8, "BINARY", field.name)) continue; 1497 if (comptime std.mem.eql(u8, "EVENTS", field.name)) continue; 1498 try t.expectEqual(@field(ContentType, field.name), ContentType.forExtension(field.name)); 1499 try t.expectEqual(@field(ContentType, field.name), ContentType.forExtension("." ++ field.name)); 1500 try t.expectEqual(@field(ContentType, field.name), ContentType.forFile("some_file." ++ field.name)); 1501 } 1502 // variations 1503 try t.expectEqual(ContentType.HTML, ContentType.forExtension(".htm")); 1504 try t.expectEqual(ContentType.JPG, ContentType.forExtension(".jpeg")); 1505 1506 try t.expectEqual(ContentType.UNKNOWN, ContentType.forExtension(".spice")); 1507 try t.expectEqual(ContentType.UNKNOWN, ContentType.forExtension("")); 1508 try t.expectEqual(ContentType.UNKNOWN, ContentType.forExtension(".x")); 1509 try t.expectEqual(ContentType.UNKNOWN, ContentType.forFile("")); 1510 try t.expectEqual(ContentType.UNKNOWN, ContentType.forFile("css")); 1511 try t.expectEqual(ContentType.UNKNOWN, ContentType.forFile("css")); 1512 try t.expectEqual(ContentType.UNKNOWN, ContentType.forFile("must.spice")); 1513} 1514 1515fn testStream(port: u16) std.net.Stream { 1516 const timeout = std.mem.toBytes(posix.timeval{ 1517 .sec = 0, 1518 .usec = 20_000, 1519 }); 1520 1521 const address = std.net.Address.parseIp("127.0.0.1", port) catch unreachable; 1522 const stream = std.net.tcpConnectToAddress(address) catch unreachable; 1523 posix.setsockopt(stream.handle, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &timeout) catch unreachable; 1524 posix.setsockopt(stream.handle, posix.SOL.SOCKET, posix.SO.SNDTIMEO, &timeout) catch unreachable; 1525 return stream; 1526} 1527 1528fn testReadAll(stream: std.net.Stream, buf: []u8) []u8 { 1529 var pos: usize = 0; 1530 var blocked = false; 1531 while (true) { 1532 std.debug.assert(pos < buf.len); 1533 const n = stream.read(buf[pos..]) catch |err| switch (err) { 1534 error.WouldBlock => { 1535 if (blocked) return buf[0..pos]; 1536 blocked = true; 1537 std.Thread.sleep(std.time.ns_per_ms); 1538 continue; 1539 }, 1540 error.ConnectionResetByPeer => return buf[0..pos], 1541 else => @panic(@errorName(err)), 1542 }; 1543 if (n == 0) { 1544 return buf[0..pos]; 1545 } 1546 pos += n; 1547 blocked = false; 1548 } 1549 unreachable; 1550} 1551 1552fn testReadParsed(stream: std.net.Stream) testing.Testing.Response { 1553 var buf: [4096]u8 = undefined; 1554 const data = testReadAll(stream, &buf); 1555 return testing.parse(data) catch unreachable; 1556} 1557 1558fn testReadHeader(stream: std.net.Stream) testing.Testing.Response { 1559 var pos: usize = 0; 1560 var blocked = false; 1561 var buf: [1024]u8 = undefined; 1562 while (true) { 1563 std.debug.assert(pos < buf.len); 1564 const n = stream.read(buf[pos..]) catch |err| switch (err) { 1565 error.WouldBlock => { 1566 if (blocked) unreachable; 1567 blocked = true; 1568 std.Thread.sleep(std.time.ns_per_ms); 1569 continue; 1570 }, 1571 else => @panic(@errorName(err)), 1572 }; 1573 1574 if (n == 0) unreachable; 1575 1576 pos += n; 1577 if (std.mem.endsWith(u8, buf[0..pos], "\r\n\r\n")) { 1578 return testing.parse(buf[0..pos]) catch unreachable; 1579 } 1580 blocked = false; 1581 } 1582 unreachable; 1583} 1584 1585const TestUser = struct { 1586 id: []const u8, 1587 power: usize, 1588}; 1589 1590// simulates having a void handler, but keeps the test actions organized within 1591// this namespace. 1592const TestDummyHandler = struct { 1593 const RouteData = struct { 1594 power: usize, 1595 }; 1596 1597 fn fail(_: *Request, _: *Response) !void { 1598 return error.Failure; 1599 } 1600 1601 fn reqQuery(req: *Request, res: *Response) !void { 1602 res.status = 200; 1603 const query = try req.query(); 1604 res.body = query.get("fav").?; 1605 } 1606 1607 fn method(req: *Request, res: *Response) !void { 1608 try res.json(.{ .method = req.method, .string = req.method_string }, .{}); 1609 } 1610 1611 fn chunked(_: *Request, res: *Response) !void { 1612 res.header("Over", "9000!"); 1613 res.status = 200; 1614 try res.chunk("Chunk 1"); 1615 try res.chunk("and another chunk"); 1616 } 1617 1618 fn jsonRes(_: *Request, res: *Response) !void { 1619 res.setStatus(.created); 1620 try res.json(.{ .over = 9000, .teg = "soup" }, .{}); 1621 } 1622 1623 fn routeData(req: *Request, res: *Response) !void { 1624 const rd: *const RouteData = @ptrCast(@alignCast(req.route_data.?)); 1625 try res.json(.{ .power = rd.power }, .{}); 1626 } 1627 1628 fn eventStream(_: *Request, res: *Response) !void { 1629 res.status = 818; 1630 try res.startEventStream(StreamContext{ .data = "hello" }, StreamContext.handle); 1631 } 1632 1633 fn eventStreamSync(_: *Request, res: *Response) !void { 1634 res.status = 818; 1635 const stream = try res.startEventStreamSync(); 1636 var w = stream.writer(&.{}); 1637 w.interface.writeAll("hello") catch unreachable; 1638 w.interface.writeAll("a sync message") catch unreachable; 1639 } 1640 1641 fn reqReader(req: *Request, res: *Response) !void { 1642 var reader = try req.reader(2000); 1643 1644 var l: usize = 0; 1645 var buf: [1024]u8 = undefined; 1646 while (true) { 1647 const n = try reader.read(&buf); 1648 if (n == 0) { 1649 break; 1650 } 1651 if (req.body_len > 10 and std.mem.indexOfNonePos(u8, buf[0..n], 0, "a") != null) { 1652 return error.InvalidData; 1653 } 1654 l += n; 1655 } 1656 return res.json(.{ .length = l }, .{}); 1657 } 1658 1659 const StreamContext = struct { 1660 data: []const u8, 1661 1662 fn handle(self: StreamContext, stream: std.net.Stream) void { 1663 stream.writeAll(self.data) catch unreachable; 1664 stream.writeAll("a message") catch unreachable; 1665 } 1666 }; 1667 1668 fn routeSpecificDispacthcer(action: Action(void), req: *Request, res: *Response) !void { 1669 res.header("dispatcher", "test-dispatcher-1"); 1670 return action(req, res); 1671 } 1672 1673 fn dispatchedAction(_: *Request, res: *Response) !void { 1674 var writer = res.writer(); 1675 return writer.interface.writeAll("action"); 1676 } 1677 1678 fn middlewares(req: *Request, res: *Response) !void { 1679 return res.json(.{ 1680 .v1 = TestMiddleware.value1(req), 1681 .v2 = TestMiddleware.value2(req), 1682 }, .{}); 1683 } 1684 1685 // called by the re-use server, but put here because, like the default server 1686 // this is a handler-less server 1687 fn reuseWriter(req: *Request, res: *Response) !void { 1688 res.status = 200; 1689 const query = try req.query(); 1690 const count = try std.fmt.parseInt(u16, query.get("count").?, 10); 1691 1692 var data = try res.arena.alloc(TestUser, count); 1693 for (0..count) |i| { 1694 data[i] = .{ 1695 .id = try std.fmt.allocPrint(res.arena, "id-{d}", .{i}), 1696 .power = i, 1697 }; 1698 } 1699 return res.json(.{ .data = data }, .{}); 1700 } 1701}; 1702 1703const TestHandlerDefaultDispatch = struct { 1704 state: usize, 1705 1706 fn dispatch2(h: *TestHandlerDefaultDispatch, action: Action(*TestHandlerDefaultDispatch), req: *Request, res: *Response) !void { 1707 res.header("dispatcher", "test-dispatcher-2"); 1708 return action(h, req, res); 1709 } 1710 1711 fn dispatch3(h: *TestHandlerDefaultDispatch, action: Action(*TestHandlerDefaultDispatch), req: *Request, res: *Response) !void { 1712 res.header("dispatcher", "test-dispatcher-3"); 1713 return action(h, req, res); 1714 } 1715 1716 fn echo(h: *TestHandlerDefaultDispatch, req: *Request, res: *Response) !void { 1717 return res.json(.{ 1718 .state = h.state, 1719 .method = @tagName(req.method), 1720 .path = req.url.path, 1721 }, .{}); 1722 } 1723 1724 fn echoWrite(h: *TestHandlerDefaultDispatch, req: *Request, res: *Response) !void { 1725 const json_writer = std.json.fmt(.{ 1726 .state = h.state, 1727 .method = @tagName(req.method), 1728 .path = req.url.path, 1729 }, .{}); 1730 1731 var aw: std.io.Writer.Allocating = .init(res.arena); 1732 try json_writer.format(&aw.writer); 1733 1734 res.body = aw.written(); 1735 return res.write(); 1736 } 1737 1738 fn params(_: *TestHandlerDefaultDispatch, req: *Request, res: *Response) !void { 1739 const args = .{ req.param("version").?, req.param("UserId").? }; 1740 res.body = try std.fmt.allocPrint(req.arena, "version={s},user={s}", args); 1741 } 1742 1743 fn headers(h: *TestHandlerDefaultDispatch, req: *Request, res: *Response) !void { 1744 res.header("state", try std.fmt.allocPrint(res.arena, "{d}", .{h.state})); 1745 res.header("Echo", req.header("header-name").?); 1746 res.header("other", "test-value"); 1747 } 1748 1749 fn clBody(_: *TestHandlerDefaultDispatch, req: *Request, res: *Response) !void { 1750 res.header("Echo-Body", req.body().?); 1751 } 1752 1753 fn fail(_: *TestHandlerDefaultDispatch, _: *Request, _: *Response) !void { 1754 return error.TestUnhandledError; 1755 } 1756 1757 pub fn notFound(h: *TestHandlerDefaultDispatch, _: *Request, res: *Response) !void { 1758 res.status = 404; 1759 res.header("state", try std.fmt.allocPrint(res.arena, "{d}", .{h.state})); 1760 res.body = "where lah?"; 1761 } 1762 1763 pub fn uncaughtError(h: *TestHandlerDefaultDispatch, _: *Request, res: *Response, err: anyerror) void { 1764 res.status = 500; 1765 res.header("state", std.fmt.allocPrint(res.arena, "{d}", .{h.state}) catch unreachable); 1766 res.header("err", @errorName(err)); 1767 res.body = "#/why/arent/tags/hierarchical"; 1768 } 1769}; 1770 1771const TestHandlerDispatch = struct { 1772 state: usize, 1773 1774 pub fn dispatch(self: *TestHandlerDispatch, action: Action(*TestHandlerDispatch), req: *Request, res: *Response) !void { 1775 res.header("dstate", try std.fmt.allocPrint(res.arena, "{d}", .{self.state})); 1776 res.header("dispatch", "TestHandlerDispatch"); 1777 return action(self, req, res); 1778 } 1779 1780 fn root(h: *TestHandlerDispatch, _: *Request, res: *Response) !void { 1781 return res.json(.{ .state = h.state }, .{}); 1782 } 1783}; 1784 1785const TestHandlerDispatchContext = struct { 1786 state: usize, 1787 1788 const ActionContext = struct { 1789 other: usize, 1790 }; 1791 1792 pub fn dispatch(self: *TestHandlerDispatchContext, action: Action(*ActionContext), req: *Request, res: *Response) !void { 1793 res.header("dstate", try std.fmt.allocPrint(res.arena, "{d}", .{self.state})); 1794 res.header("dispatch", "TestHandlerDispatchContext"); 1795 var action_context = ActionContext{ .other = self.state + 10 }; 1796 return action(&action_context, req, res); 1797 } 1798 1799 pub fn root(a: *const ActionContext, _: *Request, res: *Response) !void { 1800 return res.json(.{ .other = a.other }, .{}); 1801 } 1802}; 1803 1804const TestHandlerHandle = struct { 1805 pub fn handle(_: TestHandlerHandle, req: *Request, res: *Response) void { 1806 const query = req.query() catch unreachable; 1807 var writer = res.writer(); 1808 writer.interface.print("hello {s}", .{query.get("name") orelse "world"}) catch unreachable; 1809 } 1810}; 1811 1812const TestWebsocketHandler = struct { 1813 pub const WebsocketHandler = struct { 1814 ctx: u32, 1815 conn: *websocket.Conn, 1816 1817 pub fn init(conn: *websocket.Conn, ctx: u32) !WebsocketHandler { 1818 return .{ 1819 .ctx = ctx, 1820 .conn = conn, 1821 }; 1822 } 1823 1824 pub fn afterInit(self: *WebsocketHandler, ctx: u32) !void { 1825 try t.expectEqual(self.ctx, ctx); 1826 } 1827 1828 pub fn clientMessage(self: *WebsocketHandler, data: []const u8) !void { 1829 if (std.mem.eql(u8, data, "close")) { 1830 self.conn.close(.{}) catch {}; 1831 return; 1832 } 1833 try self.conn.write(data); 1834 } 1835 }; 1836 1837 pub fn upgrade(_: TestWebsocketHandler, req: *Request, res: *Response) !void { 1838 if (try upgradeWebsocket(WebsocketHandler, req, res, 9001) == false) { 1839 res.status = 500; 1840 res.body = "invalid websocket"; 1841 } 1842 } 1843}; 1844 1845const TestMiddleware = struct { 1846 const Config = struct { 1847 id: i32, 1848 }; 1849 1850 allocator: Allocator, 1851 v1: []const u8, 1852 v2: []const u8, 1853 1854 fn init(config: TestMiddleware.Config, mc: MiddlewareConfig) !TestMiddleware { 1855 return .{ 1856 .allocator = mc.allocator, 1857 .v1 = try std.fmt.allocPrint(mc.arena, "tm1-{d}", .{config.id}), 1858 .v2 = try std.fmt.allocPrint(mc.allocator, "tm2-{d}", .{config.id}), 1859 }; 1860 } 1861 1862 pub fn deinit(self: *const TestMiddleware) void { 1863 self.allocator.free(self.v2); 1864 } 1865 1866 fn value1(req: *const Request) []const u8 { 1867 const v: [*]u8 = @ptrCast(req.middlewares.get("text_middleware_1").?); 1868 return v[0..7]; 1869 } 1870 1871 fn value2(req: *const Request) []const u8 { 1872 const v: [*]u8 = @ptrCast(req.middlewares.get("text_middleware_2").?); 1873 return v[0..7]; 1874 } 1875 1876 fn execute(self: *const TestMiddleware, req: *Request, _: *Response, executor: anytype) !void { 1877 try req.middlewares.put("text_middleware_1", (try req.arena.dupe(u8, self.v1)).ptr); 1878 try req.middlewares.put("text_middleware_2", (try req.arena.dupe(u8, self.v2)).ptr); 1879 return executor.next(); 1880 } 1881};