github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/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 16659: log errors from short read
   587  func TestReverseProxy_CopyBuffer(t *testing.T) {
   588  	backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   589  		out := "this call was relayed by the reverse proxy"
   590  		// Coerce a wrong content length to induce io.UnexpectedEOF
   591  		w.Header().Set("Content-Length", fmt.Sprintf("%d", len(out)*2))
   592  		fmt.Fprintln(w, out)
   593  	}))
   594  	defer backendServer.Close()
   595  
   596  	rpURL, err := url.Parse(backendServer.URL)
   597  	if err != nil {
   598  		t.Fatal(err)
   599  	}
   600  
   601  	var proxyLog bytes.Buffer
   602  	rproxy := NewSingleHostReverseProxy(rpURL)
   603  	rproxy.ErrorLog = log.New(&proxyLog, "", log.Lshortfile)
   604  	frontendProxy := httptest.NewServer(rproxy)
   605  	defer frontendProxy.Close()
   606  
   607  	resp, err := http.Get(frontendProxy.URL)
   608  	if err != nil {
   609  		t.Fatalf("failed to reach proxy: %v", err)
   610  	}
   611  	defer resp.Body.Close()
   612  
   613  	if _, err := ioutil.ReadAll(resp.Body); err == nil {
   614  		t.Fatalf("want non-nil error")
   615  	}
   616  	expected := []string{
   617  		"EOF",
   618  		"read",
   619  	}
   620  	for _, phrase := range expected {
   621  		if !bytes.Contains(proxyLog.Bytes(), []byte(phrase)) {
   622  			t.Errorf("expected log to contain phrase %q", phrase)
   623  		}
   624  	}
   625  }