github.com/slayercat/go@v0.0.0-20170428012452-c51559813f61/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  }