...
Run Format

Source file src/net/http/httputil/dump_test.go

Documentation: net/http/httputil

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

View as plain text