Home | History | Annotate | Download | only in httputil
      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