github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/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  
   189  func TestDumpRequest(t *testing.T) {
   190  	numg0 := runtime.NumGoroutine()
   191  	for i, tt := range dumpTests {
   192  		setBody := func() {
   193  			if tt.Body == nil {
   194  				return
   195  			}
   196  			switch b := tt.Body.(type) {
   197  			case []byte:
   198  				tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b))
   199  			case func() io.ReadCloser:
   200  				tt.Req.Body = b()
   201  			default:
   202  				t.Fatalf("Test %d: unsupported Body of %T", i, tt.Body)
   203  			}
   204  		}
   205  		setBody()
   206  		if tt.Req.Header == nil {
   207  			tt.Req.Header = make(http.Header)
   208  		}
   209  
   210  		if tt.WantDump != "" {
   211  			setBody()
   212  			dump, err := DumpRequest(&tt.Req, !tt.NoBody)
   213  			if err != nil {
   214  				t.Errorf("DumpRequest #%d: %s", i, err)
   215  				continue
   216  			}
   217  			if string(dump) != tt.WantDump {
   218  				t.Errorf("DumpRequest %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDump, string(dump))
   219  				continue
   220  			}
   221  		}
   222  
   223  		if tt.WantDumpOut != "" {
   224  			setBody()
   225  			dump, err := DumpRequestOut(&tt.Req, !tt.NoBody)
   226  			if err != nil {
   227  				t.Errorf("DumpRequestOut #%d: %s", i, err)
   228  				continue
   229  			}
   230  			if string(dump) != tt.WantDumpOut {
   231  				t.Errorf("DumpRequestOut %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDumpOut, string(dump))
   232  				continue
   233  			}
   234  		}
   235  	}
   236  	if dg := runtime.NumGoroutine() - numg0; dg > 4 {
   237  		buf := make([]byte, 4096)
   238  		buf = buf[:runtime.Stack(buf, true)]
   239  		t.Errorf("Unexpectedly large number of new goroutines: %d new: %s", dg, buf)
   240  	}
   241  }
   242  
   243  func chunk(s string) string {
   244  	return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
   245  }
   246  
   247  func mustParseURL(s string) *url.URL {
   248  	u, err := url.Parse(s)
   249  	if err != nil {
   250  		panic(fmt.Sprintf("Error parsing URL %q: %v", s, err))
   251  	}
   252  	return u
   253  }
   254  
   255  func mustNewRequest(method, url string, body io.Reader) *http.Request {
   256  	req, err := http.NewRequest(method, url, body)
   257  	if err != nil {
   258  		panic(fmt.Sprintf("NewRequest(%q, %q, %p) err = %v", method, url, body, err))
   259  	}
   260  	return req
   261  }
   262  
   263  func mustReadRequest(s string) *http.Request {
   264  	req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(s)))
   265  	if err != nil {
   266  		panic(err)
   267  	}
   268  	return req
   269  }
   270  
   271  var dumpResTests = []struct {
   272  	res  *http.Response
   273  	body bool
   274  	want string
   275  }{
   276  	{
   277  		res: &http.Response{
   278  			Status:        "200 OK",
   279  			StatusCode:    200,
   280  			Proto:         "HTTP/1.1",
   281  			ProtoMajor:    1,
   282  			ProtoMinor:    1,
   283  			ContentLength: 50,
   284  			Header: http.Header{
   285  				"Foo": []string{"Bar"},
   286  			},
   287  			Body: ioutil.NopCloser(strings.NewReader("foo")), // shouldn't be used
   288  		},
   289  		body: false, // to verify we see 50, not empty or 3.
   290  		want: `HTTP/1.1 200 OK
   291  Content-Length: 50
   292  Foo: Bar`,
   293  	},
   294  
   295  	{
   296  		res: &http.Response{
   297  			Status:        "200 OK",
   298  			StatusCode:    200,
   299  			Proto:         "HTTP/1.1",
   300  			ProtoMajor:    1,
   301  			ProtoMinor:    1,
   302  			ContentLength: 3,
   303  			Body:          ioutil.NopCloser(strings.NewReader("foo")),
   304  		},
   305  		body: true,
   306  		want: `HTTP/1.1 200 OK
   307  Content-Length: 3
   308  
   309  foo`,
   310  	},
   311  
   312  	{
   313  		res: &http.Response{
   314  			Status:           "200 OK",
   315  			StatusCode:       200,
   316  			Proto:            "HTTP/1.1",
   317  			ProtoMajor:       1,
   318  			ProtoMinor:       1,
   319  			ContentLength:    -1,
   320  			Body:             ioutil.NopCloser(strings.NewReader("foo")),
   321  			TransferEncoding: []string{"chunked"},
   322  		},
   323  		body: true,
   324  		want: `HTTP/1.1 200 OK
   325  Transfer-Encoding: chunked
   326  
   327  3
   328  foo
   329  0`,
   330  	},
   331  	{
   332  		res: &http.Response{
   333  			Status:        "200 OK",
   334  			StatusCode:    200,
   335  			Proto:         "HTTP/1.1",
   336  			ProtoMajor:    1,
   337  			ProtoMinor:    1,
   338  			ContentLength: 0,
   339  			Header: http.Header{
   340  				// To verify if headers are not filtered out.
   341  				"Foo1": []string{"Bar1"},
   342  				"Foo2": []string{"Bar2"},
   343  			},
   344  			Body: nil,
   345  		},
   346  		body: false, // to verify we see 0, not empty.
   347  		want: `HTTP/1.1 200 OK
   348  Foo1: Bar1
   349  Foo2: Bar2
   350  Content-Length: 0`,
   351  	},
   352  }
   353  
   354  func TestDumpResponse(t *testing.T) {
   355  	for i, tt := range dumpResTests {
   356  		gotb, err := DumpResponse(tt.res, tt.body)
   357  		if err != nil {
   358  			t.Errorf("%d. DumpResponse = %v", i, err)
   359  			continue
   360  		}
   361  		got := string(gotb)
   362  		got = strings.TrimSpace(got)
   363  		got = strings.Replace(got, "\r", "", -1)
   364  
   365  		if got != tt.want {
   366  			t.Errorf("%d.\nDumpResponse got:\n%s\n\nWant:\n%s\n", i, got, tt.want)
   367  		}
   368  	}
   369  }