authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-28 19:19:41-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
logc7040171fb06bc4300547a9f4550346b847fc406
tree68a0528fb8337b613a0c5f67989d80c467695899
parentaef0434c014d85d4f5ab8afa931ea1848c8bbd16

std.http: mostly finish the rewrite


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();
478478
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 {
517517
518 var cwd_cache: ?[]const u8 = null;518 var cwd_cache: ?[]const u8 = null;
519519
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 };
522522
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 }
533533
534 try body_writer.end();534 try body.end();
535}535}
536536
537fn memoizedCwd(arena: Allocator, opt_ptr: *?[]const u8) ![]const u8 {537fn 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};
296296
297pub const ContentEncoding = enum {297pub 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};
306317
307pub const Connection = enum {318pub 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,
335345
336 pub const max_chunk_header_len = 22;346 pub const max_chunk_header_len = 22;
337347
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 }
418420
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/18937472 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 }
486501
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};
722737
738pub 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.
724pub const BodyWriter = struct {785pub const BodyWriter = struct {
725 /// Until the lifetime of `BodyWriter` ends, it is illegal to modify the786 /// 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;
8686
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;
8989
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 {
227227
228pub const Connection = struct {228pub 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 }
278281
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 true341 // 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 };
366370
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 be404 /// 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,
458466
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 };
467475
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, " ");
538546
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 supported548 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 {
556564
557 const trimmed = mem.trim(u8, header_value, " ");565 const trimmed = mem.trim(u8, header_value, " ");
558566
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 };
666674
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};
673720
...@@ -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,
691739
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,
707755
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 }
845901
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/18937903 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 }
851916
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 }
886951
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 be955 /// 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 };
903974
904 /// If handling redirects and the request has no payload, then this975 /// 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 {
9571028
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 }
9651035
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 }
9721038
973 return response;1039 return response;
974 }1040 }
975 }1041 }
9761042
977 pub const RedirectError = error{
978 HttpRedirectLocationOversize,
979 HttpRedirectLocationInvalid,
980 };
981
982 /// This function takes an auxiliary buffer to store the arbitrarily large1043 /// 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 data1044 /// 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 input1045 /// 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 in1055 // 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);
10071068
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 }
10241085
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 {
10421103
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
1263pub 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
1272pub 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;
12061285
...@@ -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}
12691348
1270/// Connect to `tunnel_host:tunnel_port` using the specified proxy with HTTP1349/// 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.
1274pub fn connectTunnel(1353pub 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;
12811360
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;
12881366
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 }
12961380
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();
13111392
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;
13141395
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 }
13191400
1320 if (req.response.status != .ok) break :tunnel error.ConnectionRefused;1401 if (response.head.status != .ok) break :tunnel error.ConnectionRefused;
13211402
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;
13241406
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);
13281408
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 tunnel1411 // 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}
13391416
1340// Prevents a dependency loop in open()1417pub const ConnectError = ConnectTcpError || RequestError;
1341const ConnectErrorPartial = ConnectTcpError || error{ UnsupportedUriScheme, ConnectionRefused };
1342pub const ConnectError = ConnectErrorPartial || RequestError;
13431418
1344/// Connect to `host:port` using the specified protocol. This will reuse a1419/// 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 to1422/// 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 }
13671443
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 }
13741450
1375 // fall back to using the proxy as a normal http proxy1451 // 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}
13851456
1386/// TODO collapse each error set into its own meta error code, and store1457pub const RequestError = ConnectTcpError || error{
1387/// the underlying error code as a field on Request1458 UnsupportedUriScheme,
1388pub 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 };
13941463
1395pub const RequestOptions = struct {1464pub 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.
1443pub fn open(1512pub 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}
15001574
1501pub const FetchOptions = struct {1575pub 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,
15041581 /// 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,
15091583
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 };
15311605
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};
15391613
...@@ -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};
15431617
1618pub 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.
1547pub fn fetch(client: *Client, options: FetchOptions) !FetchResult {1627pub 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 orelse1632 const method: http.Method = options.method orelse
1555 if (options.payload != null) .POST else .GET;1633 if (options.payload != null) .POST else .GET;
15561634
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 orelse1637
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();
15671646
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 }
15761656
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);
15791660
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 }
16021662
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}
16071698
1608pub fn sameParentDomain(parent_host: []const u8, child_host: []const u8) bool {1699pub 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,
5757
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,
7668
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 };
131122
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 {
156147
157 const trimmed = mem.trim(u8, header_value, " ");148 const trimmed = mem.trim(u8, header_value, " ");
158149
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, " ");
183174
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 supported177 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";
238229
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));
243232
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 {
252241
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 };
258246
...@@ -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 }
534522
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 };
238238
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);
6262
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();
6666
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);
265265
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);
333333
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" {
563563
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();
568568
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" {
586586
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();
591591
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" {
608608
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();
613613
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" {
632632
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();
637637
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" {
655655
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();
660660
661 try req.sendBodiless();661 try req.sendBodiless();
662 try req.receiveHead(&redirect_buffer);662 var response = try req.receiveHead(&redirect_buffer);
663663
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);
666666
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 }
671671
672 // connection has been kept alive672 // 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" {
679679
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();
686686
687 try req.sendBodiless();687 try req.sendBodiless();
688 try req.receiveHead(&redirect_buffer);688 var response = try req.receiveHead(&redirect_buffer);
689689
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);
692692
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 }
696696
697 // connection has been closed697 // connection has been closed
...@@ -704,7 +704,7 @@ test "general client/server API coverage" {...@@ -704,7 +704,7 @@ test "general client/server API coverage" {
704704
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();
713713
714 try req.sendBodiless();714 try req.sendBodiless();
715 try req.receiveHead(&redirect_buffer);715 var response = try req.receiveHead(&redirect_buffer);
716716
717 try std.testing.expectEqual(.ok, req.response.status);717 try std.testing.expectEqual(.ok, response.head.status);
718718
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);
721721
722 try expectEqualStrings("", body);722 try expectEqualStrings("", body);
723723
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" {
747747
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();
752752
753 try req.sendBodiless();753 try req.sendBodiless();
754 try req.receiveHead(&redirect_buffer);754 var response = try req.receiveHead(&redirect_buffer);
755755
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);
758758
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" {
769769
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();
774774
775 try req.sendBodiless();775 try req.sendBodiless();
776 try req.receiveHead(&redirect_buffer);776 var response = try req.receiveHead(&redirect_buffer);
777777
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);
780780
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" {
791791
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();
796796
797 try req.sendBodiless();797 try req.sendBodiless();
798 try req.receiveHead(&redirect_buffer);798 var response = try req.receiveHead(&redirect_buffer);
799799
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);
802802
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" {
813813
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();
818818
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 }
825827
826 { // redirect to encoded url828 { // redirect to encoded url
...@@ -830,13 +832,13 @@ test "general client/server API coverage" {...@@ -830,13 +832,13 @@ test "general client/server API coverage" {
830832
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();
835837
836 try req.sendBodiless();838 try req.sendBodiless();
837 try req.receiveHead(&redirect_buffer);839 var response = try req.receiveHead(&redirect_buffer);
838840
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);
841843
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" {
852854
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();
857859
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 alive869 // 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);
869871
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();
901873
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();
951923
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);
984956
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 ));
1018990
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);
10501022
1051 var body = std.ArrayList(u8).init(gpa);1023 var body: std.ArrayListUnmanaged(u8) = .empty;
1052 defer body.deinit();1024 defer body.deinit(gpa);
10531025
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);
10711043
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);
11021074
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" {
12221194
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();
12271199
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 {
18981898
1899 pub const Error = ReadError;1899 pub const Error = ReadError;
19001900
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 };
19731981
...@@ -1987,6 +1995,10 @@ pub const Stream = struct {...@@ -1987,6 +1995,10 @@ pub const Stream = struct {
1987 };1995 };
1988 }1996 }
19891997
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 };
21352151