Skip to content

Commit 3b69c3b

Browse files
odeke-embradfitz
authored andcommitted
net/http: deep copy Request.URL also in Request.WithContext's copy
Despite the previously known behavior of Request.WithContext shallow copying a request, usage of the request inside server.ServeHTTP mutates the request's URL. This CL implements deep copying of the URL. Fixes #20068 Change-Id: I86857d7259e23ac624d196401bf12dde401c42af Reviewed-on: https://go-review.googlesource.com/41308 Run-TryBot: Emmanuel Odeke <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 88a2350 commit 3b69c3b

File tree

3 files changed

+62
-0
lines changed

3 files changed

+62
-0
lines changed

src/net/http/httputil/reverseproxy_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,3 +698,41 @@ func BenchmarkServeHTTP(b *testing.B) {
698698
proxy.ServeHTTP(w, r)
699699
}
700700
}
701+
702+
func TestServeHTTPDeepCopy(t *testing.T) {
703+
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
704+
w.Write([]byte("Hello Gopher!"))
705+
}))
706+
defer backend.Close()
707+
backendURL, err := url.Parse(backend.URL)
708+
if err != nil {
709+
t.Fatal(err)
710+
}
711+
712+
type result struct {
713+
before, after string
714+
}
715+
716+
resultChan := make(chan result, 1)
717+
proxyHandler := NewSingleHostReverseProxy(backendURL)
718+
frontend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
719+
before := r.URL.String()
720+
proxyHandler.ServeHTTP(w, r)
721+
after := r.URL.String()
722+
resultChan <- result{before: before, after: after}
723+
}))
724+
defer frontend.Close()
725+
726+
want := result{before: "/", after: "/"}
727+
728+
res, err := frontend.Client().Get(frontend.URL)
729+
if err != nil {
730+
t.Fatalf("Do: %v", err)
731+
}
732+
res.Body.Close()
733+
734+
got := <-resultChan
735+
if got != want {
736+
t.Errorf("got = %+v; want = %+v", got, want)
737+
}
738+
}

src/net/http/request.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,14 @@ func (r *Request) WithContext(ctx context.Context) *Request {
329329
r2 := new(Request)
330330
*r2 = *r
331331
r2.ctx = ctx
332+
333+
// Deep copy the URL because it isn't
334+
// a map and the URL is mutable by users
335+
// of WithContext.
336+
r2URL := new(url.URL)
337+
*r2URL = *r.URL
338+
r2.URL = r2URL
339+
332340
return r2
333341
}
334342

src/net/http/request_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package http_test
77
import (
88
"bufio"
99
"bytes"
10+
"context"
1011
"encoding/base64"
1112
"fmt"
1213
"io"
@@ -785,6 +786,21 @@ func TestMaxBytesReaderStickyError(t *testing.T) {
785786
}
786787
}
787788

789+
func TestWithContextDeepCopiesURL(t *testing.T) {
790+
req, err := NewRequest("POST", "https://golang.org/", nil)
791+
if err != nil {
792+
t.Fatal(err)
793+
}
794+
795+
reqCopy := req.WithContext(context.Background())
796+
reqCopy.URL.Scheme = "http"
797+
798+
firstURL, secondURL := req.URL.String(), reqCopy.URL.String()
799+
if firstURL == secondURL {
800+
t.Errorf("unexpected change to original request's URL")
801+
}
802+
}
803+
788804
// verify that NewRequest sets Request.GetBody and that it works
789805
func TestNewRequestGetBody(t *testing.T) {
790806
tests := []struct {

0 commit comments

Comments
 (0)