1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Reverse proxy tests.
28 const fakeHopHeader
= "X-Fake-Hop-Header-For-Test"
31 hopHeaders
= append(hopHeaders
, fakeHopHeader
)
34 func TestReverseProxy(t
*testing
.T
) {
35 const backendResponse
= "I am the backend"
36 const backendStatus
= 404
37 backend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
38 if r
.Method
== "GET" && r
.FormValue("mode") == "hangup" {
39 c
, _
, _
:= w
.(http
.Hijacker
).Hijack()
43 if len(r
.TransferEncoding
) > 0 {
44 t
.Errorf("backend got unexpected TransferEncoding: %v", r
.TransferEncoding
)
46 if r
.Header
.Get("X-Forwarded-For") == "" {
47 t
.Errorf("didn't get X-Forwarded-For header")
49 if c
:= r
.Header
.Get("Connection"); c
!= "" {
50 t
.Errorf("handler got Connection header value %q", c
)
52 if c
:= r
.Header
.Get("Upgrade"); c
!= "" {
53 t
.Errorf("handler got Upgrade header value %q", c
)
55 if c
:= r
.Header
.Get("Proxy-Connection"); c
!= "" {
56 t
.Errorf("handler got Proxy-Connection header value %q", c
)
58 if g
, e
:= r
.Host
, "some-name"; g
!= e
{
59 t
.Errorf("backend got Host header %q, want %q", g
, e
)
61 w
.Header().Set("Trailers", "not a special header field name")
62 w
.Header().Set("Trailer", "X-Trailer")
63 w
.Header().Set("X-Foo", "bar")
64 w
.Header().Set("Upgrade", "foo")
65 w
.Header().Set(fakeHopHeader
, "foo")
66 w
.Header().Add("X-Multi-Value", "foo")
67 w
.Header().Add("X-Multi-Value", "bar")
68 http
.SetCookie(w
, &http
.Cookie
{Name
: "flavor", Value
: "chocolateChip"})
69 w
.WriteHeader(backendStatus
)
70 w
.Write([]byte(backendResponse
))
71 w
.Header().Set("X-Trailer", "trailer_value")
72 w
.Header().Set(http
.TrailerPrefix
+"X-Unannounced-Trailer", "unannounced_trailer_value")
75 backendURL
, err
:= url
.Parse(backend
.URL
)
79 proxyHandler
:= NewSingleHostReverseProxy(backendURL
)
80 proxyHandler
.ErrorLog
= log
.New(ioutil
.Discard
, "", 0) // quiet for tests
81 frontend
:= httptest
.NewServer(proxyHandler
)
82 defer frontend
.Close()
83 frontendClient
:= frontend
.Client()
85 getReq
, _
:= http
.NewRequest("GET", frontend
.URL
, nil)
86 getReq
.Host
= "some-name"
87 getReq
.Header
.Set("Connection", "close")
88 getReq
.Header
.Set("Proxy-Connection", "should be deleted")
89 getReq
.Header
.Set("Upgrade", "foo")
91 res
, err
:= frontendClient
.Do(getReq
)
93 t
.Fatalf("Get: %v", err
)
95 if g
, e
:= res
.StatusCode
, backendStatus
; g
!= e
{
96 t
.Errorf("got res.StatusCode %d; expected %d", g
, e
)
98 if g
, e
:= res
.Header
.Get("X-Foo"), "bar"; g
!= e
{
99 t
.Errorf("got X-Foo %q; expected %q", g
, e
)
101 if c
:= res
.Header
.Get(fakeHopHeader
); c
!= "" {
102 t
.Errorf("got %s header value %q", fakeHopHeader
, c
)
104 if g
, e
:= res
.Header
.Get("Trailers"), "not a special header field name"; g
!= e
{
105 t
.Errorf("header Trailers = %q; want %q", g
, e
)
107 if g
, e
:= len(res
.Header
["X-Multi-Value"]), 2; g
!= e
{
108 t
.Errorf("got %d X-Multi-Value header values; expected %d", g
, e
)
110 if g
, e
:= len(res
.Header
["Set-Cookie"]), 1; g
!= e
{
111 t
.Fatalf("got %d SetCookies, want %d", g
, e
)
113 if g
, e
:= res
.Trailer
, (http
.Header
{"X-Trailer": nil}); !reflect
.DeepEqual(g
, e
) {
114 t
.Errorf("before reading body, Trailer = %#v; want %#v", g
, e
)
116 if cookie
:= res
.Cookies()[0]; cookie
.Name
!= "flavor" {
117 t
.Errorf("unexpected cookie %q", cookie
.Name
)
119 bodyBytes
, _
:= ioutil
.ReadAll(res
.Body
)
120 if g
, e
:= string(bodyBytes
), backendResponse
; g
!= e
{
121 t
.Errorf("got body %q; expected %q", g
, e
)
123 if g
, e
:= res
.Trailer
.Get("X-Trailer"), "trailer_value"; g
!= e
{
124 t
.Errorf("Trailer(X-Trailer) = %q ; want %q", g
, e
)
126 if g
, e
:= res
.Trailer
.Get("X-Unannounced-Trailer"), "unannounced_trailer_value"; g
!= e
{
127 t
.Errorf("Trailer(X-Unannounced-Trailer) = %q ; want %q", g
, e
)
130 // Test that a backend failing to be reached or one which doesn't return
131 // a response results in a StatusBadGateway.
132 getReq
, _
= http
.NewRequest("GET", frontend
.URL
+"/?mode=hangup", nil)
134 res
, err
= frontendClient
.Do(getReq
)
139 if res
.StatusCode
!= http
.StatusBadGateway
{
140 t
.Errorf("request to bad proxy = %v; want 502 StatusBadGateway", res
.Status
)
145 // Issue 16875: remove any proxied headers mentioned in the "Connection"
147 func TestReverseProxyStripHeadersPresentInConnection(t
*testing
.T
) {
148 const fakeConnectionToken
= "X-Fake-Connection-Token"
149 const backendResponse
= "I am the backend"
150 backend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
151 if c
:= r
.Header
.Get(fakeConnectionToken
); c
!= "" {
152 t
.Errorf("handler got header %q = %q; want empty", fakeConnectionToken
, c
)
154 if c
:= r
.Header
.Get("Upgrade"); c
!= "" {
155 t
.Errorf("handler got header %q = %q; want empty", "Upgrade", c
)
157 w
.Header().Set("Connection", "Upgrade, "+fakeConnectionToken
)
158 w
.Header().Set("Upgrade", "should be deleted")
159 w
.Header().Set(fakeConnectionToken
, "should be deleted")
160 io
.WriteString(w
, backendResponse
)
162 defer backend
.Close()
163 backendURL
, err
:= url
.Parse(backend
.URL
)
167 proxyHandler
:= NewSingleHostReverseProxy(backendURL
)
168 frontend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
169 proxyHandler
.ServeHTTP(w
, r
)
170 if c
:= r
.Header
.Get("Upgrade"); c
!= "original value" {
171 t
.Errorf("handler modified header %q = %q; want %q", "Upgrade", c
, "original value")
174 defer frontend
.Close()
176 getReq
, _
:= http
.NewRequest("GET", frontend
.URL
, nil)
177 getReq
.Header
.Set("Connection", "Upgrade, "+fakeConnectionToken
)
178 getReq
.Header
.Set("Upgrade", "original value")
179 getReq
.Header
.Set(fakeConnectionToken
, "should be deleted")
180 res
, err
:= frontend
.Client().Do(getReq
)
182 t
.Fatalf("Get: %v", err
)
184 defer res
.Body
.Close()
185 bodyBytes
, err
:= ioutil
.ReadAll(res
.Body
)
187 t
.Fatalf("reading body: %v", err
)
189 if got
, want
:= string(bodyBytes
), backendResponse
; got
!= want
{
190 t
.Errorf("got body %q; want %q", got
, want
)
192 if c
:= res
.Header
.Get("Upgrade"); c
!= "" {
193 t
.Errorf("handler got header %q = %q; want empty", "Upgrade", c
)
195 if c
:= res
.Header
.Get(fakeConnectionToken
); c
!= "" {
196 t
.Errorf("handler got header %q = %q; want empty", fakeConnectionToken
, c
)
200 func TestXForwardedFor(t
*testing
.T
) {
201 const prevForwardedFor
= "client ip"
202 const backendResponse
= "I am the backend"
203 const backendStatus
= 404
204 backend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
205 if r
.Header
.Get("X-Forwarded-For") == "" {
206 t
.Errorf("didn't get X-Forwarded-For header")
208 if !strings
.Contains(r
.Header
.Get("X-Forwarded-For"), prevForwardedFor
) {
209 t
.Errorf("X-Forwarded-For didn't contain prior data")
211 w
.WriteHeader(backendStatus
)
212 w
.Write([]byte(backendResponse
))
214 defer backend
.Close()
215 backendURL
, err
:= url
.Parse(backend
.URL
)
219 proxyHandler
:= NewSingleHostReverseProxy(backendURL
)
220 frontend
:= httptest
.NewServer(proxyHandler
)
221 defer frontend
.Close()
223 getReq
, _
:= http
.NewRequest("GET", frontend
.URL
, nil)
224 getReq
.Host
= "some-name"
225 getReq
.Header
.Set("Connection", "close")
226 getReq
.Header
.Set("X-Forwarded-For", prevForwardedFor
)
228 res
, err
:= frontend
.Client().Do(getReq
)
230 t
.Fatalf("Get: %v", err
)
232 if g
, e
:= res
.StatusCode
, backendStatus
; g
!= e
{
233 t
.Errorf("got res.StatusCode %d; expected %d", g
, e
)
235 bodyBytes
, _
:= ioutil
.ReadAll(res
.Body
)
236 if g
, e
:= string(bodyBytes
), backendResponse
; g
!= e
{
237 t
.Errorf("got body %q; expected %q", g
, e
)
241 var proxyQueryTests
= []struct {
242 baseSuffix
string // suffix to add to backend URL
243 reqSuffix
string // suffix to add to frontend's request URL
244 want
string // what backend should see for final request URL (without ?)
247 {"?sta=tic", "?us=er", "sta=tic&us=er"},
248 {"", "?us=er", "us=er"},
249 {"?sta=tic", "", "sta=tic"},
252 func TestReverseProxyQuery(t
*testing
.T
) {
253 backend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
254 w
.Header().Set("X-Got-Query", r
.URL
.RawQuery
)
255 w
.Write([]byte("hi"))
257 defer backend
.Close()
259 for i
, tt
:= range proxyQueryTests
{
260 backendURL
, err
:= url
.Parse(backend
.URL
+ tt
.baseSuffix
)
264 frontend
:= httptest
.NewServer(NewSingleHostReverseProxy(backendURL
))
265 req
, _
:= http
.NewRequest("GET", frontend
.URL
+tt
.reqSuffix
, nil)
267 res
, err
:= frontend
.Client().Do(req
)
269 t
.Fatalf("%d. Get: %v", i
, err
)
271 if g
, e
:= res
.Header
.Get("X-Got-Query"), tt
.want
; g
!= e
{
272 t
.Errorf("%d. got query %q; expected %q", i
, g
, e
)
279 func TestReverseProxyFlushInterval(t
*testing
.T
) {
280 const expected
= "hi"
281 backend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
282 w
.Write([]byte(expected
))
284 defer backend
.Close()
286 backendURL
, err
:= url
.Parse(backend
.URL
)
291 proxyHandler
:= NewSingleHostReverseProxy(backendURL
)
292 proxyHandler
.FlushInterval
= time
.Microsecond
294 done
:= make(chan bool)
295 onExitFlushLoop
= func() { done
<- true }
296 defer func() { onExitFlushLoop
= nil }()
298 frontend
:= httptest
.NewServer(proxyHandler
)
299 defer frontend
.Close()
301 req
, _
:= http
.NewRequest("GET", frontend
.URL
, nil)
303 res
, err
:= frontend
.Client().Do(req
)
305 t
.Fatalf("Get: %v", err
)
307 defer res
.Body
.Close()
308 if bodyBytes
, _
:= ioutil
.ReadAll(res
.Body
); string(bodyBytes
) != expected
{
309 t
.Errorf("got body %q; expected %q", bodyBytes
, expected
)
315 case <-time
.After(5 * time
.Second
):
316 t
.Error("maxLatencyWriter flushLoop() never exited")
320 func TestReverseProxyCancelation(t
*testing
.T
) {
321 const backendResponse
= "I am the backend"
323 reqInFlight
:= make(chan struct{})
324 backend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
325 close(reqInFlight
) // cause the client to cancel its request
328 case <-time
.After(10 * time
.Second
):
329 // Note: this should only happen in broken implementations, and the
330 // closenotify case should be instantaneous.
331 t
.Error("Handler never saw CloseNotify")
333 case <-w
.(http
.CloseNotifier
).CloseNotify():
336 w
.WriteHeader(http
.StatusOK
)
337 w
.Write([]byte(backendResponse
))
340 defer backend
.Close()
342 backend
.Config
.ErrorLog
= log
.New(ioutil
.Discard
, "", 0)
344 backendURL
, err
:= url
.Parse(backend
.URL
)
349 proxyHandler
:= NewSingleHostReverseProxy(backendURL
)
351 // Discards errors of the form:
352 // http: proxy error: read tcp 127.0.0.1:44643: use of closed network connection
353 proxyHandler
.ErrorLog
= log
.New(ioutil
.Discard
, "", 0)
355 frontend
:= httptest
.NewServer(proxyHandler
)
356 defer frontend
.Close()
357 frontendClient
:= frontend
.Client()
359 getReq
, _
:= http
.NewRequest("GET", frontend
.URL
, nil)
362 frontendClient
.Transport
.(*http
.Transport
).CancelRequest(getReq
)
364 res
, err
:= frontendClient
.Do(getReq
)
366 t
.Errorf("got response %v; want nil", res
.Status
)
369 // This should be an error like:
370 // Get http://127.0.0.1:58079: read tcp 127.0.0.1:58079:
371 // use of closed network connection
372 t
.Error("Server.Client().Do() returned nil error; want non-nil error")
376 func req(t
*testing
.T
, v
string) *http
.Request
{
377 req
, err
:= http
.ReadRequest(bufio
.NewReader(strings
.NewReader(v
)))
385 func TestNilBody(t
*testing
.T
) {
386 backend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
387 w
.Write([]byte("hi"))
389 defer backend
.Close()
391 frontend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, _
*http
.Request
) {
392 backURL
, _
:= url
.Parse(backend
.URL
)
393 rp
:= NewSingleHostReverseProxy(backURL
)
394 r
:= req(t
, "GET / HTTP/1.0\r\n\r\n")
395 r
.Body
= nil // this accidentally worked in Go 1.4 and below, so keep it working
398 defer frontend
.Close()
400 res
, err
:= http
.Get(frontend
.URL
)
404 defer res
.Body
.Close()
405 slurp
, err
:= ioutil
.ReadAll(res
.Body
)
409 if string(slurp
) != "hi" {
410 t
.Errorf("Got %q; want %q", slurp
, "hi")
415 func TestUserAgentHeader(t
*testing
.T
) {
416 const explicitUA
= "explicit UA"
417 backend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
418 if r
.URL
.Path
== "/noua" {
419 if c
:= r
.Header
.Get("User-Agent"); c
!= "" {
420 t
.Errorf("handler got non-empty User-Agent header %q", c
)
424 if c
:= r
.Header
.Get("User-Agent"); c
!= explicitUA
{
425 t
.Errorf("handler got unexpected User-Agent header %q", c
)
428 defer backend
.Close()
429 backendURL
, err
:= url
.Parse(backend
.URL
)
433 proxyHandler
:= NewSingleHostReverseProxy(backendURL
)
434 proxyHandler
.ErrorLog
= log
.New(ioutil
.Discard
, "", 0) // quiet for tests
435 frontend
:= httptest
.NewServer(proxyHandler
)
436 defer frontend
.Close()
437 frontendClient
:= frontend
.Client()
439 getReq
, _
:= http
.NewRequest("GET", frontend
.URL
, nil)
440 getReq
.Header
.Set("User-Agent", explicitUA
)
442 res
, err
:= frontendClient
.Do(getReq
)
444 t
.Fatalf("Get: %v", err
)
448 getReq
, _
= http
.NewRequest("GET", frontend
.URL
+"/noua", nil)
449 getReq
.Header
.Set("User-Agent", "")
451 res
, err
= frontendClient
.Do(getReq
)
453 t
.Fatalf("Get: %v", err
)
458 type bufferPool
struct {
463 func (bp bufferPool
) Get() []byte { return bp
.get() }
464 func (bp bufferPool
) Put(v
[]byte) { bp
.put(v
) }
466 func TestReverseProxyGetPutBuffer(t
*testing
.T
) {
468 backend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
469 io
.WriteString(w
, msg
)
471 defer backend
.Close()
473 backendURL
, err
:= url
.Parse(backend
.URL
)
482 addLog
:= func(event
string) {
485 log
= append(log
, event
)
487 rp
:= NewSingleHostReverseProxy(backendURL
)
489 rp
.BufferPool
= bufferPool
{
492 return make([]byte, size
)
494 put
: func(p
[]byte) {
495 addLog("putBuf-" + strconv
.Itoa(len(p
)))
498 frontend
:= httptest
.NewServer(rp
)
499 defer frontend
.Close()
501 req
, _
:= http
.NewRequest("GET", frontend
.URL
, nil)
503 res
, err
:= frontend
.Client().Do(req
)
505 t
.Fatalf("Get: %v", err
)
507 slurp
, err
:= ioutil
.ReadAll(res
.Body
)
510 t
.Fatalf("reading body: %v", err
)
512 if string(slurp
) != msg
{
513 t
.Errorf("msg = %q; want %q", slurp
, msg
)
515 wantLog
:= []string{"getBuf", "putBuf-" + strconv
.Itoa(size
)}
518 if !reflect
.DeepEqual(log
, wantLog
) {
519 t
.Errorf("Log events = %q; want %q", log
, wantLog
)
523 func TestReverseProxy_Post(t
*testing
.T
) {
524 const backendResponse
= "I am the backend"
525 const backendStatus
= 200
526 var requestBody
= bytes
.Repeat([]byte("a"), 1<<20)
527 backend
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
528 slurp
, err
:= ioutil
.ReadAll(r
.Body
)
530 t
.Errorf("Backend body read = %v", err
)
532 if len(slurp
) != len(requestBody
) {
533 t
.Errorf("Backend read %d request body bytes; want %d", len(slurp
), len(requestBody
))
535 if !bytes
.Equal(slurp
, requestBody
) {
536 t
.Error("Backend read wrong request body.") // 1MB; omitting details
538 w
.Write([]byte(backendResponse
))
540 defer backend
.Close()
541 backendURL
, err
:= url
.Parse(backend
.URL
)
545 proxyHandler
:= NewSingleHostReverseProxy(backendURL
)
546 frontend
:= httptest
.NewServer(proxyHandler
)
547 defer frontend
.Close()
549 postReq
, _
:= http
.NewRequest("POST", frontend
.URL
, bytes
.NewReader(requestBody
))
550 res
, err
:= frontend
.Client().Do(postReq
)
552 t
.Fatalf("Do: %v", err
)
554 if g
, e
:= res
.StatusCode
, backendStatus
; g
!= e
{
555 t
.Errorf("got res.StatusCode %d; expected %d", g
, e
)
557 bodyBytes
, _
:= ioutil
.ReadAll(res
.Body
)
558 if g
, e
:= string(bodyBytes
), backendResponse
; g
!= e
{
559 t
.Errorf("got body %q; expected %q", g
, e
)
563 type RoundTripperFunc
func(*http
.Request
) (*http
.Response
, error
)
565 func (fn RoundTripperFunc
) RoundTrip(req
*http
.Request
) (*http
.Response
, error
) {
569 // Issue 16036: send a Request with a nil Body when possible
570 func TestReverseProxy_NilBody(t
*testing
.T
) {
571 backendURL
, _
:= url
.Parse("http://fake.tld/")
572 proxyHandler
:= NewSingleHostReverseProxy(backendURL
)
573 proxyHandler
.ErrorLog
= log
.New(ioutil
.Discard
, "", 0) // quiet for tests
574 proxyHandler
.Transport
= RoundTripperFunc(func(req
*http
.Request
) (*http
.Response
, error
) {
576 t
.Error("Body != nil; want a nil Body")
578 return nil, errors
.New("done testing the interesting part; so force a 502 Gateway error")
580 frontend
:= httptest
.NewServer(proxyHandler
)
581 defer frontend
.Close()
583 res
, err
:= frontend
.Client().Get(frontend
.URL
)
587 defer res
.Body
.Close()
588 if res
.StatusCode
!= 502 {
589 t
.Errorf("status code = %v; want 502 (Gateway Error)", res
.Status
)
593 // Issue 14237. Test ModifyResponse and that an error from it
594 // causes the proxy to return StatusBadGateway, or StatusOK otherwise.
595 func TestReverseProxyModifyResponse(t
*testing
.T
) {
596 backendServer
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
597 w
.Header().Add("X-Hit-Mod", fmt
.Sprintf("%v", r
.URL
.Path
== "/mod"))
599 defer backendServer
.Close()
601 rpURL
, _
:= url
.Parse(backendServer
.URL
)
602 rproxy
:= NewSingleHostReverseProxy(rpURL
)
603 rproxy
.ErrorLog
= log
.New(ioutil
.Discard
, "", 0) // quiet for tests
604 rproxy
.ModifyResponse
= func(resp
*http
.Response
) error
{
605 if resp
.Header
.Get("X-Hit-Mod") != "true" {
606 return fmt
.Errorf("tried to by-pass proxy")
611 frontendProxy
:= httptest
.NewServer(rproxy
)
612 defer frontendProxy
.Close()
618 {frontendProxy
.URL
+ "/mod", http
.StatusOK
},
619 {frontendProxy
.URL
+ "/schedule", http
.StatusBadGateway
},
622 for i
, tt
:= range tests
{
623 resp
, err
:= http
.Get(tt
.url
)
625 t
.Fatalf("failed to reach proxy: %v", err
)
627 if g
, e
:= resp
.StatusCode
, tt
.wantCode
; g
!= e
{
628 t
.Errorf("#%d: got res.StatusCode %d; expected %d", i
, g
, e
)
634 // Issue 16659: log errors from short read
635 func TestReverseProxy_CopyBuffer(t
*testing
.T
) {
636 backendServer
:= httptest
.NewServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
637 out
:= "this call was relayed by the reverse proxy"
638 // Coerce a wrong content length to induce io.UnexpectedEOF
639 w
.Header().Set("Content-Length", fmt
.Sprintf("%d", len(out
)*2))
642 defer backendServer
.Close()
644 rpURL
, err
:= url
.Parse(backendServer
.URL
)
649 var proxyLog bytes
.Buffer
650 rproxy
:= NewSingleHostReverseProxy(rpURL
)
651 rproxy
.ErrorLog
= log
.New(&proxyLog
, "", log
.Lshortfile
)
652 frontendProxy
:= httptest
.NewServer(rproxy
)
653 defer frontendProxy
.Close()
655 resp
, err
:= http
.Get(frontendProxy
.URL
)
657 t
.Fatalf("failed to reach proxy: %v", err
)
659 defer resp
.Body
.Close()
661 if _
, err
:= ioutil
.ReadAll(resp
.Body
); err
== nil {
662 t
.Fatalf("want non-nil error")
664 expected
:= []string{
668 for _
, phrase
:= range expected
{
669 if !bytes
.Contains(proxyLog
.Bytes(), []byte(phrase
)) {
670 t
.Errorf("expected log to contain phrase %q", phrase
)
675 type staticTransport
struct {
679 func (t
*staticTransport
) RoundTrip(r
*http
.Request
) (*http
.Response
, error
) {
683 func BenchmarkServeHTTP(b
*testing
.B
) {
684 res
:= &http
.Response
{
686 Body
: ioutil
.NopCloser(strings
.NewReader("")),
688 proxy
:= &ReverseProxy
{
689 Director
: func(*http
.Request
) {},
690 Transport
: &staticTransport
{res
},
693 w
:= httptest
.NewRecorder()
694 r
:= httptest
.NewRequest("GET", "/", nil)
697 for i
:= 0; i
< b
.N
; i
++ {
698 proxy
.ServeHTTP(w
, r
)
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!"))
706 defer backend
.Close()
707 backendURL
, err
:= url
.Parse(backend
.URL
)
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
}
724 defer frontend
.Close()
726 want
:= result
{before
: "/", after
: "/"}
728 res
, err
:= frontend
.Client().Get(frontend
.URL
)
730 t
.Fatalf("Do: %v", err
)
736 t
.Errorf("got = %+v; want = %+v", got
, want
)
740 // Issue 18327: verify we always do a deep copy of the Request.Header map
741 // before any mutations.
742 func TestClonesRequestHeaders(t
*testing
.T
) {
743 req
, _
:= http
.NewRequest("GET", "http://foo.tld/", nil)
744 req
.RemoteAddr
= "1.2.3.4:56789"
746 Director
: func(req
*http
.Request
) {
747 req
.Header
.Set("From-Director", "1")
749 Transport
: roundTripperFunc(func(req
*http
.Request
) (*http
.Response
, error
) {
750 if v
:= req
.Header
.Get("From-Director"); v
!= "1" {
751 t
.Errorf("From-Directory value = %q; want 1", v
)
756 rp
.ServeHTTP(httptest
.NewRecorder(), req
)
758 if req
.Header
.Get("From-Director") == "1" {
759 t
.Error("Director header mutation modified caller's request")
761 if req
.Header
.Get("X-Forwarded-For") != "" {
762 t
.Error("X-Forward-For header mutation modified caller's request")
767 type roundTripperFunc
func(req
*http
.Request
) (*http
.Response
, error
)
769 func (fn roundTripperFunc
) RoundTrip(req
*http
.Request
) (*http
.Response
, error
) {