diff mbox series

[bug#54241,3/4] http-client: Correctly handle redirects when #:keep-alive? #t.

Message ID 20220303211444.19928-3-ludo@gnu.org
State Accepted
Headers show
Series 'github' importer gracefully handles rate limiting | expand

Checks

Context Check Description
cbaines/comparison success View comparision
cbaines/git branch success View Git branch
cbaines/applying patch success View Laminar job
cbaines/issue success View issue

Commit Message

Ludovic Courtès March 3, 2022, 9:14 p.m. UTC
Previously PORT would be closed unconditionally, which broke redirects
when #:keep-alive? #t is given.

* guix/http-client.scm (http-fetch): Make 'port' a parameter of 'loop'.
Upon 3xx responses, do not close PORT is KEEP-ALIVE? is true, but consume
RESP's body.  Add second argument to 'loop'.
---
 guix/http-client.scm | 26 +++++++++++++++++---------
 1 file changed, 17 insertions(+), 9 deletions(-)

Comments

M March 4, 2022, 12:39 p.m. UTC | #1
Ludovic Courtès schreef op do 03-03-2022 om 22:14 [+0100]:
> Previously PORT would be closed unconditionally, which broke redirects
> when #:keep-alive? #t is given.
> 
> * guix/http-client.scm (http-fetch): Make 'port' a parameter of 'loop'.
> Upon 3xx responses, do not close PORT is KEEP-ALIVE? is true, but consume
> RESP's body.  Add second argument to 'loop'.
> ---

HTTP things can become complicated, with lots of edge cases. Could an
appropriate test be added to prevent this becoming buggy in the future,
in case of future changed to 'http-fetch'?  And a few source code
comments about what is going on exactly?

Greetings,
Maxime.
Ludovic Courtès March 6, 2022, 9:55 p.m. UTC | #2
Hello,

I committed an updated version of these patches, taking some of your
suggestions into account:

  a8d3033da6 import: github: Reuse HTTP connection for the /tags URL fallback.
  8786c2e8d7 http-client: Correctly handle redirects when #:keep-alive? #t.
  55e8e283ae import: github: Gracefully handle rate limit exhaustion.
  ecad9b2213 http-client: Add response headers to '&http-get-error'.
  049aefddb2 tests: Add (guix http-client) tests.

The first patch adds tests for ‘http-fetch’, as you suggested, which
allowed me to find a thinko in the keep-alive patch.  The test doesn’t
test keep-alive support though, because the server in (guix tests http)
doesn’t support it currently (I tried to retrofit it based on (web
server http) but that’s not really possible because we’d need to either
access internals of the <http> record type, specifically it’s poll set,
or re-implement it entirely.)

Thanks a lot for your help!

Ludo’.
diff mbox series

Patch

diff --git a/guix/http-client.scm b/guix/http-client.scm
index 4b01e31165..4784497e5f 100644
--- a/guix/http-client.scm
+++ b/guix/http-client.scm
@@ -102,12 +102,12 @@  (define* (http-fetch uri #:key port (text? #f) (buffered? #t)
 Raise an '&http-get-error' condition if downloading fails."
   (let loop ((uri (if (string? uri)
                       (string->uri uri)
-                      uri)))
-    (let ((port (or port (open-connection uri
-                                          #:verify-certificate?
-                                          verify-certificate?
-                                          #:timeout timeout)))
-          (headers (match (uri-userinfo uri)
+                      uri))
+             (port (or port (open-connection uri
+                                             #:verify-certificate?
+                                             verify-certificate?
+                                             #:timeout timeout))))
+    (let ((headers (match (uri-userinfo uri)
                      ((? string? str)
                       (cons (cons 'Authorization
                                   (string-append "Basic "
@@ -131,11 +131,19 @@  (define* (http-fetch uri #:key port (text? #f) (buffered? #t)
             303                                   ; see other
             307                                   ; temporary redirection
             308)                                  ; permanent redirection
-           (let ((uri (resolve-uri-reference (response-location resp) uri)))
-             (close-port port)
+           (let ((host (uri-host uri))
+                 (uri  (resolve-uri-reference (response-location resp) uri)))
+             (if keep-alive?
+                 (dump-port data (%make-void-port "w0")
+                            (response-content-length resp))
+                 (close-port port))
              (format log-port (G_ "following redirection to `~a'...~%")
                      (uri->string uri))
-             (loop uri)))
+             (loop uri
+                   (and keep-alive?
+                        (or (not (uri-host uri))
+                            (string=? host (uri-host uri)))
+                        port))))
           (else
            (raise (condition (&http-get-error
                               (uri uri)