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