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