authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-11 22:44:31-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 02:37:11-07:00
logf46447e6a1cda2b3b2e0ee90a68b2cca112f8742
treec86cad17aecbf5f7757560046da5d9ecc7dc8689
parent00acf8a66dec78743d26d62e86391b8794a1c627

std.http.Client.fetch: add redirect behavior to options


1 files changed, 40 insertions(+), 25 deletions(-)

lib/std/http/Client.zig+40-25
...@@ -580,11 +580,7 @@ pub const Request = struct {...@@ -580,11 +580,7 @@ pub const Request = struct {
580 /// The transfer encoding of the request body.580 /// The transfer encoding of the request body.
581 transfer_encoding: RequestTransfer = .none,581 transfer_encoding: RequestTransfer = .none,
582582
583 /// The redirect quota left for this request.583 redirect_behavior: RedirectBehavior,
584 redirects_left: u32,
585
586 /// Whether the request should follow redirects.
587 handle_redirects: bool,
588584
589 /// Whether the request should handle a 100-continue response before sending the request body.585 /// Whether the request should handle a 100-continue response before sending the request body.
590 handle_continue: bool,586 handle_continue: bool,
...@@ -597,6 +593,25 @@ pub const Request = struct {...@@ -597,6 +593,25 @@ pub const Request = struct {
597 /// Used as a allocator for resolving redirects locations.593 /// Used as a allocator for resolving redirects locations.
598 arena: std.heap.ArenaAllocator,594 arena: std.heap.ArenaAllocator,
599595
596 /// Any value other than `not_allowed` or `unhandled` means that integer represents
597 /// how many remaining redirects are allowed.
598 pub const RedirectBehavior = enum(u16) {
599 /// The next redirect will cause an error.
600 not_allowed = 0,
601 /// Redirects are passed to the client to analyze the redirect response
602 /// directly.
603 unhandled = std.math.maxInt(u16),
604 _,
605
606 pub fn subtractOne(rb: *RedirectBehavior) void {
607 switch (rb.*) {
608 .not_allowed => unreachable,
609 .unhandled => unreachable,
610 _ => rb.* = @enumFromInt(@intFromEnum(rb.*) - 1),
611 }
612 }
613 };
614
600 /// Frees all resources associated with the request.615 /// Frees all resources associated with the request.
601 pub fn deinit(req: *Request) void {616 pub fn deinit(req: *Request) void {
602 switch (req.response.compression) {617 switch (req.response.compression) {
...@@ -621,8 +636,9 @@ pub const Request = struct {...@@ -621,8 +636,9 @@ pub const Request = struct {
621 req.* = undefined;636 req.* = undefined;
622 }637 }
623638
624 // This function must deallocate all resources associated with the request, or keep those which will be used639 // This function must deallocate all resources associated with the request,
625 // This needs to be kept in sync with deinit and request640 // or keep those which will be used.
641 // This needs to be kept in sync with deinit and request.
626 fn redirect(req: *Request, uri: Uri) !void {642 fn redirect(req: *Request, uri: Uri) !void {
627 assert(req.response.parser.state == .complete);643 assert(req.response.parser.state == .complete);
628644
...@@ -647,7 +663,7 @@ pub const Request = struct {...@@ -647,7 +663,7 @@ pub const Request = struct {
647663
648 req.uri = uri;664 req.uri = uri;
649 req.connection = try req.client.connect(host, port, protocol);665 req.connection = try req.client.connect(host, port, protocol);
650 req.redirects_left -= 1;666 req.redirect_behavior.subtractOne();
651 req.response.headers.clearRetainingCapacity();667 req.response.headers.clearRetainingCapacity();
652 req.response.parser.reset();668 req.response.parser.reset();
653669
...@@ -819,7 +835,7 @@ pub const Request = struct {...@@ -819,7 +835,7 @@ pub const Request = struct {
819 /// Waits for a response from the server and parses any headers that are sent.835 /// Waits for a response from the server and parses any headers that are sent.
820 /// This function will block until the final response is received.836 /// This function will block until the final response is received.
821 ///837 ///
822 /// If `handle_redirects` is true and the request has no payload, then this838 /// If handling redirects and the request has no payload, then this
823 /// function will automatically follow redirects. If a request payload is839 /// function will automatically follow redirects. If a request payload is
824 /// present, then this function will error with840 /// present, then this function will error with
825 /// error.RedirectRequiresResend.841 /// error.RedirectRequiresResend.
...@@ -897,15 +913,14 @@ pub const Request = struct {...@@ -897,15 +913,14 @@ pub const Request = struct {
897 req.response.parser.next_chunk_length = std.math.maxInt(u64);913 req.response.parser.next_chunk_length = std.math.maxInt(u64);
898 }914 }
899915
900 if (req.response.status.class() == .redirect and req.handle_redirects) {916 if (req.response.status.class() == .redirect and req.redirect_behavior != .unhandled) {
901 req.response.skip = true;917 req.response.skip = true;
902918
903 // skip the body of the redirect response, this will at least919 // skip the body of the redirect response, this will at least
904 // leave the connection in a known good state.920 // leave the connection in a known good state.
905 const empty = @as([*]u8, undefined)[0..0];921 assert(try req.transferRead(&.{}) == 0); // we're skipping, no buffer is necessary
906 assert(try req.transferRead(empty) == 0); // we're skipping, no buffer is necessary
907922
908 if (req.redirects_left == 0) return error.TooManyHttpRedirects;923 if (req.redirect_behavior == .not_allowed) return error.TooManyHttpRedirects;
909924
910 const location = req.response.headers.getFirstValue("location") orelse925 const location = req.response.headers.getFirstValue("location") orelse
911 return error.HttpRedirectMissingLocation;926 return error.HttpRedirectMissingLocation;
...@@ -1380,7 +1395,7 @@ pub fn connectTunnel(...@@ -1380,7 +1395,7 @@ pub fn connectTunnel(
13801395
1381 var buffer: [8096]u8 = undefined;1396 var buffer: [8096]u8 = undefined;
1382 var req = client.open(.CONNECT, uri, proxy.headers, .{1397 var req = client.open(.CONNECT, uri, proxy.headers, .{
1383 .handle_redirects = false,1398 .redirect_behavior = .unhandled,
1384 .connection = conn,1399 .connection = conn,
1385 .server_header_buffer = &buffer,1400 .server_header_buffer = &buffer,
1386 }) catch |err| {1401 }) catch |err| {
...@@ -1481,13 +1496,13 @@ pub const RequestOptions = struct {...@@ -1481,13 +1496,13 @@ pub const RequestOptions = struct {
1481 /// you finish the request, then the request *will* deadlock.1496 /// you finish the request, then the request *will* deadlock.
1482 handle_continue: bool = true,1497 handle_continue: bool = true,
14831498
1484 /// Automatically follow redirects. This will only follow redirects for1499 /// This field specifies whether to automatically follow redirects, and if
1485 /// repeatable requests (ie. with no payload or the server has acknowledged1500 /// so, how many redirects to follow before returning an error.
1486 /// the payload).1501 ///
1487 handle_redirects: bool = true,1502 /// This will only follow redirects for repeatable requests (ie. with no
1503 /// payload or the server has acknowledged the payload).
1504 redirect_behavior: Request.RedirectBehavior = @enumFromInt(3),
14881505
1489 /// How many redirects to follow before returning an error.
1490 max_redirects: u32 = 3,
1491 /// Externally-owned memory used to store the server's entire HTTP header.1506 /// Externally-owned memory used to store the server's entire HTTP header.
1492 /// `error.HttpHeadersOversize` is returned from read() when a1507 /// `error.HttpHeadersOversize` is returned from read() when a
1493 /// client sends too many bytes of HTTP headers.1508 /// client sends too many bytes of HTTP headers.
...@@ -1548,8 +1563,7 @@ pub fn open(...@@ -1548,8 +1563,7 @@ pub fn open(
1548 .headers = try headers.clone(client.allocator), // Headers must be cloned to properly handle header transformations in redirects.1563 .headers = try headers.clone(client.allocator), // Headers must be cloned to properly handle header transformations in redirects.
1549 .method = method,1564 .method = method,
1550 .version = options.version,1565 .version = options.version,
1551 .redirects_left = options.max_redirects,1566 .redirect_behavior = options.redirect_behavior,
1552 .handle_redirects = options.handle_redirects,
1553 .handle_continue = options.handle_continue,1567 .handle_continue = options.handle_continue,
1554 .response = .{1568 .response = .{
1555 .status = undefined,1569 .status = undefined,
...@@ -1600,6 +1614,7 @@ pub const FetchOptions = struct {...@@ -1600,6 +1614,7 @@ pub const FetchOptions = struct {
16001614
1601 server_header_buffer: ?[]u8 = null,1615 server_header_buffer: ?[]u8 = null,
1602 response_strategy: ResponseStrategy = .{ .storage = .{ .dynamic = 16 * 1024 * 1024 } },1616 response_strategy: ResponseStrategy = .{ .storage = .{ .dynamic = 16 * 1024 * 1024 } },
1617 redirect_behavior: ?Request.RedirectBehavior = null,
16031618
1604 location: Location,1619 location: Location,
1605 method: http.Method = .GET,1620 method: http.Method = .GET,
...@@ -1642,7 +1657,8 @@ pub fn fetch(client: *Client, allocator: Allocator, options: FetchOptions) !Fetc...@@ -1642,7 +1657,8 @@ pub fn fetch(client: *Client, allocator: Allocator, options: FetchOptions) !Fetc
16421657
1643 var req = try open(client, options.method, uri, options.headers, .{1658 var req = try open(client, options.method, uri, options.headers, .{
1644 .server_header_buffer = options.server_header_buffer orelse &server_header_buffer,1659 .server_header_buffer = options.server_header_buffer orelse &server_header_buffer,
1645 .handle_redirects = options.payload == .none,1660 .redirect_behavior = options.redirect_behavior orelse
1661 if (options.payload == .none) @enumFromInt(3) else .unhandled,
1646 });1662 });
1647 defer req.deinit();1663 defer req.deinit();
16481664
...@@ -1694,8 +1710,7 @@ pub fn fetch(client: *Client, allocator: Allocator, options: FetchOptions) !Fetc...@@ -1694,8 +1710,7 @@ pub fn fetch(client: *Client, allocator: Allocator, options: FetchOptions) !Fetc
1694 .none => { // Take advantage of request internals to discard the response body and make the connection available for another request.1710 .none => { // Take advantage of request internals to discard the response body and make the connection available for another request.
1695 req.response.skip = true;1711 req.response.skip = true;
16961712
1697 const empty = @as([*]u8, undefined)[0..0];1713 assert(try req.transferRead(&.{}) == 0); // we're skipping, no buffer is necessary
1698 assert(try req.transferRead(empty) == 0); // we're skipping, no buffer is necessary
1699 },1714 },
1700 }1715 }
17011716