github.com/zxy12/go_duplicate_112_new@v0.0.0-20200807091221-747231827200/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 "context" 13 "errors" 14 "fmt" 15 "io" 16 "io/ioutil" 17 "log" 18 "net/http" 19 "net/http/httptest" 20 "net/url" 21 "os" 22 "reflect" 23 "strconv" 24 "strings" 25 "sync" 26 "testing" 27 "time" 28 ) 29 30 const fakeHopHeader = "X-Fake-Hop-Header-For-Test" 31 32 func init() { 33 inOurTests = true 34 hopHeaders = append(hopHeaders, fakeHopHeader) 35 } 36 37 func TestReverseProxy(t *testing.T) { 38 const backendResponse = "I am the backend" 39 const backendStatus = 404 40 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 41 if r.Method == "GET" && r.FormValue("mode") == "hangup" { 42 c, _, _ := w.(http.Hijacker).Hijack() 43 c.Close() 44 return 45 } 46 if len(r.TransferEncoding) > 0 { 47 t.Errorf("backend got unexpected TransferEncoding: %v", r.TransferEncoding) 48 } 49 if r.Header.Get("X-Forwarded-For") == "" { 50 t.Errorf("didn't get X-Forwarded-For header") 51 } 52 if c := r.Header.Get("Connection"); c != "" { 53 t.Errorf("handler got Connection header value %q", c) 54 } 55 if c := r.Header.Get("Te"); c != "trailers" { 56 t.Errorf("handler got Te header value %q; want 'trailers'", c) 57 } 58 if c := r.Header.Get("Upgrade"); c != "" { 59 t.Errorf("handler got Upgrade header value %q", c) 60 } 61 if c := r.Header.Get("Proxy-Connection"); c != "" { 62 t.Errorf("handler got Proxy-Connection header value %q", c) 63 } 64 if g, e := r.Host, "some-name"; g != e { 65 t.Errorf("backend got Host header %q, want %q", g, e) 66 } 67 w.Header().Set("Trailers", "not a special header field name") 68 w.Header().Set("Trailer", "X-Trailer") 69 w.Header().Set("X-Foo", "bar") 70 w.Header().Set("Upgrade", "foo") 71 w.Header().Set(fakeHopHeader, "foo") 72 w.Header().Add("X-Multi-Value", "foo") 73 w.Header().Add("X-Multi-Value", "bar") 74 http.SetCookie(w, &http.Cookie{Name: "flavor", Value: "chocolateChip"}) 75 w.WriteHeader(backendStatus) 76 w.Write([]byte(backendResponse)) 77 w.Header().Set("X-Trailer", "trailer_value") 78 w.Header().Set(http.TrailerPrefix+"X-Unannounced-Trailer", "unannounced_trailer_value") 79 })) 80 defer backend.Close() 81 backendURL, err := url.Parse(backend.URL) 82 if err != nil { 83 t.Fatal(err) 84 } 85 proxyHandler := NewSingleHostReverseProxy(backendURL) 86 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 87 frontend := httptest.NewServer(proxyHandler) 88 defer frontend.Close() 89 frontendClient := frontend.Client() 90 91 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 92 getReq.Host = "some-name" 93 getReq.Header.Set("Connection", "close") 94 getReq.Header.Set("Te", "trailers") 95 getReq.Header.Set("Proxy-Connection", "should be deleted") 96 getReq.Header.Set("Upgrade", "foo") 97 getReq.Close = true 98 res, err := frontendClient.Do(getReq) 99 if err != nil { 100 t.Fatalf("Get: %v", err) 101 } 102 if g, e := res.StatusCode, backendStatus; g != e { 103 t.Errorf("got res.StatusCode %d; expected %d", g, e) 104 } 105 if g, e := res.Header.Get("X-Foo"), "bar"; g != e { 106 t.Errorf("got X-Foo %q; expected %q", g, e) 107 } 108 if c := res.Header.Get(fakeHopHeader); c != "" { 109 t.Errorf("got %s header value %q", fakeHopHeader, c) 110 } 111 if g, e := res.Header.Get("Trailers"), "not a special header field name"; g != e { 112 t.Errorf("header Trailers = %q; want %q", g, e) 113 } 114 if g, e := len(res.Header["X-Multi-Value"]), 2; g != e { 115 t.Errorf("got %d X-Multi-Value header values; expected %d", g, e) 116 } 117 if g, e := len(res.Header["Set-Cookie"]), 1; g != e { 118 t.Fatalf("got %d SetCookies, want %d", g, e) 119 } 120 if g, e := res.Trailer, (http.Header{"X-Trailer": nil}); !reflect.DeepEqual(g, e) { 121 t.Errorf("before reading body, Trailer = %#v; want %#v", g, e) 122 } 123 if cookie := res.Cookies()[0]; cookie.Name != "flavor" { 124 t.Errorf("unexpected cookie %q", cookie.Name) 125 } 126 bodyBytes, _ := ioutil.ReadAll(res.Body) 127 if g, e := string(bodyBytes), backendResponse; g != e { 128 t.Errorf("got body %q; expected %q", g, e) 129 } 130 if g, e := res.Trailer.Get("X-Trailer"), "trailer_value"; g != e { 131 t.Errorf("Trailer(X-Trailer) = %q ; want %q", g, e) 132 } 133 if g, e := res.Trailer.Get("X-Unannounced-Trailer"), "unannounced_trailer_value"; g != e { 134 t.Errorf("Trailer(X-Unannounced-Trailer) = %q ; want %q", g, e) 135 } 136 137 // Test that a backend failing to be reached or one which doesn't return 138 // a response results in a StatusBadGateway. 139 getReq, _ = http.NewRequest("GET", frontend.URL+"/?mode=hangup", nil) 140 getReq.Close = true 141 res, err = frontendClient.Do(getReq) 142 if err != nil { 143 t.Fatal(err) 144 } 145 res.Body.Close() 146 if res.StatusCode != http.StatusBadGateway { 147 t.Errorf("request to bad proxy = %v; want 502 StatusBadGateway", res.Status) 148 } 149 150 } 151 152 // Issue 16875: remove any proxied headers mentioned in the "Connection" 153 // header value. 154 func TestReverseProxyStripHeadersPresentInConnection(t *testing.T) { 155 const fakeConnectionToken = "X-Fake-Connection-Token" 156 const backendResponse = "I am the backend" 157 158 // someConnHeader is some arbitrary header to be declared as a hop-by-hop header 159 // in the Request's Connection header. 160 const someConnHeader = "X-Some-Conn-Header" 161 162 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 163 if c := r.Header.Get(fakeConnectionToken); c != "" { 164 t.Errorf("handler got header %q = %q; want empty", fakeConnectionToken, c) 165 } 166 if c := r.Header.Get(someConnHeader); c != "" { 167 t.Errorf("handler got header %q = %q; want empty", someConnHeader, c) 168 } 169 w.Header().Set("Connection", someConnHeader+", "+fakeConnectionToken) 170 w.Header().Set(someConnHeader, "should be deleted") 171 w.Header().Set(fakeConnectionToken, "should be deleted") 172 io.WriteString(w, backendResponse) 173 })) 174 defer backend.Close() 175 backendURL, err := url.Parse(backend.URL) 176 if err != nil { 177 t.Fatal(err) 178 } 179 proxyHandler := NewSingleHostReverseProxy(backendURL) 180 frontend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 181 proxyHandler.ServeHTTP(w, r) 182 if c := r.Header.Get(someConnHeader); c != "original value" { 183 t.Errorf("handler modified header %q = %q; want %q", someConnHeader, c, "original value") 184 } 185 })) 186 defer frontend.Close() 187 188 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 189 getReq.Header.Set("Connection", someConnHeader+", "+fakeConnectionToken) 190 getReq.Header.Set(someConnHeader, "original value") 191 getReq.Header.Set(fakeConnectionToken, "should be deleted") 192 res, err := frontend.Client().Do(getReq) 193 if err != nil { 194 t.Fatalf("Get: %v", err) 195 } 196 defer res.Body.Close() 197 bodyBytes, err := ioutil.ReadAll(res.Body) 198 if err != nil { 199 t.Fatalf("reading body: %v", err) 200 } 201 if got, want := string(bodyBytes), backendResponse; got != want { 202 t.Errorf("got body %q; want %q", got, want) 203 } 204 if c := res.Header.Get(someConnHeader); c != "" { 205 t.Errorf("handler got header %q = %q; want empty", someConnHeader, c) 206 } 207 if c := res.Header.Get(fakeConnectionToken); c != "" { 208 t.Errorf("handler got header %q = %q; want empty", fakeConnectionToken, c) 209 } 210 } 211 212 func TestXForwardedFor(t *testing.T) { 213 const prevForwardedFor = "client ip" 214 const backendResponse = "I am the backend" 215 const backendStatus = 404 216 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 217 if r.Header.Get("X-Forwarded-For") == "" { 218 t.Errorf("didn't get X-Forwarded-For header") 219 } 220 if !strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) { 221 t.Errorf("X-Forwarded-For didn't contain prior data") 222 } 223 w.WriteHeader(backendStatus) 224 w.Write([]byte(backendResponse)) 225 })) 226 defer backend.Close() 227 backendURL, err := url.Parse(backend.URL) 228 if err != nil { 229 t.Fatal(err) 230 } 231 proxyHandler := NewSingleHostReverseProxy(backendURL) 232 frontend := httptest.NewServer(proxyHandler) 233 defer frontend.Close() 234 235 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 236 getReq.Host = "some-name" 237 getReq.Header.Set("Connection", "close") 238 getReq.Header.Set("X-Forwarded-For", prevForwardedFor) 239 getReq.Close = true 240 res, err := frontend.Client().Do(getReq) 241 if err != nil { 242 t.Fatalf("Get: %v", err) 243 } 244 if g, e := res.StatusCode, backendStatus; g != e { 245 t.Errorf("got res.StatusCode %d; expected %d", g, e) 246 } 247 bodyBytes, _ := ioutil.ReadAll(res.Body) 248 if g, e := string(bodyBytes), backendResponse; g != e { 249 t.Errorf("got body %q; expected %q", g, e) 250 } 251 } 252 253 var proxyQueryTests = []struct { 254 baseSuffix string // suffix to add to backend URL 255 reqSuffix string // suffix to add to frontend's request URL 256 want string // what backend should see for final request URL (without ?) 257 }{ 258 {"", "", ""}, 259 {"?sta=tic", "?us=er", "sta=tic&us=er"}, 260 {"", "?us=er", "us=er"}, 261 {"?sta=tic", "", "sta=tic"}, 262 } 263 264 func TestReverseProxyQuery(t *testing.T) { 265 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 266 w.Header().Set("X-Got-Query", r.URL.RawQuery) 267 w.Write([]byte("hi")) 268 })) 269 defer backend.Close() 270 271 for i, tt := range proxyQueryTests { 272 backendURL, err := url.Parse(backend.URL + tt.baseSuffix) 273 if err != nil { 274 t.Fatal(err) 275 } 276 frontend := httptest.NewServer(NewSingleHostReverseProxy(backendURL)) 277 req, _ := http.NewRequest("GET", frontend.URL+tt.reqSuffix, nil) 278 req.Close = true 279 res, err := frontend.Client().Do(req) 280 if err != nil { 281 t.Fatalf("%d. Get: %v", i, err) 282 } 283 if g, e := res.Header.Get("X-Got-Query"), tt.want; g != e { 284 t.Errorf("%d. got query %q; expected %q", i, g, e) 285 } 286 res.Body.Close() 287 frontend.Close() 288 } 289 } 290 291 func TestReverseProxyFlushInterval(t *testing.T) { 292 const expected = "hi" 293 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 294 w.Write([]byte(expected)) 295 })) 296 defer backend.Close() 297 298 backendURL, err := url.Parse(backend.URL) 299 if err != nil { 300 t.Fatal(err) 301 } 302 303 proxyHandler := NewSingleHostReverseProxy(backendURL) 304 proxyHandler.FlushInterval = time.Microsecond 305 306 frontend := httptest.NewServer(proxyHandler) 307 defer frontend.Close() 308 309 req, _ := http.NewRequest("GET", frontend.URL, nil) 310 req.Close = true 311 res, err := frontend.Client().Do(req) 312 if err != nil { 313 t.Fatalf("Get: %v", err) 314 } 315 defer res.Body.Close() 316 if bodyBytes, _ := ioutil.ReadAll(res.Body); string(bodyBytes) != expected { 317 t.Errorf("got body %q; expected %q", bodyBytes, expected) 318 } 319 } 320 321 func TestReverseProxyFlushIntervalHeaders(t *testing.T) { 322 const expected = "hi" 323 stopCh := make(chan struct{}) 324 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 325 w.Header().Add("MyHeader", expected) 326 w.WriteHeader(200) 327 w.(http.Flusher).Flush() 328 <-stopCh 329 })) 330 defer backend.Close() 331 defer close(stopCh) 332 333 backendURL, err := url.Parse(backend.URL) 334 if err != nil { 335 t.Fatal(err) 336 } 337 338 proxyHandler := NewSingleHostReverseProxy(backendURL) 339 proxyHandler.FlushInterval = time.Microsecond 340 341 frontend := httptest.NewServer(proxyHandler) 342 defer frontend.Close() 343 344 req, _ := http.NewRequest("GET", frontend.URL, nil) 345 req.Close = true 346 347 ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second) 348 defer cancel() 349 req = req.WithContext(ctx) 350 351 res, err := frontend.Client().Do(req) 352 if err != nil { 353 t.Fatalf("Get: %v", err) 354 } 355 defer res.Body.Close() 356 357 if res.Header.Get("MyHeader") != expected { 358 t.Errorf("got header %q; expected %q", res.Header.Get("MyHeader"), expected) 359 } 360 } 361 362 func TestReverseProxyCancelation(t *testing.T) { 363 const backendResponse = "I am the backend" 364 365 reqInFlight := make(chan struct{}) 366 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 367 close(reqInFlight) // cause the client to cancel its request 368 369 select { 370 case <-time.After(10 * time.Second): 371 // Note: this should only happen in broken implementations, and the 372 // closenotify case should be instantaneous. 373 t.Error("Handler never saw CloseNotify") 374 return 375 case <-w.(http.CloseNotifier).CloseNotify(): 376 } 377 378 w.WriteHeader(http.StatusOK) 379 w.Write([]byte(backendResponse)) 380 })) 381 382 defer backend.Close() 383 384 backend.Config.ErrorLog = log.New(ioutil.Discard, "", 0) 385 386 backendURL, err := url.Parse(backend.URL) 387 if err != nil { 388 t.Fatal(err) 389 } 390 391 proxyHandler := NewSingleHostReverseProxy(backendURL) 392 393 // Discards errors of the form: 394 // http: proxy error: read tcp 127.0.0.1:44643: use of closed network connection 395 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) 396 397 frontend := httptest.NewServer(proxyHandler) 398 defer frontend.Close() 399 frontendClient := frontend.Client() 400 401 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 402 go func() { 403 <-reqInFlight 404 frontendClient.Transport.(*http.Transport).CancelRequest(getReq) 405 }() 406 res, err := frontendClient.Do(getReq) 407 if res != nil { 408 t.Errorf("got response %v; want nil", res.Status) 409 } 410 if err == nil { 411 // This should be an error like: 412 // Get http://127.0.0.1:58079: read tcp 127.0.0.1:58079: 413 // use of closed network connection 414 t.Error("Server.Client().Do() returned nil error; want non-nil error") 415 } 416 } 417 418 func req(t *testing.T, v string) *http.Request { 419 req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(v))) 420 if err != nil { 421 t.Fatal(err) 422 } 423 return req 424 } 425 426 // Issue 12344 427 func TestNilBody(t *testing.T) { 428 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 429 w.Write([]byte("hi")) 430 })) 431 defer backend.Close() 432 433 frontend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 434 backURL, _ := url.Parse(backend.URL) 435 rp := NewSingleHostReverseProxy(backURL) 436 r := req(t, "GET / HTTP/1.0\r\n\r\n") 437 r.Body = nil // this accidentally worked in Go 1.4 and below, so keep it working 438 rp.ServeHTTP(w, r) 439 })) 440 defer frontend.Close() 441 442 res, err := http.Get(frontend.URL) 443 if err != nil { 444 t.Fatal(err) 445 } 446 defer res.Body.Close() 447 slurp, err := ioutil.ReadAll(res.Body) 448 if err != nil { 449 t.Fatal(err) 450 } 451 if string(slurp) != "hi" { 452 t.Errorf("Got %q; want %q", slurp, "hi") 453 } 454 } 455 456 // Issue 15524 457 func TestUserAgentHeader(t *testing.T) { 458 const explicitUA = "explicit UA" 459 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 460 if r.URL.Path == "/noua" { 461 if c := r.Header.Get("User-Agent"); c != "" { 462 t.Errorf("handler got non-empty User-Agent header %q", c) 463 } 464 return 465 } 466 if c := r.Header.Get("User-Agent"); c != explicitUA { 467 t.Errorf("handler got unexpected User-Agent header %q", c) 468 } 469 })) 470 defer backend.Close() 471 backendURL, err := url.Parse(backend.URL) 472 if err != nil { 473 t.Fatal(err) 474 } 475 proxyHandler := NewSingleHostReverseProxy(backendURL) 476 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 477 frontend := httptest.NewServer(proxyHandler) 478 defer frontend.Close() 479 frontendClient := frontend.Client() 480 481 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 482 getReq.Header.Set("User-Agent", explicitUA) 483 getReq.Close = true 484 res, err := frontendClient.Do(getReq) 485 if err != nil { 486 t.Fatalf("Get: %v", err) 487 } 488 res.Body.Close() 489 490 getReq, _ = http.NewRequest("GET", frontend.URL+"/noua", nil) 491 getReq.Header.Set("User-Agent", "") 492 getReq.Close = true 493 res, err = frontendClient.Do(getReq) 494 if err != nil { 495 t.Fatalf("Get: %v", err) 496 } 497 res.Body.Close() 498 } 499 500 type bufferPool struct { 501 get func() []byte 502 put func([]byte) 503 } 504 505 func (bp bufferPool) Get() []byte { return bp.get() } 506 func (bp bufferPool) Put(v []byte) { bp.put(v) } 507 508 func TestReverseProxyGetPutBuffer(t *testing.T) { 509 const msg = "hi" 510 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 511 io.WriteString(w, msg) 512 })) 513 defer backend.Close() 514 515 backendURL, err := url.Parse(backend.URL) 516 if err != nil { 517 t.Fatal(err) 518 } 519 520 var ( 521 mu sync.Mutex 522 log []string 523 ) 524 addLog := func(event string) { 525 mu.Lock() 526 defer mu.Unlock() 527 log = append(log, event) 528 } 529 rp := NewSingleHostReverseProxy(backendURL) 530 const size = 1234 531 rp.BufferPool = bufferPool{ 532 get: func() []byte { 533 addLog("getBuf") 534 return make([]byte, size) 535 }, 536 put: func(p []byte) { 537 addLog("putBuf-" + strconv.Itoa(len(p))) 538 }, 539 } 540 frontend := httptest.NewServer(rp) 541 defer frontend.Close() 542 543 req, _ := http.NewRequest("GET", frontend.URL, nil) 544 req.Close = true 545 res, err := frontend.Client().Do(req) 546 if err != nil { 547 t.Fatalf("Get: %v", err) 548 } 549 slurp, err := ioutil.ReadAll(res.Body) 550 res.Body.Close() 551 if err != nil { 552 t.Fatalf("reading body: %v", err) 553 } 554 if string(slurp) != msg { 555 t.Errorf("msg = %q; want %q", slurp, msg) 556 } 557 wantLog := []string{"getBuf", "putBuf-" + strconv.Itoa(size)} 558 mu.Lock() 559 defer mu.Unlock() 560 if !reflect.DeepEqual(log, wantLog) { 561 t.Errorf("Log events = %q; want %q", log, wantLog) 562 } 563 } 564 565 func TestReverseProxy_Post(t *testing.T) { 566 const backendResponse = "I am the backend" 567 const backendStatus = 200 568 var requestBody = bytes.Repeat([]byte("a"), 1<<20) 569 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 570 slurp, err := ioutil.ReadAll(r.Body) 571 if err != nil { 572 t.Errorf("Backend body read = %v", err) 573 } 574 if len(slurp) != len(requestBody) { 575 t.Errorf("Backend read %d request body bytes; want %d", len(slurp), len(requestBody)) 576 } 577 if !bytes.Equal(slurp, requestBody) { 578 t.Error("Backend read wrong request body.") // 1MB; omitting details 579 } 580 w.Write([]byte(backendResponse)) 581 })) 582 defer backend.Close() 583 backendURL, err := url.Parse(backend.URL) 584 if err != nil { 585 t.Fatal(err) 586 } 587 proxyHandler := NewSingleHostReverseProxy(backendURL) 588 frontend := httptest.NewServer(proxyHandler) 589 defer frontend.Close() 590 591 postReq, _ := http.NewRequest("POST", frontend.URL, bytes.NewReader(requestBody)) 592 res, err := frontend.Client().Do(postReq) 593 if err != nil { 594 t.Fatalf("Do: %v", err) 595 } 596 if g, e := res.StatusCode, backendStatus; g != e { 597 t.Errorf("got res.StatusCode %d; expected %d", g, e) 598 } 599 bodyBytes, _ := ioutil.ReadAll(res.Body) 600 if g, e := string(bodyBytes), backendResponse; g != e { 601 t.Errorf("got body %q; expected %q", g, e) 602 } 603 } 604 605 type RoundTripperFunc func(*http.Request) (*http.Response, error) 606 607 func (fn RoundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { 608 return fn(req) 609 } 610 611 // Issue 16036: send a Request with a nil Body when possible 612 func TestReverseProxy_NilBody(t *testing.T) { 613 backendURL, _ := url.Parse("http://fake.tld/") 614 proxyHandler := NewSingleHostReverseProxy(backendURL) 615 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 616 proxyHandler.Transport = RoundTripperFunc(func(req *http.Request) (*http.Response, error) { 617 if req.Body != nil { 618 t.Error("Body != nil; want a nil Body") 619 } 620 return nil, errors.New("done testing the interesting part; so force a 502 Gateway error") 621 }) 622 frontend := httptest.NewServer(proxyHandler) 623 defer frontend.Close() 624 625 res, err := frontend.Client().Get(frontend.URL) 626 if err != nil { 627 t.Fatal(err) 628 } 629 defer res.Body.Close() 630 if res.StatusCode != 502 { 631 t.Errorf("status code = %v; want 502 (Gateway Error)", res.Status) 632 } 633 } 634 635 // Issue 14237. Test ModifyResponse and that an error from it 636 // causes the proxy to return StatusBadGateway, or StatusOK otherwise. 637 func TestReverseProxyModifyResponse(t *testing.T) { 638 backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 639 w.Header().Add("X-Hit-Mod", fmt.Sprintf("%v", r.URL.Path == "/mod")) 640 })) 641 defer backendServer.Close() 642 643 rpURL, _ := url.Parse(backendServer.URL) 644 rproxy := NewSingleHostReverseProxy(rpURL) 645 rproxy.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 646 rproxy.ModifyResponse = func(resp *http.Response) error { 647 if resp.Header.Get("X-Hit-Mod") != "true" { 648 return fmt.Errorf("tried to by-pass proxy") 649 } 650 return nil 651 } 652 653 frontendProxy := httptest.NewServer(rproxy) 654 defer frontendProxy.Close() 655 656 tests := []struct { 657 url string 658 wantCode int 659 }{ 660 {frontendProxy.URL + "/mod", http.StatusOK}, 661 {frontendProxy.URL + "/schedule", http.StatusBadGateway}, 662 } 663 664 for i, tt := range tests { 665 resp, err := http.Get(tt.url) 666 if err != nil { 667 t.Fatalf("failed to reach proxy: %v", err) 668 } 669 if g, e := resp.StatusCode, tt.wantCode; g != e { 670 t.Errorf("#%d: got res.StatusCode %d; expected %d", i, g, e) 671 } 672 resp.Body.Close() 673 } 674 } 675 676 type failingRoundTripper struct{} 677 678 func (failingRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { 679 return nil, errors.New("some error") 680 } 681 682 type staticResponseRoundTripper struct{ res *http.Response } 683 684 func (rt staticResponseRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { 685 return rt.res, nil 686 } 687 688 func TestReverseProxyErrorHandler(t *testing.T) { 689 tests := []struct { 690 name string 691 wantCode int 692 errorHandler func(http.ResponseWriter, *http.Request, error) 693 transport http.RoundTripper // defaults to failingRoundTripper 694 modifyResponse func(*http.Response) error 695 }{ 696 { 697 name: "default", 698 wantCode: http.StatusBadGateway, 699 }, 700 { 701 name: "errorhandler", 702 wantCode: http.StatusTeapot, 703 errorHandler: func(rw http.ResponseWriter, req *http.Request, err error) { rw.WriteHeader(http.StatusTeapot) }, 704 }, 705 { 706 name: "modifyresponse_noerr", 707 transport: staticResponseRoundTripper{ 708 &http.Response{StatusCode: 345, Body: http.NoBody}, 709 }, 710 modifyResponse: func(res *http.Response) error { 711 res.StatusCode++ 712 return nil 713 }, 714 errorHandler: func(rw http.ResponseWriter, req *http.Request, err error) { rw.WriteHeader(http.StatusTeapot) }, 715 wantCode: 346, 716 }, 717 { 718 name: "modifyresponse_err", 719 transport: staticResponseRoundTripper{ 720 &http.Response{StatusCode: 345, Body: http.NoBody}, 721 }, 722 modifyResponse: func(res *http.Response) error { 723 res.StatusCode++ 724 return errors.New("some error to trigger errorHandler") 725 }, 726 errorHandler: func(rw http.ResponseWriter, req *http.Request, err error) { rw.WriteHeader(http.StatusTeapot) }, 727 wantCode: http.StatusTeapot, 728 }, 729 } 730 731 for _, tt := range tests { 732 t.Run(tt.name, func(t *testing.T) { 733 target := &url.URL{ 734 Scheme: "http", 735 Host: "dummy.tld", 736 Path: "/", 737 } 738 rproxy := NewSingleHostReverseProxy(target) 739 rproxy.Transport = tt.transport 740 rproxy.ModifyResponse = tt.modifyResponse 741 if rproxy.Transport == nil { 742 rproxy.Transport = failingRoundTripper{} 743 } 744 rproxy.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 745 if tt.errorHandler != nil { 746 rproxy.ErrorHandler = tt.errorHandler 747 } 748 frontendProxy := httptest.NewServer(rproxy) 749 defer frontendProxy.Close() 750 751 resp, err := http.Get(frontendProxy.URL + "/test") 752 if err != nil { 753 t.Fatalf("failed to reach proxy: %v", err) 754 } 755 if g, e := resp.StatusCode, tt.wantCode; g != e { 756 t.Errorf("got res.StatusCode %d; expected %d", g, e) 757 } 758 resp.Body.Close() 759 }) 760 } 761 } 762 763 // Issue 16659: log errors from short read 764 func TestReverseProxy_CopyBuffer(t *testing.T) { 765 backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 766 out := "this call was relayed by the reverse proxy" 767 // Coerce a wrong content length to induce io.UnexpectedEOF 768 w.Header().Set("Content-Length", fmt.Sprintf("%d", len(out)*2)) 769 fmt.Fprintln(w, out) 770 })) 771 defer backendServer.Close() 772 773 rpURL, err := url.Parse(backendServer.URL) 774 if err != nil { 775 t.Fatal(err) 776 } 777 778 var proxyLog bytes.Buffer 779 rproxy := NewSingleHostReverseProxy(rpURL) 780 rproxy.ErrorLog = log.New(&proxyLog, "", log.Lshortfile) 781 donec := make(chan bool, 1) 782 frontendProxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 783 defer func() { donec <- true }() 784 rproxy.ServeHTTP(w, r) 785 })) 786 defer frontendProxy.Close() 787 788 if _, err = frontendProxy.Client().Get(frontendProxy.URL); err == nil { 789 t.Fatalf("want non-nil error") 790 } 791 // The race detector complains about the proxyLog usage in logf in copyBuffer 792 // and our usage below with proxyLog.Bytes() so we're explicitly using a 793 // channel to ensure that the ReverseProxy's ServeHTTP is done before we 794 // continue after Get. 795 <-donec 796 797 expected := []string{ 798 "EOF", 799 "read", 800 } 801 for _, phrase := range expected { 802 if !bytes.Contains(proxyLog.Bytes(), []byte(phrase)) { 803 t.Errorf("expected log to contain phrase %q", phrase) 804 } 805 } 806 } 807 808 type staticTransport struct { 809 res *http.Response 810 } 811 812 func (t *staticTransport) RoundTrip(r *http.Request) (*http.Response, error) { 813 return t.res, nil 814 } 815 816 func BenchmarkServeHTTP(b *testing.B) { 817 res := &http.Response{ 818 StatusCode: 200, 819 Body: ioutil.NopCloser(strings.NewReader("")), 820 } 821 proxy := &ReverseProxy{ 822 Director: func(*http.Request) {}, 823 Transport: &staticTransport{res}, 824 } 825 826 w := httptest.NewRecorder() 827 r := httptest.NewRequest("GET", "/", nil) 828 829 b.ReportAllocs() 830 for i := 0; i < b.N; i++ { 831 proxy.ServeHTTP(w, r) 832 } 833 } 834 835 func TestServeHTTPDeepCopy(t *testing.T) { 836 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 837 w.Write([]byte("Hello Gopher!")) 838 })) 839 defer backend.Close() 840 backendURL, err := url.Parse(backend.URL) 841 if err != nil { 842 t.Fatal(err) 843 } 844 845 type result struct { 846 before, after string 847 } 848 849 resultChan := make(chan result, 1) 850 proxyHandler := NewSingleHostReverseProxy(backendURL) 851 frontend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 852 before := r.URL.String() 853 proxyHandler.ServeHTTP(w, r) 854 after := r.URL.String() 855 resultChan <- result{before: before, after: after} 856 })) 857 defer frontend.Close() 858 859 want := result{before: "/", after: "/"} 860 861 res, err := frontend.Client().Get(frontend.URL) 862 if err != nil { 863 t.Fatalf("Do: %v", err) 864 } 865 res.Body.Close() 866 867 got := <-resultChan 868 if got != want { 869 t.Errorf("got = %+v; want = %+v", got, want) 870 } 871 } 872 873 // Issue 18327: verify we always do a deep copy of the Request.Header map 874 // before any mutations. 875 func TestClonesRequestHeaders(t *testing.T) { 876 log.SetOutput(ioutil.Discard) 877 defer log.SetOutput(os.Stderr) 878 req, _ := http.NewRequest("GET", "http://foo.tld/", nil) 879 req.RemoteAddr = "1.2.3.4:56789" 880 rp := &ReverseProxy{ 881 Director: func(req *http.Request) { 882 req.Header.Set("From-Director", "1") 883 }, 884 Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) { 885 if v := req.Header.Get("From-Director"); v != "1" { 886 t.Errorf("From-Directory value = %q; want 1", v) 887 } 888 return nil, io.EOF 889 }), 890 } 891 rp.ServeHTTP(httptest.NewRecorder(), req) 892 893 if req.Header.Get("From-Director") == "1" { 894 t.Error("Director header mutation modified caller's request") 895 } 896 if req.Header.Get("X-Forwarded-For") != "" { 897 t.Error("X-Forward-For header mutation modified caller's request") 898 } 899 900 } 901 902 type roundTripperFunc func(req *http.Request) (*http.Response, error) 903 904 func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { 905 return fn(req) 906 } 907 908 func TestModifyResponseClosesBody(t *testing.T) { 909 req, _ := http.NewRequest("GET", "http://foo.tld/", nil) 910 req.RemoteAddr = "1.2.3.4:56789" 911 closeCheck := new(checkCloser) 912 logBuf := new(bytes.Buffer) 913 outErr := errors.New("ModifyResponse error") 914 rp := &ReverseProxy{ 915 Director: func(req *http.Request) {}, 916 Transport: &staticTransport{&http.Response{ 917 StatusCode: 200, 918 Body: closeCheck, 919 }}, 920 ErrorLog: log.New(logBuf, "", 0), 921 ModifyResponse: func(*http.Response) error { 922 return outErr 923 }, 924 } 925 rec := httptest.NewRecorder() 926 rp.ServeHTTP(rec, req) 927 res := rec.Result() 928 if g, e := res.StatusCode, http.StatusBadGateway; g != e { 929 t.Errorf("got res.StatusCode %d; expected %d", g, e) 930 } 931 if !closeCheck.closed { 932 t.Errorf("body should have been closed") 933 } 934 if g, e := logBuf.String(), outErr.Error(); !strings.Contains(g, e) { 935 t.Errorf("ErrorLog %q does not contain %q", g, e) 936 } 937 } 938 939 type checkCloser struct { 940 closed bool 941 } 942 943 func (cc *checkCloser) Close() error { 944 cc.closed = true 945 return nil 946 } 947 948 func (cc *checkCloser) Read(b []byte) (int, error) { 949 return len(b), nil 950 } 951 952 // Issue 23643: panic on body copy error 953 func TestReverseProxy_PanicBodyError(t *testing.T) { 954 log.SetOutput(ioutil.Discard) 955 defer log.SetOutput(os.Stderr) 956 backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 957 out := "this call was relayed by the reverse proxy" 958 // Coerce a wrong content length to induce io.ErrUnexpectedEOF 959 w.Header().Set("Content-Length", fmt.Sprintf("%d", len(out)*2)) 960 fmt.Fprintln(w, out) 961 })) 962 defer backendServer.Close() 963 964 rpURL, err := url.Parse(backendServer.URL) 965 if err != nil { 966 t.Fatal(err) 967 } 968 969 rproxy := NewSingleHostReverseProxy(rpURL) 970 971 // Ensure that the handler panics when the body read encounters an 972 // io.ErrUnexpectedEOF 973 defer func() { 974 err := recover() 975 if err == nil { 976 t.Fatal("handler should have panicked") 977 } 978 if err != http.ErrAbortHandler { 979 t.Fatal("expected ErrAbortHandler, got", err) 980 } 981 }() 982 req, _ := http.NewRequest("GET", "http://foo.tld/", nil) 983 rproxy.ServeHTTP(httptest.NewRecorder(), req) 984 } 985 986 func TestSelectFlushInterval(t *testing.T) { 987 tests := []struct { 988 name string 989 p *ReverseProxy 990 req *http.Request 991 res *http.Response 992 want time.Duration 993 }{ 994 { 995 name: "default", 996 res: &http.Response{}, 997 p: &ReverseProxy{FlushInterval: 123}, 998 want: 123, 999 }, 1000 { 1001 name: "server-sent events overrides non-zero", 1002 res: &http.Response{ 1003 Header: http.Header{ 1004 "Content-Type": {"text/event-stream"}, 1005 }, 1006 }, 1007 p: &ReverseProxy{FlushInterval: 123}, 1008 want: -1, 1009 }, 1010 { 1011 name: "server-sent events overrides zero", 1012 res: &http.Response{ 1013 Header: http.Header{ 1014 "Content-Type": {"text/event-stream"}, 1015 }, 1016 }, 1017 p: &ReverseProxy{FlushInterval: 0}, 1018 want: -1, 1019 }, 1020 } 1021 for _, tt := range tests { 1022 t.Run(tt.name, func(t *testing.T) { 1023 got := tt.p.flushInterval(tt.req, tt.res) 1024 if got != tt.want { 1025 t.Errorf("flushLatency = %v; want %v", got, tt.want) 1026 } 1027 }) 1028 } 1029 } 1030 1031 func TestReverseProxyWebSocket(t *testing.T) { 1032 backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1033 if upgradeType(r.Header) != "websocket" { 1034 t.Error("unexpected backend request") 1035 http.Error(w, "unexpected request", 400) 1036 return 1037 } 1038 c, _, err := w.(http.Hijacker).Hijack() 1039 if err != nil { 1040 t.Error(err) 1041 return 1042 } 1043 defer c.Close() 1044 io.WriteString(c, "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nUpgrade: WebSocket\r\n\r\n") 1045 bs := bufio.NewScanner(c) 1046 if !bs.Scan() { 1047 t.Errorf("backend failed to read line from client: %v", bs.Err()) 1048 return 1049 } 1050 fmt.Fprintf(c, "backend got %q\n", bs.Text()) 1051 })) 1052 defer backendServer.Close() 1053 1054 backURL, _ := url.Parse(backendServer.URL) 1055 rproxy := NewSingleHostReverseProxy(backURL) 1056 rproxy.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 1057 rproxy.ModifyResponse = func(res *http.Response) error { 1058 res.Header.Add("X-Modified", "true") 1059 return nil 1060 } 1061 1062 handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 1063 rw.Header().Set("X-Header", "X-Value") 1064 rproxy.ServeHTTP(rw, req) 1065 }) 1066 1067 frontendProxy := httptest.NewServer(handler) 1068 defer frontendProxy.Close() 1069 1070 req, _ := http.NewRequest("GET", frontendProxy.URL, nil) 1071 req.Header.Set("Connection", "Upgrade") 1072 req.Header.Set("Upgrade", "websocket") 1073 1074 c := frontendProxy.Client() 1075 res, err := c.Do(req) 1076 if err != nil { 1077 t.Fatal(err) 1078 } 1079 if res.StatusCode != 101 { 1080 t.Fatalf("status = %v; want 101", res.Status) 1081 } 1082 1083 got := res.Header.Get("X-Header") 1084 want := "X-Value" 1085 if got != want { 1086 t.Errorf("Header(XHeader) = %q; want %q", got, want) 1087 } 1088 1089 if upgradeType(res.Header) != "websocket" { 1090 t.Fatalf("not websocket upgrade; got %#v", res.Header) 1091 } 1092 rwc, ok := res.Body.(io.ReadWriteCloser) 1093 if !ok { 1094 t.Fatalf("response body is of type %T; does not implement ReadWriteCloser", res.Body) 1095 } 1096 defer rwc.Close() 1097 1098 if got, want := res.Header.Get("X-Modified"), "true"; got != want { 1099 t.Errorf("response X-Modified header = %q; want %q", got, want) 1100 } 1101 1102 io.WriteString(rwc, "Hello\n") 1103 bs := bufio.NewScanner(rwc) 1104 if !bs.Scan() { 1105 t.Fatalf("Scan: %v", bs.Err()) 1106 } 1107 got = bs.Text() 1108 want = `backend got "Hello"` 1109 if got != want { 1110 t.Errorf("got %#q, want %#q", got, want) 1111 } 1112 } 1113 1114 func TestUnannouncedTrailer(t *testing.T) { 1115 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1116 w.WriteHeader(http.StatusOK) 1117 w.(http.Flusher).Flush() 1118 w.Header().Set(http.TrailerPrefix+"X-Unannounced-Trailer", "unannounced_trailer_value") 1119 })) 1120 defer backend.Close() 1121 backendURL, err := url.Parse(backend.URL) 1122 if err != nil { 1123 t.Fatal(err) 1124 } 1125 proxyHandler := NewSingleHostReverseProxy(backendURL) 1126 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 1127 frontend := httptest.NewServer(proxyHandler) 1128 defer frontend.Close() 1129 frontendClient := frontend.Client() 1130 1131 res, err := frontendClient.Get(frontend.URL) 1132 if err != nil { 1133 t.Fatalf("Get: %v", err) 1134 } 1135 1136 ioutil.ReadAll(res.Body) 1137 1138 if g, w := res.Trailer.Get("X-Unannounced-Trailer"), "unannounced_trailer_value"; g != w { 1139 t.Errorf("Trailer(X-Unannounced-Trailer) = %q; want %q", g, w) 1140 } 1141 1142 } 1143 1144 func TestSingleJoinSlash(t *testing.T) { 1145 tests := []struct { 1146 slasha string 1147 slashb string 1148 expected string 1149 }{ 1150 {"https://www.google.com/", "/favicon.ico", "https://www.google.com/favicon.ico"}, 1151 {"https://www.google.com", "/favicon.ico", "https://www.google.com/favicon.ico"}, 1152 {"https://www.google.com", "favicon.ico", "https://www.google.com/favicon.ico"}, 1153 {"https://www.google.com", "", "https://www.google.com/"}, 1154 {"", "favicon.ico", "/favicon.ico"}, 1155 } 1156 for _, tt := range tests { 1157 if got := singleJoiningSlash(tt.slasha, tt.slashb); got != tt.expected { 1158 t.Errorf("singleJoiningSlash(%s,%s) want %s got %s", 1159 tt.slasha, 1160 tt.slashb, 1161 tt.expected, 1162 got) 1163 } 1164 } 1165 }