github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/src/net/http/httputil/reverseproxy_test.go (about) 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. 4 5 // Reverse proxy tests. 6 7 package httputil 8 9 import ( 10 "bufio" 11 "bytes" 12 "errors" 13 "fmt" 14 "io" 15 "io/ioutil" 16 "log" 17 "net/http" 18 "net/http/httptest" 19 "net/url" 20 "reflect" 21 "strconv" 22 "strings" 23 "sync" 24 "testing" 25 "time" 26 ) 27 28 const fakeHopHeader = "X-Fake-Hop-Header-For-Test" 29 30 func init() { 31 hopHeaders = append(hopHeaders, fakeHopHeader) 32 } 33 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() 40 c.Close() 41 return 42 } 43 if len(r.TransferEncoding) > 0 { 44 t.Errorf("backend got unexpected TransferEncoding: %v", r.TransferEncoding) 45 } 46 if r.Header.Get("X-Forwarded-For") == "" { 47 t.Errorf("didn't get X-Forwarded-For header") 48 } 49 if c := r.Header.Get("Connection"); c != "" { 50 t.Errorf("handler got Connection header value %q", c) 51 } 52 if c := r.Header.Get("Upgrade"); c != "" { 53 t.Errorf("handler got Upgrade header value %q", c) 54 } 55 if c := r.Header.Get("Proxy-Connection"); c != "" { 56 t.Errorf("handler got Proxy-Connection header value %q", c) 57 } 58 if g, e := r.Host, "some-name"; g != e { 59 t.Errorf("backend got Host header %q, want %q", g, e) 60 } 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 })) 73 defer backend.Close() 74 backendURL, err := url.Parse(backend.URL) 75 if err != nil { 76 t.Fatal(err) 77 } 78 proxyHandler := NewSingleHostReverseProxy(backendURL) 79 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 80 frontend := httptest.NewServer(proxyHandler) 81 defer frontend.Close() 82 83 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 84 getReq.Host = "some-name" 85 getReq.Header.Set("Connection", "close") 86 getReq.Header.Set("Proxy-Connection", "should be deleted") 87 getReq.Header.Set("Upgrade", "foo") 88 getReq.Close = true 89 res, err := http.DefaultClient.Do(getReq) 90 if err != nil { 91 t.Fatalf("Get: %v", err) 92 } 93 if g, e := res.StatusCode, backendStatus; g != e { 94 t.Errorf("got res.StatusCode %d; expected %d", g, e) 95 } 96 if g, e := res.Header.Get("X-Foo"), "bar"; g != e { 97 t.Errorf("got X-Foo %q; expected %q", g, e) 98 } 99 if c := res.Header.Get(fakeHopHeader); c != "" { 100 t.Errorf("got %s header value %q", fakeHopHeader, c) 101 } 102 if g, e := res.Header.Get("Trailers"), "not a special header field name"; g != e { 103 t.Errorf("header Trailers = %q; want %q", g, e) 104 } 105 if g, e := len(res.Header["X-Multi-Value"]), 2; g != e { 106 t.Errorf("got %d X-Multi-Value header values; expected %d", g, e) 107 } 108 if g, e := len(res.Header["Set-Cookie"]), 1; g != e { 109 t.Fatalf("got %d SetCookies, want %d", g, e) 110 } 111 if g, e := res.Trailer, (http.Header{"X-Trailer": nil}); !reflect.DeepEqual(g, e) { 112 t.Errorf("before reading body, Trailer = %#v; want %#v", g, e) 113 } 114 if cookie := res.Cookies()[0]; cookie.Name != "flavor" { 115 t.Errorf("unexpected cookie %q", cookie.Name) 116 } 117 bodyBytes, _ := ioutil.ReadAll(res.Body) 118 if g, e := string(bodyBytes), backendResponse; g != e { 119 t.Errorf("got body %q; expected %q", g, e) 120 } 121 if g, e := res.Trailer.Get("X-Trailer"), "trailer_value"; g != e { 122 t.Errorf("Trailer(X-Trailer) = %q ; want %q", g, e) 123 } 124 125 // Test that a backend failing to be reached or one which doesn't return 126 // a response results in a StatusBadGateway. 127 getReq, _ = http.NewRequest("GET", frontend.URL+"/?mode=hangup", nil) 128 getReq.Close = true 129 res, err = http.DefaultClient.Do(getReq) 130 if err != nil { 131 t.Fatal(err) 132 } 133 res.Body.Close() 134 if res.StatusCode != http.StatusBadGateway { 135 t.Errorf("request to bad proxy = %v; want 502 StatusBadGateway", res.Status) 136 } 137 138 } 139 140 // Issue 16875: remove any proxied headers mentioned in the "Connection" 141 // header value. 142 func TestReverseProxyStripHeadersPresentInConnection(t *testing.T) { 143 const fakeConnectionToken = "X-Fake-Connection-Token" 144 const backendResponse = "I am the backend" 145 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 146 if c := r.Header.Get(fakeConnectionToken); c != "" { 147 t.Errorf("handler got header %q = %q; want empty", fakeConnectionToken, c) 148 } 149 if c := r.Header.Get("Upgrade"); c != "" { 150 t.Errorf("handler got header %q = %q; want empty", "Upgrade", c) 151 } 152 w.Header().Set("Connection", "Upgrade, "+fakeConnectionToken) 153 w.Header().Set("Upgrade", "should be deleted") 154 w.Header().Set(fakeConnectionToken, "should be deleted") 155 io.WriteString(w, backendResponse) 156 })) 157 defer backend.Close() 158 backendURL, err := url.Parse(backend.URL) 159 if err != nil { 160 t.Fatal(err) 161 } 162 proxyHandler := NewSingleHostReverseProxy(backendURL) 163 frontend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 164 proxyHandler.ServeHTTP(w, r) 165 if c := r.Header.Get("Upgrade"); c != "original value" { 166 t.Errorf("handler modified header %q = %q; want %q", "Upgrade", c, "original value") 167 } 168 })) 169 defer frontend.Close() 170 171 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 172 getReq.Header.Set("Connection", "Upgrade, "+fakeConnectionToken) 173 getReq.Header.Set("Upgrade", "original value") 174 getReq.Header.Set(fakeConnectionToken, "should be deleted") 175 res, err := http.DefaultClient.Do(getReq) 176 if err != nil { 177 t.Fatalf("Get: %v", err) 178 } 179 defer res.Body.Close() 180 bodyBytes, err := ioutil.ReadAll(res.Body) 181 if err != nil { 182 t.Fatalf("reading body: %v", err) 183 } 184 if got, want := string(bodyBytes), backendResponse; got != want { 185 t.Errorf("got body %q; want %q", got, want) 186 } 187 if c := res.Header.Get("Upgrade"); c != "" { 188 t.Errorf("handler got header %q = %q; want empty", "Upgrade", c) 189 } 190 if c := res.Header.Get(fakeConnectionToken); c != "" { 191 t.Errorf("handler got header %q = %q; want empty", fakeConnectionToken, c) 192 } 193 } 194 195 func TestXForwardedFor(t *testing.T) { 196 const prevForwardedFor = "client ip" 197 const backendResponse = "I am the backend" 198 const backendStatus = 404 199 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 200 if r.Header.Get("X-Forwarded-For") == "" { 201 t.Errorf("didn't get X-Forwarded-For header") 202 } 203 if !strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) { 204 t.Errorf("X-Forwarded-For didn't contain prior data") 205 } 206 w.WriteHeader(backendStatus) 207 w.Write([]byte(backendResponse)) 208 })) 209 defer backend.Close() 210 backendURL, err := url.Parse(backend.URL) 211 if err != nil { 212 t.Fatal(err) 213 } 214 proxyHandler := NewSingleHostReverseProxy(backendURL) 215 frontend := httptest.NewServer(proxyHandler) 216 defer frontend.Close() 217 218 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 219 getReq.Host = "some-name" 220 getReq.Header.Set("Connection", "close") 221 getReq.Header.Set("X-Forwarded-For", prevForwardedFor) 222 getReq.Close = true 223 res, err := http.DefaultClient.Do(getReq) 224 if err != nil { 225 t.Fatalf("Get: %v", err) 226 } 227 if g, e := res.StatusCode, backendStatus; g != e { 228 t.Errorf("got res.StatusCode %d; expected %d", g, e) 229 } 230 bodyBytes, _ := ioutil.ReadAll(res.Body) 231 if g, e := string(bodyBytes), backendResponse; g != e { 232 t.Errorf("got body %q; expected %q", g, e) 233 } 234 } 235 236 var proxyQueryTests = []struct { 237 baseSuffix string // suffix to add to backend URL 238 reqSuffix string // suffix to add to frontend's request URL 239 want string // what backend should see for final request URL (without ?) 240 }{ 241 {"", "", ""}, 242 {"?sta=tic", "?us=er", "sta=tic&us=er"}, 243 {"", "?us=er", "us=er"}, 244 {"?sta=tic", "", "sta=tic"}, 245 } 246 247 func TestReverseProxyQuery(t *testing.T) { 248 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 249 w.Header().Set("X-Got-Query", r.URL.RawQuery) 250 w.Write([]byte("hi")) 251 })) 252 defer backend.Close() 253 254 for i, tt := range proxyQueryTests { 255 backendURL, err := url.Parse(backend.URL + tt.baseSuffix) 256 if err != nil { 257 t.Fatal(err) 258 } 259 frontend := httptest.NewServer(NewSingleHostReverseProxy(backendURL)) 260 req, _ := http.NewRequest("GET", frontend.URL+tt.reqSuffix, nil) 261 req.Close = true 262 res, err := http.DefaultClient.Do(req) 263 if err != nil { 264 t.Fatalf("%d. Get: %v", i, err) 265 } 266 if g, e := res.Header.Get("X-Got-Query"), tt.want; g != e { 267 t.Errorf("%d. got query %q; expected %q", i, g, e) 268 } 269 res.Body.Close() 270 frontend.Close() 271 } 272 } 273 274 func TestReverseProxyFlushInterval(t *testing.T) { 275 const expected = "hi" 276 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 277 w.Write([]byte(expected)) 278 })) 279 defer backend.Close() 280 281 backendURL, err := url.Parse(backend.URL) 282 if err != nil { 283 t.Fatal(err) 284 } 285 286 proxyHandler := NewSingleHostReverseProxy(backendURL) 287 proxyHandler.FlushInterval = time.Microsecond 288 289 done := make(chan bool) 290 onExitFlushLoop = func() { done <- true } 291 defer func() { onExitFlushLoop = nil }() 292 293 frontend := httptest.NewServer(proxyHandler) 294 defer frontend.Close() 295 296 req, _ := http.NewRequest("GET", frontend.URL, nil) 297 req.Close = true 298 res, err := http.DefaultClient.Do(req) 299 if err != nil { 300 t.Fatalf("Get: %v", err) 301 } 302 defer res.Body.Close() 303 if bodyBytes, _ := ioutil.ReadAll(res.Body); string(bodyBytes) != expected { 304 t.Errorf("got body %q; expected %q", bodyBytes, expected) 305 } 306 307 select { 308 case <-done: 309 // OK 310 case <-time.After(5 * time.Second): 311 t.Error("maxLatencyWriter flushLoop() never exited") 312 } 313 } 314 315 func TestReverseProxyCancelation(t *testing.T) { 316 const backendResponse = "I am the backend" 317 318 reqInFlight := make(chan struct{}) 319 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 320 close(reqInFlight) // cause the client to cancel its request 321 322 select { 323 case <-time.After(10 * time.Second): 324 // Note: this should only happen in broken implementations, and the 325 // closenotify case should be instantaneous. 326 t.Error("Handler never saw CloseNotify") 327 return 328 case <-w.(http.CloseNotifier).CloseNotify(): 329 } 330 331 w.WriteHeader(http.StatusOK) 332 w.Write([]byte(backendResponse)) 333 })) 334 335 defer backend.Close() 336 337 backend.Config.ErrorLog = log.New(ioutil.Discard, "", 0) 338 339 backendURL, err := url.Parse(backend.URL) 340 if err != nil { 341 t.Fatal(err) 342 } 343 344 proxyHandler := NewSingleHostReverseProxy(backendURL) 345 346 // Discards errors of the form: 347 // http: proxy error: read tcp 127.0.0.1:44643: use of closed network connection 348 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) 349 350 frontend := httptest.NewServer(proxyHandler) 351 defer frontend.Close() 352 353 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 354 go func() { 355 <-reqInFlight 356 http.DefaultTransport.(*http.Transport).CancelRequest(getReq) 357 }() 358 res, err := http.DefaultClient.Do(getReq) 359 if res != nil { 360 t.Errorf("got response %v; want nil", res.Status) 361 } 362 if err == nil { 363 // This should be an error like: 364 // Get http://127.0.0.1:58079: read tcp 127.0.0.1:58079: 365 // use of closed network connection 366 t.Error("DefaultClient.Do() returned nil error; want non-nil error") 367 } 368 } 369 370 func req(t *testing.T, v string) *http.Request { 371 req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(v))) 372 if err != nil { 373 t.Fatal(err) 374 } 375 return req 376 } 377 378 // Issue 12344 379 func TestNilBody(t *testing.T) { 380 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 381 w.Write([]byte("hi")) 382 })) 383 defer backend.Close() 384 385 frontend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 386 backURL, _ := url.Parse(backend.URL) 387 rp := NewSingleHostReverseProxy(backURL) 388 r := req(t, "GET / HTTP/1.0\r\n\r\n") 389 r.Body = nil // this accidentally worked in Go 1.4 and below, so keep it working 390 rp.ServeHTTP(w, r) 391 })) 392 defer frontend.Close() 393 394 res, err := http.Get(frontend.URL) 395 if err != nil { 396 t.Fatal(err) 397 } 398 defer res.Body.Close() 399 slurp, err := ioutil.ReadAll(res.Body) 400 if err != nil { 401 t.Fatal(err) 402 } 403 if string(slurp) != "hi" { 404 t.Errorf("Got %q; want %q", slurp, "hi") 405 } 406 } 407 408 // Issue 15524 409 func TestUserAgentHeader(t *testing.T) { 410 const explicitUA = "explicit UA" 411 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 412 if r.URL.Path == "/noua" { 413 if c := r.Header.Get("User-Agent"); c != "" { 414 t.Errorf("handler got non-empty User-Agent header %q", c) 415 } 416 return 417 } 418 if c := r.Header.Get("User-Agent"); c != explicitUA { 419 t.Errorf("handler got unexpected User-Agent header %q", c) 420 } 421 })) 422 defer backend.Close() 423 backendURL, err := url.Parse(backend.URL) 424 if err != nil { 425 t.Fatal(err) 426 } 427 proxyHandler := NewSingleHostReverseProxy(backendURL) 428 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 429 frontend := httptest.NewServer(proxyHandler) 430 defer frontend.Close() 431 432 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 433 getReq.Header.Set("User-Agent", explicitUA) 434 getReq.Close = true 435 res, err := http.DefaultClient.Do(getReq) 436 if err != nil { 437 t.Fatalf("Get: %v", err) 438 } 439 res.Body.Close() 440 441 getReq, _ = http.NewRequest("GET", frontend.URL+"/noua", nil) 442 getReq.Header.Set("User-Agent", "") 443 getReq.Close = true 444 res, err = http.DefaultClient.Do(getReq) 445 if err != nil { 446 t.Fatalf("Get: %v", err) 447 } 448 res.Body.Close() 449 } 450 451 type bufferPool struct { 452 get func() []byte 453 put func([]byte) 454 } 455 456 func (bp bufferPool) Get() []byte { return bp.get() } 457 func (bp bufferPool) Put(v []byte) { bp.put(v) } 458 459 func TestReverseProxyGetPutBuffer(t *testing.T) { 460 const msg = "hi" 461 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 462 io.WriteString(w, msg) 463 })) 464 defer backend.Close() 465 466 backendURL, err := url.Parse(backend.URL) 467 if err != nil { 468 t.Fatal(err) 469 } 470 471 var ( 472 mu sync.Mutex 473 log []string 474 ) 475 addLog := func(event string) { 476 mu.Lock() 477 defer mu.Unlock() 478 log = append(log, event) 479 } 480 rp := NewSingleHostReverseProxy(backendURL) 481 const size = 1234 482 rp.BufferPool = bufferPool{ 483 get: func() []byte { 484 addLog("getBuf") 485 return make([]byte, size) 486 }, 487 put: func(p []byte) { 488 addLog("putBuf-" + strconv.Itoa(len(p))) 489 }, 490 } 491 frontend := httptest.NewServer(rp) 492 defer frontend.Close() 493 494 req, _ := http.NewRequest("GET", frontend.URL, nil) 495 req.Close = true 496 res, err := http.DefaultClient.Do(req) 497 if err != nil { 498 t.Fatalf("Get: %v", err) 499 } 500 slurp, err := ioutil.ReadAll(res.Body) 501 res.Body.Close() 502 if err != nil { 503 t.Fatalf("reading body: %v", err) 504 } 505 if string(slurp) != msg { 506 t.Errorf("msg = %q; want %q", slurp, msg) 507 } 508 wantLog := []string{"getBuf", "putBuf-" + strconv.Itoa(size)} 509 mu.Lock() 510 defer mu.Unlock() 511 if !reflect.DeepEqual(log, wantLog) { 512 t.Errorf("Log events = %q; want %q", log, wantLog) 513 } 514 } 515 516 func TestReverseProxy_Post(t *testing.T) { 517 const backendResponse = "I am the backend" 518 const backendStatus = 200 519 var requestBody = bytes.Repeat([]byte("a"), 1<<20) 520 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 521 slurp, err := ioutil.ReadAll(r.Body) 522 if err != nil { 523 t.Errorf("Backend body read = %v", err) 524 } 525 if len(slurp) != len(requestBody) { 526 t.Errorf("Backend read %d request body bytes; want %d", len(slurp), len(requestBody)) 527 } 528 if !bytes.Equal(slurp, requestBody) { 529 t.Error("Backend read wrong request body.") // 1MB; omitting details 530 } 531 w.Write([]byte(backendResponse)) 532 })) 533 defer backend.Close() 534 backendURL, err := url.Parse(backend.URL) 535 if err != nil { 536 t.Fatal(err) 537 } 538 proxyHandler := NewSingleHostReverseProxy(backendURL) 539 frontend := httptest.NewServer(proxyHandler) 540 defer frontend.Close() 541 542 postReq, _ := http.NewRequest("POST", frontend.URL, bytes.NewReader(requestBody)) 543 res, err := http.DefaultClient.Do(postReq) 544 if err != nil { 545 t.Fatalf("Do: %v", err) 546 } 547 if g, e := res.StatusCode, backendStatus; g != e { 548 t.Errorf("got res.StatusCode %d; expected %d", g, e) 549 } 550 bodyBytes, _ := ioutil.ReadAll(res.Body) 551 if g, e := string(bodyBytes), backendResponse; g != e { 552 t.Errorf("got body %q; expected %q", g, e) 553 } 554 } 555 556 type RoundTripperFunc func(*http.Request) (*http.Response, error) 557 558 func (fn RoundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { 559 return fn(req) 560 } 561 562 // Issue 16036: send a Request with a nil Body when possible 563 func TestReverseProxy_NilBody(t *testing.T) { 564 backendURL, _ := url.Parse("http://fake.tld/") 565 proxyHandler := NewSingleHostReverseProxy(backendURL) 566 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 567 proxyHandler.Transport = RoundTripperFunc(func(req *http.Request) (*http.Response, error) { 568 if req.Body != nil { 569 t.Error("Body != nil; want a nil Body") 570 } 571 return nil, errors.New("done testing the interesting part; so force a 502 Gateway error") 572 }) 573 frontend := httptest.NewServer(proxyHandler) 574 defer frontend.Close() 575 576 res, err := http.DefaultClient.Get(frontend.URL) 577 if err != nil { 578 t.Fatal(err) 579 } 580 defer res.Body.Close() 581 if res.StatusCode != 502 { 582 t.Errorf("status code = %v; want 502 (Gateway Error)", res.Status) 583 } 584 } 585 586 // Issue 14237. Test ModifyResponse and that an error from it 587 // causes the proxy to return StatusBadGateway, or StatusOK otherwise. 588 func TestReverseProxyModifyResponse(t *testing.T) { 589 backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 590 w.Header().Add("X-Hit-Mod", fmt.Sprintf("%v", r.URL.Path == "/mod")) 591 })) 592 defer backendServer.Close() 593 594 rpURL, _ := url.Parse(backendServer.URL) 595 rproxy := NewSingleHostReverseProxy(rpURL) 596 rproxy.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 597 rproxy.ModifyResponse = func(resp *http.Response) error { 598 if resp.Header.Get("X-Hit-Mod") != "true" { 599 return fmt.Errorf("tried to by-pass proxy") 600 } 601 return nil 602 } 603 604 frontendProxy := httptest.NewServer(rproxy) 605 defer frontendProxy.Close() 606 607 tests := []struct { 608 url string 609 wantCode int 610 }{ 611 {frontendProxy.URL + "/mod", http.StatusOK}, 612 {frontendProxy.URL + "/schedule", http.StatusBadGateway}, 613 } 614 615 for i, tt := range tests { 616 resp, err := http.Get(tt.url) 617 if err != nil { 618 t.Fatalf("failed to reach proxy: %v", err) 619 } 620 if g, e := resp.StatusCode, tt.wantCode; g != e { 621 t.Errorf("#%d: got res.StatusCode %d; expected %d", i, g, e) 622 } 623 resp.Body.Close() 624 } 625 } 626 627 // Issue 16659: log errors from short read 628 func TestReverseProxy_CopyBuffer(t *testing.T) { 629 backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 630 out := "this call was relayed by the reverse proxy" 631 // Coerce a wrong content length to induce io.UnexpectedEOF 632 w.Header().Set("Content-Length", fmt.Sprintf("%d", len(out)*2)) 633 fmt.Fprintln(w, out) 634 })) 635 defer backendServer.Close() 636 637 rpURL, err := url.Parse(backendServer.URL) 638 if err != nil { 639 t.Fatal(err) 640 } 641 642 var proxyLog bytes.Buffer 643 rproxy := NewSingleHostReverseProxy(rpURL) 644 rproxy.ErrorLog = log.New(&proxyLog, "", log.Lshortfile) 645 frontendProxy := httptest.NewServer(rproxy) 646 defer frontendProxy.Close() 647 648 resp, err := http.Get(frontendProxy.URL) 649 if err != nil { 650 t.Fatalf("failed to reach proxy: %v", err) 651 } 652 defer resp.Body.Close() 653 654 if _, err := ioutil.ReadAll(resp.Body); err == nil { 655 t.Fatalf("want non-nil error") 656 } 657 expected := []string{ 658 "EOF", 659 "read", 660 } 661 for _, phrase := range expected { 662 if !bytes.Contains(proxyLog.Bytes(), []byte(phrase)) { 663 t.Errorf("expected log to contain phrase %q", phrase) 664 } 665 } 666 } 667 668 type staticTransport struct { 669 res *http.Response 670 } 671 672 func (t *staticTransport) RoundTrip(r *http.Request) (*http.Response, error) { 673 return t.res, nil 674 } 675 676 func BenchmarkServeHTTP(b *testing.B) { 677 res := &http.Response{ 678 StatusCode: 200, 679 Body: ioutil.NopCloser(strings.NewReader("")), 680 } 681 proxy := &ReverseProxy{ 682 Director: func(*http.Request) {}, 683 Transport: &staticTransport{res}, 684 } 685 686 w := httptest.NewRecorder() 687 r := httptest.NewRequest("GET", "/", nil) 688 689 b.ReportAllocs() 690 for i := 0; i < b.N; i++ { 691 proxy.ServeHTTP(w, r) 692 } 693 }