github.com/guyezi/gofrontend@v0.0.0-20200228202240-7a62a49e62c0/libgo/go/net/http/httputil/dump_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  package httputil
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"net/url"
    15  	"runtime"
    16  	"strings"
    17  	"testing"
    18  )
    19  
    20  type eofReader struct{}
    21  
    22  func (n eofReader) Close() error { return nil }
    23  
    24  func (n eofReader) Read([]byte) (int, error) { return 0, io.EOF }
    25  
    26  type dumpTest struct {
    27  	// Either Req or GetReq can be set/nil but not both.
    28  	Req    *http.Request
    29  	GetReq func() *http.Request
    30  
    31  	Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body
    32  
    33  	WantDump    string
    34  	WantDumpOut string
    35  	MustError   bool // if true, the test is expected to throw an error
    36  	NoBody      bool // if true, set DumpRequest{,Out} body to false
    37  }
    38  
    39  var dumpTests = []dumpTest{
    40  	// HTTP/1.1 => chunked coding; body; empty trailer
    41  	{
    42  		Req: &http.Request{
    43  			Method: "GET",
    44  			URL: &url.URL{
    45  				Scheme: "http",
    46  				Host:   "www.google.com",
    47  				Path:   "/search",
    48  			},
    49  			ProtoMajor:       1,
    50  			ProtoMinor:       1,
    51  			TransferEncoding: []string{"chunked"},
    52  		},
    53  
    54  		Body: []byte("abcdef"),
    55  
    56  		WantDump: "GET /search HTTP/1.1\r\n" +
    57  			"Host: www.google.com\r\n" +
    58  			"Transfer-Encoding: chunked\r\n\r\n" +
    59  			chunk("abcdef") + chunk(""),
    60  	},
    61  
    62  	// Verify that DumpRequest preserves the HTTP version number, doesn't add a Host,
    63  	// and doesn't add a User-Agent.
    64  	{
    65  		Req: &http.Request{
    66  			Method:     "GET",
    67  			URL:        mustParseURL("/foo"),
    68  			ProtoMajor: 1,
    69  			ProtoMinor: 0,
    70  			Header: http.Header{
    71  				"X-Foo": []string{"X-Bar"},
    72  			},
    73  		},
    74  
    75  		WantDump: "GET /foo HTTP/1.0\r\n" +
    76  			"X-Foo: X-Bar\r\n\r\n",
    77  	},
    78  
    79  	{
    80  		Req: mustNewRequest("GET", "http://example.com/foo", nil),
    81  
    82  		WantDumpOut: "GET /foo HTTP/1.1\r\n" +
    83  			"Host: example.com\r\n" +
    84  			"User-Agent: Go-http-client/1.1\r\n" +
    85  			"Accept-Encoding: gzip\r\n\r\n",
    86  	},
    87  
    88  	// Test that an https URL doesn't try to do an SSL negotiation
    89  	// with a bytes.Buffer and hang with all goroutines not
    90  	// runnable.
    91  	{
    92  		Req: mustNewRequest("GET", "https://example.com/foo", nil),
    93  		WantDumpOut: "GET /foo HTTP/1.1\r\n" +
    94  			"Host: example.com\r\n" +
    95  			"User-Agent: Go-http-client/1.1\r\n" +
    96  			"Accept-Encoding: gzip\r\n\r\n",
    97  	},
    98  
    99  	// Request with Body, but Dump requested without it.
   100  	{
   101  		Req: &http.Request{
   102  			Method: "POST",
   103  			URL: &url.URL{
   104  				Scheme: "http",
   105  				Host:   "post.tld",
   106  				Path:   "/",
   107  			},
   108  			ContentLength: 6,
   109  			ProtoMajor:    1,
   110  			ProtoMinor:    1,
   111  		},
   112  
   113  		Body: []byte("abcdef"),
   114  
   115  		WantDumpOut: "POST / HTTP/1.1\r\n" +
   116  			"Host: post.tld\r\n" +
   117  			"User-Agent: Go-http-client/1.1\r\n" +
   118  			"Content-Length: 6\r\n" +
   119  			"Accept-Encoding: gzip\r\n\r\n",
   120  
   121  		NoBody: true,
   122  	},
   123  
   124  	// Request with Body > 8196 (default buffer size)
   125  	{
   126  		Req: &http.Request{
   127  			Method: "POST",
   128  			URL: &url.URL{
   129  				Scheme: "http",
   130  				Host:   "post.tld",
   131  				Path:   "/",
   132  			},
   133  			Header: http.Header{
   134  				"Content-Length": []string{"8193"},
   135  			},
   136  
   137  			ContentLength: 8193,
   138  			ProtoMajor:    1,
   139  			ProtoMinor:    1,
   140  		},
   141  
   142  		Body: bytes.Repeat([]byte("a"), 8193),
   143  
   144  		WantDumpOut: "POST / HTTP/1.1\r\n" +
   145  			"Host: post.tld\r\n" +
   146  			"User-Agent: Go-http-client/1.1\r\n" +
   147  			"Content-Length: 8193\r\n" +
   148  			"Accept-Encoding: gzip\r\n\r\n" +
   149  			strings.Repeat("a", 8193),
   150  		WantDump: "POST / HTTP/1.1\r\n" +
   151  			"Host: post.tld\r\n" +
   152  			"Content-Length: 8193\r\n\r\n" +
   153  			strings.Repeat("a", 8193),
   154  	},
   155  
   156  	{
   157  		GetReq: func() *http.Request {
   158  			return mustReadRequest("GET http://foo.com/ HTTP/1.1\r\n" +
   159  				"User-Agent: blah\r\n\r\n")
   160  		},
   161  		NoBody: true,
   162  		WantDump: "GET http://foo.com/ HTTP/1.1\r\n" +
   163  			"User-Agent: blah\r\n\r\n",
   164  	},
   165  
   166  	// Issue #7215. DumpRequest should return the "Content-Length" when set
   167  	{
   168  		GetReq: func() *http.Request {
   169  			return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" +
   170  				"Host: passport.myhost.com\r\n" +
   171  				"Content-Length: 3\r\n" +
   172  				"\r\nkey1=name1&key2=name2")
   173  		},
   174  		WantDump: "POST /v2/api/?login HTTP/1.1\r\n" +
   175  			"Host: passport.myhost.com\r\n" +
   176  			"Content-Length: 3\r\n" +
   177  			"\r\nkey",
   178  	},
   179  	// Issue #7215. DumpRequest should return the "Content-Length" in ReadRequest
   180  	{
   181  		GetReq: func() *http.Request {
   182  			return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" +
   183  				"Host: passport.myhost.com\r\n" +
   184  				"Content-Length: 0\r\n" +
   185  				"\r\nkey1=name1&key2=name2")
   186  		},
   187  		WantDump: "POST /v2/api/?login HTTP/1.1\r\n" +
   188  			"Host: passport.myhost.com\r\n" +
   189  			"Content-Length: 0\r\n\r\n",
   190  	},
   191  
   192  	// Issue #7215. DumpRequest should not return the "Content-Length" if unset
   193  	{
   194  		GetReq: func() *http.Request {
   195  			return mustReadRequest("POST /v2/api/?login HTTP/1.1\r\n" +
   196  				"Host: passport.myhost.com\r\n" +
   197  				"\r\nkey1=name1&key2=name2")
   198  		},
   199  		WantDump: "POST /v2/api/?login HTTP/1.1\r\n" +
   200  			"Host: passport.myhost.com\r\n\r\n",
   201  	},
   202  
   203  	// Issue 18506: make drainBody recognize NoBody. Otherwise
   204  	// this was turning into a chunked request.
   205  	{
   206  		Req: mustNewRequest("POST", "http://example.com/foo", http.NoBody),
   207  		WantDumpOut: "POST /foo HTTP/1.1\r\n" +
   208  			"Host: example.com\r\n" +
   209  			"User-Agent: Go-http-client/1.1\r\n" +
   210  			"Content-Length: 0\r\n" +
   211  			"Accept-Encoding: gzip\r\n\r\n",
   212  	},
   213  
   214  	// Issue 34504: a non-nil Body without ContentLength set should be chunked
   215  	{
   216  		Req: &http.Request{
   217  			Method: "PUT",
   218  			URL: &url.URL{
   219  				Scheme: "http",
   220  				Host:   "post.tld",
   221  				Path:   "/test",
   222  			},
   223  			ContentLength: 0,
   224  			Proto:         "HTTP/1.1",
   225  			ProtoMajor:    1,
   226  			ProtoMinor:    1,
   227  			Body:          &eofReader{},
   228  		},
   229  		NoBody: true,
   230  		WantDumpOut: "PUT /test HTTP/1.1\r\n" +
   231  			"Host: post.tld\r\n" +
   232  			"User-Agent: Go-http-client/1.1\r\n" +
   233  			"Transfer-Encoding: chunked\r\n" +
   234  			"Accept-Encoding: gzip\r\n\r\n",
   235  	},
   236  }
   237  
   238  func TestDumpRequest(t *testing.T) {
   239  	// Make a copy of dumpTests and add 10 new cases with an empty URL
   240  	// to test that no goroutines are leaked. See golang.org/issue/32571.
   241  	// 10 seems to be a decent number which always triggers the failure.
   242  	dumpTests := dumpTests[:]
   243  	for i := 0; i < 10; i++ {
   244  		dumpTests = append(dumpTests, dumpTest{
   245  			Req:       mustNewRequest("GET", "", nil),
   246  			MustError: true,
   247  		})
   248  	}
   249  	numg0 := runtime.NumGoroutine()
   250  	for i, tt := range dumpTests {
   251  		if tt.Req != nil && tt.GetReq != nil || tt.Req == nil && tt.GetReq == nil {
   252  			t.Errorf("#%d: either .Req(%p) or .GetReq(%p) can be set/nil but not both", i, tt.Req, tt.GetReq)
   253  			continue
   254  		}
   255  
   256  		freshReq := func(ti dumpTest) *http.Request {
   257  			req := ti.Req
   258  			if req == nil {
   259  				req = ti.GetReq()
   260  			}
   261  
   262  			if req.Header == nil {
   263  				req.Header = make(http.Header)
   264  			}
   265  
   266  			if ti.Body == nil {
   267  				return req
   268  			}
   269  			switch b := ti.Body.(type) {
   270  			case []byte:
   271  				req.Body = ioutil.NopCloser(bytes.NewReader(b))
   272  			case func() io.ReadCloser:
   273  				req.Body = b()
   274  			default:
   275  				t.Fatalf("Test %d: unsupported Body of %T", i, ti.Body)
   276  			}
   277  			return req
   278  		}
   279  
   280  		if tt.WantDump != "" {
   281  			req := freshReq(tt)
   282  			dump, err := DumpRequest(req, !tt.NoBody)
   283  			if err != nil {
   284  				t.Errorf("DumpRequest #%d: %s\nWantDump:\n%s", i, err, tt.WantDump)
   285  				continue
   286  			}
   287  			if string(dump) != tt.WantDump {
   288  				t.Errorf("DumpRequest %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDump, string(dump))
   289  				continue
   290  			}
   291  		}
   292  
   293  		if tt.MustError {
   294  			req := freshReq(tt)
   295  			_, err := DumpRequestOut(req, !tt.NoBody)
   296  			if err == nil {
   297  				t.Errorf("DumpRequestOut #%d: expected an error, got nil", i)
   298  			}
   299  			continue
   300  		}
   301  
   302  		if tt.WantDumpOut != "" {
   303  			req := freshReq(tt)
   304  			dump, err := DumpRequestOut(req, !tt.NoBody)
   305  			if err != nil {
   306  				t.Errorf("DumpRequestOut #%d: %s", i, err)
   307  				continue
   308  			}
   309  			if string(dump) != tt.WantDumpOut {
   310  				t.Errorf("DumpRequestOut %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDumpOut, string(dump))
   311  				continue
   312  			}
   313  		}
   314  	}
   315  	if dg := runtime.NumGoroutine() - numg0; dg > 4 {
   316  		buf := make([]byte, 4096)
   317  		buf = buf[:runtime.Stack(buf, true)]
   318  		t.Errorf("Unexpectedly large number of new goroutines: %d new: %s", dg, buf)
   319  	}
   320  }
   321  
   322  func chunk(s string) string {
   323  	return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
   324  }
   325  
   326  func mustParseURL(s string) *url.URL {
   327  	u, err := url.Parse(s)
   328  	if err != nil {
   329  		panic(fmt.Sprintf("Error parsing URL %q: %v", s, err))
   330  	}
   331  	return u
   332  }
   333  
   334  func mustNewRequest(method, url string, body io.Reader) *http.Request {
   335  	req, err := http.NewRequest(method, url, body)
   336  	if err != nil {
   337  		panic(fmt.Sprintf("NewRequest(%q, %q, %p) err = %v", method, url, body, err))
   338  	}
   339  	return req
   340  }
   341  
   342  func mustReadRequest(s string) *http.Request {
   343  	req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(s)))
   344  	if err != nil {
   345  		panic(err)
   346  	}
   347  	return req
   348  }
   349  
   350  var dumpResTests = []struct {
   351  	res  *http.Response
   352  	body bool
   353  	want string
   354  }{
   355  	{
   356  		res: &http.Response{
   357  			Status:        "200 OK",
   358  			StatusCode:    200,
   359  			Proto:         "HTTP/1.1",
   360  			ProtoMajor:    1,
   361  			ProtoMinor:    1,
   362  			ContentLength: 50,
   363  			Header: http.Header{
   364  				"Foo": []string{"Bar"},
   365  			},
   366  			Body: ioutil.NopCloser(strings.NewReader("foo")), // shouldn't be used
   367  		},
   368  		body: false, // to verify we see 50, not empty or 3.
   369  		want: `HTTP/1.1 200 OK
   370  Content-Length: 50
   371  Foo: Bar`,
   372  	},
   373  
   374  	{
   375  		res: &http.Response{
   376  			Status:        "200 OK",
   377  			StatusCode:    200,
   378  			Proto:         "HTTP/1.1",
   379  			ProtoMajor:    1,
   380  			ProtoMinor:    1,
   381  			ContentLength: 3,
   382  			Body:          ioutil.NopCloser(strings.NewReader("foo")),
   383  		},
   384  		body: true,
   385  		want: `HTTP/1.1 200 OK
   386  Content-Length: 3
   387  
   388  foo`,
   389  	},
   390  
   391  	{
   392  		res: &http.Response{
   393  			Status:           "200 OK",
   394  			StatusCode:       200,
   395  			Proto:            "HTTP/1.1",
   396  			ProtoMajor:       1,
   397  			ProtoMinor:       1,
   398  			ContentLength:    -1,
   399  			Body:             ioutil.NopCloser(strings.NewReader("foo")),
   400  			TransferEncoding: []string{"chunked"},
   401  		},
   402  		body: true,
   403  		want: `HTTP/1.1 200 OK
   404  Transfer-Encoding: chunked
   405  
   406  3
   407  foo
   408  0`,
   409  	},
   410  	{
   411  		res: &http.Response{
   412  			Status:        "200 OK",
   413  			StatusCode:    200,
   414  			Proto:         "HTTP/1.1",
   415  			ProtoMajor:    1,
   416  			ProtoMinor:    1,
   417  			ContentLength: 0,
   418  			Header: http.Header{
   419  				// To verify if headers are not filtered out.
   420  				"Foo1": []string{"Bar1"},
   421  				"Foo2": []string{"Bar2"},
   422  			},
   423  			Body: nil,
   424  		},
   425  		body: false, // to verify we see 0, not empty.
   426  		want: `HTTP/1.1 200 OK
   427  Foo1: Bar1
   428  Foo2: Bar2
   429  Content-Length: 0`,
   430  	},
   431  }
   432  
   433  func TestDumpResponse(t *testing.T) {
   434  	for i, tt := range dumpResTests {
   435  		gotb, err := DumpResponse(tt.res, tt.body)
   436  		if err != nil {
   437  			t.Errorf("%d. DumpResponse = %v", i, err)
   438  			continue
   439  		}
   440  		got := string(gotb)
   441  		got = strings.TrimSpace(got)
   442  		got = strings.ReplaceAll(got, "\r", "")
   443  
   444  		if got != tt.want {
   445  			t.Errorf("%d.\nDumpResponse got:\n%s\n\nWant:\n%s\n", i, got, tt.want)
   446  		}
   447  	}
   448  }