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 <sean@liao.dev>
Auto-Submit: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Marc-Antoine Ruel 2025-04-18 13:48:04 -04:00 committed by Gopher Robot
parent df9888ea4e
commit b69f50faef
2 changed files with 56 additions and 0 deletions

View File

@ -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
}

View File

@ -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") }