From: Marc-Antoine Ruel Date: Fri, 18 Apr 2025 17:48:04 +0000 (-0400) Subject: net/http: upon http redirect, copy Request.GetBody in new request X-Git-Tag: go1.25rc1~209 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=b69f50faef360beedd408048d19909c85a2e0de0;p=gostls13.git net/http: upon http redirect, copy Request.GetBody in new request This enable http.RoundTripper implementation to retry POST request (let's say after a 500) after a 307/308 redirect. Fixes #73439 Change-Id: I4365ff58b012c7f0d60e0317a08c98b1d48f657e Reviewed-on: https://go-review.googlesource.com/c/go/+/666735 Reviewed-by: Sean Liao Auto-Submit: Damien Neil LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov Reviewed-by: Damien Neil --- diff --git a/src/net/http/client.go b/src/net/http/client.go index a281a1ca6a..43a7a06bfb 100644 --- a/src/net/http/client.go +++ b/src/net/http/client.go @@ -672,6 +672,7 @@ func (c *Client) do(req *Request) (retres *Response, reterr error) { resp.closeBody() return nil, uerr(err) } + req.GetBody = ireq.GetBody req.ContentLength = ireq.ContentLength } diff --git a/src/net/http/client_test.go b/src/net/http/client_test.go index ec5ac7ffeb..f2e04ca4e8 100644 --- a/src/net/http/client_test.go +++ b/src/net/http/client_test.go @@ -1962,6 +1962,61 @@ func testTransportBodyReadError(t *testing.T, mode testMode) { } } +// Make sure the retries copies the GetBody in the request. +func TestRedirectGetBody(t *testing.T) { run(t, testRedirectGetBody) } + +func testRedirectGetBody(t *testing.T, mode testMode) { + ts := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) { + b, err := io.ReadAll(r.Body) + if err != nil { + t.Error(err) + } + if err = r.Body.Close(); err != nil { + t.Error(err) + } + if s := string(b); s != "hello" { + t.Errorf("expected hello, got %s", s) + } + if r.URL.Path == "/first" { + Redirect(w, r, "/second", StatusTemporaryRedirect) + return + } + w.Write([]byte("world")) + })).ts + c := ts.Client() + c.Transport = &roundTripperGetBody{c.Transport, t} + req, err := NewRequest("POST", ts.URL+"/first", strings.NewReader("hello")) + if err != nil { + t.Fatal(err) + } + res, err := c.Do(req.WithT(t)) + if err != nil { + t.Fatal(err) + } + b, err := io.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + if err = res.Body.Close(); err != nil { + t.Fatal(err) + } + if s := string(b); s != "world" { + t.Fatalf("expected world, got %s", s) + } +} + +type roundTripperGetBody struct { + Transport RoundTripper + t *testing.T +} + +func (r *roundTripperGetBody) RoundTrip(req *Request) (*Response, error) { + if req.GetBody == nil { + r.t.Error("missing Request.GetBody") + } + return r.Transport.RoundTrip(req) +} + type roundTripperWithoutCloseIdle struct{} func (roundTripperWithoutCloseIdle) RoundTrip(*Request) (*Response, error) { panic("unused") }