github.com/yanyiwu/go@v0.0.0-20150106053140-03d6637dbb7f/src/net/http/response_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  	"bufio"
     9  	"bytes"
    10  	"compress/gzip"
    11  	"crypto/rand"
    12  	"fmt"
    13  	"io"
    14  	"io/ioutil"
    15  	"net/http/internal"
    16  	"net/url"
    17  	"reflect"
    18  	"regexp"
    19  	"strings"
    20  	"testing"
    21  )
    22  
    23  type respTest struct {
    24  	Raw  string
    25  	Resp Response
    26  	Body string
    27  }
    28  
    29  func dummyReq(method string) *Request {
    30  	return &Request{Method: method}
    31  }
    32  
    33  func dummyReq11(method string) *Request {
    34  	return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1}
    35  }
    36  
    37  var respTests = []respTest{
    38  	// Unchunked response without Content-Length.
    39  	{
    40  		"HTTP/1.0 200 OK\r\n" +
    41  			"Connection: close\r\n" +
    42  			"\r\n" +
    43  			"Body here\n",
    44  
    45  		Response{
    46  			Status:     "200 OK",
    47  			StatusCode: 200,
    48  			Proto:      "HTTP/1.0",
    49  			ProtoMajor: 1,
    50  			ProtoMinor: 0,
    51  			Request:    dummyReq("GET"),
    52  			Header: Header{
    53  				"Connection": {"close"}, // TODO(rsc): Delete?
    54  			},
    55  			Close:         true,
    56  			ContentLength: -1,
    57  		},
    58  
    59  		"Body here\n",
    60  	},
    61  
    62  	// Unchunked HTTP/1.1 response without Content-Length or
    63  	// Connection headers.
    64  	{
    65  		"HTTP/1.1 200 OK\r\n" +
    66  			"\r\n" +
    67  			"Body here\n",
    68  
    69  		Response{
    70  			Status:        "200 OK",
    71  			StatusCode:    200,
    72  			Proto:         "HTTP/1.1",
    73  			ProtoMajor:    1,
    74  			ProtoMinor:    1,
    75  			Header:        Header{},
    76  			Request:       dummyReq("GET"),
    77  			Close:         true,
    78  			ContentLength: -1,
    79  		},
    80  
    81  		"Body here\n",
    82  	},
    83  
    84  	// Unchunked HTTP/1.1 204 response without Content-Length.
    85  	{
    86  		"HTTP/1.1 204 No Content\r\n" +
    87  			"\r\n" +
    88  			"Body should not be read!\n",
    89  
    90  		Response{
    91  			Status:        "204 No Content",
    92  			StatusCode:    204,
    93  			Proto:         "HTTP/1.1",
    94  			ProtoMajor:    1,
    95  			ProtoMinor:    1,
    96  			Header:        Header{},
    97  			Request:       dummyReq("GET"),
    98  			Close:         false,
    99  			ContentLength: 0,
   100  		},
   101  
   102  		"",
   103  	},
   104  
   105  	// Unchunked response with Content-Length.
   106  	{
   107  		"HTTP/1.0 200 OK\r\n" +
   108  			"Content-Length: 10\r\n" +
   109  			"Connection: close\r\n" +
   110  			"\r\n" +
   111  			"Body here\n",
   112  
   113  		Response{
   114  			Status:     "200 OK",
   115  			StatusCode: 200,
   116  			Proto:      "HTTP/1.0",
   117  			ProtoMajor: 1,
   118  			ProtoMinor: 0,
   119  			Request:    dummyReq("GET"),
   120  			Header: Header{
   121  				"Connection":     {"close"},
   122  				"Content-Length": {"10"},
   123  			},
   124  			Close:         true,
   125  			ContentLength: 10,
   126  		},
   127  
   128  		"Body here\n",
   129  	},
   130  
   131  	// Chunked response without Content-Length.
   132  	{
   133  		"HTTP/1.1 200 OK\r\n" +
   134  			"Transfer-Encoding: chunked\r\n" +
   135  			"\r\n" +
   136  			"0a\r\n" +
   137  			"Body here\n\r\n" +
   138  			"09\r\n" +
   139  			"continued\r\n" +
   140  			"0\r\n" +
   141  			"\r\n",
   142  
   143  		Response{
   144  			Status:           "200 OK",
   145  			StatusCode:       200,
   146  			Proto:            "HTTP/1.1",
   147  			ProtoMajor:       1,
   148  			ProtoMinor:       1,
   149  			Request:          dummyReq("GET"),
   150  			Header:           Header{},
   151  			Close:            false,
   152  			ContentLength:    -1,
   153  			TransferEncoding: []string{"chunked"},
   154  		},
   155  
   156  		"Body here\ncontinued",
   157  	},
   158  
   159  	// Chunked response with Content-Length.
   160  	{
   161  		"HTTP/1.1 200 OK\r\n" +
   162  			"Transfer-Encoding: chunked\r\n" +
   163  			"Content-Length: 10\r\n" +
   164  			"\r\n" +
   165  			"0a\r\n" +
   166  			"Body here\n\r\n" +
   167  			"0\r\n" +
   168  			"\r\n",
   169  
   170  		Response{
   171  			Status:           "200 OK",
   172  			StatusCode:       200,
   173  			Proto:            "HTTP/1.1",
   174  			ProtoMajor:       1,
   175  			ProtoMinor:       1,
   176  			Request:          dummyReq("GET"),
   177  			Header:           Header{},
   178  			Close:            false,
   179  			ContentLength:    -1,
   180  			TransferEncoding: []string{"chunked"},
   181  		},
   182  
   183  		"Body here\n",
   184  	},
   185  
   186  	// Chunked response in response to a HEAD request
   187  	{
   188  		"HTTP/1.1 200 OK\r\n" +
   189  			"Transfer-Encoding: chunked\r\n" +
   190  			"\r\n",
   191  
   192  		Response{
   193  			Status:           "200 OK",
   194  			StatusCode:       200,
   195  			Proto:            "HTTP/1.1",
   196  			ProtoMajor:       1,
   197  			ProtoMinor:       1,
   198  			Request:          dummyReq("HEAD"),
   199  			Header:           Header{},
   200  			TransferEncoding: []string{"chunked"},
   201  			Close:            false,
   202  			ContentLength:    -1,
   203  		},
   204  
   205  		"",
   206  	},
   207  
   208  	// Content-Length in response to a HEAD request
   209  	{
   210  		"HTTP/1.0 200 OK\r\n" +
   211  			"Content-Length: 256\r\n" +
   212  			"\r\n",
   213  
   214  		Response{
   215  			Status:           "200 OK",
   216  			StatusCode:       200,
   217  			Proto:            "HTTP/1.0",
   218  			ProtoMajor:       1,
   219  			ProtoMinor:       0,
   220  			Request:          dummyReq("HEAD"),
   221  			Header:           Header{"Content-Length": {"256"}},
   222  			TransferEncoding: nil,
   223  			Close:            true,
   224  			ContentLength:    256,
   225  		},
   226  
   227  		"",
   228  	},
   229  
   230  	// Content-Length in response to a HEAD request with HTTP/1.1
   231  	{
   232  		"HTTP/1.1 200 OK\r\n" +
   233  			"Content-Length: 256\r\n" +
   234  			"\r\n",
   235  
   236  		Response{
   237  			Status:           "200 OK",
   238  			StatusCode:       200,
   239  			Proto:            "HTTP/1.1",
   240  			ProtoMajor:       1,
   241  			ProtoMinor:       1,
   242  			Request:          dummyReq("HEAD"),
   243  			Header:           Header{"Content-Length": {"256"}},
   244  			TransferEncoding: nil,
   245  			Close:            false,
   246  			ContentLength:    256,
   247  		},
   248  
   249  		"",
   250  	},
   251  
   252  	// No Content-Length or Chunked in response to a HEAD request
   253  	{
   254  		"HTTP/1.0 200 OK\r\n" +
   255  			"\r\n",
   256  
   257  		Response{
   258  			Status:           "200 OK",
   259  			StatusCode:       200,
   260  			Proto:            "HTTP/1.0",
   261  			ProtoMajor:       1,
   262  			ProtoMinor:       0,
   263  			Request:          dummyReq("HEAD"),
   264  			Header:           Header{},
   265  			TransferEncoding: nil,
   266  			Close:            true,
   267  			ContentLength:    -1,
   268  		},
   269  
   270  		"",
   271  	},
   272  
   273  	// explicit Content-Length of 0.
   274  	{
   275  		"HTTP/1.1 200 OK\r\n" +
   276  			"Content-Length: 0\r\n" +
   277  			"\r\n",
   278  
   279  		Response{
   280  			Status:     "200 OK",
   281  			StatusCode: 200,
   282  			Proto:      "HTTP/1.1",
   283  			ProtoMajor: 1,
   284  			ProtoMinor: 1,
   285  			Request:    dummyReq("GET"),
   286  			Header: Header{
   287  				"Content-Length": {"0"},
   288  			},
   289  			Close:         false,
   290  			ContentLength: 0,
   291  		},
   292  
   293  		"",
   294  	},
   295  
   296  	// Status line without a Reason-Phrase, but trailing space.
   297  	// (permitted by RFC 2616)
   298  	{
   299  		"HTTP/1.0 303 \r\n\r\n",
   300  		Response{
   301  			Status:        "303 ",
   302  			StatusCode:    303,
   303  			Proto:         "HTTP/1.0",
   304  			ProtoMajor:    1,
   305  			ProtoMinor:    0,
   306  			Request:       dummyReq("GET"),
   307  			Header:        Header{},
   308  			Close:         true,
   309  			ContentLength: -1,
   310  		},
   311  
   312  		"",
   313  	},
   314  
   315  	// Status line without a Reason-Phrase, and no trailing space.
   316  	// (not permitted by RFC 2616, but we'll accept it anyway)
   317  	{
   318  		"HTTP/1.0 303\r\n\r\n",
   319  		Response{
   320  			Status:        "303 ",
   321  			StatusCode:    303,
   322  			Proto:         "HTTP/1.0",
   323  			ProtoMajor:    1,
   324  			ProtoMinor:    0,
   325  			Request:       dummyReq("GET"),
   326  			Header:        Header{},
   327  			Close:         true,
   328  			ContentLength: -1,
   329  		},
   330  
   331  		"",
   332  	},
   333  
   334  	// golang.org/issue/4767: don't special-case multipart/byteranges responses
   335  	{
   336  		`HTTP/1.1 206 Partial Content
   337  Connection: close
   338  Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
   339  
   340  some body`,
   341  		Response{
   342  			Status:     "206 Partial Content",
   343  			StatusCode: 206,
   344  			Proto:      "HTTP/1.1",
   345  			ProtoMajor: 1,
   346  			ProtoMinor: 1,
   347  			Request:    dummyReq("GET"),
   348  			Header: Header{
   349  				"Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
   350  			},
   351  			Close:         true,
   352  			ContentLength: -1,
   353  		},
   354  
   355  		"some body",
   356  	},
   357  
   358  	// Unchunked response without Content-Length, Request is nil
   359  	{
   360  		"HTTP/1.0 200 OK\r\n" +
   361  			"Connection: close\r\n" +
   362  			"\r\n" +
   363  			"Body here\n",
   364  
   365  		Response{
   366  			Status:     "200 OK",
   367  			StatusCode: 200,
   368  			Proto:      "HTTP/1.0",
   369  			ProtoMajor: 1,
   370  			ProtoMinor: 0,
   371  			Header: Header{
   372  				"Connection": {"close"}, // TODO(rsc): Delete?
   373  			},
   374  			Close:         true,
   375  			ContentLength: -1,
   376  		},
   377  
   378  		"Body here\n",
   379  	},
   380  
   381  	// 206 Partial Content. golang.org/issue/8923
   382  	{
   383  		"HTTP/1.1 206 Partial Content\r\n" +
   384  			"Content-Type: text/plain; charset=utf-8\r\n" +
   385  			"Accept-Ranges: bytes\r\n" +
   386  			"Content-Range: bytes 0-5/1862\r\n" +
   387  			"Content-Length: 6\r\n\r\n" +
   388  			"foobar",
   389  
   390  		Response{
   391  			Status:     "206 Partial Content",
   392  			StatusCode: 206,
   393  			Proto:      "HTTP/1.1",
   394  			ProtoMajor: 1,
   395  			ProtoMinor: 1,
   396  			Request:    dummyReq("GET"),
   397  			Header: Header{
   398  				"Accept-Ranges":  []string{"bytes"},
   399  				"Content-Length": []string{"6"},
   400  				"Content-Type":   []string{"text/plain; charset=utf-8"},
   401  				"Content-Range":  []string{"bytes 0-5/1862"},
   402  			},
   403  			ContentLength: 6,
   404  		},
   405  
   406  		"foobar",
   407  	},
   408  }
   409  
   410  func TestReadResponse(t *testing.T) {
   411  	for i, tt := range respTests {
   412  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
   413  		if err != nil {
   414  			t.Errorf("#%d: %v", i, err)
   415  			continue
   416  		}
   417  		rbody := resp.Body
   418  		resp.Body = nil
   419  		diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
   420  		var bout bytes.Buffer
   421  		if rbody != nil {
   422  			_, err = io.Copy(&bout, rbody)
   423  			if err != nil {
   424  				t.Errorf("#%d: %v", i, err)
   425  				continue
   426  			}
   427  			rbody.Close()
   428  		}
   429  		body := bout.String()
   430  		if body != tt.Body {
   431  			t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
   432  		}
   433  	}
   434  }
   435  
   436  func TestWriteResponse(t *testing.T) {
   437  	for i, tt := range respTests {
   438  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
   439  		if err != nil {
   440  			t.Errorf("#%d: %v", i, err)
   441  			continue
   442  		}
   443  		err = resp.Write(ioutil.Discard)
   444  		if err != nil {
   445  			t.Errorf("#%d: %v", i, err)
   446  			continue
   447  		}
   448  	}
   449  }
   450  
   451  var readResponseCloseInMiddleTests = []struct {
   452  	chunked, compressed bool
   453  }{
   454  	{false, false},
   455  	{true, false},
   456  	{true, true},
   457  }
   458  
   459  // TestReadResponseCloseInMiddle tests that closing a body after
   460  // reading only part of its contents advances the read to the end of
   461  // the request, right up until the next request.
   462  func TestReadResponseCloseInMiddle(t *testing.T) {
   463  	for _, test := range readResponseCloseInMiddleTests {
   464  		fatalf := func(format string, args ...interface{}) {
   465  			args = append([]interface{}{test.chunked, test.compressed}, args...)
   466  			t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
   467  		}
   468  		checkErr := func(err error, msg string) {
   469  			if err == nil {
   470  				return
   471  			}
   472  			fatalf(msg+": %v", err)
   473  		}
   474  		var buf bytes.Buffer
   475  		buf.WriteString("HTTP/1.1 200 OK\r\n")
   476  		if test.chunked {
   477  			buf.WriteString("Transfer-Encoding: chunked\r\n")
   478  		} else {
   479  			buf.WriteString("Content-Length: 1000000\r\n")
   480  		}
   481  		var wr io.Writer = &buf
   482  		if test.chunked {
   483  			wr = internal.NewChunkedWriter(wr)
   484  		}
   485  		if test.compressed {
   486  			buf.WriteString("Content-Encoding: gzip\r\n")
   487  			wr = gzip.NewWriter(wr)
   488  		}
   489  		buf.WriteString("\r\n")
   490  
   491  		chunk := bytes.Repeat([]byte{'x'}, 1000)
   492  		for i := 0; i < 1000; i++ {
   493  			if test.compressed {
   494  				// Otherwise this compresses too well.
   495  				_, err := io.ReadFull(rand.Reader, chunk)
   496  				checkErr(err, "rand.Reader ReadFull")
   497  			}
   498  			wr.Write(chunk)
   499  		}
   500  		if test.compressed {
   501  			err := wr.(*gzip.Writer).Close()
   502  			checkErr(err, "compressor close")
   503  		}
   504  		if test.chunked {
   505  			buf.WriteString("0\r\n\r\n")
   506  		}
   507  		buf.WriteString("Next Request Here")
   508  
   509  		bufr := bufio.NewReader(&buf)
   510  		resp, err := ReadResponse(bufr, dummyReq("GET"))
   511  		checkErr(err, "ReadResponse")
   512  		expectedLength := int64(-1)
   513  		if !test.chunked {
   514  			expectedLength = 1000000
   515  		}
   516  		if resp.ContentLength != expectedLength {
   517  			fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
   518  		}
   519  		if resp.Body == nil {
   520  			fatalf("nil body")
   521  		}
   522  		if test.compressed {
   523  			gzReader, err := gzip.NewReader(resp.Body)
   524  			checkErr(err, "gzip.NewReader")
   525  			resp.Body = &readerAndCloser{gzReader, resp.Body}
   526  		}
   527  
   528  		rbuf := make([]byte, 2500)
   529  		n, err := io.ReadFull(resp.Body, rbuf)
   530  		checkErr(err, "2500 byte ReadFull")
   531  		if n != 2500 {
   532  			fatalf("ReadFull only read %d bytes", n)
   533  		}
   534  		if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
   535  			fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
   536  		}
   537  		resp.Body.Close()
   538  
   539  		rest, err := ioutil.ReadAll(bufr)
   540  		checkErr(err, "ReadAll on remainder")
   541  		if e, g := "Next Request Here", string(rest); e != g {
   542  			g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string {
   543  				return fmt.Sprintf("x(repeated x%d)", len(match))
   544  			})
   545  			fatalf("remainder = %q, expected %q", g, e)
   546  		}
   547  	}
   548  }
   549  
   550  func diff(t *testing.T, prefix string, have, want interface{}) {
   551  	hv := reflect.ValueOf(have).Elem()
   552  	wv := reflect.ValueOf(want).Elem()
   553  	if hv.Type() != wv.Type() {
   554  		t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
   555  	}
   556  	for i := 0; i < hv.NumField(); i++ {
   557  		hf := hv.Field(i).Interface()
   558  		wf := wv.Field(i).Interface()
   559  		if !reflect.DeepEqual(hf, wf) {
   560  			t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf)
   561  		}
   562  	}
   563  }
   564  
   565  type responseLocationTest struct {
   566  	location string // Response's Location header or ""
   567  	requrl   string // Response.Request.URL or ""
   568  	want     string
   569  	wantErr  error
   570  }
   571  
   572  var responseLocationTests = []responseLocationTest{
   573  	{"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
   574  	{"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
   575  	{"", "http://bar.com/baz", "", ErrNoLocation},
   576  }
   577  
   578  func TestLocationResponse(t *testing.T) {
   579  	for i, tt := range responseLocationTests {
   580  		res := new(Response)
   581  		res.Header = make(Header)
   582  		res.Header.Set("Location", tt.location)
   583  		if tt.requrl != "" {
   584  			res.Request = &Request{}
   585  			var err error
   586  			res.Request.URL, err = url.Parse(tt.requrl)
   587  			if err != nil {
   588  				t.Fatalf("bad test URL %q: %v", tt.requrl, err)
   589  			}
   590  		}
   591  
   592  		got, err := res.Location()
   593  		if tt.wantErr != nil {
   594  			if err == nil {
   595  				t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
   596  				continue
   597  			}
   598  			if g, e := err.Error(), tt.wantErr.Error(); g != e {
   599  				t.Errorf("%d. err=%q; want %q", i, g, e)
   600  				continue
   601  			}
   602  			continue
   603  		}
   604  		if err != nil {
   605  			t.Errorf("%d. err=%q", i, err)
   606  			continue
   607  		}
   608  		if g, e := got.String(), tt.want; g != e {
   609  			t.Errorf("%d. Location=%q; want %q", i, g, e)
   610  		}
   611  	}
   612  }
   613  
   614  func TestResponseStatusStutter(t *testing.T) {
   615  	r := &Response{
   616  		Status:     "123 some status",
   617  		StatusCode: 123,
   618  		ProtoMajor: 1,
   619  		ProtoMinor: 3,
   620  	}
   621  	var buf bytes.Buffer
   622  	r.Write(&buf)
   623  	if strings.Contains(buf.String(), "123 123") {
   624  		t.Errorf("stutter in status: %s", buf.String())
   625  	}
   626  }
   627  
   628  func TestResponseContentLengthShortBody(t *testing.T) {
   629  	const shortBody = "Short body, not 123 bytes."
   630  	br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" +
   631  		"Content-Length: 123\r\n" +
   632  		"\r\n" +
   633  		shortBody))
   634  	res, err := ReadResponse(br, &Request{Method: "GET"})
   635  	if err != nil {
   636  		t.Fatal(err)
   637  	}
   638  	if res.ContentLength != 123 {
   639  		t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
   640  	}
   641  	var buf bytes.Buffer
   642  	n, err := io.Copy(&buf, res.Body)
   643  	if n != int64(len(shortBody)) {
   644  		t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody)
   645  	}
   646  	if buf.String() != shortBody {
   647  		t.Errorf("Read body %q; want %q", buf.String(), shortBody)
   648  	}
   649  	if err != io.ErrUnexpectedEOF {
   650  		t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
   651  	}
   652  }
   653  
   654  func TestReadResponseUnexpectedEOF(t *testing.T) {
   655  	br := bufio.NewReader(strings.NewReader("HTTP/1.1 301 Moved Permanently\r\n" +
   656  		"Location: http://example.com"))
   657  	_, err := ReadResponse(br, nil)
   658  	if err != io.ErrUnexpectedEOF {
   659  		t.Errorf("ReadResponse = %v; want io.ErrUnexpectedEOF", err)
   660  	}
   661  }
   662  
   663  func TestNeedsSniff(t *testing.T) {
   664  	// needsSniff returns true with an empty response.
   665  	r := &response{}
   666  	if got, want := r.needsSniff(), true; got != want {
   667  		t.Errorf("needsSniff = %t; want %t", got, want)
   668  	}
   669  	// needsSniff returns false when Content-Type = nil.
   670  	r.handlerHeader = Header{"Content-Type": nil}
   671  	if got, want := r.needsSniff(), false; got != want {
   672  		t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want)
   673  	}
   674  }