| author | |
| committer | |
| log | c7040171fb06bc4300547a9f4550346b847fc406 |
| tree | 68a0528fb8337b613a0c5f67989d80c467695899 |
| parent | aef0434c014d85d4f5ab8afa931ea1848c8bbd16 |
7 files changed, 433 insertions(+), 305 deletions(-)
lib/std/Build/Fuzz/WebServer.zig+3-3| ... | ... | @@ -476,7 +476,7 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { |
| 476 | 476 | defer arena_instance.deinit(); |
| 477 | 477 | const arena = arena_instance.allocator(); |
| 478 | 478 | |
| 479 | var body_writer = try request.respondStreaming(.{ | |
| 479 | var body = try request.respondStreaming(.{ | |
| 480 | 480 | .respond_options = .{ |
| 481 | 481 | .extra_headers = &.{ |
| 482 | 482 | .{ .name = "content-type", .value = "application/x-tar" }, |
| ... | ... | @@ -517,7 +517,7 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { |
| 517 | 517 | |
| 518 | 518 | var cwd_cache: ?[]const u8 = null; |
| 519 | 519 | |
| 520 | var response_writer = body_writer.interface().unbuffered(); | |
| 520 | var response_writer = body.writer().unbuffered(); | |
| 521 | 521 | var archiver: std.tar.Writer = .{ .underlying_writer = &response_writer }; |
| 522 | 522 | |
| 523 | 523 | for (deduped_paths) |joined_path| { |
| ... | ... | @@ -531,7 +531,7 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { |
| 531 | 531 | try archiver.writeFile(joined_path.sub_path, file, try file.stat()); |
| 532 | 532 | } |
| 533 | 533 | |
| 534 | try body_writer.end(); | |
| 534 | try body.end(); | |
| 535 | 535 | } |
| 536 | 536 | |
| 537 | 537 | fn memoizedCwd(arena: Allocator, opt_ptr: *?[]const u8) ![]const u8 { |
lib/std/http.zig+117-56| ... | ... | @@ -295,13 +295,24 @@ pub const TransferEncoding = enum { |
| 295 | 295 | }; |
| 296 | 296 | |
| 297 | 297 | pub const ContentEncoding = enum { |
| 298 | identity, | |
| 299 | compress, | |
| 300 | @"x-compress", | |
| 301 | deflate, | |
| 302 | gzip, | |
| 303 | @"x-gzip", | |
| 304 | 298 | zstd, |
| 299 | gzip, | |
| 300 | deflate, | |
| 301 | compress, | |
| 302 | identity, | |
| 303 | ||
| 304 | pub fn fromString(s: []const u8) ?ContentEncoding { | |
| 305 | const map = std.StaticStringMap(ContentEncoding).initComptime(.{ | |
| 306 | .{ "zstd", .zstd }, | |
| 307 | .{ "gzip", .gzip }, | |
| 308 | .{ "x-gzip", .gzip }, | |
| 309 | .{ "deflate", .deflate }, | |
| 310 | .{ "compress", .compress }, | |
| 311 | .{ "x-compress", .compress }, | |
| 312 | .{ "identity", .identity }, | |
| 313 | }); | |
| 314 | return map.get(s); | |
| 315 | } | |
| 305 | 316 | }; |
| 306 | 317 | |
| 307 | 318 | pub const Connection = enum { |
| ... | ... | @@ -331,18 +342,9 @@ pub const Reader = struct { |
| 331 | 342 | body_err: ?BodyError = null, |
| 332 | 343 | /// Stolen from `in`. |
| 333 | 344 | head_buffer: []u8 = &.{}, |
| 334 | compression: Compression, | |
| 335 | 345 | |
| 336 | 346 | pub const max_chunk_header_len = 22; |
| 337 | 347 | |
| 338 | pub const Compression = union(enum) { | |
| 339 | deflate: std.compress.zlib.Decompressor, | |
| 340 | gzip: std.compress.gzip.Decompressor, | |
| 341 | // https://github.com/ziglang/zig/issues/18937 | |
| 342 | //zstd: std.compress.zstd.Decompressor, | |
| 343 | none: void, | |
| 344 | }; | |
| 345 | ||
| 346 | 348 | pub const RemainingChunkLen = enum(u64) { |
| 347 | 349 | head = 0, |
| 348 | 350 | n = 1, |
| ... | ... | @@ -416,19 +418,19 @@ pub const Reader = struct { |
| 416 | 418 | } |
| 417 | 419 | } |
| 418 | 420 | |
| 421 | /// If compressed body has been negotiated this will return compressed bytes. | |
| 422 | /// | |
| 419 | 423 | /// Asserts only called once and after `receiveHead`. |
| 420 | pub fn interface( | |
| 421 | reader: *Reader, | |
| 422 | transfer_encoding: TransferEncoding, | |
| 423 | content_length: ?u64, | |
| 424 | content_encoding: ContentEncoding, | |
| 425 | ) std.io.Reader { | |
| 424 | /// | |
| 425 | /// See also: | |
| 426 | /// * `interfaceDecompressing` | |
| 427 | pub fn bodyReader(reader: *Reader, transfer_encoding: TransferEncoding, content_length: ?u64) std.io.Reader { | |
| 426 | 428 | assert(reader.state == .received_head); |
| 427 | 429 | reader.state = .receiving_body; |
| 428 | reader.transfer_br.unbuffered_reader = switch (transfer_encoding) { | |
| 429 | .chunked => r: { | |
| 430 | return switch (transfer_encoding) { | |
| 431 | .chunked => { | |
| 430 | 432 | reader.body_state = .{ .remaining_chunk_len = .head }; |
| 431 | break :r .{ | |
| 433 | return .{ | |
| 432 | 434 | .context = reader, |
| 433 | 435 | .vtable = &.{ |
| 434 | 436 | .read = &chunkedRead, |
| ... | ... | @@ -437,10 +439,10 @@ pub const Reader = struct { |
| 437 | 439 | }, |
| 438 | 440 | }; |
| 439 | 441 | }, |
| 440 | .none => r: { | |
| 442 | .none => { | |
| 441 | 443 | if (content_length) |len| { |
| 442 | 444 | reader.body_state = .{ .remaining_content_length = len }; |
| 443 | break :r .{ | |
| 445 | return .{ | |
| 444 | 446 | .context = reader, |
| 445 | 447 | .vtable = &.{ |
| 446 | 448 | .read = &contentLengthRead, |
| ... | ... | @@ -448,40 +450,53 @@ pub const Reader = struct { |
| 448 | 450 | .discard = &contentLengthDiscard, |
| 449 | 451 | }, |
| 450 | 452 | }; |
| 451 | } else switch (content_encoding) { | |
| 452 | .identity => { | |
| 453 | reader.compression = .none; | |
| 454 | return reader.in.reader(); | |
| 455 | }, | |
| 456 | .deflate => { | |
| 457 | reader.compression = .{ .deflate = .init(reader.in) }; | |
| 458 | return reader.compression.deflate.reader(); | |
| 459 | }, | |
| 460 | .gzip, .@"x-gzip" => { | |
| 461 | reader.compression = .{ .gzip = .init(reader.in) }; | |
| 462 | return reader.compression.gzip.reader(); | |
| 463 | }, | |
| 464 | .compress, .@"x-compress" => unreachable, | |
| 465 | .zstd => unreachable, // https://github.com/ziglang/zig/issues/18937 | |
| 453 | } else { | |
| 454 | return reader.in.reader(); | |
| 466 | 455 | } |
| 467 | 456 | }, |
| 468 | 457 | }; |
| 469 | switch (content_encoding) { | |
| 470 | .identity => { | |
| 471 | reader.compression = .none; | |
| 472 | return reader.transfer_br.unbuffered_reader; | |
| 473 | }, | |
| 474 | .deflate => { | |
| 475 | reader.compression = .{ .deflate = .init(&reader.transfer_br) }; | |
| 476 | return reader.compression.deflate.reader(); | |
| 477 | }, | |
| 478 | .gzip, .@"x-gzip" => { | |
| 479 | reader.compression = .{ .gzip = .init(&reader.transfer_br) }; | |
| 480 | return reader.compression.gzip.reader(); | |
| 481 | }, | |
| 482 | .compress, .@"x-compress" => unreachable, | |
| 483 | .zstd => unreachable, // https://github.com/ziglang/zig/issues/18937 | |
| 458 | } | |
| 459 | ||
| 460 | /// If compressed body has been negotiated this will return decompressed bytes. | |
| 461 | /// | |
| 462 | /// Asserts only called once and after `receiveHead`. | |
| 463 | /// | |
| 464 | /// See also: | |
| 465 | /// * `interface` | |
| 466 | pub fn bodyReaderDecompressing( | |
| 467 | reader: *Reader, | |
| 468 | transfer_encoding: TransferEncoding, | |
| 469 | content_length: ?u64, | |
| 470 | content_encoding: ContentEncoding, | |
| 471 | decompressor: *Decompressor, | |
| 472 | decompression_buffer: []u8, | |
| 473 | ) std.io.Reader { | |
| 474 | if (transfer_encoding == .none and content_length == null) { | |
| 475 | assert(reader.state == .received_head); | |
| 476 | reader.state = .receiving_body; | |
| 477 | switch (content_encoding) { | |
| 478 | .identity => { | |
| 479 | return reader.in.reader(); | |
| 480 | }, | |
| 481 | .deflate => { | |
| 482 | decompressor.compression = .{ .deflate = .init(reader.in) }; | |
| 483 | return decompressor.compression.deflate.reader(); | |
| 484 | }, | |
| 485 | .gzip => { | |
| 486 | decompressor.compression = .{ .gzip = .init(reader.in) }; | |
| 487 | return decompressor.compression.gzip.reader(); | |
| 488 | }, | |
| 489 | .zstd => { | |
| 490 | decompressor.compression = .{ .zstd = .init(reader.in, .{ | |
| 491 | .window_buffer = decompression_buffer, | |
| 492 | }) }; | |
| 493 | return decompressor.compression.zstd.reader(); | |
| 494 | }, | |
| 495 | .compress => unreachable, | |
| 496 | } | |
| 484 | 497 | } |
| 498 | const transfer_reader = bodyReader(reader, transfer_encoding, content_length); | |
| 499 | return decompressor.reader(transfer_reader, decompression_buffer, content_encoding); | |
| 485 | 500 | } |
| 486 | 501 | |
| 487 | 502 | fn contentLengthRead( |
| ... | ... | @@ -720,6 +735,52 @@ pub const Reader = struct { |
| 720 | 735 | } |
| 721 | 736 | }; |
| 722 | 737 | |
| 738 | pub const Decompressor = struct { | |
| 739 | compression: Compression, | |
| 740 | buffered_reader: std.io.BufferedReader, | |
| 741 | ||
| 742 | pub const Compression = union(enum) { | |
| 743 | deflate: std.compress.zlib.Decompressor, | |
| 744 | gzip: std.compress.gzip.Decompressor, | |
| 745 | zstd: std.compress.zstd.Decompressor, | |
| 746 | none: void, | |
| 747 | }; | |
| 748 | ||
| 749 | pub fn reader( | |
| 750 | decompressor: *Decompressor, | |
| 751 | transfer_reader: std.io.Reader, | |
| 752 | buffer: []u8, | |
| 753 | content_encoding: ContentEncoding, | |
| 754 | ) std.io.Reader { | |
| 755 | switch (content_encoding) { | |
| 756 | .identity => { | |
| 757 | decompressor.compression = .none; | |
| 758 | return transfer_reader; | |
| 759 | }, | |
| 760 | .deflate => { | |
| 761 | decompressor.buffered_reader = transfer_reader.buffered(buffer); | |
| 762 | decompressor.compression = .{ .deflate = .init(&decompressor.buffered_reader) }; | |
| 763 | return decompressor.compression.deflate.reader(); | |
| 764 | }, | |
| 765 | .gzip => { | |
| 766 | decompressor.buffered_reader = transfer_reader.buffered(buffer); | |
| 767 | decompressor.compression = .{ .gzip = .init(&decompressor.buffered_reader) }; | |
| 768 | return decompressor.compression.gzip.reader(); | |
| 769 | }, | |
| 770 | .zstd => { | |
| 771 | const first_half = buffer[0 .. buffer.len / 2]; | |
| 772 | const second_half = buffer[buffer.len / 2 ..]; | |
| 773 | decompressor.buffered_reader = transfer_reader.buffered(first_half); | |
| 774 | decompressor.compression = .{ .zstd = .init(&decompressor.buffered_reader, .{ | |
| 775 | .window_buffer = second_half, | |
| 776 | }) }; | |
| 777 | return decompressor.compression.gzip.reader(); | |
| 778 | }, | |
| 779 | .compress => unreachable, | |
| 780 | } | |
| 781 | } | |
| 782 | }; | |
| 783 | ||
| 723 | 784 | /// Request or response body. |
| 724 | 785 | pub const BodyWriter = struct { |
| 725 | 786 | /// Until the lifetime of `BodyWriter` ends, it is illegal to modify the |
lib/std/http/Client.zig+243-152| ... | ... | @@ -85,7 +85,7 @@ pub const ConnectionPool = struct { |
| 85 | 85 | if (connection.port != criteria.port) continue; |
| 86 | 86 | |
| 87 | 87 | // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4) |
| 88 | if (!std.ascii.eqlIgnoreCase(connection.host, criteria.host)) continue; | |
| 88 | if (!std.ascii.eqlIgnoreCase(connection.host(), criteria.host)) continue; | |
| 89 | 89 | |
| 90 | 90 | pool.acquireUnsafe(connection); |
| 91 | 91 | return connection; |
| ... | ... | @@ -227,7 +227,8 @@ pub const Protocol = enum { |
| 227 | 227 | |
| 228 | 228 | pub const Connection = struct { |
| 229 | 229 | client: *Client, |
| 230 | stream: net.Stream, | |
| 230 | stream_writer: net.Stream.Writer, | |
| 231 | stream_reader: net.Stream.Reader, | |
| 231 | 232 | /// HTTP protocol from client to server. |
| 232 | 233 | /// This either goes directly to `stream`, or to a TLS client. |
| 233 | 234 | writer: std.io.BufferedWriter, |
| ... | ... | @@ -249,7 +250,7 @@ pub const Connection = struct { |
| 249 | 250 | remote_host: []const u8, |
| 250 | 251 | port: u16, |
| 251 | 252 | stream: net.Stream, |
| 252 | ) error{OutOfMemory}!*Connection { | |
| 253 | ) error{OutOfMemory}!*Plain { | |
| 253 | 254 | const gpa = client.allocator; |
| 254 | 255 | const alloc_len = allocLen(client, remote_host.len); |
| 255 | 256 | const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len); |
| ... | ... | @@ -263,17 +264,19 @@ pub const Connection = struct { |
| 263 | 264 | plain.* = .{ |
| 264 | 265 | .connection = .{ |
| 265 | 266 | .client = client, |
| 266 | .stream = stream, | |
| 267 | .writer = stream.writer().buffered(socket_write_buffer), | |
| 267 | .stream_writer = stream.writer(), | |
| 268 | .stream_reader = stream.reader(), | |
| 269 | .writer = plain.connection.stream_writer.interface().buffered(socket_write_buffer), | |
| 268 | 270 | .pool_node = .{}, |
| 269 | 271 | .port = port, |
| 272 | .host_len = @intCast(remote_host.len), | |
| 270 | 273 | .proxied = false, |
| 271 | 274 | .closing = false, |
| 272 | 275 | .protocol = .plain, |
| 273 | 276 | }, |
| 274 | .reader = undefined, | |
| 277 | .reader = plain.connection.stream_reader.interface().buffered(socket_read_buffer), | |
| 275 | 278 | }; |
| 276 | plain.reader.init(stream.reader(), socket_read_buffer); | |
| 279 | return plain; | |
| 277 | 280 | } |
| 278 | 281 | |
| 279 | 282 | fn destroy(plain: *Plain) void { |
| ... | ... | @@ -321,19 +324,20 @@ pub const Connection = struct { |
| 321 | 324 | tls.* = .{ |
| 322 | 325 | .connection = .{ |
| 323 | 326 | .client = client, |
| 324 | .stream = stream, | |
| 327 | .stream_writer = stream.writer(), | |
| 328 | .stream_reader = stream.reader(), | |
| 325 | 329 | .writer = tls.client.writer().buffered(socket_write_buffer), |
| 326 | 330 | .pool_node = .{}, |
| 327 | 331 | .port = port, |
| 332 | .host_len = @intCast(remote_host.len), | |
| 328 | 333 | .proxied = false, |
| 329 | 334 | .closing = false, |
| 330 | 335 | .protocol = .tls, |
| 331 | 336 | }, |
| 332 | .writer = stream.writer().buffered(tls_write_buffer), | |
| 333 | .reader = undefined, | |
| 337 | .writer = tls.connection.stream_writer.interface().buffered(tls_write_buffer), | |
| 338 | .reader = tls.connection.stream_reader.interface().buffered(tls_read_buffer), | |
| 334 | 339 | .client = undefined, |
| 335 | 340 | }; |
| 336 | tls.reader.init(stream.reader(), tls_read_buffer); | |
| 337 | 341 | // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true |
| 338 | 342 | tls.client.init(&tls.reader, &tls.writer, .{ |
| 339 | 343 | .host = .{ .explicit = remote_host }, |
| ... | ... | @@ -364,6 +368,10 @@ pub const Connection = struct { |
| 364 | 368 | } |
| 365 | 369 | }; |
| 366 | 370 | |
| 371 | fn getStream(c: *Connection) net.Stream { | |
| 372 | return c.stream_reader.getStream(); | |
| 373 | } | |
| 374 | ||
| 367 | 375 | fn host(c: *Connection) []u8 { |
| 368 | 376 | return switch (c.protocol) { |
| 369 | 377 | .tls => { |
| ... | ... | @@ -396,7 +404,7 @@ pub const Connection = struct { |
| 396 | 404 | /// If this is called without calling `flush` or `end`, data will be |
| 397 | 405 | /// dropped unsent. |
| 398 | 406 | pub fn destroy(c: *Connection) void { |
| 399 | c.stream.close(); | |
| 407 | c.getStream().close(); | |
| 400 | 408 | switch (c.protocol) { |
| 401 | 409 | .tls => { |
| 402 | 410 | if (disable_tls) unreachable; |
| ... | ... | @@ -457,12 +465,12 @@ pub const Response = struct { |
| 457 | 465 | content_encoding: http.ContentEncoding = .identity, |
| 458 | 466 | |
| 459 | 467 | pub const ParseError = error{ |
| 460 | HttpHeadersInvalid, | |
| 468 | HttpConnectionHeaderUnsupported, | |
| 469 | HttpContentEncodingUnsupported, | |
| 461 | 470 | HttpHeaderContinuationsUnsupported, |
| 471 | HttpHeadersInvalid, | |
| 462 | 472 | HttpTransferEncodingUnsupported, |
| 463 | HttpConnectionHeaderUnsupported, | |
| 464 | 473 | InvalidContentLength, |
| 465 | CompressionUnsupported, | |
| 466 | 474 | }; |
| 467 | 475 | |
| 468 | 476 | pub fn parse(bytes: []const u8) ParseError!Head { |
| ... | ... | @@ -536,7 +544,7 @@ pub const Response = struct { |
| 536 | 544 | if (next) |second| { |
| 537 | 545 | const trimmed_second = mem.trim(u8, second, " "); |
| 538 | 546 | |
| 539 | if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| { | |
| 547 | if (http.ContentEncoding.fromString(trimmed_second)) |transfer| { | |
| 540 | 548 | if (res.content_encoding != .identity) return error.HttpHeadersInvalid; // double compression is not supported |
| 541 | 549 | res.content_encoding = transfer; |
| 542 | 550 | } else { |
| ... | ... | @@ -556,10 +564,10 @@ pub const Response = struct { |
| 556 | 564 | |
| 557 | 565 | const trimmed = mem.trim(u8, header_value, " "); |
| 558 | 566 | |
| 559 | if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| { | |
| 567 | if (http.ContentEncoding.fromString(trimmed)) |ce| { | |
| 560 | 568 | res.content_encoding = ce; |
| 561 | 569 | } else { |
| 562 | return error.HttpTransferEncodingUnsupported; | |
| 570 | return error.HttpContentEncodingUnsupported; | |
| 563 | 571 | } |
| 564 | 572 | } |
| 565 | 573 | } |
| ... | ... | @@ -664,10 +672,49 @@ pub const Response = struct { |
| 664 | 672 | } |
| 665 | 673 | }; |
| 666 | 674 | |
| 675 | /// If compressed body has been negotiated this will return compressed bytes. | |
| 676 | /// | |
| 677 | /// If the returned `std.io.Reader` returns `error.ReadFailed` the error is | |
| 678 | /// available via `bodyErr`. | |
| 679 | /// | |
| 667 | 680 | /// Asserts that this function is only called once. |
| 681 | /// | |
| 682 | /// See also: | |
| 683 | /// * `readerDecompressing` | |
| 668 | 684 | pub fn reader(response: *Response) std.io.Reader { |
| 669 | 685 | const head = &response.head; |
| 670 | return response.request.reader.interface(head.transfer_encoding, head.content_length, head.content_encoding); | |
| 686 | return response.request.reader.bodyReader(head.transfer_encoding, head.content_length); | |
| 687 | } | |
| 688 | ||
| 689 | /// If compressed body has been negotiated this will return decompressed bytes. | |
| 690 | /// | |
| 691 | /// If the returned `std.io.Reader` returns `error.ReadFailed` the error is | |
| 692 | /// available via `bodyErr`. | |
| 693 | /// | |
| 694 | /// Asserts that this function is only called once. | |
| 695 | /// | |
| 696 | /// See also: | |
| 697 | /// * `reader` | |
| 698 | pub fn readerDecompressing( | |
| 699 | response: *Response, | |
| 700 | decompressor: *http.Decompressor, | |
| 701 | decompression_buffer: []u8, | |
| 702 | ) std.io.Reader { | |
| 703 | const head = &response.head; | |
| 704 | return response.request.reader.bodyReaderDecompressing( | |
| 705 | head.transfer_encoding, | |
| 706 | head.content_length, | |
| 707 | head.content_encoding, | |
| 708 | decompressor, | |
| 709 | decompression_buffer, | |
| 710 | ); | |
| 711 | } | |
| 712 | ||
| 713 | /// After receiving `error.ReadFailed` from the `std.io.Reader` returned by | |
| 714 | /// `reader` or `readerDecompressing`, this function accesses the | |
| 715 | /// more specific error code. | |
| 716 | pub fn bodyErr(response: *const Response) ?http.Reader.BodyError { | |
| 717 | return response.request.reader.body_err; | |
| 671 | 718 | } |
| 672 | 719 | }; |
| 673 | 720 | |
| ... | ... | @@ -688,6 +735,7 @@ pub const Request = struct { |
| 688 | 735 | version: http.Version = .@"HTTP/1.1", |
| 689 | 736 | transfer_encoding: TransferEncoding, |
| 690 | 737 | redirect_behavior: RedirectBehavior, |
| 738 | accept_encoding: @TypeOf(default_accept_encoding) = default_accept_encoding, | |
| 691 | 739 | |
| 692 | 740 | /// Whether the request should handle a 100-continue response before sending the request body. |
| 693 | 741 | handle_continue: bool, |
| ... | ... | @@ -705,6 +753,14 @@ pub const Request = struct { |
| 705 | 753 | /// Externally-owned; must outlive the Request. |
| 706 | 754 | privileged_headers: []const http.Header, |
| 707 | 755 | |
| 756 | pub const default_accept_encoding: [@typeInfo(http.ContentEncoding).@"enum".fields.len]bool = b: { | |
| 757 | var result: [@typeInfo(http.ContentEncoding).@"enum".fields.len]bool = @splat(false); | |
| 758 | result[@intFromEnum(http.ContentEncoding.gzip)] = true; | |
| 759 | result[@intFromEnum(http.ContentEncoding.deflate)] = true; | |
| 760 | result[@intFromEnum(http.ContentEncoding.identity)] = true; | |
| 761 | break :b result; | |
| 762 | }; | |
| 763 | ||
| 708 | 764 | pub const TransferEncoding = union(enum) { |
| 709 | 765 | content_length: u64, |
| 710 | 766 | chunked: void, |
| ... | ... | @@ -844,9 +900,18 @@ pub const Request = struct { |
| 844 | 900 | } |
| 845 | 901 | |
| 846 | 902 | if (try emitOverridableHeader("accept-encoding: ", r.headers.accept_encoding, w)) { |
| 847 | // https://github.com/ziglang/zig/issues/18937 | |
| 848 | //try w.writeAll("accept-encoding: gzip, deflate, zstd\r\n"); | |
| 849 | try w.writeAll("accept-encoding: gzip, deflate\r\n"); | |
| 903 | try w.writeAll("accept-encoding: "); | |
| 904 | for (r.accept_encoding, 0..) |enabled, i| { | |
| 905 | if (!enabled) continue; | |
| 906 | const tag: http.ContentEncoding = @enumFromInt(i); | |
| 907 | if (tag == .identity) continue; | |
| 908 | const tag_name = @tagName(tag); | |
| 909 | try w.ensureUnusedCapacity(tag_name.len + 2); | |
| 910 | try w.writeAll(tag_name); | |
| 911 | try w.writeAll(", "); | |
| 912 | } | |
| 913 | w.undo(2); | |
| 914 | try w.writeAll("\r\n"); | |
| 850 | 915 | } |
| 851 | 916 | |
| 852 | 917 | switch (r.transfer_encoding) { |
| ... | ... | @@ -884,7 +949,7 @@ pub const Request = struct { |
| 884 | 949 | try w.writeAll("\r\n"); |
| 885 | 950 | } |
| 886 | 951 | |
| 887 | pub const ReceiveHeadError = std.io.Writer.Error || http.Reader.HeadError || error{ | |
| 952 | pub const ReceiveHeadError = http.Reader.HeadError || ConnectError || error{ | |
| 888 | 953 | /// Server sent headers that did not conform to the HTTP protocol. |
| 889 | 954 | /// |
| 890 | 955 | /// To find out more detailed diagnostics, `http.Reader.head_buffer` can be |
| ... | ... | @@ -897,8 +962,14 @@ pub const Request = struct { |
| 897 | 962 | HttpRedirectLocationMissing, |
| 898 | 963 | HttpRedirectLocationOversize, |
| 899 | 964 | HttpRedirectLocationInvalid, |
| 900 | CompressionInitializationFailed, | |
| 901 | CompressionUnsupported, | |
| 965 | HttpContentEncodingUnsupported, | |
| 966 | HttpChunkInvalid, | |
| 967 | HttpHeadersOversize, | |
| 968 | UnsupportedUriScheme, | |
| 969 | ||
| 970 | /// Sending the request failed. Error code can be found on the | |
| 971 | /// `Connection` object. | |
| 972 | WriteFailed, | |
| 902 | 973 | }; |
| 903 | 974 | |
| 904 | 975 | /// If handling redirects and the request has no payload, then this |
| ... | ... | @@ -957,44 +1028,35 @@ pub const Request = struct { |
| 957 | 1028 | |
| 958 | 1029 | if (head.status.class() == .redirect and r.redirect_behavior != .unhandled) { |
| 959 | 1030 | if (r.redirect_behavior == .not_allowed) return error.TooManyHttpRedirects; |
| 960 | const location = head.location orelse return error.HttpRedirectLocationMissing; | |
| 961 | try r.redirect(location, &aux_buf); | |
| 1031 | try r.redirect(head, &aux_buf); | |
| 962 | 1032 | try r.sendBodiless(); |
| 963 | 1033 | continue; |
| 964 | 1034 | } |
| 965 | 1035 | |
| 966 | switch (head.content_encoding) { | |
| 967 | .identity, .deflate, .gzip, .@"x-gzip" => {}, | |
| 968 | .compress, .@"x-compress" => return error.CompressionUnsupported, | |
| 969 | // https://github.com/ziglang/zig/issues/18937 | |
| 970 | .zstd => return error.CompressionUnsupported, | |
| 971 | } | |
| 1036 | if (!r.accept_encoding[@intFromEnum(head.content_encoding)]) | |
| 1037 | return error.HttpContentEncodingUnsupported; | |
| 972 | 1038 | |
| 973 | 1039 | return response; |
| 974 | 1040 | } |
| 975 | 1041 | } |
| 976 | 1042 | |
| 977 | pub const RedirectError = error{ | |
| 978 | HttpRedirectLocationOversize, | |
| 979 | HttpRedirectLocationInvalid, | |
| 980 | }; | |
| 981 | ||
| 982 | 1043 | /// This function takes an auxiliary buffer to store the arbitrarily large |
| 983 | 1044 | /// URI which may need to be merged with the previous URI, and that data |
| 984 | 1045 | /// needs to survive across different connections, which is where the input |
| 985 | 1046 | /// buffer lives. |
| 986 | 1047 | /// |
| 987 | 1048 | /// `aux_buf` must outlive accesses to `Request.uri`. |
| 988 | fn redirect(r: *Request, new_location: []const u8, aux_buf: *[]u8) RedirectError!void { | |
| 1049 | fn redirect(r: *Request, head: *const Response.Head, aux_buf: *[]u8) !void { | |
| 1050 | const new_location = head.location orelse return error.HttpRedirectLocationMissing; | |
| 989 | 1051 | if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize; |
| 990 | 1052 | const location = aux_buf.*[0..new_location.len]; |
| 991 | 1053 | @memcpy(location, new_location); |
| 992 | 1054 | { |
| 993 | 1055 | // Skip the body of the redirect response to leave the connection in |
| 994 | 1056 | // the correct state. This causes `new_location` to be invalidated. |
| 995 | var reader = r.reader.interface(); | |
| 1057 | var reader = r.reader.bodyReader(head.transfer_encoding, head.content_length); | |
| 996 | 1058 | _ = reader.discardRemaining() catch |err| switch (err) { |
| 997 | error.ReadFailed => return r.reader.err.?, | |
| 1059 | error.ReadFailed => return r.reader.body_err.?, | |
| 998 | 1060 | }; |
| 999 | 1061 | } |
| 1000 | 1062 | const new_uri = r.uri.resolveInPlace(location.len, aux_buf) catch |err| switch (err) { |
| ... | ... | @@ -1003,7 +1065,6 @@ pub const Request = struct { |
| 1003 | 1065 | error.InvalidPort => return error.HttpRedirectLocationInvalid, |
| 1004 | 1066 | error.NoSpaceLeft => return error.HttpRedirectLocationOversize, |
| 1005 | 1067 | }; |
| 1006 | const resolved_len = location.len + (aux_buf.*.ptr - location.ptr); | |
| 1007 | 1068 | |
| 1008 | 1069 | const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme; |
| 1009 | 1070 | const old_connection = r.connection.?; |
| ... | ... | @@ -1022,7 +1083,7 @@ pub const Request = struct { |
| 1022 | 1083 | r.privileged_headers = &.{}; |
| 1023 | 1084 | } |
| 1024 | 1085 | |
| 1025 | if (switch (r.response.status) { | |
| 1086 | if (switch (head.status) { | |
| 1026 | 1087 | .see_other => true, |
| 1027 | 1088 | .moved_permanently, .found => r.method == .POST, |
| 1028 | 1089 | else => false, |
| ... | ... | @@ -1042,7 +1103,6 @@ pub const Request = struct { |
| 1042 | 1103 | |
| 1043 | 1104 | const new_connection = try r.client.connect(new_host, uriPort(new_uri, protocol), protocol); |
| 1044 | 1105 | r.uri = new_uri; |
| 1045 | r.stolen_bytes_len = resolved_len; | |
| 1046 | 1106 | r.connection = new_connection; |
| 1047 | 1107 | r.redirect_behavior.subtractOne(); |
| 1048 | 1108 | } |
| ... | ... | @@ -1054,9 +1114,8 @@ pub const Request = struct { |
| 1054 | 1114 | .default => return true, |
| 1055 | 1115 | .omit => return false, |
| 1056 | 1116 | .override => |x| { |
| 1057 | try bw.writeAll(prefix); | |
| 1058 | try bw.writeAll(x); | |
| 1059 | try bw.writeAll("\r\n"); | |
| 1117 | var vecs: [3][]const u8 = .{ prefix, x, "\r\n" }; | |
| 1118 | try bw.writeVecAll(&vecs); | |
| 1060 | 1119 | return false; |
| 1061 | 1120 | }, |
| 1062 | 1121 | } |
| ... | ... | @@ -1198,9 +1257,29 @@ pub fn connectTcp( |
| 1198 | 1257 | port: u16, |
| 1199 | 1258 | protocol: Protocol, |
| 1200 | 1259 | ) ConnectTcpError!*Connection { |
| 1260 | return connectTcpOptions(client, .{ .host = host, .port = port, .protocol = protocol }); | |
| 1261 | } | |
| 1262 | ||
| 1263 | pub const ConnectTcpOptions = struct { | |
| 1264 | host: []const u8, | |
| 1265 | port: u16, | |
| 1266 | protocol: Protocol, | |
| 1267 | ||
| 1268 | proxied_host: ?[]const u8 = null, | |
| 1269 | proxied_port: ?u16 = null, | |
| 1270 | }; | |
| 1271 | ||
| 1272 | pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection { | |
| 1273 | const host = options.host; | |
| 1274 | const port = options.port; | |
| 1275 | const protocol = options.protocol; | |
| 1276 | ||
| 1277 | const proxied_host = options.proxied_host orelse host; | |
| 1278 | const proxied_port = options.proxied_port orelse port; | |
| 1279 | ||
| 1201 | 1280 | if (client.connection_pool.findConnection(.{ |
| 1202 | .host = host, | |
| 1203 | .port = port, | |
| 1281 | .host = proxied_host, | |
| 1282 | .port = proxied_port, | |
| 1204 | 1283 | .protocol = protocol, |
| 1205 | 1284 | })) |conn| return conn; |
| 1206 | 1285 | |
| ... | ... | @@ -1220,12 +1299,12 @@ pub fn connectTcp( |
| 1220 | 1299 | switch (protocol) { |
| 1221 | 1300 | .tls => { |
| 1222 | 1301 | if (disable_tls) return error.TlsInitializationFailed; |
| 1223 | const tc = try Connection.Tls.create(client, host, port, stream); | |
| 1302 | const tc = try Connection.Tls.create(client, proxied_host, proxied_port, stream); | |
| 1224 | 1303 | client.connection_pool.addUsed(&tc.connection); |
| 1225 | 1304 | return &tc.connection; |
| 1226 | 1305 | }, |
| 1227 | 1306 | .plain => { |
| 1228 | const pc = try Connection.Plain.create(client, host, port, stream); | |
| 1307 | const pc = try Connection.Plain.create(client, proxied_host, proxied_port, stream); | |
| 1229 | 1308 | client.connection_pool.addUsed(&pc.connection); |
| 1230 | 1309 | return &pc.connection; |
| 1231 | 1310 | }, |
| ... | ... | @@ -1267,69 +1346,67 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti |
| 1267 | 1346 | return &conn.data; |
| 1268 | 1347 | } |
| 1269 | 1348 | |
| 1270 | /// Connect to `tunnel_host:tunnel_port` using the specified proxy with HTTP | |
| 1349 | /// Connect to `proxied_host:proxied_port` using the specified proxy with HTTP | |
| 1271 | 1350 | /// CONNECT. This will reuse a connection if one is already open. |
| 1272 | 1351 | /// |
| 1273 | 1352 | /// This function is threadsafe. |
| 1274 | pub fn connectTunnel( | |
| 1353 | pub fn connectProxied( | |
| 1275 | 1354 | client: *Client, |
| 1276 | 1355 | proxy: *Proxy, |
| 1277 | tunnel_host: []const u8, | |
| 1278 | tunnel_port: u16, | |
| 1356 | proxied_host: []const u8, | |
| 1357 | proxied_port: u16, | |
| 1279 | 1358 | ) !*Connection { |
| 1280 | 1359 | if (!proxy.supports_connect) return error.TunnelNotSupported; |
| 1281 | 1360 | |
| 1282 | 1361 | if (client.connection_pool.findConnection(.{ |
| 1283 | .host = tunnel_host, | |
| 1284 | .port = tunnel_port, | |
| 1362 | .host = proxied_host, | |
| 1363 | .port = proxied_port, | |
| 1285 | 1364 | .protocol = proxy.protocol, |
| 1286 | })) |node| | |
| 1287 | return node; | |
| 1365 | })) |node| return node; | |
| 1288 | 1366 | |
| 1289 | 1367 | var maybe_valid = false; |
| 1290 | 1368 | (tunnel: { |
| 1291 | const conn = try client.connectTcp(proxy.host, proxy.port, proxy.protocol); | |
| 1369 | const connection = try client.connectTcpOptions(.{ | |
| 1370 | .host = proxy.host, | |
| 1371 | .port = proxy.port, | |
| 1372 | .protocol = proxy.protocol, | |
| 1373 | .proxied_host = proxied_host, | |
| 1374 | .proxied_port = proxied_port, | |
| 1375 | }); | |
| 1292 | 1376 | errdefer { |
| 1293 | conn.closing = true; | |
| 1294 | client.connection_pool.release(conn); | |
| 1377 | connection.closing = true; | |
| 1378 | client.connection_pool.release(connection); | |
| 1295 | 1379 | } |
| 1296 | 1380 | |
| 1297 | var buffer: [8096]u8 = undefined; | |
| 1298 | var req = client.open(.CONNECT, .{ | |
| 1381 | var req = client.request(.CONNECT, .{ | |
| 1299 | 1382 | .scheme = "http", |
| 1300 | .host = .{ .raw = tunnel_host }, | |
| 1301 | .port = tunnel_port, | |
| 1383 | .host = .{ .raw = proxied_host }, | |
| 1384 | .port = proxied_port, | |
| 1302 | 1385 | }, .{ |
| 1303 | 1386 | .redirect_behavior = .unhandled, |
| 1304 | .connection = conn, | |
| 1305 | .server_header_buffer = &buffer, | |
| 1387 | .connection = connection, | |
| 1306 | 1388 | }) catch |err| { |
| 1307 | std.log.debug("err {}", .{err}); | |
| 1308 | 1389 | break :tunnel err; |
| 1309 | 1390 | }; |
| 1310 | 1391 | defer req.deinit(); |
| 1311 | 1392 | |
| 1312 | req.send() catch |err| break :tunnel err; | |
| 1313 | req.wait() catch |err| break :tunnel err; | |
| 1393 | req.sendBodiless() catch |err| break :tunnel err; | |
| 1394 | const response = req.receiveHead(&.{}) catch |err| break :tunnel err; | |
| 1314 | 1395 | |
| 1315 | if (req.response.status.class() == .server_error) { | |
| 1396 | if (response.head.status.class() == .server_error) { | |
| 1316 | 1397 | maybe_valid = true; |
| 1317 | 1398 | break :tunnel error.ServerError; |
| 1318 | 1399 | } |
| 1319 | 1400 | |
| 1320 | if (req.response.status != .ok) break :tunnel error.ConnectionRefused; | |
| 1401 | if (response.head.status != .ok) break :tunnel error.ConnectionRefused; | |
| 1321 | 1402 | |
| 1322 | // this connection is now a tunnel, so we can't use it for anything else, it will only be released when the client is de-initialized. | |
| 1403 | // this connection is now a tunnel, so we can't use it for anything | |
| 1404 | // else, it will only be released when the client is de-initialized. | |
| 1323 | 1405 | req.connection = null; |
| 1324 | 1406 | |
| 1325 | client.allocator.free(conn.host); | |
| 1326 | conn.host = try client.allocator.dupe(u8, tunnel_host); | |
| 1327 | errdefer client.allocator.free(conn.host); | |
| 1407 | connection.closing = false; | |
| 1328 | 1408 | |
| 1329 | conn.port = tunnel_port; | |
| 1330 | conn.closing = false; | |
| 1331 | ||
| 1332 | return conn; | |
| 1409 | return connection; | |
| 1333 | 1410 | }) catch { |
| 1334 | 1411 | // something went wrong with the tunnel |
| 1335 | 1412 | proxy.supports_connect = maybe_valid; |
| ... | ... | @@ -1337,12 +1414,11 @@ pub fn connectTunnel( |
| 1337 | 1414 | }; |
| 1338 | 1415 | } |
| 1339 | 1416 | |
| 1340 | // Prevents a dependency loop in open() | |
| 1341 | const ConnectErrorPartial = ConnectTcpError || error{ UnsupportedUriScheme, ConnectionRefused }; | |
| 1342 | pub const ConnectError = ConnectErrorPartial || RequestError; | |
| 1417 | pub const ConnectError = ConnectTcpError || RequestError; | |
| 1343 | 1418 | |
| 1344 | 1419 | /// Connect to `host:port` using the specified protocol. This will reuse a |
| 1345 | 1420 | /// connection if one is already open. |
| 1421 | /// | |
| 1346 | 1422 | /// If a proxy is configured for the client, then the proxy will be used to |
| 1347 | 1423 | /// connect to the host. |
| 1348 | 1424 | /// |
| ... | ... | @@ -1366,31 +1442,24 @@ pub fn connect( |
| 1366 | 1442 | } |
| 1367 | 1443 | |
| 1368 | 1444 | if (proxy.supports_connect) tunnel: { |
| 1369 | return connectTunnel(client, proxy, host, port) catch |err| switch (err) { | |
| 1445 | return connectProxied(client, proxy, host, port) catch |err| switch (err) { | |
| 1370 | 1446 | error.TunnelNotSupported => break :tunnel, |
| 1371 | 1447 | else => |e| return e, |
| 1372 | 1448 | }; |
| 1373 | 1449 | } |
| 1374 | 1450 | |
| 1375 | 1451 | // fall back to using the proxy as a normal http proxy |
| 1376 | const conn = try client.connectTcp(proxy.host, proxy.port, proxy.protocol); | |
| 1377 | errdefer { | |
| 1378 | conn.closing = true; | |
| 1379 | client.connection_pool.release(conn); | |
| 1380 | } | |
| 1381 | ||
| 1382 | conn.proxied = true; | |
| 1383 | return conn; | |
| 1452 | const connection = try client.connectTcp(proxy.host, proxy.port, proxy.protocol); | |
| 1453 | connection.proxied = true; | |
| 1454 | return connection; | |
| 1384 | 1455 | } |
| 1385 | 1456 | |
| 1386 | /// TODO collapse each error set into its own meta error code, and store | |
| 1387 | /// the underlying error code as a field on Request | |
| 1388 | pub const RequestError = ConnectTcpError || ConnectErrorPartial || std.io.Writer.Error || std.fmt.ParseIntError || | |
| 1389 | error{ | |
| 1390 | UnsupportedUriScheme, | |
| 1391 | UriMissingHost, | |
| 1392 | CertificateBundleLoadFailure, | |
| 1393 | }; | |
| 1457 | pub const RequestError = ConnectTcpError || error{ | |
| 1458 | UnsupportedUriScheme, | |
| 1459 | UriMissingHost, | |
| 1460 | UriHostTooLong, | |
| 1461 | CertificateBundleLoadFailure, | |
| 1462 | }; | |
| 1394 | 1463 | |
| 1395 | 1464 | pub const RequestOptions = struct { |
| 1396 | 1465 | version: http.Version = .@"HTTP/1.1", |
| ... | ... | @@ -1440,7 +1509,7 @@ fn uriPort(uri: Uri, protocol: Protocol) u16 { |
| 1440 | 1509 | /// This function is threadsafe. |
| 1441 | 1510 | /// |
| 1442 | 1511 | /// Asserts that "\r\n" does not occur in any header name or value. |
| 1443 | pub fn open( | |
| 1512 | pub fn request( | |
| 1444 | 1513 | client: *Client, |
| 1445 | 1514 | method: http.Method, |
| 1446 | 1515 | uri: Uri, |
| ... | ... | @@ -1486,6 +1555,11 @@ pub fn open( |
| 1486 | 1555 | .uri = uri, |
| 1487 | 1556 | .client = client, |
| 1488 | 1557 | .connection = connection, |
| 1558 | .reader = .{ | |
| 1559 | .in = connection.reader(), | |
| 1560 | .state = .ready, | |
| 1561 | .body_state = undefined, | |
| 1562 | }, | |
| 1489 | 1563 | .keep_alive = options.keep_alive, |
| 1490 | 1564 | .method = method, |
| 1491 | 1565 | .version = options.version, |
| ... | ... | @@ -1499,13 +1573,13 @@ pub fn open( |
| 1499 | 1573 | } |
| 1500 | 1574 | |
| 1501 | 1575 | pub const FetchOptions = struct { |
| 1502 | server_header_buffer: ?[]u8 = null, | |
| 1576 | /// `null` means it will be heap-allocated. | |
| 1577 | redirect_buffer: ?[]u8 = null, | |
| 1578 | /// `null` means it will be heap-allocated. | |
| 1579 | decompress_buffer: ?[]u8 = null, | |
| 1503 | 1580 | redirect_behavior: ?Request.RedirectBehavior = null, |
| 1504 | ||
| 1505 | /// If the server sends a body, it will be appended to this ArrayList. | |
| 1506 | /// `max_append_size` provides an upper limit for how much they can grow. | |
| 1507 | response_storage: ResponseStorage = .ignore, | |
| 1508 | max_append_size: ?usize = null, | |
| 1581 | /// If the server sends a body, it will be stored here. | |
| 1582 | response_storage: ?ResponseStorage = null, | |
| 1509 | 1583 | |
| 1510 | 1584 | location: Location, |
| 1511 | 1585 | method: ?http.Method = null, |
| ... | ... | @@ -1529,11 +1603,11 @@ pub const FetchOptions = struct { |
| 1529 | 1603 | uri: Uri, |
| 1530 | 1604 | }; |
| 1531 | 1605 | |
| 1532 | pub const ResponseStorage = union(enum) { | |
| 1533 | ignore, | |
| 1534 | /// Only the existing capacity will be used. | |
| 1535 | static: *std.ArrayListUnmanaged(u8), | |
| 1536 | dynamic: *std.ArrayList(u8), | |
| 1606 | pub const ResponseStorage = struct { | |
| 1607 | list: *std.ArrayListUnmanaged(u8), | |
| 1608 | /// If null then only the existing capacity will be used. | |
| 1609 | allocator: ?Allocator = null, | |
| 1610 | append_limit: std.io.Reader.Limit = .unlimited, | |
| 1537 | 1611 | }; |
| 1538 | 1612 | }; |
| 1539 | 1613 | |
| ... | ... | @@ -1541,23 +1615,28 @@ pub const FetchResult = struct { |
| 1541 | 1615 | status: http.Status, |
| 1542 | 1616 | }; |
| 1543 | 1617 | |
| 1618 | pub const FetchError = Uri.ParseError || RequestError || Request.ReceiveHeadError || error{ | |
| 1619 | StreamTooLong, | |
| 1620 | /// TODO provide optional diagnostics when this occurs or break into more error codes | |
| 1621 | WriteFailed, | |
| 1622 | }; | |
| 1623 | ||
| 1544 | 1624 | /// Perform a one-shot HTTP request with the provided options. |
| 1545 | 1625 | /// |
| 1546 | 1626 | /// This function is threadsafe. |
| 1547 | pub fn fetch(client: *Client, options: FetchOptions) !FetchResult { | |
| 1627 | pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult { | |
| 1548 | 1628 | const uri = switch (options.location) { |
| 1549 | 1629 | .url => |u| try Uri.parse(u), |
| 1550 | 1630 | .uri => |u| u, |
| 1551 | 1631 | }; |
| 1552 | var server_header_buffer: [16 * 1024]u8 = undefined; | |
| 1553 | ||
| 1554 | 1632 | const method: http.Method = options.method orelse |
| 1555 | 1633 | if (options.payload != null) .POST else .GET; |
| 1556 | 1634 | |
| 1557 | var req = try open(client, method, uri, .{ | |
| 1558 | .server_header_buffer = options.server_header_buffer orelse &server_header_buffer, | |
| 1559 | .redirect_behavior = options.redirect_behavior orelse | |
| 1560 | if (options.payload == null) @enumFromInt(3) else .unhandled, | |
| 1635 | const redirect_behavior: Request.RedirectBehavior = options.redirect_behavior orelse | |
| 1636 | if (options.payload == null) @enumFromInt(3) else .unhandled; | |
| 1637 | ||
| 1638 | var req = try request(client, method, uri, .{ | |
| 1639 | .redirect_behavior = redirect_behavior, | |
| 1561 | 1640 | .headers = options.headers, |
| 1562 | 1641 | .extra_headers = options.extra_headers, |
| 1563 | 1642 | .privileged_headers = options.privileged_headers, |
| ... | ... | @@ -1565,44 +1644,56 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult { |
| 1565 | 1644 | }); |
| 1566 | 1645 | defer req.deinit(); |
| 1567 | 1646 | |
| 1568 | if (options.payload) |payload| req.transfer_encoding = .{ .content_length = payload.len }; | |
| 1569 | ||
| 1570 | try req.send(); | |
| 1571 | ||
| 1572 | 1647 | if (options.payload) |payload| { |
| 1573 | var w = req.writer().unbuffered(); | |
| 1574 | try w.writeAll(payload); | |
| 1648 | req.transfer_encoding = .{ .content_length = payload.len }; | |
| 1649 | var body = try req.sendBody(); | |
| 1650 | var bw = body.writer().unbuffered(); | |
| 1651 | try bw.writeAll(payload); | |
| 1652 | try body.end(); | |
| 1653 | } else { | |
| 1654 | try req.sendBodiless(); | |
| 1575 | 1655 | } |
| 1576 | 1656 | |
| 1577 | try req.finish(); | |
| 1578 | try req.wait(); | |
| 1657 | const redirect_buffer: []u8 = if (redirect_behavior == .unhandled) &.{} else options.redirect_buffer orelse | |
| 1658 | try client.allocator.alloc(u8, 8 * 1024); | |
| 1659 | defer if (options.redirect_buffer == null) client.allocator.free(redirect_buffer); | |
| 1579 | 1660 | |
| 1580 | switch (options.response_storage) { | |
| 1581 | .ignore => { | |
| 1582 | // Take advantage of request internals to discard the response body | |
| 1583 | // and make the connection available for another request. | |
| 1584 | req.response.skip = true; | |
| 1585 | assert(try req.transferRead(&.{}) == 0); // No buffer is necessary when skipping. | |
| 1586 | }, | |
| 1587 | .dynamic => |list| { | |
| 1588 | const max_append_size = options.max_append_size orelse 2 * 1024 * 1024; | |
| 1589 | try req.reader().readAllArrayList(list, max_append_size); | |
| 1590 | }, | |
| 1591 | .static => |list| { | |
| 1592 | const buf = b: { | |
| 1593 | const buf = list.unusedCapacitySlice(); | |
| 1594 | if (options.max_append_size) |len| { | |
| 1595 | if (len < buf.len) break :b buf[0..len]; | |
| 1596 | } | |
| 1597 | break :b buf; | |
| 1598 | }; | |
| 1599 | list.items.len += try req.reader().readAll(buf); | |
| 1600 | }, | |
| 1601 | } | |
| 1661 | var response = try req.receiveHead(redirect_buffer); | |
| 1602 | 1662 | |
| 1603 | return .{ | |
| 1604 | .status = req.response.status, | |
| 1663 | const storage = options.response_storage orelse { | |
| 1664 | var reader = response.reader(); | |
| 1665 | _ = reader.discardRemaining() catch |err| switch (err) { | |
| 1666 | error.ReadFailed => return response.bodyErr().?, | |
| 1667 | }; | |
| 1668 | return .{ .status = response.head.status }; | |
| 1605 | 1669 | }; |
| 1670 | ||
| 1671 | const decompress_buffer: []u8 = switch (response.head.content_encoding) { | |
| 1672 | .identity => &.{}, | |
| 1673 | .zstd => options.decompress_buffer orelse | |
| 1674 | try client.allocator.alloc(u8, std.compress.zstd.Decompressor.Options.default_window_buffer_len * 2), | |
| 1675 | else => options.decompress_buffer orelse try client.allocator.alloc(u8, 8 * 1024), | |
| 1676 | }; | |
| 1677 | defer if (options.decompress_buffer == null) client.allocator.free(decompress_buffer); | |
| 1678 | ||
| 1679 | var decompressor: http.Decompressor = undefined; | |
| 1680 | var reader = response.readerDecompressing(&decompressor, decompress_buffer); | |
| 1681 | const list = storage.list; | |
| 1682 | ||
| 1683 | if (storage.allocator) |allocator| { | |
| 1684 | reader.readRemainingArrayList(allocator, null, list, storage.append_limit) catch |err| switch (err) { | |
| 1685 | error.ReadFailed => return response.bodyErr().?, | |
| 1686 | else => |e| return e, | |
| 1687 | }; | |
| 1688 | } else { | |
| 1689 | var br = reader.unbuffered(); | |
| 1690 | const buf = storage.append_limit.slice(list.unusedCapacitySlice()); | |
| 1691 | list.items.len += br.readSliceShort(buf) catch |err| switch (err) { | |
| 1692 | error.ReadFailed => return response.bodyErr().?, | |
| 1693 | }; | |
| 1694 | } | |
| 1695 | ||
| 1696 | return .{ .status = response.head.status }; | |
| 1606 | 1697 | } |
| 1607 | 1698 | |
| 1608 | 1699 | pub fn sameParentDomain(parent_host: []const u8, child_host: []const u8) bool { |
lib/std/http/Server.zig+4-16| ... | ... | @@ -55,13 +55,6 @@ pub const Request = struct { |
| 55 | 55 | /// `receiveHead`. |
| 56 | 56 | head: Head, |
| 57 | 57 | |
| 58 | pub const Compression = union(enum) { | |
| 59 | deflate: std.compress.zlib.Decompressor, | |
| 60 | gzip: std.compress.gzip.Decompressor, | |
| 61 | zstd: std.compress.zstd.Decompressor, | |
| 62 | none: void, | |
| 63 | }; | |
| 64 | ||
| 65 | 58 | pub const Head = struct { |
| 66 | 59 | method: http.Method, |
| 67 | 60 | target: []const u8, |
| ... | ... | @@ -72,7 +65,6 @@ pub const Request = struct { |
| 72 | 65 | transfer_encoding: http.TransferEncoding, |
| 73 | 66 | transfer_compression: http.ContentEncoding, |
| 74 | 67 | keep_alive: bool, |
| 75 | compression: Compression, | |
| 76 | 68 | |
| 77 | 69 | pub const ParseError = error{ |
| 78 | 70 | UnknownHttpMethod, |
| ... | ... | @@ -126,7 +118,6 @@ pub const Request = struct { |
| 126 | 118 | .@"HTTP/1.0" => false, |
| 127 | 119 | .@"HTTP/1.1" => true, |
| 128 | 120 | }, |
| 129 | .compression = .none, | |
| 130 | 121 | }; |
| 131 | 122 | |
| 132 | 123 | while (it.next()) |line| { |
| ... | ... | @@ -156,7 +147,7 @@ pub const Request = struct { |
| 156 | 147 | |
| 157 | 148 | const trimmed = mem.trim(u8, header_value, " "); |
| 158 | 149 | |
| 159 | if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| { | |
| 150 | if (http.ContentEncoding.fromString(trimmed)) |ce| { | |
| 160 | 151 | head.transfer_compression = ce; |
| 161 | 152 | } else { |
| 162 | 153 | return error.HttpTransferEncodingUnsupported; |
| ... | ... | @@ -181,7 +172,7 @@ pub const Request = struct { |
| 181 | 172 | if (next) |second| { |
| 182 | 173 | const trimmed_second = mem.trim(u8, second, " "); |
| 183 | 174 | |
| 184 | if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| { | |
| 175 | if (http.ContentEncoding.fromString(trimmed_second)) |transfer| { | |
| 185 | 176 | if (head.transfer_compression != .identity) |
| 186 | 177 | return error.HttpHeadersInvalid; // double compression is not supported |
| 187 | 178 | head.transfer_compression = transfer; |
| ... | ... | @@ -236,10 +227,8 @@ pub const Request = struct { |
| 236 | 227 | "TRansfer-encoding:\tdeflate, chunked \r\n" ++ |
| 237 | 228 | "connectioN:\t keep-alive \r\n\r\n"; |
| 238 | 229 | |
| 239 | var read_buffer: [500]u8 = undefined; | |
| 240 | @memcpy(read_buffer[0..request_bytes.len], request_bytes); | |
| 241 | 230 | var br: std.io.BufferedReader = undefined; |
| 242 | br.initFixed(&read_buffer); | |
| 231 | br.initFixed(@constCast(request_bytes)); | |
| 243 | 232 | |
| 244 | 233 | var server: Server = .{ |
| 245 | 234 | .reader = .{ |
| ... | ... | @@ -252,7 +241,6 @@ pub const Request = struct { |
| 252 | 241 | |
| 253 | 242 | var request: Request = .{ |
| 254 | 243 | .server = &server, |
| 255 | .trailers_len = 0, | |
| 256 | 244 | .head = undefined, |
| 257 | 245 | }; |
| 258 | 246 | |
| ... | ... | @@ -529,7 +517,7 @@ pub const Request = struct { |
| 529 | 517 | return error.HttpExpectationFailed; |
| 530 | 518 | } |
| 531 | 519 | } |
| 532 | return request.server.reader.interface(request.head.transfer_encoding, request.head.content_length); | |
| 520 | return request.server.reader.bodyReader(request.head.transfer_encoding, request.head.content_length); | |
| 533 | 521 | } |
| 534 | 522 | |
| 535 | 523 | /// Returns whether the connection should remain persistent. |
lib/std/http/WebSocket.zig+1-1| ... | ... | @@ -236,7 +236,7 @@ pub fn writeMessagev(ws: *WebSocket, message: []const std.posix.iovec_const, opc |
| 236 | 236 | }, |
| 237 | 237 | }; |
| 238 | 238 | |
| 239 | var bw = ws.body_writer.interface().unbuffered(); | |
| 239 | var bw = ws.body_writer.writer().unbuffered(); | |
| 240 | 240 | try bw.writeAll(header); |
| 241 | 241 | for (message) |iovec| try bw.writeAll(iovec.base[0..iovec.len]); |
| 242 | 242 | try bw.flush(); |
lib/std/http/test.zig+49-77| ... | ... | @@ -61,7 +61,7 @@ test "trailers" { |
| 61 | 61 | const uri = try std.Uri.parse(location); |
| 62 | 62 | |
| 63 | 63 | { |
| 64 | var req = try client.open(.GET, uri, .{}); | |
| 64 | var req = try client.request(.GET, uri, .{}); | |
| 65 | 65 | defer req.deinit(); |
| 66 | 66 | |
| 67 | 67 | try req.sendBodiless(); |
| ... | ... | @@ -263,7 +263,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { |
| 263 | 263 | var connection_bw = stream_writer.interface().buffered(&send_buffer); |
| 264 | 264 | var server = http.Server.init(&connection_br, &connection_bw); |
| 265 | 265 | |
| 266 | try expectEqual(.ready, server.state); | |
| 266 | try expectEqual(.ready, server.reader.state); | |
| 267 | 267 | var request = try server.receiveHead(); |
| 268 | 268 | try expectEqualStrings(request.head.target, "/foo"); |
| 269 | 269 | var response = try request.respondStreaming(.{ |
| ... | ... | @@ -278,7 +278,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { |
| 278 | 278 | } |
| 279 | 279 | try expectEqual(7390, bw.count); |
| 280 | 280 | try response.end(); |
| 281 | try expectEqual(.closing, server.state); | |
| 281 | try expectEqual(.closing, server.reader.state); | |
| 282 | 282 | } |
| 283 | 283 | } |
| 284 | 284 | }); |
| ... | ... | @@ -331,7 +331,7 @@ test "receiving arbitrary http headers from the client" { |
| 331 | 331 | var connection_bw = stream_writer.interface().buffered(&send_buffer); |
| 332 | 332 | var server = http.Server.init(&connection_br, &connection_bw); |
| 333 | 333 | |
| 334 | try expectEqual(.ready, server.state); | |
| 334 | try expectEqual(.ready, server.reader.state); | |
| 335 | 335 | var request = try server.receiveHead(); |
| 336 | 336 | try expectEqualStrings("/bar", request.head.target); |
| 337 | 337 | var it = request.iterateHeaders(); |
| ... | ... | @@ -563,7 +563,7 @@ test "general client/server API coverage" { |
| 563 | 563 | |
| 564 | 564 | log.info("{s}", .{location}); |
| 565 | 565 | var redirect_buffer: [1024]u8 = undefined; |
| 566 | var req = try client.open(.GET, uri, .{}); | |
| 566 | var req = try client.request(.GET, uri, .{}); | |
| 567 | 567 | defer req.deinit(); |
| 568 | 568 | |
| 569 | 569 | try req.sendBodiless(); |
| ... | ... | @@ -586,7 +586,7 @@ test "general client/server API coverage" { |
| 586 | 586 | |
| 587 | 587 | log.info("{s}", .{location}); |
| 588 | 588 | var redirect_buffer: [1024]u8 = undefined; |
| 589 | var req = try client.open(.GET, uri, .{}); | |
| 589 | var req = try client.request(.GET, uri, .{}); | |
| 590 | 590 | defer req.deinit(); |
| 591 | 591 | |
| 592 | 592 | try req.sendBodiless(); |
| ... | ... | @@ -608,7 +608,7 @@ test "general client/server API coverage" { |
| 608 | 608 | |
| 609 | 609 | log.info("{s}", .{location}); |
| 610 | 610 | var redirect_buffer: [1024]u8 = undefined; |
| 611 | var req = try client.open(.HEAD, uri, .{}); | |
| 611 | var req = try client.request(.HEAD, uri, .{}); | |
| 612 | 612 | defer req.deinit(); |
| 613 | 613 | |
| 614 | 614 | try req.sendBodiless(); |
| ... | ... | @@ -632,7 +632,7 @@ test "general client/server API coverage" { |
| 632 | 632 | |
| 633 | 633 | log.info("{s}", .{location}); |
| 634 | 634 | var redirect_buffer: [1024]u8 = undefined; |
| 635 | var req = try client.open(.GET, uri, .{}); | |
| 635 | var req = try client.request(.GET, uri, .{}); | |
| 636 | 636 | defer req.deinit(); |
| 637 | 637 | |
| 638 | 638 | try req.sendBodiless(); |
| ... | ... | @@ -655,18 +655,18 @@ test "general client/server API coverage" { |
| 655 | 655 | |
| 656 | 656 | log.info("{s}", .{location}); |
| 657 | 657 | var redirect_buffer: [1024]u8 = undefined; |
| 658 | var req = try client.open(.HEAD, uri, .{}); | |
| 658 | var req = try client.request(.HEAD, uri, .{}); | |
| 659 | 659 | defer req.deinit(); |
| 660 | 660 | |
| 661 | 661 | try req.sendBodiless(); |
| 662 | try req.receiveHead(&redirect_buffer); | |
| 662 | var response = try req.receiveHead(&redirect_buffer); | |
| 663 | 663 | |
| 664 | const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 664 | const body = try response.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 665 | 665 | defer gpa.free(body); |
| 666 | 666 | |
| 667 | 667 | try expectEqualStrings("", body); |
| 668 | try expectEqualStrings("text/plain", req.response.content_type.?); | |
| 669 | try expect(req.response.transfer_encoding == .chunked); | |
| 668 | try expectEqualStrings("text/plain", response.head.content_type.?); | |
| 669 | try expect(response.head.transfer_encoding == .chunked); | |
| 670 | 670 | } |
| 671 | 671 | |
| 672 | 672 | // connection has been kept alive |
| ... | ... | @@ -679,19 +679,19 @@ test "general client/server API coverage" { |
| 679 | 679 | |
| 680 | 680 | log.info("{s}", .{location}); |
| 681 | 681 | var redirect_buffer: [1024]u8 = undefined; |
| 682 | var req = try client.open(.GET, uri, .{ | |
| 682 | var req = try client.request(.GET, uri, .{ | |
| 683 | 683 | .keep_alive = false, |
| 684 | 684 | }); |
| 685 | 685 | defer req.deinit(); |
| 686 | 686 | |
| 687 | 687 | try req.sendBodiless(); |
| 688 | try req.receiveHead(&redirect_buffer); | |
| 688 | var response = try req.receiveHead(&redirect_buffer); | |
| 689 | 689 | |
| 690 | const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 690 | const body = try response.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 691 | 691 | defer gpa.free(body); |
| 692 | 692 | |
| 693 | 693 | try expectEqualStrings("Hello, World!\n", body); |
| 694 | try expectEqualStrings("text/plain", req.response.content_type.?); | |
| 694 | try expectEqualStrings("text/plain", response.head.content_type.?); | |
| 695 | 695 | } |
| 696 | 696 | |
| 697 | 697 | // connection has been closed |
| ... | ... | @@ -704,7 +704,7 @@ test "general client/server API coverage" { |
| 704 | 704 | |
| 705 | 705 | log.info("{s}", .{location}); |
| 706 | 706 | var redirect_buffer: [1024]u8 = undefined; |
| 707 | var req = try client.open(.GET, uri, .{ | |
| 707 | var req = try client.request(.GET, uri, .{ | |
| 708 | 708 | .extra_headers = &.{ |
| 709 | 709 | .{ .name = "empty", .value = "" }, |
| 710 | 710 | }, |
| ... | ... | @@ -712,16 +712,16 @@ test "general client/server API coverage" { |
| 712 | 712 | defer req.deinit(); |
| 713 | 713 | |
| 714 | 714 | try req.sendBodiless(); |
| 715 | try req.receiveHead(&redirect_buffer); | |
| 715 | var response = try req.receiveHead(&redirect_buffer); | |
| 716 | 716 | |
| 717 | try std.testing.expectEqual(.ok, req.response.status); | |
| 717 | try std.testing.expectEqual(.ok, response.head.status); | |
| 718 | 718 | |
| 719 | const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 719 | const body = try response.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 720 | 720 | defer gpa.free(body); |
| 721 | 721 | |
| 722 | 722 | try expectEqualStrings("", body); |
| 723 | 723 | |
| 724 | var it = req.response.iterateHeaders(); | |
| 724 | var it = response.head.iterateHeaders(); | |
| 725 | 725 | { |
| 726 | 726 | const header = it.next().?; |
| 727 | 727 | try expect(!it.is_trailer); |
| ... | ... | @@ -747,13 +747,13 @@ test "general client/server API coverage" { |
| 747 | 747 | |
| 748 | 748 | log.info("{s}", .{location}); |
| 749 | 749 | var redirect_buffer: [1024]u8 = undefined; |
| 750 | var req = try client.open(.GET, uri, .{}); | |
| 750 | var req = try client.request(.GET, uri, .{}); | |
| 751 | 751 | defer req.deinit(); |
| 752 | 752 | |
| 753 | 753 | try req.sendBodiless(); |
| 754 | try req.receiveHead(&redirect_buffer); | |
| 754 | var response = try req.receiveHead(&redirect_buffer); | |
| 755 | 755 | |
| 756 | const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 756 | const body = try response.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 757 | 757 | defer gpa.free(body); |
| 758 | 758 | |
| 759 | 759 | try expectEqualStrings("Hello, World!\n", body); |
| ... | ... | @@ -769,13 +769,13 @@ test "general client/server API coverage" { |
| 769 | 769 | |
| 770 | 770 | log.info("{s}", .{location}); |
| 771 | 771 | var redirect_buffer: [1024]u8 = undefined; |
| 772 | var req = try client.open(.GET, uri, .{}); | |
| 772 | var req = try client.request(.GET, uri, .{}); | |
| 773 | 773 | defer req.deinit(); |
| 774 | 774 | |
| 775 | 775 | try req.sendBodiless(); |
| 776 | try req.receiveHead(&redirect_buffer); | |
| 776 | var response = try req.receiveHead(&redirect_buffer); | |
| 777 | 777 | |
| 778 | const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 778 | const body = try response.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 779 | 779 | defer gpa.free(body); |
| 780 | 780 | |
| 781 | 781 | try expectEqualStrings("Hello, World!\n", body); |
| ... | ... | @@ -791,13 +791,13 @@ test "general client/server API coverage" { |
| 791 | 791 | |
| 792 | 792 | log.info("{s}", .{location}); |
| 793 | 793 | var redirect_buffer: [1024]u8 = undefined; |
| 794 | var req = try client.open(.GET, uri, .{}); | |
| 794 | var req = try client.request(.GET, uri, .{}); | |
| 795 | 795 | defer req.deinit(); |
| 796 | 796 | |
| 797 | 797 | try req.sendBodiless(); |
| 798 | try req.receiveHead(&redirect_buffer); | |
| 798 | var response = try req.receiveHead(&redirect_buffer); | |
| 799 | 799 | |
| 800 | const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 800 | const body = try response.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 801 | 801 | defer gpa.free(body); |
| 802 | 802 | |
| 803 | 803 | try expectEqualStrings("Hello, World!\n", body); |
| ... | ... | @@ -813,14 +813,16 @@ test "general client/server API coverage" { |
| 813 | 813 | |
| 814 | 814 | log.info("{s}", .{location}); |
| 815 | 815 | var redirect_buffer: [1024]u8 = undefined; |
| 816 | var req = try client.open(.GET, uri, .{}); | |
| 816 | var req = try client.request(.GET, uri, .{}); | |
| 817 | 817 | defer req.deinit(); |
| 818 | 818 | |
| 819 | 819 | try req.sendBodiless(); |
| 820 | req.receiveHead(&redirect_buffer) catch |err| switch (err) { | |
| 820 | if (req.receiveHead(&redirect_buffer)) |_| { | |
| 821 | return error.TestFailed; | |
| 822 | } else |err| switch (err) { | |
| 821 | 823 | error.TooManyHttpRedirects => {}, |
| 822 | 824 | else => return err, |
| 823 | }; | |
| 825 | } | |
| 824 | 826 | } |
| 825 | 827 | |
| 826 | 828 | { // redirect to encoded url |
| ... | ... | @@ -830,13 +832,13 @@ test "general client/server API coverage" { |
| 830 | 832 | |
| 831 | 833 | log.info("{s}", .{location}); |
| 832 | 834 | var redirect_buffer: [1024]u8 = undefined; |
| 833 | var req = try client.open(.GET, uri, .{}); | |
| 835 | var req = try client.request(.GET, uri, .{}); | |
| 834 | 836 | defer req.deinit(); |
| 835 | 837 | |
| 836 | 838 | try req.sendBodiless(); |
| 837 | try req.receiveHead(&redirect_buffer); | |
| 839 | var response = try req.receiveHead(&redirect_buffer); | |
| 838 | 840 | |
| 839 | const body = try req.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 841 | const body = try response.reader().readRemainingAlloc(gpa, .limited(8192)); | |
| 840 | 842 | defer gpa.free(body); |
| 841 | 843 | |
| 842 | 844 | try expectEqualStrings("Encoded redirect successful!\n", body); |
| ... | ... | @@ -852,7 +854,7 @@ test "general client/server API coverage" { |
| 852 | 854 | |
| 853 | 855 | log.info("{s}", .{location}); |
| 854 | 856 | var redirect_buffer: [1024]u8 = undefined; |
| 855 | var req = try client.open(.GET, uri, .{}); | |
| 857 | var req = try client.request(.GET, uri, .{}); | |
| 856 | 858 | defer req.deinit(); |
| 857 | 859 | |
| 858 | 860 | try req.sendBodiless(); |
| ... | ... | @@ -867,36 +869,6 @@ test "general client/server API coverage" { |
| 867 | 869 | // connection has been kept alive |
| 868 | 870 | try expect(client.http_proxy != null or client.connection_pool.free_len == 1); |
| 869 | 871 | |
| 870 | { // issue 16282 *** This test leaves the client in an invalid state, it must be last *** | |
| 871 | const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/get", .{port}); | |
| 872 | defer gpa.free(location); | |
| 873 | const uri = try std.Uri.parse(location); | |
| 874 | ||
| 875 | const total_connections = client.connection_pool.free_size + 64; | |
| 876 | var requests = try gpa.alloc(http.Client.Request, total_connections); | |
| 877 | defer gpa.free(requests); | |
| 878 | ||
| 879 | var header_bufs = std.ArrayList([]u8).init(gpa); | |
| 880 | defer header_bufs.deinit(); | |
| 881 | defer for (header_bufs.items) |item| gpa.free(item); | |
| 882 | ||
| 883 | for (0..total_connections) |i| { | |
| 884 | const headers_buf = try gpa.alloc(u8, 1024); | |
| 885 | try header_bufs.append(headers_buf); | |
| 886 | var req = try client.open(.GET, uri, .{}); | |
| 887 | req.response.parser.done = true; | |
| 888 | req.connection.?.closing = false; | |
| 889 | requests[i] = req; | |
| 890 | } | |
| 891 | ||
| 892 | for (0..total_connections) |i| { | |
| 893 | requests[i].deinit(); | |
| 894 | } | |
| 895 | ||
| 896 | // free connections should be full now | |
| 897 | try expect(client.connection_pool.free_len == client.connection_pool.free_size); | |
| 898 | } | |
| 899 | ||
| 900 | 872 | client.deinit(); |
| 901 | 873 | |
| 902 | 874 | { |
| ... | ... | @@ -950,7 +922,7 @@ test "Server streams both reading and writing" { |
| 950 | 922 | defer client.deinit(); |
| 951 | 923 | |
| 952 | 924 | var redirect_buffer: [555]u8 = undefined; |
| 953 | var req = try client.open(.POST, .{ | |
| 925 | var req = try client.request(.POST, .{ | |
| 954 | 926 | .scheme = "http", |
| 955 | 927 | .host = .{ .raw = "127.0.0.1" }, |
| 956 | 928 | .port = test_server.port(), |
| ... | ... | @@ -983,7 +955,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 983 | 955 | const uri = try std.Uri.parse(location); |
| 984 | 956 | |
| 985 | 957 | var redirect_buffer: [1024]u8 = undefined; |
| 986 | var req = try client.open(.POST, uri, .{ | |
| 958 | var req = try client.request(.POST, uri, .{ | |
| 987 | 959 | .extra_headers = &.{ |
| 988 | 960 | .{ .name = "content-type", .value = "text/plain" }, |
| 989 | 961 | }, |
| ... | ... | @@ -1017,7 +989,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1017 | 989 | )); |
| 1018 | 990 | |
| 1019 | 991 | var redirect_buffer: [1024]u8 = undefined; |
| 1020 | var req = try client.open(.POST, uri, .{ | |
| 992 | var req = try client.request(.POST, uri, .{ | |
| 1021 | 993 | .extra_headers = &.{ |
| 1022 | 994 | .{ .name = "content-type", .value = "text/plain" }, |
| 1023 | 995 | }, |
| ... | ... | @@ -1048,8 +1020,8 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1048 | 1020 | const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/echo-content#fetch", .{port}); |
| 1049 | 1021 | defer gpa.free(location); |
| 1050 | 1022 | |
| 1051 | var body = std.ArrayList(u8).init(gpa); | |
| 1052 | defer body.deinit(); | |
| 1023 | var body: std.ArrayListUnmanaged(u8) = .empty; | |
| 1024 | defer body.deinit(gpa); | |
| 1053 | 1025 | |
| 1054 | 1026 | const res = try client.fetch(.{ |
| 1055 | 1027 | .location = .{ .url = location }, |
| ... | ... | @@ -1058,7 +1030,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1058 | 1030 | .extra_headers = &.{ |
| 1059 | 1031 | .{ .name = "content-type", .value = "text/plain" }, |
| 1060 | 1032 | }, |
| 1061 | .response_storage = .{ .dynamic = &body }, | |
| 1033 | .response_storage = .{ .allocator = gpa, .list = &body }, | |
| 1062 | 1034 | }); |
| 1063 | 1035 | try expectEqual(.ok, res.status); |
| 1064 | 1036 | try expectEqualStrings("Hello, World!\n", body.items); |
| ... | ... | @@ -1070,7 +1042,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1070 | 1042 | const uri = try std.Uri.parse(location); |
| 1071 | 1043 | |
| 1072 | 1044 | var redirect_buffer: [1024]u8 = undefined; |
| 1073 | var req = try client.open(.POST, uri, .{ | |
| 1045 | var req = try client.request(.POST, uri, .{ | |
| 1074 | 1046 | .extra_headers = &.{ |
| 1075 | 1047 | .{ .name = "expect", .value = "100-continue" }, |
| 1076 | 1048 | .{ .name = "content-type", .value = "text/plain" }, |
| ... | ... | @@ -1101,7 +1073,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1101 | 1073 | const uri = try std.Uri.parse(location); |
| 1102 | 1074 | |
| 1103 | 1075 | var redirect_buffer: [1024]u8 = undefined; |
| 1104 | var req = try client.open(.POST, uri, .{ | |
| 1076 | var req = try client.request(.POST, uri, .{ | |
| 1105 | 1077 | .extra_headers = &.{ |
| 1106 | 1078 | .{ .name = "content-type", .value = "text/plain" }, |
| 1107 | 1079 | .{ .name = "expect", .value = "garbage" }, |
| ... | ... | @@ -1222,7 +1194,7 @@ test "redirect to different connection" { |
| 1222 | 1194 | |
| 1223 | 1195 | { |
| 1224 | 1196 | var redirect_buffer: [666]u8 = undefined; |
| 1225 | var req = try client.open(.GET, uri, .{}); | |
| 1197 | var req = try client.request(.GET, uri, .{}); | |
| 1226 | 1198 | defer req.deinit(); |
| 1227 | 1199 | |
| 1228 | 1200 | try req.sendBodiless(); |
lib/std/net.zig+16| ... | ... | @@ -1898,6 +1898,10 @@ pub const Stream = struct { |
| 1898 | 1898 | |
| 1899 | 1899 | pub const Error = ReadError; |
| 1900 | 1900 | |
| 1901 | pub fn getStream(r: *const Reader) Stream { | |
| 1902 | return r.stream; | |
| 1903 | } | |
| 1904 | ||
| 1901 | 1905 | pub fn interface(r: *Reader) std.io.Reader { |
| 1902 | 1906 | return .{ |
| 1903 | 1907 | .context = r.stream.handle, |
| ... | ... | @@ -1968,6 +1972,10 @@ pub const Stream = struct { |
| 1968 | 1972 | pub fn interface(r: *Reader) std.io.Reader { |
| 1969 | 1973 | return r.file_reader.interface(); |
| 1970 | 1974 | } |
| 1975 | ||
| 1976 | pub fn getStream(r: *const Reader) Stream { | |
| 1977 | return .{ .handle = r.file_reader.file.handle }; | |
| 1978 | } | |
| 1971 | 1979 | }, |
| 1972 | 1980 | }; |
| 1973 | 1981 | |
| ... | ... | @@ -1987,6 +1995,10 @@ pub const Stream = struct { |
| 1987 | 1995 | }; |
| 1988 | 1996 | } |
| 1989 | 1997 | |
| 1998 | pub fn getStream(w: *const Writer) Stream { | |
| 1999 | return w.stream; | |
| 2000 | } | |
| 2001 | ||
| 1990 | 2002 | fn writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize { |
| 1991 | 2003 | comptime assert(native_os == .windows); |
| 1992 | 2004 | if (data.len == 1 and splat == 0) return 0; |
| ... | ... | @@ -2130,6 +2142,10 @@ pub const Stream = struct { |
| 2130 | 2142 | return error.WriteFailed; |
| 2131 | 2143 | }; |
| 2132 | 2144 | } |
| 2145 | ||
| 2146 | pub fn getStream(w: *const Writer) Stream { | |
| 2147 | return .{ .handle = w.file_writer.file.handle }; | |
| 2148 | } | |
| 2133 | 2149 | }, |
| 2134 | 2150 | }; |
| 2135 | 2151 |