| author | |
| committer | |
| log | fef41c66dbe9e6ce3e9567b7d6d06ebf4d0cba06 |
| tree | 8436017ad76255c87f55d7a5016307a2cd9d4f37 |
| parent | e2d81bf6c04d73cbe236e4b497fd2c8c54916077 |
3 files changed, 90 insertions(+), 103 deletions(-)
lib/std/Build/Fuzz.zig+16-23| ... | ... | @@ -234,7 +234,7 @@ pub const Previous = struct { |
| 234 | 234 | }; |
| 235 | 235 | pub fn sendUpdate( |
| 236 | 236 | fuzz: *Fuzz, |
| 237 | socket: *std.http.WebSocket, | |
| 237 | socket: *std.http.Server.WebSocket, | |
| 238 | 238 | prev: *Previous, |
| 239 | 239 | ) !void { |
| 240 | 240 | fuzz.coverage_mutex.lock(); |
| ... | ... | @@ -263,36 +263,36 @@ pub fn sendUpdate( |
| 263 | 263 | .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len), |
| 264 | 264 | .start_timestamp = coverage_map.start_timestamp, |
| 265 | 265 | }; |
| 266 | const iovecs: [5]std.posix.iovec_const = .{ | |
| 267 | makeIov(@ptrCast(&header)), | |
| 268 | makeIov(@ptrCast(coverage_map.coverage.directories.keys())), | |
| 269 | makeIov(@ptrCast(coverage_map.coverage.files.keys())), | |
| 270 | makeIov(@ptrCast(coverage_map.source_locations)), | |
| 271 | makeIov(coverage_map.coverage.string_bytes.items), | |
| 266 | var iovecs: [5][]const u8 = .{ | |
| 267 | @ptrCast(&header), | |
| 268 | @ptrCast(coverage_map.coverage.directories.keys()), | |
| 269 | @ptrCast(coverage_map.coverage.files.keys()), | |
| 270 | @ptrCast(coverage_map.source_locations), | |
| 271 | coverage_map.coverage.string_bytes.items, | |
| 272 | 272 | }; |
| 273 | try socket.writeMessagev(&iovecs, .binary); | |
| 273 | try socket.writeMessageVec(&iovecs, .binary); | |
| 274 | 274 | } |
| 275 | 275 | |
| 276 | 276 | const header: abi.CoverageUpdateHeader = .{ |
| 277 | 277 | .n_runs = n_runs, |
| 278 | 278 | .unique_runs = unique_runs, |
| 279 | 279 | }; |
| 280 | const iovecs: [2]std.posix.iovec_const = .{ | |
| 281 | makeIov(@ptrCast(&header)), | |
| 282 | makeIov(@ptrCast(seen_pcs)), | |
| 280 | var iovecs: [2][]const u8 = .{ | |
| 281 | @ptrCast(&header), | |
| 282 | @ptrCast(seen_pcs), | |
| 283 | 283 | }; |
| 284 | try socket.writeMessagev(&iovecs, .binary); | |
| 284 | try socket.writeMessageVec(&iovecs, .binary); | |
| 285 | 285 | |
| 286 | 286 | prev.unique_runs = unique_runs; |
| 287 | 287 | } |
| 288 | 288 | |
| 289 | 289 | if (prev.entry_points != coverage_map.entry_points.items.len) { |
| 290 | 290 | const header: abi.EntryPointHeader = .init(@intCast(coverage_map.entry_points.items.len)); |
| 291 | const iovecs: [2]std.posix.iovec_const = .{ | |
| 292 | makeIov(@ptrCast(&header)), | |
| 293 | makeIov(@ptrCast(coverage_map.entry_points.items)), | |
| 291 | var iovecs: [2][]const u8 = .{ | |
| 292 | @ptrCast(&header), | |
| 293 | @ptrCast(coverage_map.entry_points.items), | |
| 294 | 294 | }; |
| 295 | try socket.writeMessagev(&iovecs, .binary); | |
| 295 | try socket.writeMessageVec(&iovecs, .binary); | |
| 296 | 296 | |
| 297 | 297 | prev.entry_points = coverage_map.entry_points.items.len; |
| 298 | 298 | } |
| ... | ... | @@ -448,10 +448,3 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte |
| 448 | 448 | } |
| 449 | 449 | try coverage_map.entry_points.append(fuzz.ws.gpa, @intCast(index)); |
| 450 | 450 | } |
| 451 | ||
| 452 | fn makeIov(s: []const u8) std.posix.iovec_const { | |
| 453 | return .{ | |
| 454 | .base = s.ptr, | |
| 455 | .len = s.len, | |
| 456 | }; | |
| 457 | } |
lib/std/Build/WebServer.zig+42-53| ... | ... | @@ -251,48 +251,44 @@ pub fn now(s: *const WebServer) i64 { |
| 251 | 251 | fn accept(ws: *WebServer, connection: std.net.Server.Connection) void { |
| 252 | 252 | defer connection.stream.close(); |
| 253 | 253 | |
| 254 | var read_buf: [0x4000]u8 = undefined; | |
| 255 | var server: std.http.Server = .init(connection, &read_buf); | |
| 254 | var send_buffer: [4096]u8 = undefined; | |
| 255 | var recv_buffer: [4096]u8 = undefined; | |
| 256 | var connection_reader = connection.stream.reader(&recv_buffer); | |
| 257 | var connection_writer = connection.stream.writer(&send_buffer); | |
| 258 | var server: http.Server = .init(connection_reader.interface(), &connection_writer.interface); | |
| 256 | 259 | |
| 257 | 260 | while (true) { |
| 258 | 261 | var request = server.receiveHead() catch |err| switch (err) { |
| 259 | 262 | error.HttpConnectionClosing => return, |
| 260 | else => { | |
| 261 | log.err("failed to receive http request: {s}", .{@errorName(err)}); | |
| 262 | return; | |
| 263 | }, | |
| 263 | else => return log.err("failed to receive http request: {t}", .{err}), | |
| 264 | 264 | }; |
| 265 | var ws_send_buf: [0x4000]u8 = undefined; | |
| 266 | var ws_recv_buf: [0x4000]u8 align(4) = undefined; | |
| 267 | if (std.http.WebSocket.init(&request, &ws_send_buf, &ws_recv_buf) catch |err| { | |
| 268 | log.err("failed to initialize websocket connection: {s}", .{@errorName(err)}); | |
| 269 | return; | |
| 270 | }) |ws_init| { | |
| 271 | var web_socket = ws_init; | |
| 272 | ws.serveWebSocket(&web_socket) catch |err| { | |
| 273 | log.err("failed to serve websocket: {s}", .{@errorName(err)}); | |
| 274 | return; | |
| 275 | }; | |
| 276 | comptime unreachable; | |
| 277 | } else { | |
| 278 | ws.serveRequest(&request) catch |err| switch (err) { | |
| 279 | error.AlreadyReported => return, | |
| 280 | else => { | |
| 281 | log.err("failed to serve '{s}': {s}", .{ request.head.target, @errorName(err) }); | |
| 265 | switch (request.upgradeRequested()) { | |
| 266 | .websocket => |opt_key| { | |
| 267 | const key = opt_key orelse return log.err("missing websocket key", .{}); | |
| 268 | var web_socket = request.respondWebSocket(.{ .key = key }) catch { | |
| 269 | return log.err("failed to respond web socket: {t}", .{connection_writer.err.?}); | |
| 270 | }; | |
| 271 | ws.serveWebSocket(&web_socket) catch |err| { | |
| 272 | log.err("failed to serve websocket: {t}", .{err}); | |
| 282 | 273 | return; |
| 283 | }, | |
| 284 | }; | |
| 274 | }; | |
| 275 | comptime unreachable; | |
| 276 | }, | |
| 277 | .other => |name| return log.err("unknown upgrade request: {s}", .{name}), | |
| 278 | .none => { | |
| 279 | ws.serveRequest(&request) catch |err| switch (err) { | |
| 280 | error.AlreadyReported => return, | |
| 281 | else => { | |
| 282 | log.err("failed to serve '{s}': {t}", .{ request.head.target, err }); | |
| 283 | return; | |
| 284 | }, | |
| 285 | }; | |
| 286 | }, | |
| 285 | 287 | } |
| 286 | 288 | } |
| 287 | 289 | } |
| 288 | 290 | |
| 289 | fn makeIov(s: []const u8) std.posix.iovec_const { | |
| 290 | return .{ | |
| 291 | .base = s.ptr, | |
| 292 | .len = s.len, | |
| 293 | }; | |
| 294 | } | |
| 295 | fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn { | |
| 291 | fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn { | |
| 296 | 292 | var prev_build_status = ws.build_status.load(.monotonic); |
| 297 | 293 | |
| 298 | 294 | const prev_step_status_bits = try ws.gpa.alloc(u8, ws.step_status_bits.len); |
| ... | ... | @@ -312,11 +308,8 @@ fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn { |
| 312 | 308 | .timestamp = ws.now(), |
| 313 | 309 | .steps_len = @intCast(ws.all_steps.len), |
| 314 | 310 | }; |
| 315 | try sock.writeMessagev(&.{ | |
| 316 | makeIov(@ptrCast(&hello_header)), | |
| 317 | makeIov(ws.step_names_trailing), | |
| 318 | makeIov(prev_step_status_bits), | |
| 319 | }, .binary); | |
| 311 | var bufs: [3][]const u8 = .{ @ptrCast(&hello_header), ws.step_names_trailing, prev_step_status_bits }; | |
| 312 | try sock.writeMessageVec(&bufs, .binary); | |
| 320 | 313 | } |
| 321 | 314 | |
| 322 | 315 | var prev_fuzz: Fuzz.Previous = .init; |
| ... | ... | @@ -380,7 +373,7 @@ fn serveWebSocket(ws: *WebServer, sock: *std.http.WebSocket) !noreturn { |
| 380 | 373 | std.Thread.Futex.timedWait(&ws.update_id, start_update_id, std.time.ns_per_ms * default_update_interval_ms) catch {}; |
| 381 | 374 | } |
| 382 | 375 | } |
| 383 | fn recvWebSocketMessages(ws: *WebServer, sock: *std.http.WebSocket) void { | |
| 376 | fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void { | |
| 384 | 377 | while (true) { |
| 385 | 378 | const msg = sock.readSmallMessage() catch return; |
| 386 | 379 | if (msg.opcode != .binary) continue; |
| ... | ... | @@ -402,7 +395,7 @@ fn recvWebSocketMessages(ws: *WebServer, sock: *std.http.WebSocket) void { |
| 402 | 395 | } |
| 403 | 396 | } |
| 404 | 397 | |
| 405 | fn serveRequest(ws: *WebServer, req: *std.http.Server.Request) !void { | |
| 398 | fn serveRequest(ws: *WebServer, req: *http.Server.Request) !void { | |
| 406 | 399 | // Strip an optional leading '/debug' component from the request. |
| 407 | 400 | const target: []const u8, const debug: bool = target: { |
| 408 | 401 | if (mem.eql(u8, req.head.target, "/debug")) break :target .{ "/", true }; |
| ... | ... | @@ -431,7 +424,7 @@ fn serveRequest(ws: *WebServer, req: *std.http.Server.Request) !void { |
| 431 | 424 | |
| 432 | 425 | fn serveLibFile( |
| 433 | 426 | ws: *WebServer, |
| 434 | request: *std.http.Server.Request, | |
| 427 | request: *http.Server.Request, | |
| 435 | 428 | sub_path: []const u8, |
| 436 | 429 | content_type: []const u8, |
| 437 | 430 | ) !void { |
| ... | ... | @@ -442,7 +435,7 @@ fn serveLibFile( |
| 442 | 435 | } |
| 443 | 436 | fn serveClientWasm( |
| 444 | 437 | ws: *WebServer, |
| 445 | req: *std.http.Server.Request, | |
| 438 | req: *http.Server.Request, | |
| 446 | 439 | optimize_mode: std.builtin.OptimizeMode, |
| 447 | 440 | ) !void { |
| 448 | 441 | var arena_state: std.heap.ArenaAllocator = .init(ws.gpa); |
| ... | ... | @@ -456,12 +449,12 @@ fn serveClientWasm( |
| 456 | 449 | |
| 457 | 450 | pub fn serveFile( |
| 458 | 451 | ws: *WebServer, |
| 459 | request: *std.http.Server.Request, | |
| 452 | request: *http.Server.Request, | |
| 460 | 453 | path: Cache.Path, |
| 461 | 454 | content_type: []const u8, |
| 462 | 455 | ) !void { |
| 463 | 456 | const gpa = ws.gpa; |
| 464 | // The desired API is actually sendfile, which will require enhancing std.http.Server. | |
| 457 | // The desired API is actually sendfile, which will require enhancing http.Server. | |
| 465 | 458 | // We load the file with every request so that the user can make changes to the file |
| 466 | 459 | // and refresh the HTML page without restarting this server. |
| 467 | 460 | const file_contents = path.root_dir.handle.readFileAlloc(gpa, path.sub_path, 10 * 1024 * 1024) catch |err| { |
| ... | ... | @@ -478,14 +471,13 @@ pub fn serveFile( |
| 478 | 471 | } |
| 479 | 472 | pub fn serveTarFile( |
| 480 | 473 | ws: *WebServer, |
| 481 | request: *std.http.Server.Request, | |
| 474 | request: *http.Server.Request, | |
| 482 | 475 | paths: []const Cache.Path, |
| 483 | 476 | ) !void { |
| 484 | 477 | const gpa = ws.gpa; |
| 485 | 478 | |
| 486 | var send_buf: [0x4000]u8 = undefined; | |
| 487 | var response = request.respondStreaming(.{ | |
| 488 | .send_buffer = &send_buf, | |
| 479 | var send_buffer: [0x4000]u8 = undefined; | |
| 480 | var response = try request.respondStreaming(&send_buffer, .{ | |
| 489 | 481 | .respond_options = .{ |
| 490 | 482 | .extra_headers = &.{ |
| 491 | 483 | .{ .name = "Content-Type", .value = "application/x-tar" }, |
| ... | ... | @@ -497,10 +489,7 @@ pub fn serveTarFile( |
| 497 | 489 | var cached_cwd_path: ?[]const u8 = null; |
| 498 | 490 | defer if (cached_cwd_path) |p| gpa.free(p); |
| 499 | 491 | |
| 500 | var response_buf: [1024]u8 = undefined; | |
| 501 | var adapter = response.writer().adaptToNewApi(); | |
| 502 | adapter.new_interface.buffer = &response_buf; | |
| 503 | var archiver: std.tar.Writer = .{ .underlying_writer = &adapter.new_interface }; | |
| 492 | var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer }; | |
| 504 | 493 | |
| 505 | 494 | for (paths) |path| { |
| 506 | 495 | var file = path.root_dir.handle.openFile(path.sub_path, .{}) catch |err| { |
| ... | ... | @@ -526,7 +515,6 @@ pub fn serveTarFile( |
| 526 | 515 | } |
| 527 | 516 | |
| 528 | 517 | // intentionally not calling `archiver.finishPedantically` |
| 529 | try adapter.new_interface.flush(); | |
| 530 | 518 | try response.end(); |
| 531 | 519 | } |
| 532 | 520 | |
| ... | ... | @@ -804,7 +792,7 @@ pub fn wait(ws: *WebServer) RunnerRequest { |
| 804 | 792 | } |
| 805 | 793 | } |
| 806 | 794 | |
| 807 | const cache_control_header: std.http.Header = .{ | |
| 795 | const cache_control_header: http.Header = .{ | |
| 808 | 796 | .name = "Cache-Control", |
| 809 | 797 | .value = "max-age=0, must-revalidate", |
| 810 | 798 | }; |
| ... | ... | @@ -819,5 +807,6 @@ const Build = std.Build; |
| 819 | 807 | const Cache = Build.Cache; |
| 820 | 808 | const Fuzz = Build.Fuzz; |
| 821 | 809 | const abi = Build.abi; |
| 810 | const http = std.http; | |
| 822 | 811 | |
| 823 | 812 | const WebServer = @This(); |
lib/std/http/Server.zig+32-27| ... | ... | @@ -7,6 +7,7 @@ const Uri = std.Uri; |
| 7 | 7 | const assert = std.debug.assert; |
| 8 | 8 | const testing = std.testing; |
| 9 | 9 | const Writer = std.Io.Writer; |
| 10 | const Reader = std.Io.Reader; | |
| 10 | 11 | |
| 11 | 12 | const Server = @This(); |
| 12 | 13 | |
| ... | ... | @@ -21,7 +22,7 @@ reader: http.Reader, |
| 21 | 22 | /// header, otherwise `receiveHead` returns `error.HttpHeadersOversize`. |
| 22 | 23 | /// |
| 23 | 24 | /// The returned `Server` is ready for `receiveHead` to be called. |
| 24 | pub fn init(in: *std.Io.Reader, out: *Writer) Server { | |
| 25 | pub fn init(in: *Reader, out: *Writer) Server { | |
| 25 | 26 | return .{ |
| 26 | 27 | .reader = .{ |
| 27 | 28 | .in = in, |
| ... | ... | @@ -225,7 +226,7 @@ pub const Request = struct { |
| 225 | 226 | } |
| 226 | 227 | }; |
| 227 | 228 | |
| 228 | pub fn iterateHeaders(r: *Request) http.HeaderIterator { | |
| 229 | pub fn iterateHeaders(r: *const Request) http.HeaderIterator { | |
| 229 | 230 | assert(r.server.reader.state == .received_head); |
| 230 | 231 | return http.HeaderIterator.init(r.head_buffer); |
| 231 | 232 | } |
| ... | ... | @@ -486,10 +487,11 @@ pub const Request = struct { |
| 486 | 487 | none, |
| 487 | 488 | }; |
| 488 | 489 | |
| 490 | /// Does not invalidate `request.head`. | |
| 489 | 491 | pub fn upgradeRequested(request: *const Request) UpgradeRequest { |
| 490 | 492 | switch (request.head.version) { |
| 491 | .@"HTTP/1.0" => return null, | |
| 492 | .@"HTTP/1.1" => if (request.head.method != .GET) return null, | |
| 493 | .@"HTTP/1.0" => return .none, | |
| 494 | .@"HTTP/1.1" => if (request.head.method != .GET) return .none, | |
| 493 | 495 | } |
| 494 | 496 | |
| 495 | 497 | var sec_websocket_key: ?[]const u8 = null; |
| ... | ... | @@ -517,7 +519,7 @@ pub const Request = struct { |
| 517 | 519 | |
| 518 | 520 | /// The header is not guaranteed to be sent until `WebSocket.flush` is |
| 519 | 521 | /// called on the returned struct. |
| 520 | pub fn respondWebSocket(request: *Request, options: WebSocketOptions) Writer.Error!WebSocket { | |
| 522 | pub fn respondWebSocket(request: *Request, options: WebSocketOptions) ExpectContinueError!WebSocket { | |
| 521 | 523 | if (request.head.expect != null) return error.HttpExpectationFailed; |
| 522 | 524 | |
| 523 | 525 | const out = request.server.out; |
| ... | ... | @@ -536,16 +538,14 @@ pub const Request = struct { |
| 536 | 538 | try out.print("{s} {d} {s}\r\n", .{ @tagName(version), @intFromEnum(status), phrase }); |
| 537 | 539 | try out.writeAll("connection: upgrade\r\nupgrade: websocket\r\nsec-websocket-accept: "); |
| 538 | 540 | const base64_digest = try out.writableArray(28); |
| 539 | assert(std.base64.standard.Encoder.encode(&base64_digest, &digest).len == base64_digest.len); | |
| 541 | assert(std.base64.standard.Encoder.encode(base64_digest, &digest).len == base64_digest.len); | |
| 540 | 542 | out.advance(base64_digest.len); |
| 541 | 543 | try out.writeAll("\r\n"); |
| 542 | 544 | |
| 543 | 545 | for (options.extra_headers) |header| { |
| 544 | 546 | assert(header.name.len != 0); |
| 545 | try out.writeAll(header.name); | |
| 546 | try out.writeAll(": "); | |
| 547 | try out.writeAll(header.value); | |
| 548 | try out.writeAll("\r\n"); | |
| 547 | var bufs: [4][]const u8 = .{ header.name, ": ", header.value, "\r\n" }; | |
| 548 | try out.writeVecAll(&bufs); | |
| 549 | 549 | } |
| 550 | 550 | |
| 551 | 551 | try out.writeAll("\r\n"); |
| ... | ... | @@ -566,7 +566,7 @@ pub const Request = struct { |
| 566 | 566 | /// |
| 567 | 567 | /// See `readerExpectNone` for an infallible alternative that cannot write |
| 568 | 568 | /// to the server output stream. |
| 569 | pub fn readerExpectContinue(request: *Request, buffer: []u8) ExpectContinueError!*std.Io.Reader { | |
| 569 | pub fn readerExpectContinue(request: *Request, buffer: []u8) ExpectContinueError!*Reader { | |
| 570 | 570 | const flush = request.head.expect != null; |
| 571 | 571 | try writeExpectContinue(request); |
| 572 | 572 | if (flush) try request.server.out.flush(); |
| ... | ... | @@ -578,7 +578,7 @@ pub const Request = struct { |
| 578 | 578 | /// this function. |
| 579 | 579 | /// |
| 580 | 580 | /// Asserts that this function is only called once. |
| 581 | pub fn readerExpectNone(request: *Request, buffer: []u8) *std.Io.Reader { | |
| 581 | pub fn readerExpectNone(request: *Request, buffer: []u8) *Reader { | |
| 582 | 582 | assert(request.server.reader.state == .received_head); |
| 583 | 583 | assert(request.head.expect == null); |
| 584 | 584 | if (!request.head.method.requestHasBody()) return .ending; |
| ... | ... | @@ -642,7 +642,7 @@ pub const Request = struct { |
| 642 | 642 | /// See https://tools.ietf.org/html/rfc6455 |
| 643 | 643 | pub const WebSocket = struct { |
| 644 | 644 | key: []const u8, |
| 645 | input: *std.Io.Reader, | |
| 645 | input: *Reader, | |
| 646 | 646 | output: *Writer, |
| 647 | 647 | |
| 648 | 648 | pub const Header0 = packed struct(u8) { |
| ... | ... | @@ -679,6 +679,8 @@ pub const WebSocket = struct { |
| 679 | 679 | UnexpectedOpCode, |
| 680 | 680 | MessageTooBig, |
| 681 | 681 | MissingMaskBit, |
| 682 | ReadFailed, | |
| 683 | EndOfStream, | |
| 682 | 684 | }; |
| 683 | 685 | |
| 684 | 686 | pub const SmallMessage = struct { |
| ... | ... | @@ -693,8 +695,9 @@ pub const WebSocket = struct { |
| 693 | 695 | pub fn readSmallMessage(ws: *WebSocket) ReadSmallTextMessageError!SmallMessage { |
| 694 | 696 | const in = ws.input; |
| 695 | 697 | while (true) { |
| 696 | const h0 = in.takeStruct(Header0); | |
| 697 | const h1 = in.takeStruct(Header1); | |
| 698 | const header = try in.takeArray(2); | |
| 699 | const h0: Header0 = @bitCast(header[0]); | |
| 700 | const h1: Header1 = @bitCast(header[1]); | |
| 698 | 701 | |
| 699 | 702 | switch (h0.opcode) { |
| 700 | 703 | .text, .binary, .pong, .ping => {}, |
| ... | ... | @@ -734,47 +737,49 @@ pub const WebSocket = struct { |
| 734 | 737 | } |
| 735 | 738 | |
| 736 | 739 | pub fn writeMessage(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void { |
| 737 | try writeMessageVecUnflushed(ws, &.{data}, op); | |
| 740 | var bufs: [1][]const u8 = .{data}; | |
| 741 | try writeMessageVecUnflushed(ws, &bufs, op); | |
| 738 | 742 | try ws.output.flush(); |
| 739 | 743 | } |
| 740 | 744 | |
| 741 | 745 | pub fn writeMessageUnflushed(ws: *WebSocket, data: []const u8, op: Opcode) Writer.Error!void { |
| 742 | try writeMessageVecUnflushed(ws, &.{data}, op); | |
| 746 | var bufs: [1][]const u8 = .{data}; | |
| 747 | try writeMessageVecUnflushed(ws, &bufs, op); | |
| 743 | 748 | } |
| 744 | 749 | |
| 745 | pub fn writeMessageVec(ws: *WebSocket, data: []const []const u8, op: Opcode) Writer.Error!void { | |
| 750 | pub fn writeMessageVec(ws: *WebSocket, data: [][]const u8, op: Opcode) Writer.Error!void { | |
| 746 | 751 | try writeMessageVecUnflushed(ws, data, op); |
| 747 | 752 | try ws.output.flush(); |
| 748 | 753 | } |
| 749 | 754 | |
| 750 | pub fn writeMessageVecUnflushed(ws: *WebSocket, data: []const []const u8, op: Opcode) Writer.Error!void { | |
| 755 | pub fn writeMessageVecUnflushed(ws: *WebSocket, data: [][]const u8, op: Opcode) Writer.Error!void { | |
| 751 | 756 | const total_len = l: { |
| 752 | 757 | var total_len: u64 = 0; |
| 753 | 758 | for (data) |iovec| total_len += iovec.len; |
| 754 | 759 | break :l total_len; |
| 755 | 760 | }; |
| 756 | 761 | const out = ws.output; |
| 757 | try out.writeStruct(@as(Header0, .{ | |
| 762 | try out.writeByte(@bitCast(@as(Header0, .{ | |
| 758 | 763 | .opcode = op, |
| 759 | 764 | .fin = true, |
| 760 | })); | |
| 765 | }))); | |
| 761 | 766 | switch (total_len) { |
| 762 | 0...125 => try out.writeStruct(@as(Header1, .{ | |
| 767 | 0...125 => try out.writeByte(@bitCast(@as(Header1, .{ | |
| 763 | 768 | .payload_len = @enumFromInt(total_len), |
| 764 | 769 | .mask = false, |
| 765 | })), | |
| 770 | }))), | |
| 766 | 771 | 126...0xffff => { |
| 767 | try out.writeStruct(@as(Header1, .{ | |
| 772 | try out.writeByte(@bitCast(@as(Header1, .{ | |
| 768 | 773 | .payload_len = .len16, |
| 769 | 774 | .mask = false, |
| 770 | })); | |
| 775 | }))); | |
| 771 | 776 | try out.writeInt(u16, @intCast(total_len), .big); |
| 772 | 777 | }, |
| 773 | 778 | else => { |
| 774 | try out.writeStruct(@as(Header1, .{ | |
| 779 | try out.writeByte(@bitCast(@as(Header1, .{ | |
| 775 | 780 | .payload_len = .len64, |
| 776 | 781 | .mask = false, |
| 777 | })); | |
| 782 | }))); | |
| 778 | 783 | try out.writeInt(u64, total_len, .big); |
| 779 | 784 | }, |
| 780 | 785 | } |