github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/pkg/net/http/requestwrite_test.go (about)

     1  // Copyright 2010 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 http
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"net/url"
    14  	"strings"
    15  	"testing"
    16  )
    17  
    18  type reqWriteTest struct {
    19  	Req  Request
    20  	Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body
    21  
    22  	// Any of these three may be empty to skip that test.
    23  	WantWrite string // Request.Write
    24  	WantProxy string // Request.WriteProxy
    25  
    26  	WantError error // wanted error from Request.Write
    27  }
    28  
    29  var reqWriteTests = []reqWriteTest{
    30  	// HTTP/1.1 => chunked coding; no body; no trailer
    31  	{
    32  		Req: Request{
    33  			Method: "GET",
    34  			URL: &url.URL{
    35  				Scheme: "http",
    36  				Host:   "www.techcrunch.com",
    37  				Path:   "/",
    38  			},
    39  			Proto:      "HTTP/1.1",
    40  			ProtoMajor: 1,
    41  			ProtoMinor: 1,
    42  			Header: Header{
    43  				"Accept":           {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
    44  				"Accept-Charset":   {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
    45  				"Accept-Encoding":  {"gzip,deflate"},
    46  				"Accept-Language":  {"en-us,en;q=0.5"},
    47  				"Keep-Alive":       {"300"},
    48  				"Proxy-Connection": {"keep-alive"},
    49  				"User-Agent":       {"Fake"},
    50  			},
    51  			Body:  nil,
    52  			Close: false,
    53  			Host:  "www.techcrunch.com",
    54  			Form:  map[string][]string{},
    55  		},
    56  
    57  		WantWrite: "GET / HTTP/1.1\r\n" +
    58  			"Host: www.techcrunch.com\r\n" +
    59  			"User-Agent: Fake\r\n" +
    60  			"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
    61  			"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
    62  			"Accept-Encoding: gzip,deflate\r\n" +
    63  			"Accept-Language: en-us,en;q=0.5\r\n" +
    64  			"Keep-Alive: 300\r\n" +
    65  			"Proxy-Connection: keep-alive\r\n\r\n",
    66  
    67  		WantProxy: "GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
    68  			"Host: www.techcrunch.com\r\n" +
    69  			"User-Agent: Fake\r\n" +
    70  			"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
    71  			"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
    72  			"Accept-Encoding: gzip,deflate\r\n" +
    73  			"Accept-Language: en-us,en;q=0.5\r\n" +
    74  			"Keep-Alive: 300\r\n" +
    75  			"Proxy-Connection: keep-alive\r\n\r\n",
    76  	},
    77  	// HTTP/1.1 => chunked coding; body; empty trailer
    78  	{
    79  		Req: Request{
    80  			Method: "GET",
    81  			URL: &url.URL{
    82  				Scheme: "http",
    83  				Host:   "www.google.com",
    84  				Path:   "/search",
    85  			},
    86  			ProtoMajor:       1,
    87  			ProtoMinor:       1,
    88  			Header:           Header{},
    89  			TransferEncoding: []string{"chunked"},
    90  		},
    91  
    92  		Body: []byte("abcdef"),
    93  
    94  		WantWrite: "GET /search HTTP/1.1\r\n" +
    95  			"Host: www.google.com\r\n" +
    96  			"User-Agent: Go 1.1 package http\r\n" +
    97  			"Transfer-Encoding: chunked\r\n\r\n" +
    98  			chunk("abcdef") + chunk(""),
    99  
   100  		WantProxy: "GET http://www.google.com/search HTTP/1.1\r\n" +
   101  			"Host: www.google.com\r\n" +
   102  			"User-Agent: Go 1.1 package http\r\n" +
   103  			"Transfer-Encoding: chunked\r\n\r\n" +
   104  			chunk("abcdef") + chunk(""),
   105  	},
   106  	// HTTP/1.1 POST => chunked coding; body; empty trailer
   107  	{
   108  		Req: Request{
   109  			Method: "POST",
   110  			URL: &url.URL{
   111  				Scheme: "http",
   112  				Host:   "www.google.com",
   113  				Path:   "/search",
   114  			},
   115  			ProtoMajor:       1,
   116  			ProtoMinor:       1,
   117  			Header:           Header{},
   118  			Close:            true,
   119  			TransferEncoding: []string{"chunked"},
   120  		},
   121  
   122  		Body: []byte("abcdef"),
   123  
   124  		WantWrite: "POST /search HTTP/1.1\r\n" +
   125  			"Host: www.google.com\r\n" +
   126  			"User-Agent: Go 1.1 package http\r\n" +
   127  			"Connection: close\r\n" +
   128  			"Transfer-Encoding: chunked\r\n\r\n" +
   129  			chunk("abcdef") + chunk(""),
   130  
   131  		WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
   132  			"Host: www.google.com\r\n" +
   133  			"User-Agent: Go 1.1 package http\r\n" +
   134  			"Connection: close\r\n" +
   135  			"Transfer-Encoding: chunked\r\n\r\n" +
   136  			chunk("abcdef") + chunk(""),
   137  	},
   138  
   139  	// HTTP/1.1 POST with Content-Length, no chunking
   140  	{
   141  		Req: Request{
   142  			Method: "POST",
   143  			URL: &url.URL{
   144  				Scheme: "http",
   145  				Host:   "www.google.com",
   146  				Path:   "/search",
   147  			},
   148  			ProtoMajor:    1,
   149  			ProtoMinor:    1,
   150  			Header:        Header{},
   151  			Close:         true,
   152  			ContentLength: 6,
   153  		},
   154  
   155  		Body: []byte("abcdef"),
   156  
   157  		WantWrite: "POST /search HTTP/1.1\r\n" +
   158  			"Host: www.google.com\r\n" +
   159  			"User-Agent: Go 1.1 package http\r\n" +
   160  			"Connection: close\r\n" +
   161  			"Content-Length: 6\r\n" +
   162  			"\r\n" +
   163  			"abcdef",
   164  
   165  		WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" +
   166  			"Host: www.google.com\r\n" +
   167  			"User-Agent: Go 1.1 package http\r\n" +
   168  			"Connection: close\r\n" +
   169  			"Content-Length: 6\r\n" +
   170  			"\r\n" +
   171  			"abcdef",
   172  	},
   173  
   174  	// HTTP/1.1 POST with Content-Length in headers
   175  	{
   176  		Req: Request{
   177  			Method: "POST",
   178  			URL:    mustParseURL("http://example.com/"),
   179  			Host:   "example.com",
   180  			Header: Header{
   181  				"Content-Length": []string{"10"}, // ignored
   182  			},
   183  			ContentLength: 6,
   184  		},
   185  
   186  		Body: []byte("abcdef"),
   187  
   188  		WantWrite: "POST / HTTP/1.1\r\n" +
   189  			"Host: example.com\r\n" +
   190  			"User-Agent: Go 1.1 package http\r\n" +
   191  			"Content-Length: 6\r\n" +
   192  			"\r\n" +
   193  			"abcdef",
   194  
   195  		WantProxy: "POST http://example.com/ HTTP/1.1\r\n" +
   196  			"Host: example.com\r\n" +
   197  			"User-Agent: Go 1.1 package http\r\n" +
   198  			"Content-Length: 6\r\n" +
   199  			"\r\n" +
   200  			"abcdef",
   201  	},
   202  
   203  	// default to HTTP/1.1
   204  	{
   205  		Req: Request{
   206  			Method: "GET",
   207  			URL:    mustParseURL("/search"),
   208  			Host:   "www.google.com",
   209  		},
   210  
   211  		WantWrite: "GET /search HTTP/1.1\r\n" +
   212  			"Host: www.google.com\r\n" +
   213  			"User-Agent: Go 1.1 package http\r\n" +
   214  			"\r\n",
   215  	},
   216  
   217  	// Request with a 0 ContentLength and a 0 byte body.
   218  	{
   219  		Req: Request{
   220  			Method:        "POST",
   221  			URL:           mustParseURL("/"),
   222  			Host:          "example.com",
   223  			ProtoMajor:    1,
   224  			ProtoMinor:    1,
   225  			ContentLength: 0, // as if unset by user
   226  		},
   227  
   228  		Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 0)) },
   229  
   230  		// RFC 2616 Section 14.13 says Content-Length should be specified
   231  		// unless body is prohibited by the request method.
   232  		// Also, nginx expects it for POST and PUT.
   233  		WantWrite: "POST / HTTP/1.1\r\n" +
   234  			"Host: example.com\r\n" +
   235  			"User-Agent: Go 1.1 package http\r\n" +
   236  			"Content-Length: 0\r\n" +
   237  			"\r\n",
   238  
   239  		WantProxy: "POST / HTTP/1.1\r\n" +
   240  			"Host: example.com\r\n" +
   241  			"User-Agent: Go 1.1 package http\r\n" +
   242  			"Content-Length: 0\r\n" +
   243  			"\r\n",
   244  	},
   245  
   246  	// Request with a 0 ContentLength and a 1 byte body.
   247  	{
   248  		Req: Request{
   249  			Method:        "POST",
   250  			URL:           mustParseURL("/"),
   251  			Host:          "example.com",
   252  			ProtoMajor:    1,
   253  			ProtoMinor:    1,
   254  			ContentLength: 0, // as if unset by user
   255  		},
   256  
   257  		Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 1)) },
   258  
   259  		WantWrite: "POST / HTTP/1.1\r\n" +
   260  			"Host: example.com\r\n" +
   261  			"User-Agent: Go 1.1 package http\r\n" +
   262  			"Transfer-Encoding: chunked\r\n\r\n" +
   263  			chunk("x") + chunk(""),
   264  
   265  		WantProxy: "POST / HTTP/1.1\r\n" +
   266  			"Host: example.com\r\n" +
   267  			"User-Agent: Go 1.1 package http\r\n" +
   268  			"Transfer-Encoding: chunked\r\n\r\n" +
   269  			chunk("x") + chunk(""),
   270  	},
   271  
   272  	// Request with a ContentLength of 10 but a 5 byte body.
   273  	{
   274  		Req: Request{
   275  			Method:        "POST",
   276  			URL:           mustParseURL("/"),
   277  			Host:          "example.com",
   278  			ProtoMajor:    1,
   279  			ProtoMinor:    1,
   280  			ContentLength: 10, // but we're going to send only 5 bytes
   281  		},
   282  		Body:      []byte("12345"),
   283  		WantError: errors.New("http: Request.ContentLength=10 with Body length 5"),
   284  	},
   285  
   286  	// Request with a ContentLength of 4 but an 8 byte body.
   287  	{
   288  		Req: Request{
   289  			Method:        "POST",
   290  			URL:           mustParseURL("/"),
   291  			Host:          "example.com",
   292  			ProtoMajor:    1,
   293  			ProtoMinor:    1,
   294  			ContentLength: 4, // but we're going to try to send 8 bytes
   295  		},
   296  		Body:      []byte("12345678"),
   297  		WantError: errors.New("http: Request.ContentLength=4 with Body length 8"),
   298  	},
   299  
   300  	// Request with a 5 ContentLength and nil body.
   301  	{
   302  		Req: Request{
   303  			Method:        "POST",
   304  			URL:           mustParseURL("/"),
   305  			Host:          "example.com",
   306  			ProtoMajor:    1,
   307  			ProtoMinor:    1,
   308  			ContentLength: 5, // but we'll omit the body
   309  		},
   310  		WantError: errors.New("http: Request.ContentLength=5 with nil Body"),
   311  	},
   312  
   313  	// Verify that DumpRequest preserves the HTTP version number, doesn't add a Host,
   314  	// and doesn't add a User-Agent.
   315  	{
   316  		Req: Request{
   317  			Method:     "GET",
   318  			URL:        mustParseURL("/foo"),
   319  			ProtoMajor: 1,
   320  			ProtoMinor: 0,
   321  			Header: Header{
   322  				"X-Foo": []string{"X-Bar"},
   323  			},
   324  		},
   325  
   326  		WantWrite: "GET /foo HTTP/1.1\r\n" +
   327  			"Host: \r\n" +
   328  			"User-Agent: Go 1.1 package http\r\n" +
   329  			"X-Foo: X-Bar\r\n\r\n",
   330  	},
   331  
   332  	// If no Request.Host and no Request.URL.Host, we send
   333  	// an empty Host header, and don't use
   334  	// Request.Header["Host"]. This is just testing that
   335  	// we don't change Go 1.0 behavior.
   336  	{
   337  		Req: Request{
   338  			Method: "GET",
   339  			Host:   "",
   340  			URL: &url.URL{
   341  				Scheme: "http",
   342  				Host:   "",
   343  				Path:   "/search",
   344  			},
   345  			ProtoMajor: 1,
   346  			ProtoMinor: 1,
   347  			Header: Header{
   348  				"Host": []string{"bad.example.com"},
   349  			},
   350  		},
   351  
   352  		WantWrite: "GET /search HTTP/1.1\r\n" +
   353  			"Host: \r\n" +
   354  			"User-Agent: Go 1.1 package http\r\n\r\n",
   355  	},
   356  
   357  	// Opaque test #1 from golang.org/issue/4860
   358  	{
   359  		Req: Request{
   360  			Method: "GET",
   361  			URL: &url.URL{
   362  				Scheme: "http",
   363  				Host:   "www.google.com",
   364  				Opaque: "/%2F/%2F/",
   365  			},
   366  			ProtoMajor: 1,
   367  			ProtoMinor: 1,
   368  			Header:     Header{},
   369  		},
   370  
   371  		WantWrite: "GET /%2F/%2F/ HTTP/1.1\r\n" +
   372  			"Host: www.google.com\r\n" +
   373  			"User-Agent: Go 1.1 package http\r\n\r\n",
   374  	},
   375  
   376  	// Opaque test #2 from golang.org/issue/4860
   377  	{
   378  		Req: Request{
   379  			Method: "GET",
   380  			URL: &url.URL{
   381  				Scheme: "http",
   382  				Host:   "x.google.com",
   383  				Opaque: "//y.google.com/%2F/%2F/",
   384  			},
   385  			ProtoMajor: 1,
   386  			ProtoMinor: 1,
   387  			Header:     Header{},
   388  		},
   389  
   390  		WantWrite: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" +
   391  			"Host: x.google.com\r\n" +
   392  			"User-Agent: Go 1.1 package http\r\n\r\n",
   393  	},
   394  
   395  	// Testing custom case in header keys. Issue 5022.
   396  	{
   397  		Req: Request{
   398  			Method: "GET",
   399  			URL: &url.URL{
   400  				Scheme: "http",
   401  				Host:   "www.google.com",
   402  				Path:   "/",
   403  			},
   404  			Proto:      "HTTP/1.1",
   405  			ProtoMajor: 1,
   406  			ProtoMinor: 1,
   407  			Header: Header{
   408  				"ALL-CAPS": {"x"},
   409  			},
   410  		},
   411  
   412  		WantWrite: "GET / HTTP/1.1\r\n" +
   413  			"Host: www.google.com\r\n" +
   414  			"User-Agent: Go 1.1 package http\r\n" +
   415  			"ALL-CAPS: x\r\n" +
   416  			"\r\n",
   417  	},
   418  }
   419  
   420  func TestRequestWrite(t *testing.T) {
   421  	for i := range reqWriteTests {
   422  		tt := &reqWriteTests[i]
   423  
   424  		setBody := func() {
   425  			if tt.Body == nil {
   426  				return
   427  			}
   428  			switch b := tt.Body.(type) {
   429  			case []byte:
   430  				tt.Req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
   431  			case func() io.ReadCloser:
   432  				tt.Req.Body = b()
   433  			}
   434  		}
   435  		setBody()
   436  		if tt.Req.Header == nil {
   437  			tt.Req.Header = make(Header)
   438  		}
   439  
   440  		var braw bytes.Buffer
   441  		err := tt.Req.Write(&braw)
   442  		if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e {
   443  			t.Errorf("writing #%d, err = %q, want %q", i, g, e)
   444  			continue
   445  		}
   446  		if err != nil {
   447  			continue
   448  		}
   449  
   450  		if tt.WantWrite != "" {
   451  			sraw := braw.String()
   452  			if sraw != tt.WantWrite {
   453  				t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw)
   454  				continue
   455  			}
   456  		}
   457  
   458  		if tt.WantProxy != "" {
   459  			setBody()
   460  			var praw bytes.Buffer
   461  			err = tt.Req.WriteProxy(&praw)
   462  			if err != nil {
   463  				t.Errorf("WriteProxy #%d: %s", i, err)
   464  				continue
   465  			}
   466  			sraw := praw.String()
   467  			if sraw != tt.WantProxy {
   468  				t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw)
   469  				continue
   470  			}
   471  		}
   472  	}
   473  }
   474  
   475  type closeChecker struct {
   476  	io.Reader
   477  	closed bool
   478  }
   479  
   480  func (rc *closeChecker) Close() error {
   481  	rc.closed = true
   482  	return nil
   483  }
   484  
   485  // TestRequestWriteClosesBody tests that Request.Write does close its request.Body.
   486  // It also indirectly tests NewRequest and that it doesn't wrap an existing Closer
   487  // inside a NopCloser, and that it serializes it correctly.
   488  func TestRequestWriteClosesBody(t *testing.T) {
   489  	rc := &closeChecker{Reader: strings.NewReader("my body")}
   490  	req, _ := NewRequest("POST", "http://foo.com/", rc)
   491  	if req.ContentLength != 0 {
   492  		t.Errorf("got req.ContentLength %d, want 0", req.ContentLength)
   493  	}
   494  	buf := new(bytes.Buffer)
   495  	req.Write(buf)
   496  	if !rc.closed {
   497  		t.Error("body not closed after write")
   498  	}
   499  	expected := "POST / HTTP/1.1\r\n" +
   500  		"Host: foo.com\r\n" +
   501  		"User-Agent: Go 1.1 package http\r\n" +
   502  		"Transfer-Encoding: chunked\r\n\r\n" +
   503  		// TODO: currently we don't buffer before chunking, so we get a
   504  		// single "m" chunk before the other chunks, as this was the 1-byte
   505  		// read from our MultiReader where we stiched the Body back together
   506  		// after sniffing whether the Body was 0 bytes or not.
   507  		chunk("m") +
   508  		chunk("y body") +
   509  		chunk("")
   510  	if buf.String() != expected {
   511  		t.Errorf("write:\n got: %s\nwant: %s", buf.String(), expected)
   512  	}
   513  }
   514  
   515  func chunk(s string) string {
   516  	return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
   517  }
   518  
   519  func mustParseURL(s string) *url.URL {
   520  	u, err := url.Parse(s)
   521  	if err != nil {
   522  		panic(fmt.Sprintf("Error parsing URL %q: %v", s, err))
   523  	}
   524  	return u
   525  }