@@ -365,7 +365,7 @@ pub const Response = struct {
365
365
CompressionNotSupported ,
366
366
};
367
367
368
- pub fn parse (res : * Response , bytes : []const u8 ) ParseError ! void {
368
+ pub fn parse (res : * Response , bytes : []const u8 , trailing : bool ) ParseError ! void {
369
369
var it = mem .tokenize (u8 , bytes [0 .. bytes .len - 4 ], "\r \n " );
370
370
371
371
const first_line = it .next () orelse return error .HttpHeadersInvalid ;
@@ -398,6 +398,8 @@ pub const Response = struct {
398
398
399
399
try res .headers .append (header_name , header_value );
400
400
401
+ if (trailing ) continue ;
402
+
401
403
if (std .ascii .eqlIgnoreCase (header_name , "content-length" )) {
402
404
if (res .content_length != null ) return error .HttpHeadersInvalid ;
403
405
res .content_length = std .fmt .parseInt (u64 , header_value , 10 ) catch return error .InvalidContentLength ;
@@ -480,7 +482,7 @@ pub const Response = struct {
480
482
481
483
/// A HTTP request that has been sent.
482
484
///
483
- /// Order of operations: request[ -> write -> finish] -> do -> read
485
+ /// Order of operations: request -> start [ -> write -> finish] -> wait -> read
484
486
pub const Request = struct {
485
487
uri : Uri ,
486
488
client : * Client ,
@@ -508,8 +510,9 @@ pub const Request = struct {
508
510
.zstd = > | * zstd | zstd .deinit (),
509
511
}
510
512
513
+ req .response .headers .deinit ();
514
+
511
515
if (req .response .parser .header_bytes_owned ) {
512
- req .response .headers .deinit ();
513
516
req .response .parser .header_bytes .deinit (req .client .allocator );
514
517
}
515
518
@@ -524,6 +527,44 @@ pub const Request = struct {
524
527
req .* = undefined ;
525
528
}
526
529
530
+ // This function must deallocate all resources associated with the request, or keep those which will be used
531
+ // This needs to be kept in sync with deinit and request
532
+ fn redirect (req : * Request , uri : Uri ) ! void {
533
+ assert (req .response .parser .done );
534
+
535
+ switch (req .response .compression ) {
536
+ .none = > {},
537
+ .deflate = > | * deflate | deflate .deinit (),
538
+ .gzip = > | * gzip | gzip .deinit (),
539
+ .zstd = > | * zstd | zstd .deinit (),
540
+ }
541
+
542
+ req .client .connection_pool .release (req .client , req .connection );
543
+
544
+ const protocol = protocol_map .get (uri .scheme ) orelse return error .UnsupportedUrlScheme ;
545
+
546
+ const port : u16 = uri .port orelse switch (protocol ) {
547
+ .plain = > 80 ,
548
+ .tls = > 443 ,
549
+ };
550
+
551
+ const host = uri .host orelse return error .UriMissingHost ;
552
+
553
+ req .uri = uri ;
554
+ req .connection = try req .client .connect (host , port , protocol );
555
+ req .redirects_left -= 1 ;
556
+ req .response .headers .clearRetainingCapacity ();
557
+ req .response .parser .reset ();
558
+
559
+ req .response = .{
560
+ .status = undefined ,
561
+ .reason = undefined ,
562
+ .version = undefined ,
563
+ .headers = req .response .headers ,
564
+ .parser = req .response .parser ,
565
+ };
566
+ }
567
+
527
568
pub const StartError = BufferedConnection .WriteError || error { InvalidContentLength , UnsupportedTransferEncoding };
528
569
529
570
/// Send the request to the server.
@@ -627,14 +668,14 @@ pub const Request = struct {
627
668
return index ;
628
669
}
629
670
630
- pub const DoError = RequestError || TransferReadError || proto .HeadersParser .CheckCompleteHeadError || Response .ParseError || Uri .ParseError || error { TooManyHttpRedirects , HttpRedirectMissingLocation , CompressionInitializationFailed , CompressionNotSupported };
671
+ pub const WaitError = RequestError || StartError || TransferReadError || proto .HeadersParser .CheckCompleteHeadError || Response .ParseError || Uri .ParseError || error { TooManyHttpRedirects , CannotRedirect , HttpRedirectMissingLocation , CompressionInitializationFailed , CompressionNotSupported };
631
672
632
673
/// Waits for a response from the server and parses any headers that are sent.
633
674
/// This function will block until the final response is received.
634
675
///
635
- /// If `handle_redirects` is true, then this function will automatically follow
636
- /// redirects.
637
- pub fn do (req : * Request ) DoError ! void {
676
+ /// If `handle_redirects` is true and the request has no payload , then this function will automatically follow
677
+ /// redirects. If a request payload is present, then this function will error with error.CannotRedirect.
678
+ pub fn wait (req : * Request ) WaitError ! void {
638
679
while (true ) { // handle redirects
639
680
while (true ) { // read headers
640
681
try req .connection .data .buffered .fill ();
@@ -645,7 +686,7 @@ pub const Request = struct {
645
686
if (req .response .parser .state .isContent ()) break ;
646
687
}
647
688
648
- try req .response .parse (req .response .parser .header_bytes .items );
689
+ try req .response .parse (req .response .parser .header_bytes .items , false );
649
690
650
691
if (req .response .status == .switching_protocols ) {
651
692
req .connection .data .closing = false ;
@@ -684,7 +725,7 @@ pub const Request = struct {
684
725
req .response .parser .done = true ;
685
726
}
686
727
687
- if (req .response .status .class () == .redirect and req .handle_redirects ) {
728
+ if (req .transfer_encoding == .none and req . response .status .class () == .redirect and req .handle_redirects ) {
688
729
req .response .skip = true ;
689
730
690
731
const empty = @as ([* ]u8 , undefined )[0.. 0];
@@ -694,26 +735,17 @@ pub const Request = struct {
694
735
695
736
const location = req .response .headers .getFirstValue ("location" ) orelse
696
737
return error .HttpRedirectMissingLocation ;
697
- const new_url = Uri .parse (location ) catch try Uri .parseWithoutScheme (location );
698
-
699
- var new_arena = std .heap .ArenaAllocator .init (req .client .allocator );
700
- const resolved_url = try req .uri .resolve (new_url , false , new_arena .allocator ());
701
- errdefer new_arena .deinit ();
702
-
703
- req .arena .deinit ();
704
- req .arena = new_arena ;
705
-
706
- const new_req = try req .client .request (req .method , resolved_url , req .headers , .{
707
- .version = req .version ,
708
- .max_redirects = req .redirects_left - 1 ,
709
- .header_strategy = if (req .response .parser .header_bytes_owned ) .{
710
- .dynamic = req .response .parser .max_header_bytes ,
711
- } else .{
712
- .static = req .response .parser .header_bytes .items .ptr [0.. req .response .parser .max_header_bytes ],
713
- },
714
- });
715
- req .deinit ();
716
- req .* = new_req ;
738
+
739
+ const arena = req .arena .allocator ();
740
+
741
+ const location_duped = try arena .dupe (u8 , location );
742
+
743
+ const new_url = Uri .parse (location_duped ) catch try Uri .parseWithoutScheme (location_duped );
744
+ const resolved_url = try req .uri .resolve (new_url , false , arena );
745
+
746
+ try req .redirect (resolved_url );
747
+
748
+ try req .start ();
717
749
} else {
718
750
req .response .skip = false ;
719
751
if (! req .response .parser .done ) {
@@ -731,6 +763,9 @@ pub const Request = struct {
731
763
};
732
764
}
733
765
766
+ if (req .response .status .class () == .redirect and req .handle_redirects and req .transfer_encoding != .none )
767
+ return error .CannotRedirect ; // The request body has already been sent. The request is still in a valid state, but the redirect must be handled manually.
768
+
734
769
break ;
735
770
}
736
771
}
@@ -768,7 +803,7 @@ pub const Request = struct {
768
803
769
804
// The response headers before the trailers are already guaranteed to be valid, so they will always be parsed again and cannot return an error.
770
805
// This will *only* fail for a malformed trailer.
771
- req .response .parse (req .response .parser .header_bytes .items ) catch return error .InvalidTrailers ;
806
+ req .response .parse (req .response .parser .header_bytes .items , true ) catch return error .InvalidTrailers ;
772
807
}
773
808
}
774
809
0 commit comments