github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/gateway/reverse_proxy_test.go (about)

     1  package gateway
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"net/url"
    10  	"strings"
    11  	"testing"
    12  	"text/template"
    13  	"time"
    14  
    15  	"github.com/TykTechnologies/tyk/apidef"
    16  	"github.com/TykTechnologies/tyk/config"
    17  	"github.com/TykTechnologies/tyk/ctx"
    18  	"github.com/TykTechnologies/tyk/dnscache"
    19  	"github.com/TykTechnologies/tyk/request"
    20  	"github.com/TykTechnologies/tyk/test"
    21  )
    22  
    23  func TestCopyHeader_NoDuplicateCORSHeaders(t *testing.T) {
    24  
    25  	makeHeaders := func(withCORS bool) http.Header {
    26  
    27  		var h = http.Header{}
    28  
    29  		h.Set("Vary", "Origin")
    30  		h.Set("Location", "https://tyk.io")
    31  
    32  		if withCORS {
    33  			h.Set("Access-Control-Allow-Origin", "tyk.io")
    34  		}
    35  
    36  		return h
    37  	}
    38  
    39  	tests := []struct {
    40  		src, dst http.Header
    41  	}{
    42  		{makeHeaders(true), makeHeaders(false)},
    43  		{makeHeaders(true), makeHeaders(true)},
    44  		{makeHeaders(false), makeHeaders(true)},
    45  	}
    46  
    47  	for _, v := range tests {
    48  		copyHeader(v.dst, v.src)
    49  
    50  		val := v.dst["Access-Control-Allow-Origin"]
    51  		if n := len(val); n != 1 {
    52  			t.Fatalf("%s found %d times", "Access-Control-Allow-Origin", n)
    53  		}
    54  	}
    55  
    56  }
    57  
    58  func TestReverseProxyRetainHost(t *testing.T) {
    59  	target, _ := url.Parse("http://target-host.com/targetpath")
    60  	cases := []struct {
    61  		name          string
    62  		inURL, inPath string
    63  		retainHost    bool
    64  		wantURL       string
    65  	}{
    66  		{
    67  			"no-retain-same-path",
    68  			"http://orig-host.com/origpath", "/origpath",
    69  			false, "http://target-host.com/targetpath/origpath",
    70  		},
    71  		{
    72  			"no-retain-minus-slash",
    73  			"http://orig-host.com/origpath", "origpath",
    74  			false, "http://target-host.com/targetpath/origpath",
    75  		},
    76  		{
    77  			"retain-same-path",
    78  			"http://orig-host.com/origpath", "/origpath",
    79  			true, "http://orig-host.com/origpath",
    80  		},
    81  		{
    82  			"retain-minus-slash",
    83  			"http://orig-host.com/origpath", "origpath",
    84  			true, "http://orig-host.com/origpath",
    85  		},
    86  	}
    87  	for _, tc := range cases {
    88  		t.Run(tc.name, func(t *testing.T) {
    89  			spec := &APISpec{APIDefinition: &apidef.APIDefinition{}, URLRewriteEnabled: true}
    90  			spec.URLRewriteEnabled = true
    91  
    92  			req := TestReq(t, http.MethodGet, tc.inURL, nil)
    93  			req.URL.Path = tc.inPath
    94  			if tc.retainHost {
    95  				setCtxValue(req, ctx.RetainHost, true)
    96  			}
    97  
    98  			proxy := TykNewSingleHostReverseProxy(target, spec)
    99  			proxy.Director(req)
   100  
   101  			if got := req.URL.String(); got != tc.wantURL {
   102  				t.Fatalf("wanted url %q, got %q", tc.wantURL, got)
   103  			}
   104  		})
   105  	}
   106  }
   107  
   108  type configTestReverseProxyDnsCache struct {
   109  	*testing.T
   110  
   111  	etcHostsMap map[string][]string
   112  	dnsConfig   config.DnsCacheConfig
   113  }
   114  
   115  func setupTestReverseProxyDnsCache(cfg *configTestReverseProxyDnsCache) func() {
   116  	pullDomains := mockHandle.PushDomains(cfg.etcHostsMap, nil)
   117  	dnsCacheManager.InitDNSCaching(
   118  		time.Duration(cfg.dnsConfig.TTL)*time.Second, time.Duration(cfg.dnsConfig.CheckInterval)*time.Second)
   119  
   120  	globalConf := config.Global()
   121  	enableWebSockets := globalConf.HttpServerOptions.EnableWebSockets
   122  
   123  	globalConf.HttpServerOptions.EnableWebSockets = true
   124  	config.SetGlobal(globalConf)
   125  
   126  	return func() {
   127  		pullDomains()
   128  		dnsCacheManager.DisposeCache()
   129  		globalConf.HttpServerOptions.EnableWebSockets = enableWebSockets
   130  		config.SetGlobal(globalConf)
   131  	}
   132  }
   133  
   134  func TestReverseProxyDnsCache(t *testing.T) {
   135  	const (
   136  		host   = "orig-host.com."
   137  		host2  = "orig-host2.com."
   138  		host3  = "orig-host3.com."
   139  		wsHost = "ws.orig-host.com."
   140  
   141  		hostApiUrl       = "http://orig-host.com/origpath"
   142  		host2HttpApiUrl  = "http://orig-host2.com/origpath"
   143  		host2HttpsApiUrl = "https://orig-host2.com/origpath"
   144  		host3ApiUrl      = "https://orig-host3.com/origpath"
   145  		wsHostWsApiUrl   = "ws://ws.orig-host.com/connect"
   146  		wsHostWssApiUrl  = "wss://ws.orig-host.com/connect"
   147  
   148  		cacheTTL            = 5
   149  		cacheUpdateInterval = 10
   150  	)
   151  
   152  	var (
   153  		etcHostsMap = map[string][]string{
   154  			host:   {"127.0.0.10", "127.0.0.20"},
   155  			host2:  {"10.0.20.0", "10.0.20.1", "10.0.20.2"},
   156  			host3:  {"10.0.20.15", "10.0.20.16"},
   157  			wsHost: {"127.0.0.10", "127.0.0.10"},
   158  		}
   159  	)
   160  
   161  	tearDown := setupTestReverseProxyDnsCache(&configTestReverseProxyDnsCache{t, etcHostsMap,
   162  		config.DnsCacheConfig{
   163  			Enabled: true, TTL: cacheTTL, CheckInterval: cacheUpdateInterval,
   164  			MultipleIPsHandleStrategy: config.NoCacheStrategy}})
   165  
   166  	currentStorage := dnsCacheManager.CacheStorage()
   167  	fakeDeleteStorage := &dnscache.MockStorage{
   168  		MockFetchItem: currentStorage.FetchItem,
   169  		MockGet:       currentStorage.Get,
   170  		MockSet:       currentStorage.Set,
   171  		MockDelete: func(key string) {
   172  			//prevent deletion
   173  		},
   174  		MockClear: currentStorage.Clear}
   175  	dnsCacheManager.SetCacheStorage(fakeDeleteStorage)
   176  
   177  	defer tearDown()
   178  
   179  	cases := []struct {
   180  		name string
   181  
   182  		URL     string
   183  		Method  string
   184  		Body    []byte
   185  		Headers http.Header
   186  
   187  		isWebsocket bool
   188  
   189  		expectedIPs    []string
   190  		shouldBeCached bool
   191  		isCacheEnabled bool
   192  	}{
   193  		{
   194  			"Should cache first request to Host1",
   195  			hostApiUrl,
   196  			http.MethodGet, nil, nil,
   197  			false,
   198  			etcHostsMap[host],
   199  			true, true,
   200  		},
   201  		{
   202  			"Should cache first request to Host2",
   203  			host2HttpsApiUrl,
   204  			http.MethodPost, []byte("{ \"param\": \"value\" }"), nil,
   205  			false,
   206  			etcHostsMap[host2],
   207  			true, true,
   208  		},
   209  		{
   210  			"Should populate from cache second request to Host1",
   211  			hostApiUrl,
   212  			http.MethodGet, nil, nil,
   213  			false,
   214  			etcHostsMap[host],
   215  			false, true,
   216  		},
   217  		{
   218  			"Should populate from cache second request to Host2 with different protocol",
   219  			host2HttpApiUrl,
   220  			http.MethodPost, []byte("{ \"param\": \"value2\" }"), nil,
   221  			false,
   222  			etcHostsMap[host2],
   223  			false, true,
   224  		},
   225  		{
   226  			"Shouldn't cache request with different http verb to same host",
   227  			hostApiUrl,
   228  			http.MethodPatch, []byte("{ \"param2\": \"value3\" }"), nil,
   229  			false,
   230  			etcHostsMap[host],
   231  			false, true,
   232  		},
   233  		{
   234  			"Shouldn't cache dns record when cache is disabled",
   235  			host3ApiUrl,
   236  			http.MethodGet, nil, nil,
   237  			false, etcHostsMap[host3],
   238  			false, false,
   239  		},
   240  		{
   241  			"Should cache ws protocol host dns records",
   242  			wsHostWsApiUrl,
   243  			http.MethodGet, nil,
   244  			map[string][]string{
   245  				"Upgrade":    {"websocket"},
   246  				"Connection": {"Upgrade"},
   247  			},
   248  			true,
   249  			etcHostsMap[wsHost],
   250  			true, true,
   251  		},
   252  		// {
   253  		// 	"Should cache wss protocol host dns records",
   254  		// 	wsHostWssApiUrl,
   255  		// 	http.MethodGet, nil,
   256  		// 	map[string][]string{
   257  		// 		"Upgrade":    {"websocket"},
   258  		// 		"Connection": {"Upgrade"},
   259  		// 	},
   260  		// 	true,
   261  		// 	etcHostsMap[wsHost],
   262  		// 	true, true,
   263  		// },
   264  	}
   265  	for _, tc := range cases {
   266  		t.Run(tc.name, func(t *testing.T) {
   267  			storage := dnsCacheManager.CacheStorage()
   268  			if !tc.isCacheEnabled {
   269  				dnsCacheManager.SetCacheStorage(nil)
   270  			}
   271  
   272  			spec := &APISpec{APIDefinition: &apidef.APIDefinition{},
   273  				EnforcedTimeoutEnabled: true,
   274  				GlobalConfig:           config.Config{ProxyCloseConnections: true, ProxyDefaultTimeout: 0.1}}
   275  
   276  			req := TestReq(t, tc.Method, tc.URL, tc.Body)
   277  			for name, value := range tc.Headers {
   278  				req.Header.Add(name, strings.Join(value, ";"))
   279  			}
   280  
   281  			Url, _ := url.Parse(tc.URL)
   282  			proxy := TykNewSingleHostReverseProxy(Url, spec)
   283  			recorder := httptest.NewRecorder()
   284  			proxy.WrappedServeHTTP(recorder, req, false)
   285  
   286  			host := Url.Hostname()
   287  			if tc.isCacheEnabled {
   288  				item, ok := storage.Get(host)
   289  				if !ok || !test.IsDnsRecordsAddrsEqualsTo(item.Addrs, tc.expectedIPs) {
   290  					t.Fatalf("got %q, but wanted %q. ok=%t", item, tc.expectedIPs, ok)
   291  				}
   292  			} else {
   293  				item, ok := storage.Get(host)
   294  				if ok {
   295  					t.Fatalf("got %t, but wanted %t. item=%#v", ok, false, item)
   296  				}
   297  			}
   298  
   299  			if !tc.isCacheEnabled {
   300  				dnsCacheManager.SetCacheStorage(storage)
   301  			}
   302  		})
   303  	}
   304  }
   305  
   306  func testNewWrappedServeHTTP() *ReverseProxy {
   307  	target, _ := url.Parse(TestHttpGet)
   308  	def := apidef.APIDefinition{}
   309  	def.VersionData.DefaultVersion = "Default"
   310  	def.VersionData.Versions = map[string]apidef.VersionInfo{
   311  		"Default": {
   312  			Name:             "v2",
   313  			UseExtendedPaths: true,
   314  			ExtendedPaths: apidef.ExtendedPathsSet{
   315  				TransformHeader: []apidef.HeaderInjectionMeta{
   316  					{
   317  						DeleteHeaders: []string{"header"},
   318  						AddHeaders:    map[string]string{"newheader": "newvalue"},
   319  						Path:          "/abc",
   320  						Method:        "GET",
   321  						ActOnResponse: true,
   322  					},
   323  				},
   324  				URLRewrite: []apidef.URLRewriteMeta{
   325  					{
   326  						Path:         "/get",
   327  						Method:       "GET",
   328  						MatchPattern: "/get",
   329  						RewriteTo:    "/post",
   330  					},
   331  				},
   332  			},
   333  		},
   334  	}
   335  	spec := &APISpec{
   336  		APIDefinition:          &def,
   337  		EnforcedTimeoutEnabled: true,
   338  		CircuitBreakerEnabled:  true,
   339  	}
   340  	return TykNewSingleHostReverseProxy(target, spec)
   341  }
   342  
   343  func TestWrappedServeHTTP(t *testing.T) {
   344  	proxy := testNewWrappedServeHTTP()
   345  	recorder := httptest.NewRecorder()
   346  	req, _ := http.NewRequest(http.MethodGet, "/", nil)
   347  	proxy.WrappedServeHTTP(recorder, req, false)
   348  }
   349  
   350  func TestSingleJoiningSlash(t *testing.T) {
   351  	testsFalse := []struct {
   352  		a, b, want string
   353  	}{
   354  		{"foo", "", "foo"},
   355  		{"foo", "bar", "foo/bar"},
   356  		{"foo/", "bar", "foo/bar"},
   357  		{"foo", "/bar", "foo/bar"},
   358  		{"foo/", "/bar", "foo/bar"},
   359  		{"foo//", "//bar", "foo/bar"},
   360  	}
   361  	for _, tc := range testsFalse {
   362  		t.Run(fmt.Sprintf("%s+%s", tc.a, tc.b), func(t *testing.T) {
   363  			got := singleJoiningSlash(tc.a, tc.b, false)
   364  			if got != tc.want {
   365  				t.Fatalf("want %s, got %s", tc.want, got)
   366  			}
   367  		})
   368  	}
   369  	testsTrue := []struct {
   370  		a, b, want string
   371  	}{
   372  		{"foo/", "", "foo/"},
   373  		{"foo", "", "foo"},
   374  	}
   375  	for _, tc := range testsTrue {
   376  		t.Run(fmt.Sprintf("%s+%s", tc.a, tc.b), func(t *testing.T) {
   377  			got := singleJoiningSlash(tc.a, tc.b, true)
   378  			if got != tc.want {
   379  				t.Fatalf("want %s, got %s", tc.want, got)
   380  			}
   381  		})
   382  	}
   383  }
   384  
   385  func TestRequestIP(t *testing.T) {
   386  	tests := []struct {
   387  		remote, real, forwarded, want string
   388  	}{
   389  		// missing ip or port
   390  		{want: ""},
   391  		{remote: ":80", want: ""},
   392  		{remote: "1.2.3.4", want: ""},
   393  		{remote: "[::1]", want: ""},
   394  		// no headers
   395  		{remote: "1.2.3.4:80", want: "1.2.3.4"},
   396  		{remote: "[::1]:80", want: "::1"},
   397  		// real-ip
   398  		{
   399  			remote: "1.2.3.4:80",
   400  			real:   "5.6.7.8",
   401  			want:   "5.6.7.8",
   402  		},
   403  		{
   404  			remote: "[::1]:80",
   405  			real:   "::2",
   406  			want:   "::2",
   407  		},
   408  		// forwarded-for
   409  		{
   410  			remote:    "1.2.3.4:80",
   411  			forwarded: "5.6.7.8, px1, px2",
   412  			want:      "5.6.7.8",
   413  		},
   414  		{
   415  			remote:    "[::1]:80",
   416  			forwarded: "::2",
   417  			want:      "::2",
   418  		},
   419  		// both real-ip and forwarded-for
   420  		{
   421  			remote:    "1.2.3.4:80",
   422  			real:      "5.6.7.8",
   423  			forwarded: "4.3.2.1, px1, px2",
   424  			want:      "5.6.7.8",
   425  		},
   426  	}
   427  	for _, tc := range tests {
   428  		r := &http.Request{RemoteAddr: tc.remote, Header: http.Header{}}
   429  		r.Header.Set("x-real-ip", tc.real)
   430  		r.Header.Set("x-forwarded-for", tc.forwarded)
   431  		got := request.RealIP(r)
   432  		if got != tc.want {
   433  			t.Errorf("requestIP({%q, %q, %q}) got %q, want %q",
   434  				tc.remote, tc.real, tc.forwarded, got, tc.want)
   435  		}
   436  	}
   437  }
   438  
   439  func TestCheckHeaderInRemoveList(t *testing.T) {
   440  	type testSpec struct {
   441  		UseExtendedPaths      bool
   442  		GlobalHeadersRemove   []string
   443  		ExtendedDeleteHeaders []string
   444  	}
   445  	tpl, err := template.New("test_tpl").Parse(`{
   446  		"api_id": "1",
   447  		"version_data": {
   448  			"not_versioned": true,
   449  			"versions": {
   450  				"Default": {
   451  					"name": "Default",
   452  					"use_extended_paths": {{ .UseExtendedPaths }},
   453  					"global_headers_remove": [{{ range $index, $hdr := .GlobalHeadersRemove }}{{if $index}}, {{end}}{{print "\"" . "\"" }}{{end}}],
   454  					"extended_paths": {
   455  						"transform_headers": [{
   456  							"delete_headers": [{{range $index, $hdr := .ExtendedDeleteHeaders}}{{if $index}}, {{end}}{{print "\"" . "\""}}{{end}}],
   457  							"path": "test",
   458  							"method": "GET"
   459  						}]
   460  					}
   461  				}
   462  			}
   463  		}
   464  	}`)
   465  	if err != nil {
   466  		t.Fatal(err)
   467  	}
   468  
   469  	tests := []struct {
   470  		header   string
   471  		spec     testSpec
   472  		expected bool
   473  	}{
   474  		{
   475  			header: "X-Forwarded-For",
   476  		},
   477  		{
   478  			header: "X-Forwarded-For",
   479  			spec:   testSpec{GlobalHeadersRemove: []string{"X-Random-Header"}},
   480  		},
   481  		{
   482  			header: "X-Forwarded-For",
   483  			spec: testSpec{
   484  				UseExtendedPaths:      true,
   485  				ExtendedDeleteHeaders: []string{"X-Random-Header"},
   486  			},
   487  		},
   488  		{
   489  			header:   "X-Forwarded-For",
   490  			spec:     testSpec{GlobalHeadersRemove: []string{"X-Forwarded-For"}},
   491  			expected: true,
   492  		},
   493  		{
   494  			header: "X-Forwarded-For",
   495  			spec: testSpec{
   496  				UseExtendedPaths:      true,
   497  				GlobalHeadersRemove:   []string{"X-Random-Header"},
   498  				ExtendedDeleteHeaders: []string{"X-Forwarded-For"},
   499  			},
   500  			expected: true,
   501  		},
   502  		{
   503  			header: "X-Forwarded-For",
   504  			spec: testSpec{
   505  				UseExtendedPaths:      true,
   506  				GlobalHeadersRemove:   []string{"X-Forwarded-For"},
   507  				ExtendedDeleteHeaders: []string{"X-Forwarded-For"},
   508  			},
   509  			expected: true,
   510  		},
   511  	}
   512  
   513  	for _, tc := range tests {
   514  		t.Run(fmt.Sprintf("%s:%t", tc.header, tc.expected), func(t *testing.T) {
   515  			rp := &ReverseProxy{}
   516  			r, err := http.NewRequest(http.MethodGet, "http://test/test", nil)
   517  			if err != nil {
   518  				t.Fatal(err)
   519  			}
   520  
   521  			var specOutput bytes.Buffer
   522  			if err := tpl.Execute(&specOutput, tc.spec); err != nil {
   523  				t.Fatal(err)
   524  			}
   525  
   526  			spec := CreateSpecTest(t, specOutput.String())
   527  			actual := rp.CheckHeaderInRemoveList(tc.header, spec, r)
   528  			if actual != tc.expected {
   529  				t.Fatalf("want %t, got %t", tc.expected, actual)
   530  			}
   531  		})
   532  	}
   533  }
   534  
   535  func testRequestIPHops(t testing.TB) {
   536  	req := &http.Request{
   537  		Header:     http.Header{},
   538  		RemoteAddr: "test.com:80",
   539  	}
   540  	req.Header.Set("X-Forwarded-For", "abc")
   541  	match := "abc, test.com"
   542  	clientIP := requestIPHops(req)
   543  	if clientIP != match {
   544  		t.Fatalf("Got %s, expected %s", clientIP, match)
   545  	}
   546  }
   547  
   548  func TestRequestIPHops(t *testing.T) {
   549  	testRequestIPHops(t)
   550  }
   551  
   552  func TestNopCloseRequestBody(t *testing.T) {
   553  	// try to pass nil request
   554  	var req *http.Request
   555  	nopCloseRequestBody(req)
   556  	if req != nil {
   557  		t.Error("nil Request should remain nil")
   558  	}
   559  
   560  	// try to pass nil body
   561  	req = &http.Request{}
   562  	nopCloseRequestBody(req)
   563  	if req.Body != nil {
   564  		t.Error("Request nil body should remain nil")
   565  	}
   566  
   567  	// try to pass not nil body and check that it was replaced with nopCloser
   568  	req = httptest.NewRequest(http.MethodGet, "/test", strings.NewReader("abcxyz"))
   569  	nopCloseRequestBody(req)
   570  	if body, ok := req.Body.(nopCloser); !ok {
   571  		t.Error("Request's body was not replaced with nopCloser")
   572  	} else {
   573  		// try to read body 1st time
   574  		if data, err := ioutil.ReadAll(body); err != nil {
   575  			t.Error("1st read, error while reading body:", err)
   576  		} else if !bytes.Equal(data, []byte("abcxyz")) { // compare with expected data
   577  			t.Error("1st read, body's data is not as expectd")
   578  		}
   579  
   580  		// try to read body again without closing
   581  		if data, err := ioutil.ReadAll(body); err != nil {
   582  			t.Error("2nd read, error while reading body:", err)
   583  		} else if !bytes.Equal(data, []byte("abcxyz")) { // compare with expected data
   584  			t.Error("2nd read, body's data is not as expectd")
   585  		}
   586  
   587  		// close body and try to read "closed" one
   588  		body.Close()
   589  		if data, err := ioutil.ReadAll(body); err != nil {
   590  			t.Error("3rd read, error while reading body:", err)
   591  		} else if !bytes.Equal(data, []byte("abcxyz")) { // compare with expected data
   592  			t.Error("3rd read, body's data is not as expectd")
   593  		}
   594  	}
   595  }
   596  
   597  func TestNopCloseResponseBody(t *testing.T) {
   598  	var resp *http.Response
   599  	nopCloseResponseBody(resp)
   600  	if resp != nil {
   601  		t.Error("nil Response should remain nil")
   602  	}
   603  
   604  	// try to pass nil body
   605  	resp = &http.Response{}
   606  	nopCloseResponseBody(resp)
   607  	if resp.Body != nil {
   608  		t.Error("Response nil body should remain nil")
   609  	}
   610  
   611  	// try to pass not nil body and check that it was replaced with nopCloser
   612  	resp = &http.Response{}
   613  	resp.Body = ioutil.NopCloser(strings.NewReader("abcxyz"))
   614  	nopCloseResponseBody(resp)
   615  	if body, ok := resp.Body.(nopCloser); !ok {
   616  		t.Error("Response's body was not replaced with nopCloser")
   617  	} else {
   618  		// try to read body 1st time
   619  		if data, err := ioutil.ReadAll(body); err != nil {
   620  			t.Error("1st read, error while reading body:", err)
   621  		} else if !bytes.Equal(data, []byte("abcxyz")) { // compare with expected data
   622  			t.Error("1st read, body's data is not as expectd")
   623  		}
   624  
   625  		// try to read body again without closing
   626  		if data, err := ioutil.ReadAll(body); err != nil {
   627  			t.Error("2nd read, error while reading body:", err)
   628  		} else if !bytes.Equal(data, []byte("abcxyz")) { // compare with expected data
   629  			t.Error("2nd read, body's data is not as expectd")
   630  		}
   631  
   632  		// close body and try to read "closed" one
   633  		body.Close()
   634  		if data, err := ioutil.ReadAll(body); err != nil {
   635  			t.Error("3rd read, error while reading body:", err)
   636  		} else if !bytes.Equal(data, []byte("abcxyz")) { // compare with expected data
   637  			t.Error("3rd read, body's data is not as expectd")
   638  		}
   639  	}
   640  }
   641  
   642  func BenchmarkRequestIPHops(b *testing.B) {
   643  	b.ReportAllocs()
   644  	for i := 0; i < b.N; i++ {
   645  		testRequestIPHops(b)
   646  	}
   647  }
   648  
   649  func BenchmarkWrappedServeHTTP(b *testing.B) {
   650  	b.ReportAllocs()
   651  	proxy := testNewWrappedServeHTTP()
   652  	recorder := httptest.NewRecorder()
   653  	req, _ := http.NewRequest(http.MethodGet, "/", nil)
   654  	for i := 0; i < b.N; i++ {
   655  		proxy.WrappedServeHTTP(recorder, req, false)
   656  	}
   657  }
   658  
   659  func BenchmarkCopyRequestResponse(b *testing.B) {
   660  	b.ReportAllocs()
   661  
   662  	str := strings.Repeat("very long body line that is repeated", 128)
   663  	req := &http.Request{}
   664  	res := &http.Response{}
   665  	for i := 0; i < b.N; i++ {
   666  		req.Body = ioutil.NopCloser(strings.NewReader(str))
   667  		res.Body = ioutil.NopCloser(strings.NewReader(str))
   668  		for j := 0; j < 10; j++ {
   669  			req = copyRequest(req)
   670  			res = copyResponse(res)
   671  		}
   672  	}
   673  }