@@ -1141,7 +1141,8 @@ fn initHttpResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer:
11411141 const status = response .head .status ;
11421142
11431143 if (@intFromEnum (status ) >= 500 or status == .too_many_requests or status == .not_found ) {
1144- if (parseRetryAfter (f .io , response ) catch null ) | delay_sec | {
1144+ var iter = response .head .iterateHeaders ();
1145+ if (parseRetryAfter (f .io , & iter ) catch null ) | delay_sec | {
11451146 // Set max by dividing and multiplying again, because Retry-After
11461147 // header value needs to be u32, and could be obsurdly large, and
11471148 // we do not want to multiply that large number by 1000 in case of
@@ -1170,9 +1171,8 @@ fn initHttpResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer:
11701171///
11711172/// For more information, see the MDN documentation:
11721173/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After
1173- fn parseRetryAfter (io : Io , response : * const std.http.Client.Response ) ! ? u32 {
1174- var iter = response .head .iterateHeaders ();
1175- const retry_after : []const u8 = while (iter .next ()) | header | {
1174+ fn parseRetryAfter (io : Io , header_iter : * std.http.HeaderIterator ) ! ? u32 {
1175+ const retry_after : []const u8 = while (header_iter .next ()) | header | {
11761176 if (ascii .eqlIgnoreCase (header .name , "retry-after" )) {
11771177 break header .value ;
11781178 }
@@ -2446,12 +2446,60 @@ test "retries the correct number of times" {
24462446 try std .testing .expectEqual (retry .max_retries , retry .cur_retries );
24472447}
24482448
2449+ test "parse Retry-After header" {
2450+ var arena = std .heap .ArenaAllocator .init (std .testing .allocator );
2451+ const alloc = arena .allocator ();
2452+ defer arena .deinit ();
2453+ {
2454+ var iter = try mockRetryAfterHeaderFactory (alloc , "6" );
2455+ const result = try parseRetryAfter (& iter );
2456+ try std .testing .expectEqual (6 , result .? );
2457+ }
2458+ {
2459+ var iter = try mockRetryAfterHeaderFactory (alloc , "12345" );
2460+ const result = try parseRetryAfter (& iter );
2461+ try std .testing .expectEqual (12345 , result .? );
2462+ }
2463+ {
2464+ // anything in the past should return `null`
2465+ var iter = try mockRetryAfterHeaderFactory (alloc , "Wed, 21 Oct 1970 07:28:00 GMT" );
2466+ const result = try parseRetryAfter (& iter );
2467+ try std .testing .expectEqual (null , result );
2468+ }
2469+ {
2470+ // anything in the future should return `null` (this will fail in 100 years)
2471+ const future_time = "Wed, 21 Oct 2125 16:19:10 GMT" ;
2472+ const future_epoc_seconds = 4916737150 ;
2473+ const seconds_from_now = future_epoc_seconds - std .time .timestamp ();
2474+ var iter = try mockRetryAfterHeaderFactory (alloc , future_time );
2475+ const result = try parseRetryAfter (& iter );
2476+ // The time between us calling `std.time.timestamp()` here in this test and when it's called in the
2477+ // `parseRetryAfter` function could be significant enough that they don't match. So we want approx values here.
2478+ try std .testing .expect (result .? + 5 > seconds_from_now );
2479+ try std .testing .expect (result .? - 5 < seconds_from_now );
2480+ }
2481+ {
2482+ // returns error on invalid header
2483+ var iter = try mockRetryAfterHeaderFactory (alloc , "Not a timestamp" );
2484+ try std .testing .expectError (error .InvalidHeaderValueLength , parseRetryAfter (& iter ));
2485+ }
2486+ {
2487+ // returns null on missing header
2488+ var iter = std .http .HeaderIterator .init ("HTTP/1.1 599 NOT-OK\r \n \r \n " );
2489+ try std .testing .expectEqual (null , try parseRetryAfter (& iter ));
2490+ }
2491+ }
2492+
24492493fn testFnToCallWithRetries (r : * Retry , is_spurious_error : bool ) ! void {
24502494 // set to 1 ms so the unit test doesn't take forever
24512495 r .retry_delay_override_ms = 1 ;
24522496 return if (is_spurious_error ) error .MaybeSpurious else error .NonSpurious ;
24532497}
24542498
2499+ fn mockRetryAfterHeaderFactory (alloc : std.mem.Allocator , time : []const u8 ) ! std.http.HeaderIterator {
2500+ return std .http .HeaderIterator .init (try std .fmt .allocPrint (alloc , "HTTP/1.1 599 NOT-OK\r \n Retry-After:{s}\r \n " , .{time }));
2501+ }
2502+
24552503fn saveEmbedFile (comptime tarball_name : []const u8 , dir : fs.Dir ) ! void {
24562504 //const tarball_name = "duplicate_paths_excluded.tar.gz";
24572505 const tarball_content = @embedFile ("Fetch/testdata/" ++ tarball_name );
0 commit comments