| 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,7 +476,7 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { |
| 476 | defer arena_instance.deinit(); | 476 | defer arena_instance.deinit(); |
| 477 | const arena = arena_instance.allocator(); | 477 | const arena = arena_instance.allocator(); |
| 478 | 478 | ||
| 479 | var body_writer = try request.respondStreaming(.{ | 479 | var body = try request.respondStreaming(.{ |
| 480 | .respond_options = .{ | 480 | .respond_options = .{ |
| 481 | .extra_headers = &.{ | 481 | .extra_headers = &.{ |
| 482 | .{ .name = "content-type", .value = "application/x-tar" }, | 482 | .{ .name = "content-type", .value = "application/x-tar" }, |
| ... | @@ -517,7 +517,7 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { | ... | @@ -517,7 +517,7 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { |
| 517 | 517 | ||
| 518 | var cwd_cache: ?[]const u8 = null; | 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 | var archiver: std.tar.Writer = .{ .underlying_writer = &response_writer }; | 521 | var archiver: std.tar.Writer = .{ .underlying_writer = &response_writer }; |
| 522 | 522 | ||
| 523 | for (deduped_paths) |joined_path| { | 523 | for (deduped_paths) |joined_path| { |
| ... | @@ -531,7 +531,7 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { | ... | @@ -531,7 +531,7 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void { |
| 531 | try archiver.writeFile(joined_path.sub_path, file, try file.stat()); | 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 | fn memoizedCwd(arena: Allocator, opt_ptr: *?[]const u8) ![]const u8 { | 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,13 +295,24 @@ pub const TransferEncoding = enum { |
| 295 | }; | 295 | }; |
| 296 | 296 | ||
| 297 | pub const ContentEncoding = enum { | 297 | pub const ContentEncoding = enum { |
| 298 | identity, | ||
| 299 | compress, | ||
| 300 | @"x-compress", | ||
| 301 | deflate, | ||
| 302 | gzip, | ||
| 303 | @"x-gzip", | ||
| 304 | zstd, | 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 | pub const Connection = enum { | 318 | pub const Connection = enum { |
| ... | @@ -331,18 +342,9 @@ pub const Reader = struct { | ... | @@ -331,18 +342,9 @@ pub const Reader = struct { |
| 331 | body_err: ?BodyError = null, | 342 | body_err: ?BodyError = null, |
| 332 | /// Stolen from `in`. | 343 | /// Stolen from `in`. |
| 333 | head_buffer: []u8 = &.{}, | 344 | head_buffer: []u8 = &.{}, |
| 334 | compression: Compression, | ||
| 335 | 345 | ||
| 336 | pub const max_chunk_header_len = 22; | 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 | pub const RemainingChunkLen = enum(u64) { | 348 | pub const RemainingChunkLen = enum(u64) { |
| 347 | head = 0, | 349 | head = 0, |
| 348 | n = 1, | 350 | n = 1, |
| ... | @@ -416,19 +418,19 @@ pub const Reader = struct { | ... | @@ -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 | /// Asserts only called once and after `receiveHead`. | 423 | /// Asserts only called once and after `receiveHead`. |
| 420 | pub fn interface( | 424 | /// |
| 421 | reader: *Reader, | 425 | /// See also: |
| 422 | transfer_encoding: TransferEncoding, | 426 | /// * `interfaceDecompressing` |
| 423 | content_length: ?u64, | 427 | pub fn bodyReader(reader: *Reader, transfer_encoding: TransferEncoding, content_length: ?u64) std.io.Reader { |
| 424 | content_encoding: ContentEncoding, | ||
| 425 | ) std.io.Reader { | ||
| 426 | assert(reader.state == .received_head); | 428 | assert(reader.state == .received_head); |
| 427 | reader.state = .receiving_body; | 429 | reader.state = .receiving_body; |
| 428 | reader.transfer_br.unbuffered_reader = switch (transfer_encoding) { | 430 | return switch (transfer_encoding) { |
| 429 | .chunked => r: { | 431 | .chunked => { |
| 430 | reader.body_state = .{ .remaining_chunk_len = .head }; | 432 | reader.body_state = .{ .remaining_chunk_len = .head }; |
| 431 | break :r .{ | 433 | return .{ |
| 432 | .context = reader, | 434 | .context = reader, |
| 433 | .vtable = &.{ | 435 | .vtable = &.{ |
| 434 | .read = &chunkedRead, | 436 | .read = &chunkedRead, |
| ... | @@ -437,10 +439,10 @@ pub const Reader = struct { | ... | @@ -437,10 +439,10 @@ pub const Reader = struct { |
| 437 | }, | 439 | }, |
| 438 | }; | 440 | }; |
| 439 | }, | 441 | }, |
| 440 | .none => r: { | 442 | .none => { |
| 441 | if (content_length) |len| { | 443 | if (content_length) |len| { |
| 442 | reader.body_state = .{ .remaining_content_length = len }; | 444 | reader.body_state = .{ .remaining_content_length = len }; |
| 443 | break :r .{ | 445 | return .{ |
| 444 | .context = reader, | 446 | .context = reader, |
| 445 | .vtable = &.{ | 447 | .vtable = &.{ |
| 446 | .read = &contentLengthRead, | 448 | .read = &contentLengthRead, |
| ... | @@ -448,40 +450,53 @@ pub const Reader = struct { | ... | @@ -448,40 +450,53 @@ pub const Reader = struct { |
| 448 | .discard = &contentLengthDiscard, | 450 | .discard = &contentLengthDiscard, |
| 449 | }, | 451 | }, |
| 450 | }; | 452 | }; |
| 451 | } else switch (content_encoding) { | 453 | } else { |
| 452 | .identity => { | 454 | return reader.in.reader(); |
| 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 | ||
| 466 | } | 455 | } |
| 467 | }, | 456 | }, |
| 468 | }; | 457 | }; |
| 469 | switch (content_encoding) { | 458 | } |
| 470 | .identity => { | 459 | |
| 471 | reader.compression = .none; | 460 | /// If compressed body has been negotiated this will return decompressed bytes. |
| 472 | return reader.transfer_br.unbuffered_reader; | 461 | /// |
| 473 | }, | 462 | /// Asserts only called once and after `receiveHead`. |
| 474 | .deflate => { | 463 | /// |
| 475 | reader.compression = .{ .deflate = .init(&reader.transfer_br) }; | 464 | /// See also: |
| 476 | return reader.compression.deflate.reader(); | 465 | /// * `interface` |
| 477 | }, | 466 | pub fn bodyReaderDecompressing( |
| 478 | .gzip, .@"x-gzip" => { | 467 | reader: *Reader, |
| 479 | reader.compression = .{ .gzip = .init(&reader.transfer_br) }; | 468 | transfer_encoding: TransferEncoding, |
| 480 | return reader.compression.gzip.reader(); | 469 | content_length: ?u64, |
| 481 | }, | 470 | content_encoding: ContentEncoding, |
| 482 | .compress, .@"x-compress" => unreachable, | 471 | decompressor: *Decompressor, |
| 483 | .zstd => unreachable, // https://github.com/ziglang/zig/issues/18937 | 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 | fn contentLengthRead( | 502 | fn contentLengthRead( |
| ... | @@ -720,6 +735,52 @@ pub const Reader = struct { | ... | @@ -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 | /// Request or response body. | 784 | /// Request or response body. |
| 724 | pub const BodyWriter = struct { | 785 | pub const BodyWriter = struct { |
| 725 | /// Until the lifetime of `BodyWriter` ends, it is illegal to modify the | 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,7 +85,7 @@ pub const ConnectionPool = struct { |
| 85 | if (connection.port != criteria.port) continue; | 85 | if (connection.port != criteria.port) continue; |
| 86 | 86 | ||
| 87 | // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4) | 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 | pool.acquireUnsafe(connection); | 90 | pool.acquireUnsafe(connection); |
| 91 | return connection; | 91 | return connection; |
| ... | @@ -227,7 +227,8 @@ pub const Protocol = enum { | ... | @@ -227,7 +227,8 @@ pub const Protocol = enum { |
| 227 | 227 | ||
| 228 | pub const Connection = struct { | 228 | pub const Connection = struct { |
| 229 | client: *Client, | 229 | client: *Client, |
| 230 | stream: net.Stream, | 230 | stream_writer: net.Stream.Writer, |
| 231 | stream_reader: net.Stream.Reader, | ||
| 231 | /// HTTP protocol from client to server. | 232 | /// HTTP protocol from client to server. |
| 232 | /// This either goes directly to `stream`, or to a TLS client. | 233 | /// This either goes directly to `stream`, or to a TLS client. |
| 233 | writer: std.io.BufferedWriter, | 234 | writer: std.io.BufferedWriter, |
| ... | @@ -249,7 +250,7 @@ pub const Connection = struct { | ... | @@ -249,7 +250,7 @@ pub const Connection = struct { |
| 249 | remote_host: []const u8, | 250 | remote_host: []const u8, |
| 250 | port: u16, | 251 | port: u16, |
| 251 | stream: net.Stream, | 252 | stream: net.Stream, |
| 252 | ) error{OutOfMemory}!*Connection { | 253 | ) error{OutOfMemory}!*Plain { |
| 253 | const gpa = client.allocator; | 254 | const gpa = client.allocator; |
| 254 | const alloc_len = allocLen(client, remote_host.len); | 255 | const alloc_len = allocLen(client, remote_host.len); |
| 255 | const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len); | 256 | const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len); |
| ... | @@ -263,17 +264,19 @@ pub const Connection = struct { | ... | @@ -263,17 +264,19 @@ pub const Connection = struct { |
| 263 | plain.* = .{ | 264 | plain.* = .{ |
| 264 | .connection = .{ | 265 | .connection = .{ |
| 265 | .client = client, | 266 | .client = client, |
| 266 | .stream = stream, | 267 | .stream_writer = stream.writer(), |
| 267 | .writer = stream.writer().buffered(socket_write_buffer), | 268 | .stream_reader = stream.reader(), |
| 269 | .writer = plain.connection.stream_writer.interface().buffered(socket_write_buffer), | ||
| 268 | .pool_node = .{}, | 270 | .pool_node = .{}, |
| 269 | .port = port, | 271 | .port = port, |
| 272 | .host_len = @intCast(remote_host.len), | ||
| 270 | .proxied = false, | 273 | .proxied = false, |
| 271 | .closing = false, | 274 | .closing = false, |
| 272 | .protocol = .plain, | 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 | fn destroy(plain: *Plain) void { | 282 | fn destroy(plain: *Plain) void { |
| ... | @@ -321,19 +324,20 @@ pub const Connection = struct { | ... | @@ -321,19 +324,20 @@ pub const Connection = struct { |
| 321 | tls.* = .{ | 324 | tls.* = .{ |
| 322 | .connection = .{ | 325 | .connection = .{ |
| 323 | .client = client, | 326 | .client = client, |
| 324 | .stream = stream, | 327 | .stream_writer = stream.writer(), |
| 328 | .stream_reader = stream.reader(), | ||
| 325 | .writer = tls.client.writer().buffered(socket_write_buffer), | 329 | .writer = tls.client.writer().buffered(socket_write_buffer), |
| 326 | .pool_node = .{}, | 330 | .pool_node = .{}, |
| 327 | .port = port, | 331 | .port = port, |
| 332 | .host_len = @intCast(remote_host.len), | ||
| 328 | .proxied = false, | 333 | .proxied = false, |
| 329 | .closing = false, | 334 | .closing = false, |
| 330 | .protocol = .tls, | 335 | .protocol = .tls, |
| 331 | }, | 336 | }, |
| 332 | .writer = stream.writer().buffered(tls_write_buffer), | 337 | .writer = tls.connection.stream_writer.interface().buffered(tls_write_buffer), |
| 333 | .reader = undefined, | 338 | .reader = tls.connection.stream_reader.interface().buffered(tls_read_buffer), |
| 334 | .client = undefined, | 339 | .client = undefined, |
| 335 | }; | 340 | }; |
| 336 | tls.reader.init(stream.reader(), tls_read_buffer); | ||
| 337 | // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true | 341 | // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true |
| 338 | tls.client.init(&tls.reader, &tls.writer, .{ | 342 | tls.client.init(&tls.reader, &tls.writer, .{ |
| 339 | .host = .{ .explicit = remote_host }, | 343 | .host = .{ .explicit = remote_host }, |
| ... | @@ -364,6 +368,10 @@ pub const Connection = struct { | ... | @@ -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 | fn host(c: *Connection) []u8 { | 375 | fn host(c: *Connection) []u8 { |
| 368 | return switch (c.protocol) { | 376 | return switch (c.protocol) { |
| 369 | .tls => { | 377 | .tls => { |
| ... | @@ -396,7 +404,7 @@ pub const Connection = struct { | ... | @@ -396,7 +404,7 @@ pub const Connection = struct { |
| 396 | /// If this is called without calling `flush` or `end`, data will be | 404 | /// If this is called without calling `flush` or `end`, data will be |
| 397 | /// dropped unsent. | 405 | /// dropped unsent. |
| 398 | pub fn destroy(c: *Connection) void { | 406 | pub fn destroy(c: *Connection) void { |
| 399 | c.stream.close(); | 407 | c.getStream().close(); |
| 400 | switch (c.protocol) { | 408 | switch (c.protocol) { |
| 401 | .tls => { | 409 | .tls => { |
| 402 | if (disable_tls) unreachable; | 410 | if (disable_tls) unreachable; |
| ... | @@ -457,12 +465,12 @@ pub const Response = struct { | ... | @@ -457,12 +465,12 @@ pub const Response = struct { |
| 457 | content_encoding: http.ContentEncoding = .identity, | 465 | content_encoding: http.ContentEncoding = .identity, |
| 458 | 466 | ||
| 459 | pub const ParseError = error{ | 467 | pub const ParseError = error{ |
| 460 | HttpHeadersInvalid, | 468 | HttpConnectionHeaderUnsupported, |
| 469 | HttpContentEncodingUnsupported, | ||
| 461 | HttpHeaderContinuationsUnsupported, | 470 | HttpHeaderContinuationsUnsupported, |
| 471 | HttpHeadersInvalid, | ||
| 462 | HttpTransferEncodingUnsupported, | 472 | HttpTransferEncodingUnsupported, |
| 463 | HttpConnectionHeaderUnsupported, | ||
| 464 | InvalidContentLength, | 473 | InvalidContentLength, |
| 465 | CompressionUnsupported, | ||
| 466 | }; | 474 | }; |
| 467 | 475 | ||
| 468 | pub fn parse(bytes: []const u8) ParseError!Head { | 476 | pub fn parse(bytes: []const u8) ParseError!Head { |
| ... | @@ -536,7 +544,7 @@ pub const Response = struct { | ... | @@ -536,7 +544,7 @@ pub const Response = struct { |
| 536 | if (next) |second| { | 544 | if (next) |second| { |
| 537 | const trimmed_second = mem.trim(u8, second, " "); | 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 | if (res.content_encoding != .identity) return error.HttpHeadersInvalid; // double compression is not supported | 548 | if (res.content_encoding != .identity) return error.HttpHeadersInvalid; // double compression is not supported |
| 541 | res.content_encoding = transfer; | 549 | res.content_encoding = transfer; |
| 542 | } else { | 550 | } else { |
| ... | @@ -556,10 +564,10 @@ pub const Response = struct { | ... | @@ -556,10 +564,10 @@ pub const Response = struct { |
| 556 | 564 | ||
| 557 | const trimmed = mem.trim(u8, header_value, " "); | 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 | res.content_encoding = ce; | 568 | res.content_encoding = ce; |
| 561 | } else { | 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,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 | /// Asserts that this function is only called once. | 680 | /// Asserts that this function is only called once. |
| 681 | /// | ||
| 682 | /// See also: | ||
| 683 | /// * `readerDecompressing` | ||
| 668 | pub fn reader(response: *Response) std.io.Reader { | 684 | pub fn reader(response: *Response) std.io.Reader { |
| 669 | const head = &response.head; | 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,6 +735,7 @@ pub const Request = struct { |
| 688 | version: http.Version = .@"HTTP/1.1", | 735 | version: http.Version = .@"HTTP/1.1", |
| 689 | transfer_encoding: TransferEncoding, | 736 | transfer_encoding: TransferEncoding, |
| 690 | redirect_behavior: RedirectBehavior, | 737 | redirect_behavior: RedirectBehavior, |
| 738 | accept_encoding: @TypeOf(default_accept_encoding) = default_accept_encoding, | ||
| 691 | 739 | ||
| 692 | /// Whether the request should handle a 100-continue response before sending the request body. | 740 | /// Whether the request should handle a 100-continue response before sending the request body. |
| 693 | handle_continue: bool, | 741 | handle_continue: bool, |
| ... | @@ -705,6 +753,14 @@ pub const Request = struct { | ... | @@ -705,6 +753,14 @@ pub const Request = struct { |
| 705 | /// Externally-owned; must outlive the Request. | 753 | /// Externally-owned; must outlive the Request. |
| 706 | privileged_headers: []const http.Header, | 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 | pub const TransferEncoding = union(enum) { | 764 | pub const TransferEncoding = union(enum) { |
| 709 | content_length: u64, | 765 | content_length: u64, |
| 710 | chunked: void, | 766 | chunked: void, |
| ... | @@ -844,9 +900,18 @@ pub const Request = struct { | ... | @@ -844,9 +900,18 @@ pub const Request = struct { |
| 844 | } | 900 | } |
| 845 | 901 | ||
| 846 | if (try emitOverridableHeader("accept-encoding: ", r.headers.accept_encoding, w)) { | 902 | if (try emitOverridableHeader("accept-encoding: ", r.headers.accept_encoding, w)) { |
| 847 | // https://github.com/ziglang/zig/issues/18937 | 903 | try w.writeAll("accept-encoding: "); |
| 848 | //try w.writeAll("accept-encoding: gzip, deflate, zstd\r\n"); | 904 | for (r.accept_encoding, 0..) |enabled, i| { |
| 849 | try w.writeAll("accept-encoding: gzip, deflate\r\n"); | 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 | switch (r.transfer_encoding) { | 917 | switch (r.transfer_encoding) { |
| ... | @@ -884,7 +949,7 @@ pub const Request = struct { | ... | @@ -884,7 +949,7 @@ pub const Request = struct { |
| 884 | try w.writeAll("\r\n"); | 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 | /// Server sent headers that did not conform to the HTTP protocol. | 953 | /// Server sent headers that did not conform to the HTTP protocol. |
| 889 | /// | 954 | /// |
| 890 | /// To find out more detailed diagnostics, `http.Reader.head_buffer` can be | 955 | /// To find out more detailed diagnostics, `http.Reader.head_buffer` can be |
| ... | @@ -897,8 +962,14 @@ pub const Request = struct { | ... | @@ -897,8 +962,14 @@ pub const Request = struct { |
| 897 | HttpRedirectLocationMissing, | 962 | HttpRedirectLocationMissing, |
| 898 | HttpRedirectLocationOversize, | 963 | HttpRedirectLocationOversize, |
| 899 | HttpRedirectLocationInvalid, | 964 | HttpRedirectLocationInvalid, |
| 900 | CompressionInitializationFailed, | 965 | HttpContentEncodingUnsupported, |
| 901 | CompressionUnsupported, | 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 | /// If handling redirects and the request has no payload, then this | 975 | /// If handling redirects and the request has no payload, then this |
| ... | @@ -957,44 +1028,35 @@ pub const Request = struct { | ... | @@ -957,44 +1028,35 @@ pub const Request = struct { |
| 957 | 1028 | ||
| 958 | if (head.status.class() == .redirect and r.redirect_behavior != .unhandled) { | 1029 | if (head.status.class() == .redirect and r.redirect_behavior != .unhandled) { |
| 959 | if (r.redirect_behavior == .not_allowed) return error.TooManyHttpRedirects; | 1030 | if (r.redirect_behavior == .not_allowed) return error.TooManyHttpRedirects; |
| 960 | const location = head.location orelse return error.HttpRedirectLocationMissing; | 1031 | try r.redirect(head, &aux_buf); |
| 961 | try r.redirect(location, &aux_buf); | ||
| 962 | try r.sendBodiless(); | 1032 | try r.sendBodiless(); |
| 963 | continue; | 1033 | continue; |
| 964 | } | 1034 | } |
| 965 | 1035 | ||
| 966 | switch (head.content_encoding) { | 1036 | if (!r.accept_encoding[@intFromEnum(head.content_encoding)]) |
| 967 | .identity, .deflate, .gzip, .@"x-gzip" => {}, | 1037 | return error.HttpContentEncodingUnsupported; |
| 968 | .compress, .@"x-compress" => return error.CompressionUnsupported, | ||
| 969 | // https://github.com/ziglang/zig/issues/18937 | ||
| 970 | .zstd => return error.CompressionUnsupported, | ||
| 971 | } | ||
| 972 | 1038 | ||
| 973 | return response; | 1039 | return response; |
| 974 | } | 1040 | } |
| 975 | } | 1041 | } |
| 976 | 1042 | ||
| 977 | pub const RedirectError = error{ | ||
| 978 | HttpRedirectLocationOversize, | ||
| 979 | HttpRedirectLocationInvalid, | ||
| 980 | }; | ||
| 981 | |||
| 982 | /// This function takes an auxiliary buffer to store the arbitrarily large | 1043 | /// This function takes an auxiliary buffer to store the arbitrarily large |
| 983 | /// URI which may need to be merged with the previous URI, and that data | 1044 | /// URI which may need to be merged with the previous URI, and that data |
| 984 | /// needs to survive across different connections, which is where the input | 1045 | /// needs to survive across different connections, which is where the input |
| 985 | /// buffer lives. | 1046 | /// buffer lives. |
| 986 | /// | 1047 | /// |
| 987 | /// `aux_buf` must outlive accesses to `Request.uri`. | 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 | if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize; | 1051 | if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize; |
| 990 | const location = aux_buf.*[0..new_location.len]; | 1052 | const location = aux_buf.*[0..new_location.len]; |
| 991 | @memcpy(location, new_location); | 1053 | @memcpy(location, new_location); |
| 992 | { | 1054 | { |
| 993 | // Skip the body of the redirect response to leave the connection in | 1055 | // Skip the body of the redirect response to leave the connection in |
| 994 | // the correct state. This causes `new_location` to be invalidated. | 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 | _ = reader.discardRemaining() catch |err| switch (err) { | 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 | const new_uri = r.uri.resolveInPlace(location.len, aux_buf) catch |err| switch (err) { | 1062 | const new_uri = r.uri.resolveInPlace(location.len, aux_buf) catch |err| switch (err) { |
| ... | @@ -1003,7 +1065,6 @@ pub const Request = struct { | ... | @@ -1003,7 +1065,6 @@ pub const Request = struct { |
| 1003 | error.InvalidPort => return error.HttpRedirectLocationInvalid, | 1065 | error.InvalidPort => return error.HttpRedirectLocationInvalid, |
| 1004 | error.NoSpaceLeft => return error.HttpRedirectLocationOversize, | 1066 | error.NoSpaceLeft => return error.HttpRedirectLocationOversize, |
| 1005 | }; | 1067 | }; |
| 1006 | const resolved_len = location.len + (aux_buf.*.ptr - location.ptr); | ||
| 1007 | 1068 | ||
| 1008 | const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme; | 1069 | const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme; |
| 1009 | const old_connection = r.connection.?; | 1070 | const old_connection = r.connection.?; |
| ... | @@ -1022,7 +1083,7 @@ pub const Request = struct { | ... | @@ -1022,7 +1083,7 @@ pub const Request = struct { |
| 1022 | r.privileged_headers = &.{}; | 1083 | r.privileged_headers = &.{}; |
| 1023 | } | 1084 | } |
| 1024 | 1085 | ||
| 1025 | if (switch (r.response.status) { | 1086 | if (switch (head.status) { |
| 1026 | .see_other => true, | 1087 | .see_other => true, |
| 1027 | .moved_permanently, .found => r.method == .POST, | 1088 | .moved_permanently, .found => r.method == .POST, |
| 1028 | else => false, | 1089 | else => false, |
| ... | @@ -1042,7 +1103,6 @@ pub const Request = struct { | ... | @@ -1042,7 +1103,6 @@ pub const Request = struct { |
| 1042 | 1103 | ||
| 1043 | const new_connection = try r.client.connect(new_host, uriPort(new_uri, protocol), protocol); | 1104 | const new_connection = try r.client.connect(new_host, uriPort(new_uri, protocol), protocol); |
| 1044 | r.uri = new_uri; | 1105 | r.uri = new_uri; |
| 1045 | r.stolen_bytes_len = resolved_len; | ||
| 1046 | r.connection = new_connection; | 1106 | r.connection = new_connection; |
| 1047 | r.redirect_behavior.subtractOne(); | 1107 | r.redirect_behavior.subtractOne(); |
| 1048 | } | 1108 | } |
| ... | @@ -1054,9 +1114,8 @@ pub const Request = struct { | ... | @@ -1054,9 +1114,8 @@ pub const Request = struct { |
| 1054 | .default => return true, | 1114 | .default => return true, |
| 1055 | .omit => return false, | 1115 | .omit => return false, |
| 1056 | .override => |x| { | 1116 | .override => |x| { |
| 1057 | try bw.writeAll(prefix); | 1117 | var vecs: [3][]const u8 = .{ prefix, x, "\r\n" }; |
| 1058 | try bw.writeAll(x); | 1118 | try bw.writeVecAll(&vecs); |
| 1059 | try bw.writeAll("\r\n"); | ||
| 1060 | return false; | 1119 | return false; |
| 1061 | }, | 1120 | }, |
| 1062 | } | 1121 | } |
| ... | @@ -1198,9 +1257,29 @@ pub fn connectTcp( | ... | @@ -1198,9 +1257,29 @@ pub fn connectTcp( |
| 1198 | port: u16, | 1257 | port: u16, |
| 1199 | protocol: Protocol, | 1258 | protocol: Protocol, |
| 1200 | ) ConnectTcpError!*Connection { | 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 | if (client.connection_pool.findConnection(.{ | 1280 | if (client.connection_pool.findConnection(.{ |
| 1202 | .host = host, | 1281 | .host = proxied_host, |
| 1203 | .port = port, | 1282 | .port = proxied_port, |
| 1204 | .protocol = protocol, | 1283 | .protocol = protocol, |
| 1205 | })) |conn| return conn; | 1284 | })) |conn| return conn; |
| 1206 | 1285 | ||
| ... | @@ -1220,12 +1299,12 @@ pub fn connectTcp( | ... | @@ -1220,12 +1299,12 @@ pub fn connectTcp( |
| 1220 | switch (protocol) { | 1299 | switch (protocol) { |
| 1221 | .tls => { | 1300 | .tls => { |
| 1222 | if (disable_tls) return error.TlsInitializationFailed; | 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 | client.connection_pool.addUsed(&tc.connection); | 1303 | client.connection_pool.addUsed(&tc.connection); |
| 1225 | return &tc.connection; | 1304 | return &tc.connection; |
| 1226 | }, | 1305 | }, |
| 1227 | .plain => { | 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 | client.connection_pool.addUsed(&pc.connection); | 1308 | client.connection_pool.addUsed(&pc.connection); |
| 1230 | return &pc.connection; | 1309 | return &pc.connection; |
| 1231 | }, | 1310 | }, |
| ... | @@ -1267,69 +1346,67 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti | ... | @@ -1267,69 +1346,67 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti |
| 1267 | return &conn.data; | 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 | /// CONNECT. This will reuse a connection if one is already open. | 1350 | /// CONNECT. This will reuse a connection if one is already open. |
| 1272 | /// | 1351 | /// |
| 1273 | /// This function is threadsafe. | 1352 | /// This function is threadsafe. |
| 1274 | pub fn connectTunnel( | 1353 | pub fn connectProxied( |
| 1275 | client: *Client, | 1354 | client: *Client, |
| 1276 | proxy: *Proxy, | 1355 | proxy: *Proxy, |
| 1277 | tunnel_host: []const u8, | 1356 | proxied_host: []const u8, |
| 1278 | tunnel_port: u16, | 1357 | proxied_port: u16, |
| 1279 | ) !*Connection { | 1358 | ) !*Connection { |
| 1280 | if (!proxy.supports_connect) return error.TunnelNotSupported; | 1359 | if (!proxy.supports_connect) return error.TunnelNotSupported; |
| 1281 | 1360 | ||
| 1282 | if (client.connection_pool.findConnection(.{ | 1361 | if (client.connection_pool.findConnection(.{ |
| 1283 | .host = tunnel_host, | 1362 | .host = proxied_host, |
| 1284 | .port = tunnel_port, | 1363 | .port = proxied_port, |
| 1285 | .protocol = proxy.protocol, | 1364 | .protocol = proxy.protocol, |
| 1286 | })) |node| | 1365 | })) |node| return node; |
| 1287 | return node; | ||
| 1288 | 1366 | ||
| 1289 | var maybe_valid = false; | 1367 | var maybe_valid = false; |
| 1290 | (tunnel: { | 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 | errdefer { | 1376 | errdefer { |
| 1293 | conn.closing = true; | 1377 | connection.closing = true; |
| 1294 | client.connection_pool.release(conn); | 1378 | client.connection_pool.release(connection); |
| 1295 | } | 1379 | } |
| 1296 | 1380 | ||
| 1297 | var buffer: [8096]u8 = undefined; | 1381 | var req = client.request(.CONNECT, .{ |
| 1298 | var req = client.open(.CONNECT, .{ | ||
| 1299 | .scheme = "http", | 1382 | .scheme = "http", |
| 1300 | .host = .{ .raw = tunnel_host }, | 1383 | .host = .{ .raw = proxied_host }, |
| 1301 | .port = tunnel_port, | 1384 | .port = proxied_port, |
| 1302 | }, .{ | 1385 | }, .{ |
| 1303 | .redirect_behavior = .unhandled, | 1386 | .redirect_behavior = .unhandled, |
| 1304 | .connection = conn, | 1387 | .connection = connection, |
| 1305 | .server_header_buffer = &buffer, | ||
| 1306 | }) catch |err| { | 1388 | }) catch |err| { |
| 1307 | std.log.debug("err {}", .{err}); | ||
| 1308 | break :tunnel err; | 1389 | break :tunnel err; |
| 1309 | }; | 1390 | }; |
| 1310 | defer req.deinit(); | 1391 | defer req.deinit(); |
| 1311 | 1392 | ||
| 1312 | req.send() catch |err| break :tunnel err; | 1393 | req.sendBodiless() catch |err| break :tunnel err; |
| 1313 | req.wait() 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 | maybe_valid = true; | 1397 | maybe_valid = true; |
| 1317 | break :tunnel error.ServerError; | 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 | req.connection = null; | 1405 | req.connection = null; |
| 1324 | 1406 | ||
| 1325 | client.allocator.free(conn.host); | 1407 | connection.closing = false; |
| 1326 | conn.host = try client.allocator.dupe(u8, tunnel_host); | ||
| 1327 | errdefer client.allocator.free(conn.host); | ||
| 1328 | 1408 | ||
| 1329 | conn.port = tunnel_port; | 1409 | return connection; |
| 1330 | conn.closing = false; | ||
| 1331 | |||
| 1332 | return conn; | ||
| 1333 | }) catch { | 1410 | }) catch { |
| 1334 | // something went wrong with the tunnel | 1411 | // something went wrong with the tunnel |
| 1335 | proxy.supports_connect = maybe_valid; | 1412 | proxy.supports_connect = maybe_valid; |
| ... | @@ -1337,12 +1414,11 @@ pub fn connectTunnel( | ... | @@ -1337,12 +1414,11 @@ pub fn connectTunnel( |
| 1337 | }; | 1414 | }; |
| 1338 | } | 1415 | } |
| 1339 | 1416 | ||
| 1340 | // Prevents a dependency loop in open() | 1417 | pub const ConnectError = ConnectTcpError || RequestError; |
| 1341 | const ConnectErrorPartial = ConnectTcpError || error{ UnsupportedUriScheme, ConnectionRefused }; | ||
| 1342 | pub const ConnectError = ConnectErrorPartial || RequestError; | ||
| 1343 | 1418 | ||
| 1344 | /// Connect to `host:port` using the specified protocol. This will reuse a | 1419 | /// Connect to `host:port` using the specified protocol. This will reuse a |
| 1345 | /// connection if one is already open. | 1420 | /// connection if one is already open. |
| 1421 | /// | ||
| 1346 | /// If a proxy is configured for the client, then the proxy will be used to | 1422 | /// If a proxy is configured for the client, then the proxy will be used to |
| 1347 | /// connect to the host. | 1423 | /// connect to the host. |
| 1348 | /// | 1424 | /// |
| ... | @@ -1366,31 +1442,24 @@ pub fn connect( | ... | @@ -1366,31 +1442,24 @@ pub fn connect( |
| 1366 | } | 1442 | } |
| 1367 | 1443 | ||
| 1368 | if (proxy.supports_connect) tunnel: { | 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 | error.TunnelNotSupported => break :tunnel, | 1446 | error.TunnelNotSupported => break :tunnel, |
| 1371 | else => |e| return e, | 1447 | else => |e| return e, |
| 1372 | }; | 1448 | }; |
| 1373 | } | 1449 | } |
| 1374 | 1450 | ||
| 1375 | // fall back to using the proxy as a normal http proxy | 1451 | // fall back to using the proxy as a normal http proxy |
| 1376 | const conn = try client.connectTcp(proxy.host, proxy.port, proxy.protocol); | 1452 | const connection = try client.connectTcp(proxy.host, proxy.port, proxy.protocol); |
| 1377 | errdefer { | 1453 | connection.proxied = true; |
| 1378 | conn.closing = true; | 1454 | return connection; |
| 1379 | client.connection_pool.release(conn); | ||
| 1380 | } | ||
| 1381 | |||
| 1382 | conn.proxied = true; | ||
| 1383 | return conn; | ||
| 1384 | } | 1455 | } |
| 1385 | 1456 | ||
| 1386 | /// TODO collapse each error set into its own meta error code, and store | 1457 | pub const RequestError = ConnectTcpError || error{ |
| 1387 | /// the underlying error code as a field on Request | 1458 | UnsupportedUriScheme, |
| 1388 | pub const RequestError = ConnectTcpError || ConnectErrorPartial || std.io.Writer.Error || std.fmt.ParseIntError || | 1459 | UriMissingHost, |
| 1389 | error{ | 1460 | UriHostTooLong, |
| 1390 | UnsupportedUriScheme, | 1461 | CertificateBundleLoadFailure, |
| 1391 | UriMissingHost, | 1462 | }; |
| 1392 | CertificateBundleLoadFailure, | ||
| 1393 | }; | ||
| 1394 | 1463 | ||
| 1395 | pub const RequestOptions = struct { | 1464 | pub const RequestOptions = struct { |
| 1396 | version: http.Version = .@"HTTP/1.1", | 1465 | version: http.Version = .@"HTTP/1.1", |
| ... | @@ -1440,7 +1509,7 @@ fn uriPort(uri: Uri, protocol: Protocol) u16 { | ... | @@ -1440,7 +1509,7 @@ fn uriPort(uri: Uri, protocol: Protocol) u16 { |
| 1440 | /// This function is threadsafe. | 1509 | /// This function is threadsafe. |
| 1441 | /// | 1510 | /// |
| 1442 | /// Asserts that "\r\n" does not occur in any header name or value. | 1511 | /// Asserts that "\r\n" does not occur in any header name or value. |
| 1443 | pub fn open( | 1512 | pub fn request( |
| 1444 | client: *Client, | 1513 | client: *Client, |
| 1445 | method: http.Method, | 1514 | method: http.Method, |
| 1446 | uri: Uri, | 1515 | uri: Uri, |
| ... | @@ -1486,6 +1555,11 @@ pub fn open( | ... | @@ -1486,6 +1555,11 @@ pub fn open( |
| 1486 | .uri = uri, | 1555 | .uri = uri, |
| 1487 | .client = client, | 1556 | .client = client, |
| 1488 | .connection = connection, | 1557 | .connection = connection, |
| 1558 | .reader = .{ | ||
| 1559 | .in = connection.reader(), | ||
| 1560 | .state = .ready, | ||
| 1561 | .body_state = undefined, | ||
| 1562 | }, | ||
| 1489 | .keep_alive = options.keep_alive, | 1563 | .keep_alive = options.keep_alive, |
| 1490 | .method = method, | 1564 | .method = method, |
| 1491 | .version = options.version, | 1565 | .version = options.version, |
| ... | @@ -1499,13 +1573,13 @@ pub fn open( | ... | @@ -1499,13 +1573,13 @@ pub fn open( |
| 1499 | } | 1573 | } |
| 1500 | 1574 | ||
| 1501 | pub const FetchOptions = struct { | 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 | redirect_behavior: ?Request.RedirectBehavior = null, | 1580 | redirect_behavior: ?Request.RedirectBehavior = null, |
| 1504 | 1581 | /// If the server sends a body, it will be stored here. | |
| 1505 | /// If the server sends a body, it will be appended to this ArrayList. | 1582 | response_storage: ?ResponseStorage = null, |
| 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, | ||
| 1509 | 1583 | ||
| 1510 | location: Location, | 1584 | location: Location, |
| 1511 | method: ?http.Method = null, | 1585 | method: ?http.Method = null, |
| ... | @@ -1529,11 +1603,11 @@ pub const FetchOptions = struct { | ... | @@ -1529,11 +1603,11 @@ pub const FetchOptions = struct { |
| 1529 | uri: Uri, | 1603 | uri: Uri, |
| 1530 | }; | 1604 | }; |
| 1531 | 1605 | ||
| 1532 | pub const ResponseStorage = union(enum) { | 1606 | pub const ResponseStorage = struct { |
| 1533 | ignore, | 1607 | list: *std.ArrayListUnmanaged(u8), |
| 1534 | /// Only the existing capacity will be used. | 1608 | /// If null then only the existing capacity will be used. |
| 1535 | static: *std.ArrayListUnmanaged(u8), | 1609 | allocator: ?Allocator = null, |
| 1536 | dynamic: *std.ArrayList(u8), | 1610 | append_limit: std.io.Reader.Limit = .unlimited, |
| 1537 | }; | 1611 | }; |
| 1538 | }; | 1612 | }; |
| 1539 | 1613 | ||
| ... | @@ -1541,23 +1615,28 @@ pub const FetchResult = struct { | ... | @@ -1541,23 +1615,28 @@ pub const FetchResult = struct { |
| 1541 | status: http.Status, | 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 | /// Perform a one-shot HTTP request with the provided options. | 1624 | /// Perform a one-shot HTTP request with the provided options. |
| 1545 | /// | 1625 | /// |
| 1546 | /// This function is threadsafe. | 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 | const uri = switch (options.location) { | 1628 | const uri = switch (options.location) { |
| 1549 | .url => |u| try Uri.parse(u), | 1629 | .url => |u| try Uri.parse(u), |
| 1550 | .uri => |u| u, | 1630 | .uri => |u| u, |
| 1551 | }; | 1631 | }; |
| 1552 | var server_header_buffer: [16 * 1024]u8 = undefined; | ||
| 1553 | |||
| 1554 | const method: http.Method = options.method orelse | 1632 | const method: http.Method = options.method orelse |
| 1555 | if (options.payload != null) .POST else .GET; | 1633 | if (options.payload != null) .POST else .GET; |
| 1556 | 1634 | ||
| 1557 | var req = try open(client, method, uri, .{ | 1635 | const redirect_behavior: Request.RedirectBehavior = options.redirect_behavior orelse |
| 1558 | .server_header_buffer = options.server_header_buffer orelse &server_header_buffer, | 1636 | if (options.payload == null) @enumFromInt(3) else .unhandled; |
| 1559 | .redirect_behavior = options.redirect_behavior orelse | 1637 | |
| 1560 | if (options.payload == null) @enumFromInt(3) else .unhandled, | 1638 | var req = try request(client, method, uri, .{ |
| 1639 | .redirect_behavior = redirect_behavior, | ||
| 1561 | .headers = options.headers, | 1640 | .headers = options.headers, |
| 1562 | .extra_headers = options.extra_headers, | 1641 | .extra_headers = options.extra_headers, |
| 1563 | .privileged_headers = options.privileged_headers, | 1642 | .privileged_headers = options.privileged_headers, |
| ... | @@ -1565,44 +1644,56 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult { | ... | @@ -1565,44 +1644,56 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult { |
| 1565 | }); | 1644 | }); |
| 1566 | defer req.deinit(); | 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 | if (options.payload) |payload| { | 1647 | if (options.payload) |payload| { |
| 1573 | var w = req.writer().unbuffered(); | 1648 | req.transfer_encoding = .{ .content_length = payload.len }; |
| 1574 | try w.writeAll(payload); | 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(); | 1657 | const redirect_buffer: []u8 = if (redirect_behavior == .unhandled) &.{} else options.redirect_buffer orelse |
| 1578 | try req.wait(); | 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) { | 1661 | var response = try req.receiveHead(redirect_buffer); |
| 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 | } | ||
| 1602 | 1662 | ||
| 1603 | return .{ | 1663 | const storage = options.response_storage orelse { |
| 1604 | .status = req.response.status, | 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 | pub fn sameParentDomain(parent_host: []const u8, child_host: []const u8) bool { | 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,13 +55,6 @@ pub const Request = struct { |
| 55 | /// `receiveHead`. | 55 | /// `receiveHead`. |
| 56 | head: Head, | 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 | pub const Head = struct { | 58 | pub const Head = struct { |
| 66 | method: http.Method, | 59 | method: http.Method, |
| 67 | target: []const u8, | 60 | target: []const u8, |
| ... | @@ -72,7 +65,6 @@ pub const Request = struct { | ... | @@ -72,7 +65,6 @@ pub const Request = struct { |
| 72 | transfer_encoding: http.TransferEncoding, | 65 | transfer_encoding: http.TransferEncoding, |
| 73 | transfer_compression: http.ContentEncoding, | 66 | transfer_compression: http.ContentEncoding, |
| 74 | keep_alive: bool, | 67 | keep_alive: bool, |
| 75 | compression: Compression, | ||
| 76 | 68 | ||
| 77 | pub const ParseError = error{ | 69 | pub const ParseError = error{ |
| 78 | UnknownHttpMethod, | 70 | UnknownHttpMethod, |
| ... | @@ -126,7 +118,6 @@ pub const Request = struct { | ... | @@ -126,7 +118,6 @@ pub const Request = struct { |
| 126 | .@"HTTP/1.0" => false, | 118 | .@"HTTP/1.0" => false, |
| 127 | .@"HTTP/1.1" => true, | 119 | .@"HTTP/1.1" => true, |
| 128 | }, | 120 | }, |
| 129 | .compression = .none, | ||
| 130 | }; | 121 | }; |
| 131 | 122 | ||
| 132 | while (it.next()) |line| { | 123 | while (it.next()) |line| { |
| ... | @@ -156,7 +147,7 @@ pub const Request = struct { | ... | @@ -156,7 +147,7 @@ pub const Request = struct { |
| 156 | 147 | ||
| 157 | const trimmed = mem.trim(u8, header_value, " "); | 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 | head.transfer_compression = ce; | 151 | head.transfer_compression = ce; |
| 161 | } else { | 152 | } else { |
| 162 | return error.HttpTransferEncodingUnsupported; | 153 | return error.HttpTransferEncodingUnsupported; |
| ... | @@ -181,7 +172,7 @@ pub const Request = struct { | ... | @@ -181,7 +172,7 @@ pub const Request = struct { |
| 181 | if (next) |second| { | 172 | if (next) |second| { |
| 182 | const trimmed_second = mem.trim(u8, second, " "); | 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 | if (head.transfer_compression != .identity) | 176 | if (head.transfer_compression != .identity) |
| 186 | return error.HttpHeadersInvalid; // double compression is not supported | 177 | return error.HttpHeadersInvalid; // double compression is not supported |
| 187 | head.transfer_compression = transfer; | 178 | head.transfer_compression = transfer; |
| ... | @@ -236,10 +227,8 @@ pub const Request = struct { | ... | @@ -236,10 +227,8 @@ pub const Request = struct { |
| 236 | "TRansfer-encoding:\tdeflate, chunked \r\n" ++ | 227 | "TRansfer-encoding:\tdeflate, chunked \r\n" ++ |
| 237 | "connectioN:\t keep-alive \r\n\r\n"; | 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 | var br: std.io.BufferedReader = undefined; | 230 | var br: std.io.BufferedReader = undefined; |
| 242 | br.initFixed(&read_buffer); | 231 | br.initFixed(@constCast(request_bytes)); |
| 243 | 232 | ||
| 244 | var server: Server = .{ | 233 | var server: Server = .{ |
| 245 | .reader = .{ | 234 | .reader = .{ |
| ... | @@ -252,7 +241,6 @@ pub const Request = struct { | ... | @@ -252,7 +241,6 @@ pub const Request = struct { |
| 252 | 241 | ||
| 253 | var request: Request = .{ | 242 | var request: Request = .{ |
| 254 | .server = &server, | 243 | .server = &server, |
| 255 | .trailers_len = 0, | ||
| 256 | .head = undefined, | 244 | .head = undefined, |
| 257 | }; | 245 | }; |
| 258 | 246 | ||
| ... | @@ -529,7 +517,7 @@ pub const Request = struct { | ... | @@ -529,7 +517,7 @@ pub const Request = struct { |
| 529 | return error.HttpExpectationFailed; | 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 | /// Returns whether the connection should remain persistent. | 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,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 | try bw.writeAll(header); | 240 | try bw.writeAll(header); |
| 241 | for (message) |iovec| try bw.writeAll(iovec.base[0..iovec.len]); | 241 | for (message) |iovec| try bw.writeAll(iovec.base[0..iovec.len]); |
| 242 | try bw.flush(); | 242 | try bw.flush(); |
lib/std/http/test.zig+49-77| ... | @@ -61,7 +61,7 @@ test "trailers" { | ... | @@ -61,7 +61,7 @@ test "trailers" { |
| 61 | const uri = try std.Uri.parse(location); | 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 | defer req.deinit(); | 65 | defer req.deinit(); |
| 66 | 66 | ||
| 67 | try req.sendBodiless(); | 67 | try req.sendBodiless(); |
| ... | @@ -263,7 +263,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { | ... | @@ -263,7 +263,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { |
| 263 | var connection_bw = stream_writer.interface().buffered(&send_buffer); | 263 | var connection_bw = stream_writer.interface().buffered(&send_buffer); |
| 264 | var server = http.Server.init(&connection_br, &connection_bw); | 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 | var request = try server.receiveHead(); | 267 | var request = try server.receiveHead(); |
| 268 | try expectEqualStrings(request.head.target, "/foo"); | 268 | try expectEqualStrings(request.head.target, "/foo"); |
| 269 | var response = try request.respondStreaming(.{ | 269 | var response = try request.respondStreaming(.{ |
| ... | @@ -278,7 +278,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { | ... | @@ -278,7 +278,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { |
| 278 | } | 278 | } |
| 279 | try expectEqual(7390, bw.count); | 279 | try expectEqual(7390, bw.count); |
| 280 | try response.end(); | 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,7 +331,7 @@ test "receiving arbitrary http headers from the client" { |
| 331 | var connection_bw = stream_writer.interface().buffered(&send_buffer); | 331 | var connection_bw = stream_writer.interface().buffered(&send_buffer); |
| 332 | var server = http.Server.init(&connection_br, &connection_bw); | 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 | var request = try server.receiveHead(); | 335 | var request = try server.receiveHead(); |
| 336 | try expectEqualStrings("/bar", request.head.target); | 336 | try expectEqualStrings("/bar", request.head.target); |
| 337 | var it = request.iterateHeaders(); | 337 | var it = request.iterateHeaders(); |
| ... | @@ -563,7 +563,7 @@ test "general client/server API coverage" { | ... | @@ -563,7 +563,7 @@ test "general client/server API coverage" { |
| 563 | 563 | ||
| 564 | log.info("{s}", .{location}); | 564 | log.info("{s}", .{location}); |
| 565 | var redirect_buffer: [1024]u8 = undefined; | 565 | var redirect_buffer: [1024]u8 = undefined; |
| 566 | var req = try client.open(.GET, uri, .{}); | 566 | var req = try client.request(.GET, uri, .{}); |
| 567 | defer req.deinit(); | 567 | defer req.deinit(); |
| 568 | 568 | ||
| 569 | try req.sendBodiless(); | 569 | try req.sendBodiless(); |
| ... | @@ -586,7 +586,7 @@ test "general client/server API coverage" { | ... | @@ -586,7 +586,7 @@ test "general client/server API coverage" { |
| 586 | 586 | ||
| 587 | log.info("{s}", .{location}); | 587 | log.info("{s}", .{location}); |
| 588 | var redirect_buffer: [1024]u8 = undefined; | 588 | var redirect_buffer: [1024]u8 = undefined; |
| 589 | var req = try client.open(.GET, uri, .{}); | 589 | var req = try client.request(.GET, uri, .{}); |
| 590 | defer req.deinit(); | 590 | defer req.deinit(); |
| 591 | 591 | ||
| 592 | try req.sendBodiless(); | 592 | try req.sendBodiless(); |
| ... | @@ -608,7 +608,7 @@ test "general client/server API coverage" { | ... | @@ -608,7 +608,7 @@ test "general client/server API coverage" { |
| 608 | 608 | ||
| 609 | log.info("{s}", .{location}); | 609 | log.info("{s}", .{location}); |
| 610 | var redirect_buffer: [1024]u8 = undefined; | 610 | var redirect_buffer: [1024]u8 = undefined; |
| 611 | var req = try client.open(.HEAD, uri, .{}); | 611 | var req = try client.request(.HEAD, uri, .{}); |
| 612 | defer req.deinit(); | 612 | defer req.deinit(); |
| 613 | 613 | ||
| 614 | try req.sendBodiless(); | 614 | try req.sendBodiless(); |
| ... | @@ -632,7 +632,7 @@ test "general client/server API coverage" { | ... | @@ -632,7 +632,7 @@ test "general client/server API coverage" { |
| 632 | 632 | ||
| 633 | log.info("{s}", .{location}); | 633 | log.info("{s}", .{location}); |
| 634 | var redirect_buffer: [1024]u8 = undefined; | 634 | var redirect_buffer: [1024]u8 = undefined; |
| 635 | var req = try client.open(.GET, uri, .{}); | 635 | var req = try client.request(.GET, uri, .{}); |
| 636 | defer req.deinit(); | 636 | defer req.deinit(); |
| 637 | 637 | ||
| 638 | try req.sendBodiless(); | 638 | try req.sendBodiless(); |
| ... | @@ -655,18 +655,18 @@ test "general client/server API coverage" { | ... | @@ -655,18 +655,18 @@ test "general client/server API coverage" { |
| 655 | 655 | ||
| 656 | log.info("{s}", .{location}); | 656 | log.info("{s}", .{location}); |
| 657 | var redirect_buffer: [1024]u8 = undefined; | 657 | var redirect_buffer: [1024]u8 = undefined; |
| 658 | var req = try client.open(.HEAD, uri, .{}); | 658 | var req = try client.request(.HEAD, uri, .{}); |
| 659 | defer req.deinit(); | 659 | defer req.deinit(); |
| 660 | 660 | ||
| 661 | try req.sendBodiless(); | 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 | defer gpa.free(body); | 665 | defer gpa.free(body); |
| 666 | 666 | ||
| 667 | try expectEqualStrings("", body); | 667 | try expectEqualStrings("", body); |
| 668 | try expectEqualStrings("text/plain", req.response.content_type.?); | 668 | try expectEqualStrings("text/plain", response.head.content_type.?); |
| 669 | try expect(req.response.transfer_encoding == .chunked); | 669 | try expect(response.head.transfer_encoding == .chunked); |
| 670 | } | 670 | } |
| 671 | 671 | ||
| 672 | // connection has been kept alive | 672 | // connection has been kept alive |
| ... | @@ -679,19 +679,19 @@ test "general client/server API coverage" { | ... | @@ -679,19 +679,19 @@ test "general client/server API coverage" { |
| 679 | 679 | ||
| 680 | log.info("{s}", .{location}); | 680 | log.info("{s}", .{location}); |
| 681 | var redirect_buffer: [1024]u8 = undefined; | 681 | var redirect_buffer: [1024]u8 = undefined; |
| 682 | var req = try client.open(.GET, uri, .{ | 682 | var req = try client.request(.GET, uri, .{ |
| 683 | .keep_alive = false, | 683 | .keep_alive = false, |
| 684 | }); | 684 | }); |
| 685 | defer req.deinit(); | 685 | defer req.deinit(); |
| 686 | 686 | ||
| 687 | try req.sendBodiless(); | 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 | defer gpa.free(body); | 691 | defer gpa.free(body); |
| 692 | 692 | ||
| 693 | try expectEqualStrings("Hello, World!\n", body); | 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 | // connection has been closed | 697 | // connection has been closed |
| ... | @@ -704,7 +704,7 @@ test "general client/server API coverage" { | ... | @@ -704,7 +704,7 @@ test "general client/server API coverage" { |
| 704 | 704 | ||
| 705 | log.info("{s}", .{location}); | 705 | log.info("{s}", .{location}); |
| 706 | var redirect_buffer: [1024]u8 = undefined; | 706 | var redirect_buffer: [1024]u8 = undefined; |
| 707 | var req = try client.open(.GET, uri, .{ | 707 | var req = try client.request(.GET, uri, .{ |
| 708 | .extra_headers = &.{ | 708 | .extra_headers = &.{ |
| 709 | .{ .name = "empty", .value = "" }, | 709 | .{ .name = "empty", .value = "" }, |
| 710 | }, | 710 | }, |
| ... | @@ -712,16 +712,16 @@ test "general client/server API coverage" { | ... | @@ -712,16 +712,16 @@ test "general client/server API coverage" { |
| 712 | defer req.deinit(); | 712 | defer req.deinit(); |
| 713 | 713 | ||
| 714 | try req.sendBodiless(); | 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 | defer gpa.free(body); | 720 | defer gpa.free(body); |
| 721 | 721 | ||
| 722 | try expectEqualStrings("", body); | 722 | try expectEqualStrings("", body); |
| 723 | 723 | ||
| 724 | var it = req.response.iterateHeaders(); | 724 | var it = response.head.iterateHeaders(); |
| 725 | { | 725 | { |
| 726 | const header = it.next().?; | 726 | const header = it.next().?; |
| 727 | try expect(!it.is_trailer); | 727 | try expect(!it.is_trailer); |
| ... | @@ -747,13 +747,13 @@ test "general client/server API coverage" { | ... | @@ -747,13 +747,13 @@ test "general client/server API coverage" { |
| 747 | 747 | ||
| 748 | log.info("{s}", .{location}); | 748 | log.info("{s}", .{location}); |
| 749 | var redirect_buffer: [1024]u8 = undefined; | 749 | var redirect_buffer: [1024]u8 = undefined; |
| 750 | var req = try client.open(.GET, uri, .{}); | 750 | var req = try client.request(.GET, uri, .{}); |
| 751 | defer req.deinit(); | 751 | defer req.deinit(); |
| 752 | 752 | ||
| 753 | try req.sendBodiless(); | 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 | defer gpa.free(body); | 757 | defer gpa.free(body); |
| 758 | 758 | ||
| 759 | try expectEqualStrings("Hello, World!\n", body); | 759 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -769,13 +769,13 @@ test "general client/server API coverage" { | ... | @@ -769,13 +769,13 @@ test "general client/server API coverage" { |
| 769 | 769 | ||
| 770 | log.info("{s}", .{location}); | 770 | log.info("{s}", .{location}); |
| 771 | var redirect_buffer: [1024]u8 = undefined; | 771 | var redirect_buffer: [1024]u8 = undefined; |
| 772 | var req = try client.open(.GET, uri, .{}); | 772 | var req = try client.request(.GET, uri, .{}); |
| 773 | defer req.deinit(); | 773 | defer req.deinit(); |
| 774 | 774 | ||
| 775 | try req.sendBodiless(); | 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 | defer gpa.free(body); | 779 | defer gpa.free(body); |
| 780 | 780 | ||
| 781 | try expectEqualStrings("Hello, World!\n", body); | 781 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -791,13 +791,13 @@ test "general client/server API coverage" { | ... | @@ -791,13 +791,13 @@ test "general client/server API coverage" { |
| 791 | 791 | ||
| 792 | log.info("{s}", .{location}); | 792 | log.info("{s}", .{location}); |
| 793 | var redirect_buffer: [1024]u8 = undefined; | 793 | var redirect_buffer: [1024]u8 = undefined; |
| 794 | var req = try client.open(.GET, uri, .{}); | 794 | var req = try client.request(.GET, uri, .{}); |
| 795 | defer req.deinit(); | 795 | defer req.deinit(); |
| 796 | 796 | ||
| 797 | try req.sendBodiless(); | 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 | defer gpa.free(body); | 801 | defer gpa.free(body); |
| 802 | 802 | ||
| 803 | try expectEqualStrings("Hello, World!\n", body); | 803 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -813,14 +813,16 @@ test "general client/server API coverage" { | ... | @@ -813,14 +813,16 @@ test "general client/server API coverage" { |
| 813 | 813 | ||
| 814 | log.info("{s}", .{location}); | 814 | log.info("{s}", .{location}); |
| 815 | var redirect_buffer: [1024]u8 = undefined; | 815 | var redirect_buffer: [1024]u8 = undefined; |
| 816 | var req = try client.open(.GET, uri, .{}); | 816 | var req = try client.request(.GET, uri, .{}); |
| 817 | defer req.deinit(); | 817 | defer req.deinit(); |
| 818 | 818 | ||
| 819 | try req.sendBodiless(); | 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 | error.TooManyHttpRedirects => {}, | 823 | error.TooManyHttpRedirects => {}, |
| 822 | else => return err, | 824 | else => return err, |
| 823 | }; | 825 | } |
| 824 | } | 826 | } |
| 825 | 827 | ||
| 826 | { // redirect to encoded url | 828 | { // redirect to encoded url |
| ... | @@ -830,13 +832,13 @@ test "general client/server API coverage" { | ... | @@ -830,13 +832,13 @@ test "general client/server API coverage" { |
| 830 | 832 | ||
| 831 | log.info("{s}", .{location}); | 833 | log.info("{s}", .{location}); |
| 832 | var redirect_buffer: [1024]u8 = undefined; | 834 | var redirect_buffer: [1024]u8 = undefined; |
| 833 | var req = try client.open(.GET, uri, .{}); | 835 | var req = try client.request(.GET, uri, .{}); |
| 834 | defer req.deinit(); | 836 | defer req.deinit(); |
| 835 | 837 | ||
| 836 | try req.sendBodiless(); | 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 | defer gpa.free(body); | 842 | defer gpa.free(body); |
| 841 | 843 | ||
| 842 | try expectEqualStrings("Encoded redirect successful!\n", body); | 844 | try expectEqualStrings("Encoded redirect successful!\n", body); |
| ... | @@ -852,7 +854,7 @@ test "general client/server API coverage" { | ... | @@ -852,7 +854,7 @@ test "general client/server API coverage" { |
| 852 | 854 | ||
| 853 | log.info("{s}", .{location}); | 855 | log.info("{s}", .{location}); |
| 854 | var redirect_buffer: [1024]u8 = undefined; | 856 | var redirect_buffer: [1024]u8 = undefined; |
| 855 | var req = try client.open(.GET, uri, .{}); | 857 | var req = try client.request(.GET, uri, .{}); |
| 856 | defer req.deinit(); | 858 | defer req.deinit(); |
| 857 | 859 | ||
| 858 | try req.sendBodiless(); | 860 | try req.sendBodiless(); |
| ... | @@ -867,36 +869,6 @@ test "general client/server API coverage" { | ... | @@ -867,36 +869,6 @@ test "general client/server API coverage" { |
| 867 | // connection has been kept alive | 869 | // connection has been kept alive |
| 868 | try expect(client.http_proxy != null or client.connection_pool.free_len == 1); | 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 | client.deinit(); | 872 | client.deinit(); |
| 901 | 873 | ||
| 902 | { | 874 | { |
| ... | @@ -950,7 +922,7 @@ test "Server streams both reading and writing" { | ... | @@ -950,7 +922,7 @@ test "Server streams both reading and writing" { |
| 950 | defer client.deinit(); | 922 | defer client.deinit(); |
| 951 | 923 | ||
| 952 | var redirect_buffer: [555]u8 = undefined; | 924 | var redirect_buffer: [555]u8 = undefined; |
| 953 | var req = try client.open(.POST, .{ | 925 | var req = try client.request(.POST, .{ |
| 954 | .scheme = "http", | 926 | .scheme = "http", |
| 955 | .host = .{ .raw = "127.0.0.1" }, | 927 | .host = .{ .raw = "127.0.0.1" }, |
| 956 | .port = test_server.port(), | 928 | .port = test_server.port(), |
| ... | @@ -983,7 +955,7 @@ fn echoTests(client: *http.Client, port: u16) !void { | ... | @@ -983,7 +955,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 983 | const uri = try std.Uri.parse(location); | 955 | const uri = try std.Uri.parse(location); |
| 984 | 956 | ||
| 985 | var redirect_buffer: [1024]u8 = undefined; | 957 | var redirect_buffer: [1024]u8 = undefined; |
| 986 | var req = try client.open(.POST, uri, .{ | 958 | var req = try client.request(.POST, uri, .{ |
| 987 | .extra_headers = &.{ | 959 | .extra_headers = &.{ |
| 988 | .{ .name = "content-type", .value = "text/plain" }, | 960 | .{ .name = "content-type", .value = "text/plain" }, |
| 989 | }, | 961 | }, |
| ... | @@ -1017,7 +989,7 @@ fn echoTests(client: *http.Client, port: u16) !void { | ... | @@ -1017,7 +989,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1017 | )); | 989 | )); |
| 1018 | 990 | ||
| 1019 | var redirect_buffer: [1024]u8 = undefined; | 991 | var redirect_buffer: [1024]u8 = undefined; |
| 1020 | var req = try client.open(.POST, uri, .{ | 992 | var req = try client.request(.POST, uri, .{ |
| 1021 | .extra_headers = &.{ | 993 | .extra_headers = &.{ |
| 1022 | .{ .name = "content-type", .value = "text/plain" }, | 994 | .{ .name = "content-type", .value = "text/plain" }, |
| 1023 | }, | 995 | }, |
| ... | @@ -1048,8 +1020,8 @@ fn echoTests(client: *http.Client, port: u16) !void { | ... | @@ -1048,8 +1020,8 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1048 | const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/echo-content#fetch", .{port}); | 1020 | const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/echo-content#fetch", .{port}); |
| 1049 | defer gpa.free(location); | 1021 | defer gpa.free(location); |
| 1050 | 1022 | ||
| 1051 | var body = std.ArrayList(u8).init(gpa); | 1023 | var body: std.ArrayListUnmanaged(u8) = .empty; |
| 1052 | defer body.deinit(); | 1024 | defer body.deinit(gpa); |
| 1053 | 1025 | ||
| 1054 | const res = try client.fetch(.{ | 1026 | const res = try client.fetch(.{ |
| 1055 | .location = .{ .url = location }, | 1027 | .location = .{ .url = location }, |
| ... | @@ -1058,7 +1030,7 @@ fn echoTests(client: *http.Client, port: u16) !void { | ... | @@ -1058,7 +1030,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1058 | .extra_headers = &.{ | 1030 | .extra_headers = &.{ |
| 1059 | .{ .name = "content-type", .value = "text/plain" }, | 1031 | .{ .name = "content-type", .value = "text/plain" }, |
| 1060 | }, | 1032 | }, |
| 1061 | .response_storage = .{ .dynamic = &body }, | 1033 | .response_storage = .{ .allocator = gpa, .list = &body }, |
| 1062 | }); | 1034 | }); |
| 1063 | try expectEqual(.ok, res.status); | 1035 | try expectEqual(.ok, res.status); |
| 1064 | try expectEqualStrings("Hello, World!\n", body.items); | 1036 | try expectEqualStrings("Hello, World!\n", body.items); |
| ... | @@ -1070,7 +1042,7 @@ fn echoTests(client: *http.Client, port: u16) !void { | ... | @@ -1070,7 +1042,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1070 | const uri = try std.Uri.parse(location); | 1042 | const uri = try std.Uri.parse(location); |
| 1071 | 1043 | ||
| 1072 | var redirect_buffer: [1024]u8 = undefined; | 1044 | var redirect_buffer: [1024]u8 = undefined; |
| 1073 | var req = try client.open(.POST, uri, .{ | 1045 | var req = try client.request(.POST, uri, .{ |
| 1074 | .extra_headers = &.{ | 1046 | .extra_headers = &.{ |
| 1075 | .{ .name = "expect", .value = "100-continue" }, | 1047 | .{ .name = "expect", .value = "100-continue" }, |
| 1076 | .{ .name = "content-type", .value = "text/plain" }, | 1048 | .{ .name = "content-type", .value = "text/plain" }, |
| ... | @@ -1101,7 +1073,7 @@ fn echoTests(client: *http.Client, port: u16) !void { | ... | @@ -1101,7 +1073,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1101 | const uri = try std.Uri.parse(location); | 1073 | const uri = try std.Uri.parse(location); |
| 1102 | 1074 | ||
| 1103 | var redirect_buffer: [1024]u8 = undefined; | 1075 | var redirect_buffer: [1024]u8 = undefined; |
| 1104 | var req = try client.open(.POST, uri, .{ | 1076 | var req = try client.request(.POST, uri, .{ |
| 1105 | .extra_headers = &.{ | 1077 | .extra_headers = &.{ |
| 1106 | .{ .name = "content-type", .value = "text/plain" }, | 1078 | .{ .name = "content-type", .value = "text/plain" }, |
| 1107 | .{ .name = "expect", .value = "garbage" }, | 1079 | .{ .name = "expect", .value = "garbage" }, |
| ... | @@ -1222,7 +1194,7 @@ test "redirect to different connection" { | ... | @@ -1222,7 +1194,7 @@ test "redirect to different connection" { |
| 1222 | 1194 | ||
| 1223 | { | 1195 | { |
| 1224 | var redirect_buffer: [666]u8 = undefined; | 1196 | var redirect_buffer: [666]u8 = undefined; |
| 1225 | var req = try client.open(.GET, uri, .{}); | 1197 | var req = try client.request(.GET, uri, .{}); |
| 1226 | defer req.deinit(); | 1198 | defer req.deinit(); |
| 1227 | 1199 | ||
| 1228 | try req.sendBodiless(); | 1200 | try req.sendBodiless(); |
lib/std/net.zig+16| ... | @@ -1898,6 +1898,10 @@ pub const Stream = struct { | ... | @@ -1898,6 +1898,10 @@ pub const Stream = struct { |
| 1898 | 1898 | ||
| 1899 | pub const Error = ReadError; | 1899 | pub const Error = ReadError; |
| 1900 | 1900 | ||
| 1901 | pub fn getStream(r: *const Reader) Stream { | ||
| 1902 | return r.stream; | ||
| 1903 | } | ||
| 1904 | |||
| 1901 | pub fn interface(r: *Reader) std.io.Reader { | 1905 | pub fn interface(r: *Reader) std.io.Reader { |
| 1902 | return .{ | 1906 | return .{ |
| 1903 | .context = r.stream.handle, | 1907 | .context = r.stream.handle, |
| ... | @@ -1968,6 +1972,10 @@ pub const Stream = struct { | ... | @@ -1968,6 +1972,10 @@ pub const Stream = struct { |
| 1968 | pub fn interface(r: *Reader) std.io.Reader { | 1972 | pub fn interface(r: *Reader) std.io.Reader { |
| 1969 | return r.file_reader.interface(); | 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,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 | fn writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize { | 2002 | fn writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize { |
| 1991 | comptime assert(native_os == .windows); | 2003 | comptime assert(native_os == .windows); |
| 1992 | if (data.len == 1 and splat == 0) return 0; | 2004 | if (data.len == 1 and splat == 0) return 0; |
| ... | @@ -2130,6 +2142,10 @@ pub const Stream = struct { | ... | @@ -2130,6 +2142,10 @@ pub const Stream = struct { |
| 2130 | return error.WriteFailed; | 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 |