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 {
476476 defer arena_instance.deinit();
477477 const arena = arena_instance.allocator();
478478
479 var body_writer = try request.respondStreaming(.{
479 var body = try request.respondStreaming(.{
480480 .respond_options = .{
481481 .extra_headers = &.{
482482 .{ .name = "content-type", .value = "application/x-tar" },
......@@ -517,7 +517,7 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void {
517517
518518 var cwd_cache: ?[]const u8 = null;
519519
520 var response_writer = body_writer.interface().unbuffered();
520 var response_writer = body.writer().unbuffered();
521521 var archiver: std.tar.Writer = .{ .underlying_writer = &response_writer };
522522
523523 for (deduped_paths) |joined_path| {
......@@ -531,7 +531,7 @@ fn serveSourcesTar(ws: *WebServer, request: *std.http.Server.Request) !void {
531531 try archiver.writeFile(joined_path.sub_path, file, try file.stat());
532532 }
533533
534 try body_writer.end();
534 try body.end();
535535}
536536
537537fn memoizedCwd(arena: Allocator, opt_ptr: *?[]const u8) ![]const u8 {
lib/std/http.zig+117-56
......@@ -295,13 +295,24 @@ pub const TransferEncoding = enum {
295295};
296296
297297pub const ContentEncoding = enum {
298 identity,
299 compress,
300 @"x-compress",
301 deflate,
302 gzip,
303 @"x-gzip",
304298 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 }
305316};
306317
307318pub const Connection = enum {
......@@ -331,18 +342,9 @@ pub const Reader = struct {
331342 body_err: ?BodyError = null,
332343 /// Stolen from `in`.
333344 head_buffer: []u8 = &.{},
334 compression: Compression,
335345
336346 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
346348 pub const RemainingChunkLen = enum(u64) {
347349 head = 0,
348350 n = 1,
......@@ -416,19 +418,19 @@ pub const Reader = struct {
416418 }
417419 }
418420
421 /// If compressed body has been negotiated this will return compressed bytes.
422 ///
419423 /// Asserts only called once and after `receiveHead`.
420 pub fn interface(
421 reader: *Reader,
422 transfer_encoding: TransferEncoding,
423 content_length: ?u64,
424 content_encoding: ContentEncoding,
425 ) std.io.Reader {
424 ///
425 /// See also:
426 /// * `interfaceDecompressing`
427 pub fn bodyReader(reader: *Reader, transfer_encoding: TransferEncoding, content_length: ?u64) std.io.Reader {
426428 assert(reader.state == .received_head);
427429 reader.state = .receiving_body;
428 reader.transfer_br.unbuffered_reader = switch (transfer_encoding) {
429 .chunked => r: {
430 return switch (transfer_encoding) {
431 .chunked => {
430432 reader.body_state = .{ .remaining_chunk_len = .head };
431 break :r .{
433 return .{
432434 .context = reader,
433435 .vtable = &.{
434436 .read = &chunkedRead,
......@@ -437,10 +439,10 @@ pub const Reader = struct {
437439 },
438440 };
439441 },
440 .none => r: {
442 .none => {
441443 if (content_length) |len| {
442444 reader.body_state = .{ .remaining_content_length = len };
443 break :r .{
445 return .{
444446 .context = reader,
445447 .vtable = &.{
446448 .read = &contentLengthRead,
......@@ -448,40 +450,53 @@ pub const Reader = struct {
448450 .discard = &contentLengthDiscard,
449451 },
450452 };
451 } else switch (content_encoding) {
452 .identity => {
453 reader.compression = .none;
454 return reader.in.reader();
455 },
456 .deflate => {
457 reader.compression = .{ .deflate = .init(reader.in) };
458 return reader.compression.deflate.reader();
459 },
460 .gzip, .@"x-gzip" => {
461 reader.compression = .{ .gzip = .init(reader.in) };
462 return reader.compression.gzip.reader();
463 },
464 .compress, .@"x-compress" => unreachable,
465 .zstd => unreachable, // https://github.com/ziglang/zig/issues/18937
453 } else {
454 return reader.in.reader();
466455 }
467456 },
468457 };
469 switch (content_encoding) {
470 .identity => {
471 reader.compression = .none;
472 return reader.transfer_br.unbuffered_reader;
473 },
474 .deflate => {
475 reader.compression = .{ .deflate = .init(&reader.transfer_br) };
476 return reader.compression.deflate.reader();
477 },
478 .gzip, .@"x-gzip" => {
479 reader.compression = .{ .gzip = .init(&reader.transfer_br) };
480 return reader.compression.gzip.reader();
481 },
482 .compress, .@"x-compress" => unreachable,
483 .zstd => unreachable, // https://github.com/ziglang/zig/issues/18937
458 }
459
460 /// If compressed body has been negotiated this will return decompressed bytes.
461 ///
462 /// Asserts only called once and after `receiveHead`.
463 ///
464 /// See also:
465 /// * `interface`
466 pub fn bodyReaderDecompressing(
467 reader: *Reader,
468 transfer_encoding: TransferEncoding,
469 content_length: ?u64,
470 content_encoding: ContentEncoding,
471 decompressor: *Decompressor,
472 decompression_buffer: []u8,
473 ) std.io.Reader {
474 if (transfer_encoding == .none and content_length == null) {
475 assert(reader.state == .received_head);
476 reader.state = .receiving_body;
477 switch (content_encoding) {
478 .identity => {
479 return reader.in.reader();
480 },
481 .deflate => {
482 decompressor.compression = .{ .deflate = .init(reader.in) };
483 return decompressor.compression.deflate.reader();
484 },
485 .gzip => {
486 decompressor.compression = .{ .gzip = .init(reader.in) };
487 return decompressor.compression.gzip.reader();
488 },
489 .zstd => {
490 decompressor.compression = .{ .zstd = .init(reader.in, .{
491 .window_buffer = decompression_buffer,
492 }) };
493 return decompressor.compression.zstd.reader();
494 },
495 .compress => unreachable,
496 }
484497 }
498 const transfer_reader = bodyReader(reader, transfer_encoding, content_length);
499 return decompressor.reader(transfer_reader, decompression_buffer, content_encoding);
485500 }
486501
487502 fn contentLengthRead(
......@@ -720,6 +735,52 @@ pub const Reader = struct {
720735 }
721736};
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
723784/// Request or response body.
724785pub const BodyWriter = struct {
725786 /// 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 {
8585 if (connection.port != criteria.port) continue;
8686
8787 // 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
9090 pool.acquireUnsafe(connection);
9191 return connection;
......@@ -227,7 +227,8 @@ pub const Protocol = enum {
227227
228228pub const Connection = struct {
229229 client: *Client,
230 stream: net.Stream,
230 stream_writer: net.Stream.Writer,
231 stream_reader: net.Stream.Reader,
231232 /// HTTP protocol from client to server.
232233 /// This either goes directly to `stream`, or to a TLS client.
233234 writer: std.io.BufferedWriter,
......@@ -249,7 +250,7 @@ pub const Connection = struct {
249250 remote_host: []const u8,
250251 port: u16,
251252 stream: net.Stream,
252 ) error{OutOfMemory}!*Connection {
253 ) error{OutOfMemory}!*Plain {
253254 const gpa = client.allocator;
254255 const alloc_len = allocLen(client, remote_host.len);
255256 const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len);
......@@ -263,17 +264,19 @@ pub const Connection = struct {
263264 plain.* = .{
264265 .connection = .{
265266 .client = client,
266 .stream = stream,
267 .writer = stream.writer().buffered(socket_write_buffer),
267 .stream_writer = stream.writer(),
268 .stream_reader = stream.reader(),
269 .writer = plain.connection.stream_writer.interface().buffered(socket_write_buffer),
268270 .pool_node = .{},
269271 .port = port,
272 .host_len = @intCast(remote_host.len),
270273 .proxied = false,
271274 .closing = false,
272275 .protocol = .plain,
273276 },
274 .reader = undefined,
277 .reader = plain.connection.stream_reader.interface().buffered(socket_read_buffer),
275278 };
276 plain.reader.init(stream.reader(), socket_read_buffer);
279 return plain;
277280 }
278281
279282 fn destroy(plain: *Plain) void {
......@@ -321,19 +324,20 @@ pub const Connection = struct {
321324 tls.* = .{
322325 .connection = .{
323326 .client = client,
324 .stream = stream,
327 .stream_writer = stream.writer(),
328 .stream_reader = stream.reader(),
325329 .writer = tls.client.writer().buffered(socket_write_buffer),
326330 .pool_node = .{},
327331 .port = port,
332 .host_len = @intCast(remote_host.len),
328333 .proxied = false,
329334 .closing = false,
330335 .protocol = .tls,
331336 },
332 .writer = stream.writer().buffered(tls_write_buffer),
333 .reader = undefined,
337 .writer = tls.connection.stream_writer.interface().buffered(tls_write_buffer),
338 .reader = tls.connection.stream_reader.interface().buffered(tls_read_buffer),
334339 .client = undefined,
335340 };
336 tls.reader.init(stream.reader(), tls_read_buffer);
337341 // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true
338342 tls.client.init(&tls.reader, &tls.writer, .{
339343 .host = .{ .explicit = remote_host },
......@@ -364,6 +368,10 @@ pub const Connection = struct {
364368 }
365369 };
366370
371 fn getStream(c: *Connection) net.Stream {
372 return c.stream_reader.getStream();
373 }
374
367375 fn host(c: *Connection) []u8 {
368376 return switch (c.protocol) {
369377 .tls => {
......@@ -396,7 +404,7 @@ pub const Connection = struct {
396404 /// If this is called without calling `flush` or `end`, data will be
397405 /// dropped unsent.
398406 pub fn destroy(c: *Connection) void {
399 c.stream.close();
407 c.getStream().close();
400408 switch (c.protocol) {
401409 .tls => {
402410 if (disable_tls) unreachable;
......@@ -457,12 +465,12 @@ pub const Response = struct {
457465 content_encoding: http.ContentEncoding = .identity,
458466
459467 pub const ParseError = error{
460 HttpHeadersInvalid,
468 HttpConnectionHeaderUnsupported,
469 HttpContentEncodingUnsupported,
461470 HttpHeaderContinuationsUnsupported,
471 HttpHeadersInvalid,
462472 HttpTransferEncodingUnsupported,
463 HttpConnectionHeaderUnsupported,
464473 InvalidContentLength,
465 CompressionUnsupported,
466474 };
467475
468476 pub fn parse(bytes: []const u8) ParseError!Head {
......@@ -536,7 +544,7 @@ pub const Response = struct {
536544 if (next) |second| {
537545 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| {
540548 if (res.content_encoding != .identity) return error.HttpHeadersInvalid; // double compression is not supported
541549 res.content_encoding = transfer;
542550 } else {
......@@ -556,10 +564,10 @@ pub const Response = struct {
556564
557565 const trimmed = mem.trim(u8, header_value, " ");
558566
559 if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
567 if (http.ContentEncoding.fromString(trimmed)) |ce| {
560568 res.content_encoding = ce;
561569 } else {
562 return error.HttpTransferEncodingUnsupported;
570 return error.HttpContentEncodingUnsupported;
563571 }
564572 }
565573 }
......@@ -664,10 +672,49 @@ pub const Response = struct {
664672 }
665673 };
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 ///
667680 /// Asserts that this function is only called once.
681 ///
682 /// See also:
683 /// * `readerDecompressing`
668684 pub fn reader(response: *Response) std.io.Reader {
669685 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;
671718 }
672719};
673720
......@@ -688,6 +735,7 @@ pub const Request = struct {
688735 version: http.Version = .@"HTTP/1.1",
689736 transfer_encoding: TransferEncoding,
690737 redirect_behavior: RedirectBehavior,
738 accept_encoding: @TypeOf(default_accept_encoding) = default_accept_encoding,
691739
692740 /// Whether the request should handle a 100-continue response before sending the request body.
693741 handle_continue: bool,
......@@ -705,6 +753,14 @@ pub const Request = struct {
705753 /// Externally-owned; must outlive the Request.
706754 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
708764 pub const TransferEncoding = union(enum) {
709765 content_length: u64,
710766 chunked: void,
......@@ -844,9 +900,18 @@ pub const Request = struct {
844900 }
845901
846902 if (try emitOverridableHeader("accept-encoding: ", r.headers.accept_encoding, w)) {
847 // https://github.com/ziglang/zig/issues/18937
848 //try w.writeAll("accept-encoding: gzip, deflate, zstd\r\n");
849 try w.writeAll("accept-encoding: gzip, deflate\r\n");
903 try w.writeAll("accept-encoding: ");
904 for (r.accept_encoding, 0..) |enabled, i| {
905 if (!enabled) continue;
906 const tag: http.ContentEncoding = @enumFromInt(i);
907 if (tag == .identity) continue;
908 const tag_name = @tagName(tag);
909 try w.ensureUnusedCapacity(tag_name.len + 2);
910 try w.writeAll(tag_name);
911 try w.writeAll(", ");
912 }
913 w.undo(2);
914 try w.writeAll("\r\n");
850915 }
851916
852917 switch (r.transfer_encoding) {
......@@ -884,7 +949,7 @@ pub const Request = struct {
884949 try w.writeAll("\r\n");
885950 }
886951
887 pub const ReceiveHeadError = std.io.Writer.Error || http.Reader.HeadError || error{
952 pub const ReceiveHeadError = http.Reader.HeadError || ConnectError || error{
888953 /// Server sent headers that did not conform to the HTTP protocol.
889954 ///
890955 /// To find out more detailed diagnostics, `http.Reader.head_buffer` can be
......@@ -897,8 +962,14 @@ pub const Request = struct {
897962 HttpRedirectLocationMissing,
898963 HttpRedirectLocationOversize,
899964 HttpRedirectLocationInvalid,
900 CompressionInitializationFailed,
901 CompressionUnsupported,
965 HttpContentEncodingUnsupported,
966 HttpChunkInvalid,
967 HttpHeadersOversize,
968 UnsupportedUriScheme,
969
970 /// Sending the request failed. Error code can be found on the
971 /// `Connection` object.
972 WriteFailed,
902973 };
903974
904975 /// If handling redirects and the request has no payload, then this
......@@ -957,44 +1028,35 @@ pub const Request = struct {
9571028
9581029 if (head.status.class() == .redirect and r.redirect_behavior != .unhandled) {
9591030 if (r.redirect_behavior == .not_allowed) return error.TooManyHttpRedirects;
960 const location = head.location orelse return error.HttpRedirectLocationMissing;
961 try r.redirect(location, &aux_buf);
1031 try r.redirect(head, &aux_buf);
9621032 try r.sendBodiless();
9631033 continue;
9641034 }
9651035
966 switch (head.content_encoding) {
967 .identity, .deflate, .gzip, .@"x-gzip" => {},
968 .compress, .@"x-compress" => return error.CompressionUnsupported,
969 // https://github.com/ziglang/zig/issues/18937
970 .zstd => return error.CompressionUnsupported,
971 }
1036 if (!r.accept_encoding[@intFromEnum(head.content_encoding)])
1037 return error.HttpContentEncodingUnsupported;
9721038
9731039 return response;
9741040 }
9751041 }
9761042
977 pub const RedirectError = error{
978 HttpRedirectLocationOversize,
979 HttpRedirectLocationInvalid,
980 };
981
9821043 /// This function takes an auxiliary buffer to store the arbitrarily large
9831044 /// URI which may need to be merged with the previous URI, and that data
9841045 /// needs to survive across different connections, which is where the input
9851046 /// buffer lives.
9861047 ///
9871048 /// `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;
9891051 if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize;
9901052 const location = aux_buf.*[0..new_location.len];
9911053 @memcpy(location, new_location);
9921054 {
9931055 // Skip the body of the redirect response to leave the connection in
9941056 // 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);
9961058 _ = reader.discardRemaining() catch |err| switch (err) {
997 error.ReadFailed => return r.reader.err.?,
1059 error.ReadFailed => return r.reader.body_err.?,
9981060 };
9991061 }
10001062 const new_uri = r.uri.resolveInPlace(location.len, aux_buf) catch |err| switch (err) {
......@@ -1003,7 +1065,6 @@ pub const Request = struct {
10031065 error.InvalidPort => return error.HttpRedirectLocationInvalid,
10041066 error.NoSpaceLeft => return error.HttpRedirectLocationOversize,
10051067 };
1006 const resolved_len = location.len + (aux_buf.*.ptr - location.ptr);
10071068
10081069 const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme;
10091070 const old_connection = r.connection.?;
......@@ -1022,7 +1083,7 @@ pub const Request = struct {
10221083 r.privileged_headers = &.{};
10231084 }
10241085
1025 if (switch (r.response.status) {
1086 if (switch (head.status) {
10261087 .see_other => true,
10271088 .moved_permanently, .found => r.method == .POST,
10281089 else => false,
......@@ -1042,7 +1103,6 @@ pub const Request = struct {
10421103
10431104 const new_connection = try r.client.connect(new_host, uriPort(new_uri, protocol), protocol);
10441105 r.uri = new_uri;
1045 r.stolen_bytes_len = resolved_len;
10461106 r.connection = new_connection;
10471107 r.redirect_behavior.subtractOne();
10481108 }
......@@ -1054,9 +1114,8 @@ pub const Request = struct {
10541114 .default => return true,
10551115 .omit => return false,
10561116 .override => |x| {
1057 try bw.writeAll(prefix);
1058 try bw.writeAll(x);
1059 try bw.writeAll("\r\n");
1117 var vecs: [3][]const u8 = .{ prefix, x, "\r\n" };
1118 try bw.writeVecAll(&vecs);
10601119 return false;
10611120 },
10621121 }
......@@ -1198,9 +1257,29 @@ pub fn connectTcp(
11981257 port: u16,
11991258 protocol: Protocol,
12001259) 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
12011280 if (client.connection_pool.findConnection(.{
1202 .host = host,
1203 .port = port,
1281 .host = proxied_host,
1282 .port = proxied_port,
12041283 .protocol = protocol,
12051284 })) |conn| return conn;
12061285
......@@ -1220,12 +1299,12 @@ pub fn connectTcp(
12201299 switch (protocol) {
12211300 .tls => {
12221301 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);
12241303 client.connection_pool.addUsed(&tc.connection);
12251304 return &tc.connection;
12261305 },
12271306 .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);
12291308 client.connection_pool.addUsed(&pc.connection);
12301309 return &pc.connection;
12311310 },
......@@ -1267,69 +1346,67 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
12671346 return &conn.data;
12681347}
12691348
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
12711350/// CONNECT. This will reuse a connection if one is already open.
12721351///
12731352/// This function is threadsafe.
1274pub fn connectTunnel(
1353pub fn connectProxied(
12751354 client: *Client,
12761355 proxy: *Proxy,
1277 tunnel_host: []const u8,
1278 tunnel_port: u16,
1356 proxied_host: []const u8,
1357 proxied_port: u16,
12791358) !*Connection {
12801359 if (!proxy.supports_connect) return error.TunnelNotSupported;
12811360
12821361 if (client.connection_pool.findConnection(.{
1283 .host = tunnel_host,
1284 .port = tunnel_port,
1362 .host = proxied_host,
1363 .port = proxied_port,
12851364 .protocol = proxy.protocol,
1286 })) |node|
1287 return node;
1365 })) |node| return node;
12881366
12891367 var maybe_valid = false;
12901368 (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 });
12921376 errdefer {
1293 conn.closing = true;
1294 client.connection_pool.release(conn);
1377 connection.closing = true;
1378 client.connection_pool.release(connection);
12951379 }
12961380
1297 var buffer: [8096]u8 = undefined;
1298 var req = client.open(.CONNECT, .{
1381 var req = client.request(.CONNECT, .{
12991382 .scheme = "http",
1300 .host = .{ .raw = tunnel_host },
1301 .port = tunnel_port,
1383 .host = .{ .raw = proxied_host },
1384 .port = proxied_port,
13021385 }, .{
13031386 .redirect_behavior = .unhandled,
1304 .connection = conn,
1305 .server_header_buffer = &buffer,
1387 .connection = connection,
13061388 }) catch |err| {
1307 std.log.debug("err {}", .{err});
13081389 break :tunnel err;
13091390 };
13101391 defer req.deinit();
13111392
1312 req.send() catch |err| break :tunnel err;
1313 req.wait() catch |err| break :tunnel err;
1393 req.sendBodiless() catch |err| break :tunnel err;
1394 const response = req.receiveHead(&.{}) catch |err| break :tunnel err;
13141395
1315 if (req.response.status.class() == .server_error) {
1396 if (response.head.status.class() == .server_error) {
13161397 maybe_valid = true;
13171398 break :tunnel error.ServerError;
13181399 }
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.
13231405 req.connection = null;
13241406
1325 client.allocator.free(conn.host);
1326 conn.host = try client.allocator.dupe(u8, tunnel_host);
1327 errdefer client.allocator.free(conn.host);
1407 connection.closing = false;
13281408
1329 conn.port = tunnel_port;
1330 conn.closing = false;
1331
1332 return conn;
1409 return connection;
13331410 }) catch {
13341411 // something went wrong with the tunnel
13351412 proxy.supports_connect = maybe_valid;
......@@ -1337,12 +1414,11 @@ pub fn connectTunnel(
13371414 };
13381415}
13391416
1340// Prevents a dependency loop in open()
1341const ConnectErrorPartial = ConnectTcpError || error{ UnsupportedUriScheme, ConnectionRefused };
1342pub const ConnectError = ConnectErrorPartial || RequestError;
1417pub const ConnectError = ConnectTcpError || RequestError;
13431418
13441419/// Connect to `host:port` using the specified protocol. This will reuse a
13451420/// connection if one is already open.
1421///
13461422/// If a proxy is configured for the client, then the proxy will be used to
13471423/// connect to the host.
13481424///
......@@ -1366,31 +1442,24 @@ pub fn connect(
13661442 }
13671443
13681444 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) {
13701446 error.TunnelNotSupported => break :tunnel,
13711447 else => |e| return e,
13721448 };
13731449 }
13741450
13751451 // fall back to using the proxy as a normal http proxy
1376 const conn = try client.connectTcp(proxy.host, proxy.port, proxy.protocol);
1377 errdefer {
1378 conn.closing = true;
1379 client.connection_pool.release(conn);
1380 }
1381
1382 conn.proxied = true;
1383 return conn;
1452 const connection = try client.connectTcp(proxy.host, proxy.port, proxy.protocol);
1453 connection.proxied = true;
1454 return connection;
13841455}
13851456
1386/// TODO collapse each error set into its own meta error code, and store
1387/// the underlying error code as a field on Request
1388pub const RequestError = ConnectTcpError || ConnectErrorPartial || std.io.Writer.Error || std.fmt.ParseIntError ||
1389 error{
1390 UnsupportedUriScheme,
1391 UriMissingHost,
1392 CertificateBundleLoadFailure,
1393 };
1457pub const RequestError = ConnectTcpError || error{
1458 UnsupportedUriScheme,
1459 UriMissingHost,
1460 UriHostTooLong,
1461 CertificateBundleLoadFailure,
1462};
13941463
13951464pub const RequestOptions = struct {
13961465 version: http.Version = .@"HTTP/1.1",
......@@ -1440,7 +1509,7 @@ fn uriPort(uri: Uri, protocol: Protocol) u16 {
14401509/// This function is threadsafe.
14411510///
14421511/// Asserts that "\r\n" does not occur in any header name or value.
1443pub fn open(
1512pub fn request(
14441513 client: *Client,
14451514 method: http.Method,
14461515 uri: Uri,
......@@ -1486,6 +1555,11 @@ pub fn open(
14861555 .uri = uri,
14871556 .client = client,
14881557 .connection = connection,
1558 .reader = .{
1559 .in = connection.reader(),
1560 .state = .ready,
1561 .body_state = undefined,
1562 },
14891563 .keep_alive = options.keep_alive,
14901564 .method = method,
14911565 .version = options.version,
......@@ -1499,13 +1573,13 @@ pub fn open(
14991573}
15001574
15011575pub 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,
15031580 redirect_behavior: ?Request.RedirectBehavior = null,
1504
1505 /// If the server sends a body, it will be appended to this ArrayList.
1506 /// `max_append_size` provides an upper limit for how much they can grow.
1507 response_storage: ResponseStorage = .ignore,
1508 max_append_size: ?usize = null,
1581 /// If the server sends a body, it will be stored here.
1582 response_storage: ?ResponseStorage = null,
15091583
15101584 location: Location,
15111585 method: ?http.Method = null,
......@@ -1529,11 +1603,11 @@ pub const FetchOptions = struct {
15291603 uri: Uri,
15301604 };
15311605
1532 pub const ResponseStorage = union(enum) {
1533 ignore,
1534 /// Only the existing capacity will be used.
1535 static: *std.ArrayListUnmanaged(u8),
1536 dynamic: *std.ArrayList(u8),
1606 pub const ResponseStorage = struct {
1607 list: *std.ArrayListUnmanaged(u8),
1608 /// If null then only the existing capacity will be used.
1609 allocator: ?Allocator = null,
1610 append_limit: std.io.Reader.Limit = .unlimited,
15371611 };
15381612};
15391613
......@@ -1541,23 +1615,28 @@ pub const FetchResult = struct {
15411615 status: http.Status,
15421616};
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
15441624/// Perform a one-shot HTTP request with the provided options.
15451625///
15461626/// This function is threadsafe.
1547pub fn fetch(client: *Client, options: FetchOptions) !FetchResult {
1627pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
15481628 const uri = switch (options.location) {
15491629 .url => |u| try Uri.parse(u),
15501630 .uri => |u| u,
15511631 };
1552 var server_header_buffer: [16 * 1024]u8 = undefined;
1553
15541632 const method: http.Method = options.method orelse
15551633 if (options.payload != null) .POST else .GET;
15561634
1557 var req = try open(client, method, uri, .{
1558 .server_header_buffer = options.server_header_buffer orelse &server_header_buffer,
1559 .redirect_behavior = options.redirect_behavior orelse
1560 if (options.payload == null) @enumFromInt(3) else .unhandled,
1635 const redirect_behavior: Request.RedirectBehavior = options.redirect_behavior orelse
1636 if (options.payload == null) @enumFromInt(3) else .unhandled;
1637
1638 var req = try request(client, method, uri, .{
1639 .redirect_behavior = redirect_behavior,
15611640 .headers = options.headers,
15621641 .extra_headers = options.extra_headers,
15631642 .privileged_headers = options.privileged_headers,
......@@ -1565,44 +1644,56 @@ pub fn fetch(client: *Client, options: FetchOptions) !FetchResult {
15651644 });
15661645 defer req.deinit();
15671646
1568 if (options.payload) |payload| req.transfer_encoding = .{ .content_length = payload.len };
1569
1570 try req.send();
1571
15721647 if (options.payload) |payload| {
1573 var w = req.writer().unbuffered();
1574 try w.writeAll(payload);
1648 req.transfer_encoding = .{ .content_length = payload.len };
1649 var body = try req.sendBody();
1650 var bw = body.writer().unbuffered();
1651 try bw.writeAll(payload);
1652 try body.end();
1653 } else {
1654 try req.sendBodiless();
15751655 }
15761656
1577 try req.finish();
1578 try req.wait();
1657 const redirect_buffer: []u8 = if (redirect_behavior == .unhandled) &.{} else options.redirect_buffer orelse
1658 try client.allocator.alloc(u8, 8 * 1024);
1659 defer if (options.redirect_buffer == null) client.allocator.free(redirect_buffer);
15791660
1580 switch (options.response_storage) {
1581 .ignore => {
1582 // Take advantage of request internals to discard the response body
1583 // and make the connection available for another request.
1584 req.response.skip = true;
1585 assert(try req.transferRead(&.{}) == 0); // No buffer is necessary when skipping.
1586 },
1587 .dynamic => |list| {
1588 const max_append_size = options.max_append_size orelse 2 * 1024 * 1024;
1589 try req.reader().readAllArrayList(list, max_append_size);
1590 },
1591 .static => |list| {
1592 const buf = b: {
1593 const buf = list.unusedCapacitySlice();
1594 if (options.max_append_size) |len| {
1595 if (len < buf.len) break :b buf[0..len];
1596 }
1597 break :b buf;
1598 };
1599 list.items.len += try req.reader().readAll(buf);
1600 },
1601 }
1661 var response = try req.receiveHead(redirect_buffer);
16021662
1603 return .{
1604 .status = req.response.status,
1663 const storage = options.response_storage orelse {
1664 var reader = response.reader();
1665 _ = reader.discardRemaining() catch |err| switch (err) {
1666 error.ReadFailed => return response.bodyErr().?,
1667 };
1668 return .{ .status = response.head.status };
16051669 };
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 };
16061697}
16071698
16081699pub 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 {
5555 /// `receiveHead`.
5656 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
6558 pub const Head = struct {
6659 method: http.Method,
6760 target: []const u8,
......@@ -72,7 +65,6 @@ pub const Request = struct {
7265 transfer_encoding: http.TransferEncoding,
7366 transfer_compression: http.ContentEncoding,
7467 keep_alive: bool,
75 compression: Compression,
7668
7769 pub const ParseError = error{
7870 UnknownHttpMethod,
......@@ -126,7 +118,6 @@ pub const Request = struct {
126118 .@"HTTP/1.0" => false,
127119 .@"HTTP/1.1" => true,
128120 },
129 .compression = .none,
130121 };
131122
132123 while (it.next()) |line| {
......@@ -156,7 +147,7 @@ pub const Request = struct {
156147
157148 const trimmed = mem.trim(u8, header_value, " ");
158149
159 if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
150 if (http.ContentEncoding.fromString(trimmed)) |ce| {
160151 head.transfer_compression = ce;
161152 } else {
162153 return error.HttpTransferEncodingUnsupported;
......@@ -181,7 +172,7 @@ pub const Request = struct {
181172 if (next) |second| {
182173 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| {
185176 if (head.transfer_compression != .identity)
186177 return error.HttpHeadersInvalid; // double compression is not supported
187178 head.transfer_compression = transfer;
......@@ -236,10 +227,8 @@ pub const Request = struct {
236227 "TRansfer-encoding:\tdeflate, chunked \r\n" ++
237228 "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);
241230 var br: std.io.BufferedReader = undefined;
242 br.initFixed(&read_buffer);
231 br.initFixed(@constCast(request_bytes));
243232
244233 var server: Server = .{
245234 .reader = .{
......@@ -252,7 +241,6 @@ pub const Request = struct {
252241
253242 var request: Request = .{
254243 .server = &server,
255 .trailers_len = 0,
256244 .head = undefined,
257245 };
258246
......@@ -529,7 +517,7 @@ pub const Request = struct {
529517 return error.HttpExpectationFailed;
530518 }
531519 }
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);
533521 }
534522
535523 /// 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
236236 },
237237 };
238238
239 var bw = ws.body_writer.interface().unbuffered();
239 var bw = ws.body_writer.writer().unbuffered();
240240 try bw.writeAll(header);
241241 for (message) |iovec| try bw.writeAll(iovec.base[0..iovec.len]);
242242 try bw.flush();
lib/std/http/test.zig+49-77
......@@ -61,7 +61,7 @@ test "trailers" {
6161 const uri = try std.Uri.parse(location);
6262
6363 {
64 var req = try client.open(.GET, uri, .{});
64 var req = try client.request(.GET, uri, .{});
6565 defer req.deinit();
6666
6767 try req.sendBodiless();
......@@ -263,7 +263,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
263263 var connection_bw = stream_writer.interface().buffered(&send_buffer);
264264 var server = http.Server.init(&connection_br, &connection_bw);
265265
266 try expectEqual(.ready, server.state);
266 try expectEqual(.ready, server.reader.state);
267267 var request = try server.receiveHead();
268268 try expectEqualStrings(request.head.target, "/foo");
269269 var response = try request.respondStreaming(.{
......@@ -278,7 +278,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
278278 }
279279 try expectEqual(7390, bw.count);
280280 try response.end();
281 try expectEqual(.closing, server.state);
281 try expectEqual(.closing, server.reader.state);
282282 }
283283 }
284284 });
......@@ -331,7 +331,7 @@ test "receiving arbitrary http headers from the client" {
331331 var connection_bw = stream_writer.interface().buffered(&send_buffer);
332332 var server = http.Server.init(&connection_br, &connection_bw);
333333
334 try expectEqual(.ready, server.state);
334 try expectEqual(.ready, server.reader.state);
335335 var request = try server.receiveHead();
336336 try expectEqualStrings("/bar", request.head.target);
337337 var it = request.iterateHeaders();
......@@ -563,7 +563,7 @@ test "general client/server API coverage" {
563563
564564 log.info("{s}", .{location});
565565 var redirect_buffer: [1024]u8 = undefined;
566 var req = try client.open(.GET, uri, .{});
566 var req = try client.request(.GET, uri, .{});
567567 defer req.deinit();
568568
569569 try req.sendBodiless();
......@@ -586,7 +586,7 @@ test "general client/server API coverage" {
586586
587587 log.info("{s}", .{location});
588588 var redirect_buffer: [1024]u8 = undefined;
589 var req = try client.open(.GET, uri, .{});
589 var req = try client.request(.GET, uri, .{});
590590 defer req.deinit();
591591
592592 try req.sendBodiless();
......@@ -608,7 +608,7 @@ test "general client/server API coverage" {
608608
609609 log.info("{s}", .{location});
610610 var redirect_buffer: [1024]u8 = undefined;
611 var req = try client.open(.HEAD, uri, .{});
611 var req = try client.request(.HEAD, uri, .{});
612612 defer req.deinit();
613613
614614 try req.sendBodiless();
......@@ -632,7 +632,7 @@ test "general client/server API coverage" {
632632
633633 log.info("{s}", .{location});
634634 var redirect_buffer: [1024]u8 = undefined;
635 var req = try client.open(.GET, uri, .{});
635 var req = try client.request(.GET, uri, .{});
636636 defer req.deinit();
637637
638638 try req.sendBodiless();
......@@ -655,18 +655,18 @@ test "general client/server API coverage" {
655655
656656 log.info("{s}", .{location});
657657 var redirect_buffer: [1024]u8 = undefined;
658 var req = try client.open(.HEAD, uri, .{});
658 var req = try client.request(.HEAD, uri, .{});
659659 defer req.deinit();
660660
661661 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));
665665 defer gpa.free(body);
666666
667667 try expectEqualStrings("", body);
668 try expectEqualStrings("text/plain", req.response.content_type.?);
669 try expect(req.response.transfer_encoding == .chunked);
668 try expectEqualStrings("text/plain", response.head.content_type.?);
669 try expect(response.head.transfer_encoding == .chunked);
670670 }
671671
672672 // connection has been kept alive
......@@ -679,19 +679,19 @@ test "general client/server API coverage" {
679679
680680 log.info("{s}", .{location});
681681 var redirect_buffer: [1024]u8 = undefined;
682 var req = try client.open(.GET, uri, .{
682 var req = try client.request(.GET, uri, .{
683683 .keep_alive = false,
684684 });
685685 defer req.deinit();
686686
687687 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));
691691 defer gpa.free(body);
692692
693693 try expectEqualStrings("Hello, World!\n", body);
694 try expectEqualStrings("text/plain", req.response.content_type.?);
694 try expectEqualStrings("text/plain", response.head.content_type.?);
695695 }
696696
697697 // connection has been closed
......@@ -704,7 +704,7 @@ test "general client/server API coverage" {
704704
705705 log.info("{s}", .{location});
706706 var redirect_buffer: [1024]u8 = undefined;
707 var req = try client.open(.GET, uri, .{
707 var req = try client.request(.GET, uri, .{
708708 .extra_headers = &.{
709709 .{ .name = "empty", .value = "" },
710710 },
......@@ -712,16 +712,16 @@ test "general client/server API coverage" {
712712 defer req.deinit();
713713
714714 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));
720720 defer gpa.free(body);
721721
722722 try expectEqualStrings("", body);
723723
724 var it = req.response.iterateHeaders();
724 var it = response.head.iterateHeaders();
725725 {
726726 const header = it.next().?;
727727 try expect(!it.is_trailer);
......@@ -747,13 +747,13 @@ test "general client/server API coverage" {
747747
748748 log.info("{s}", .{location});
749749 var redirect_buffer: [1024]u8 = undefined;
750 var req = try client.open(.GET, uri, .{});
750 var req = try client.request(.GET, uri, .{});
751751 defer req.deinit();
752752
753753 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));
757757 defer gpa.free(body);
758758
759759 try expectEqualStrings("Hello, World!\n", body);
......@@ -769,13 +769,13 @@ test "general client/server API coverage" {
769769
770770 log.info("{s}", .{location});
771771 var redirect_buffer: [1024]u8 = undefined;
772 var req = try client.open(.GET, uri, .{});
772 var req = try client.request(.GET, uri, .{});
773773 defer req.deinit();
774774
775775 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));
779779 defer gpa.free(body);
780780
781781 try expectEqualStrings("Hello, World!\n", body);
......@@ -791,13 +791,13 @@ test "general client/server API coverage" {
791791
792792 log.info("{s}", .{location});
793793 var redirect_buffer: [1024]u8 = undefined;
794 var req = try client.open(.GET, uri, .{});
794 var req = try client.request(.GET, uri, .{});
795795 defer req.deinit();
796796
797797 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));
801801 defer gpa.free(body);
802802
803803 try expectEqualStrings("Hello, World!\n", body);
......@@ -813,14 +813,16 @@ test "general client/server API coverage" {
813813
814814 log.info("{s}", .{location});
815815 var redirect_buffer: [1024]u8 = undefined;
816 var req = try client.open(.GET, uri, .{});
816 var req = try client.request(.GET, uri, .{});
817817 defer req.deinit();
818818
819819 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) {
821823 error.TooManyHttpRedirects => {},
822824 else => return err,
823 };
825 }
824826 }
825827
826828 { // redirect to encoded url
......@@ -830,13 +832,13 @@ test "general client/server API coverage" {
830832
831833 log.info("{s}", .{location});
832834 var redirect_buffer: [1024]u8 = undefined;
833 var req = try client.open(.GET, uri, .{});
835 var req = try client.request(.GET, uri, .{});
834836 defer req.deinit();
835837
836838 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));
840842 defer gpa.free(body);
841843
842844 try expectEqualStrings("Encoded redirect successful!\n", body);
......@@ -852,7 +854,7 @@ test "general client/server API coverage" {
852854
853855 log.info("{s}", .{location});
854856 var redirect_buffer: [1024]u8 = undefined;
855 var req = try client.open(.GET, uri, .{});
857 var req = try client.request(.GET, uri, .{});
856858 defer req.deinit();
857859
858860 try req.sendBodiless();
......@@ -867,36 +869,6 @@ test "general client/server API coverage" {
867869 // connection has been kept alive
868870 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
900872 client.deinit();
901873
902874 {
......@@ -950,7 +922,7 @@ test "Server streams both reading and writing" {
950922 defer client.deinit();
951923
952924 var redirect_buffer: [555]u8 = undefined;
953 var req = try client.open(.POST, .{
925 var req = try client.request(.POST, .{
954926 .scheme = "http",
955927 .host = .{ .raw = "127.0.0.1" },
956928 .port = test_server.port(),
......@@ -983,7 +955,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
983955 const uri = try std.Uri.parse(location);
984956
985957 var redirect_buffer: [1024]u8 = undefined;
986 var req = try client.open(.POST, uri, .{
958 var req = try client.request(.POST, uri, .{
987959 .extra_headers = &.{
988960 .{ .name = "content-type", .value = "text/plain" },
989961 },
......@@ -1017,7 +989,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
1017989 ));
1018990
1019991 var redirect_buffer: [1024]u8 = undefined;
1020 var req = try client.open(.POST, uri, .{
992 var req = try client.request(.POST, uri, .{
1021993 .extra_headers = &.{
1022994 .{ .name = "content-type", .value = "text/plain" },
1023995 },
......@@ -1048,8 +1020,8 @@ fn echoTests(client: *http.Client, port: u16) !void {
10481020 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/echo-content#fetch", .{port});
10491021 defer gpa.free(location);
10501022
1051 var body = std.ArrayList(u8).init(gpa);
1052 defer body.deinit();
1023 var body: std.ArrayListUnmanaged(u8) = .empty;
1024 defer body.deinit(gpa);
10531025
10541026 const res = try client.fetch(.{
10551027 .location = .{ .url = location },
......@@ -1058,7 +1030,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
10581030 .extra_headers = &.{
10591031 .{ .name = "content-type", .value = "text/plain" },
10601032 },
1061 .response_storage = .{ .dynamic = &body },
1033 .response_storage = .{ .allocator = gpa, .list = &body },
10621034 });
10631035 try expectEqual(.ok, res.status);
10641036 try expectEqualStrings("Hello, World!\n", body.items);
......@@ -1070,7 +1042,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
10701042 const uri = try std.Uri.parse(location);
10711043
10721044 var redirect_buffer: [1024]u8 = undefined;
1073 var req = try client.open(.POST, uri, .{
1045 var req = try client.request(.POST, uri, .{
10741046 .extra_headers = &.{
10751047 .{ .name = "expect", .value = "100-continue" },
10761048 .{ .name = "content-type", .value = "text/plain" },
......@@ -1101,7 +1073,7 @@ fn echoTests(client: *http.Client, port: u16) !void {
11011073 const uri = try std.Uri.parse(location);
11021074
11031075 var redirect_buffer: [1024]u8 = undefined;
1104 var req = try client.open(.POST, uri, .{
1076 var req = try client.request(.POST, uri, .{
11051077 .extra_headers = &.{
11061078 .{ .name = "content-type", .value = "text/plain" },
11071079 .{ .name = "expect", .value = "garbage" },
......@@ -1222,7 +1194,7 @@ test "redirect to different connection" {
12221194
12231195 {
12241196 var redirect_buffer: [666]u8 = undefined;
1225 var req = try client.open(.GET, uri, .{});
1197 var req = try client.request(.GET, uri, .{});
12261198 defer req.deinit();
12271199
12281200 try req.sendBodiless();
lib/std/net.zig+16
......@@ -1898,6 +1898,10 @@ pub const Stream = struct {
18981898
18991899 pub const Error = ReadError;
19001900
1901 pub fn getStream(r: *const Reader) Stream {
1902 return r.stream;
1903 }
1904
19011905 pub fn interface(r: *Reader) std.io.Reader {
19021906 return .{
19031907 .context = r.stream.handle,
......@@ -1968,6 +1972,10 @@ pub const Stream = struct {
19681972 pub fn interface(r: *Reader) std.io.Reader {
19691973 return r.file_reader.interface();
19701974 }
1975
1976 pub fn getStream(r: *const Reader) Stream {
1977 return .{ .handle = r.file_reader.file.handle };
1978 }
19711979 },
19721980 };
19731981
......@@ -1987,6 +1995,10 @@ pub const Stream = struct {
19871995 };
19881996 }
19891997
1998 pub fn getStream(w: *const Writer) Stream {
1999 return w.stream;
2000 }
2001
19902002 fn writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Error!usize {
19912003 comptime assert(native_os == .windows);
19922004 if (data.len == 1 and splat == 0) return 0;
......@@ -2130,6 +2142,10 @@ pub const Stream = struct {
21302142 return error.WriteFailed;
21312143 };
21322144 }
2145
2146 pub fn getStream(w: *const Writer) Stream {
2147 return .{ .handle = w.file_writer.file.handle };
2148 }
21332149 },
21342150 };
21352151