github.com/peggyl/go@v0.0.0-20151008231540-ae315999c2d5/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  	// Both keep-alive and close, on the same Connection line. (Issue 8840)
   410  	{
   411  		"HTTP/1.1 200 OK\r\n" +
   412  			"Content-Length: 256\r\n" +
   413  			"Connection: keep-alive, close\r\n" +
   414  			"\r\n",
   415  
   416  		Response{
   417  			Status:     "200 OK",
   418  			StatusCode: 200,
   419  			Proto:      "HTTP/1.1",
   420  			ProtoMajor: 1,
   421  			ProtoMinor: 1,
   422  			Request:    dummyReq("HEAD"),
   423  			Header: Header{
   424  				"Content-Length": {"256"},
   425  			},
   426  			TransferEncoding: nil,
   427  			Close:            true,
   428  			ContentLength:    256,
   429  		},
   430  
   431  		"",
   432  	},
   433  
   434  	// Both keep-alive and close, on different Connection lines. (Issue 8840)
   435  	{
   436  		"HTTP/1.1 200 OK\r\n" +
   437  			"Content-Length: 256\r\n" +
   438  			"Connection: keep-alive\r\n" +
   439  			"Connection: close\r\n" +
   440  			"\r\n",
   441  
   442  		Response{
   443  			Status:     "200 OK",
   444  			StatusCode: 200,
   445  			Proto:      "HTTP/1.1",
   446  			ProtoMajor: 1,
   447  			ProtoMinor: 1,
   448  			Request:    dummyReq("HEAD"),
   449  			Header: Header{
   450  				"Content-Length": {"256"},
   451  			},
   452  			TransferEncoding: nil,
   453  			Close:            true,
   454  			ContentLength:    256,
   455  		},
   456  
   457  		"",
   458  	},
   459  }
   460  
   461  func TestReadResponse(t *testing.T) {
   462  	for i, tt := range respTests {
   463  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
   464  		if err != nil {
   465  			t.Errorf("#%d: %v", i, err)
   466  			continue
   467  		}
   468  		rbody := resp.Body
   469  		resp.Body = nil
   470  		diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
   471  		var bout bytes.Buffer
   472  		if rbody != nil {
   473  			_, err = io.Copy(&bout, rbody)
   474  			if err != nil {
   475  				t.Errorf("#%d: %v", i, err)
   476  				continue
   477  			}
   478  			rbody.Close()
   479  		}
   480  		body := bout.String()
   481  		if body != tt.Body {
   482  			t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
   483  		}
   484  	}
   485  }
   486  
   487  func TestWriteResponse(t *testing.T) {
   488  	for i, tt := range respTests {
   489  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
   490  		if err != nil {
   491  			t.Errorf("#%d: %v", i, err)
   492  			continue
   493  		}
   494  		err = resp.Write(ioutil.Discard)
   495  		if err != nil {
   496  			t.Errorf("#%d: %v", i, err)
   497  			continue
   498  		}
   499  	}
   500  }
   501  
   502  var readResponseCloseInMiddleTests = []struct {
   503  	chunked, compressed bool
   504  }{
   505  	{false, false},
   506  	{true, false},
   507  	{true, true},
   508  }
   509  
   510  // TestReadResponseCloseInMiddle tests that closing a body after
   511  // reading only part of its contents advances the read to the end of
   512  // the request, right up until the next request.
   513  func TestReadResponseCloseInMiddle(t *testing.T) {
   514  	for _, test := range readResponseCloseInMiddleTests {
   515  		fatalf := func(format string, args ...interface{}) {
   516  			args = append([]interface{}{test.chunked, test.compressed}, args...)
   517  			t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
   518  		}
   519  		checkErr := func(err error, msg string) {
   520  			if err == nil {
   521  				return
   522  			}
   523  			fatalf(msg+": %v", err)
   524  		}
   525  		var buf bytes.Buffer
   526  		buf.WriteString("HTTP/1.1 200 OK\r\n")
   527  		if test.chunked {
   528  			buf.WriteString("Transfer-Encoding: chunked\r\n")
   529  		} else {
   530  			buf.WriteString("Content-Length: 1000000\r\n")
   531  		}
   532  		var wr io.Writer = &buf
   533  		if test.chunked {
   534  			wr = internal.NewChunkedWriter(wr)
   535  		}
   536  		if test.compressed {
   537  			buf.WriteString("Content-Encoding: gzip\r\n")
   538  			wr = gzip.NewWriter(wr)
   539  		}
   540  		buf.WriteString("\r\n")
   541  
   542  		chunk := bytes.Repeat([]byte{'x'}, 1000)
   543  		for i := 0; i < 1000; i++ {
   544  			if test.compressed {
   545  				// Otherwise this compresses too well.
   546  				_, err := io.ReadFull(rand.Reader, chunk)
   547  				checkErr(err, "rand.Reader ReadFull")
   548  			}
   549  			wr.Write(chunk)
   550  		}
   551  		if test.compressed {
   552  			err := wr.(*gzip.Writer).Close()
   553  			checkErr(err, "compressor close")
   554  		}
   555  		if test.chunked {
   556  			buf.WriteString("0\r\n\r\n")
   557  		}
   558  		buf.WriteString("Next Request Here")
   559  
   560  		bufr := bufio.NewReader(&buf)
   561  		resp, err := ReadResponse(bufr, dummyReq("GET"))
   562  		checkErr(err, "ReadResponse")
   563  		expectedLength := int64(-1)
   564  		if !test.chunked {
   565  			expectedLength = 1000000
   566  		}
   567  		if resp.ContentLength != expectedLength {
   568  			fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
   569  		}
   570  		if resp.Body == nil {
   571  			fatalf("nil body")
   572  		}
   573  		if test.compressed {
   574  			gzReader, err := gzip.NewReader(resp.Body)
   575  			checkErr(err, "gzip.NewReader")
   576  			resp.Body = &readerAndCloser{gzReader, resp.Body}
   577  		}
   578  
   579  		rbuf := make([]byte, 2500)
   580  		n, err := io.ReadFull(resp.Body, rbuf)
   581  		checkErr(err, "2500 byte ReadFull")
   582  		if n != 2500 {
   583  			fatalf("ReadFull only read %d bytes", n)
   584  		}
   585  		if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
   586  			fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
   587  		}
   588  		resp.Body.Close()
   589  
   590  		rest, err := ioutil.ReadAll(bufr)
   591  		checkErr(err, "ReadAll on remainder")
   592  		if e, g := "Next Request Here", string(rest); e != g {
   593  			g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string {
   594  				return fmt.Sprintf("x(repeated x%d)", len(match))
   595  			})
   596  			fatalf("remainder = %q, expected %q", g, e)
   597  		}
   598  	}
   599  }
   600  
   601  func diff(t *testing.T, prefix string, have, want interface{}) {
   602  	hv := reflect.ValueOf(have).Elem()
   603  	wv := reflect.ValueOf(want).Elem()
   604  	if hv.Type() != wv.Type() {
   605  		t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
   606  	}
   607  	for i := 0; i < hv.NumField(); i++ {
   608  		hf := hv.Field(i).Interface()
   609  		wf := wv.Field(i).Interface()
   610  		if !reflect.DeepEqual(hf, wf) {
   611  			t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf)
   612  		}
   613  	}
   614  }
   615  
   616  type responseLocationTest struct {
   617  	location string // Response's Location header or ""
   618  	requrl   string // Response.Request.URL or ""
   619  	want     string
   620  	wantErr  error
   621  }
   622  
   623  var responseLocationTests = []responseLocationTest{
   624  	{"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
   625  	{"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
   626  	{"", "http://bar.com/baz", "", ErrNoLocation},
   627  }
   628  
   629  func TestLocationResponse(t *testing.T) {
   630  	for i, tt := range responseLocationTests {
   631  		res := new(Response)
   632  		res.Header = make(Header)
   633  		res.Header.Set("Location", tt.location)
   634  		if tt.requrl != "" {
   635  			res.Request = &Request{}
   636  			var err error
   637  			res.Request.URL, err = url.Parse(tt.requrl)
   638  			if err != nil {
   639  				t.Fatalf("bad test URL %q: %v", tt.requrl, err)
   640  			}
   641  		}
   642  
   643  		got, err := res.Location()
   644  		if tt.wantErr != nil {
   645  			if err == nil {
   646  				t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
   647  				continue
   648  			}
   649  			if g, e := err.Error(), tt.wantErr.Error(); g != e {
   650  				t.Errorf("%d. err=%q; want %q", i, g, e)
   651  				continue
   652  			}
   653  			continue
   654  		}
   655  		if err != nil {
   656  			t.Errorf("%d. err=%q", i, err)
   657  			continue
   658  		}
   659  		if g, e := got.String(), tt.want; g != e {
   660  			t.Errorf("%d. Location=%q; want %q", i, g, e)
   661  		}
   662  	}
   663  }
   664  
   665  func TestResponseStatusStutter(t *testing.T) {
   666  	r := &Response{
   667  		Status:     "123 some status",
   668  		StatusCode: 123,
   669  		ProtoMajor: 1,
   670  		ProtoMinor: 3,
   671  	}
   672  	var buf bytes.Buffer
   673  	r.Write(&buf)
   674  	if strings.Contains(buf.String(), "123 123") {
   675  		t.Errorf("stutter in status: %s", buf.String())
   676  	}
   677  }
   678  
   679  func TestResponseContentLengthShortBody(t *testing.T) {
   680  	const shortBody = "Short body, not 123 bytes."
   681  	br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" +
   682  		"Content-Length: 123\r\n" +
   683  		"\r\n" +
   684  		shortBody))
   685  	res, err := ReadResponse(br, &Request{Method: "GET"})
   686  	if err != nil {
   687  		t.Fatal(err)
   688  	}
   689  	if res.ContentLength != 123 {
   690  		t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
   691  	}
   692  	var buf bytes.Buffer
   693  	n, err := io.Copy(&buf, res.Body)
   694  	if n != int64(len(shortBody)) {
   695  		t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody)
   696  	}
   697  	if buf.String() != shortBody {
   698  		t.Errorf("Read body %q; want %q", buf.String(), shortBody)
   699  	}
   700  	if err != io.ErrUnexpectedEOF {
   701  		t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
   702  	}
   703  }
   704  
   705  func TestReadResponseUnexpectedEOF(t *testing.T) {
   706  	br := bufio.NewReader(strings.NewReader("HTTP/1.1 301 Moved Permanently\r\n" +
   707  		"Location: http://example.com"))
   708  	_, err := ReadResponse(br, nil)
   709  	if err != io.ErrUnexpectedEOF {
   710  		t.Errorf("ReadResponse = %v; want io.ErrUnexpectedEOF", err)
   711  	}
   712  }
   713  
   714  func TestNeedsSniff(t *testing.T) {
   715  	// needsSniff returns true with an empty response.
   716  	r := &response{}
   717  	if got, want := r.needsSniff(), true; got != want {
   718  		t.Errorf("needsSniff = %t; want %t", got, want)
   719  	}
   720  	// needsSniff returns false when Content-Type = nil.
   721  	r.handlerHeader = Header{"Content-Type": nil}
   722  	if got, want := r.needsSniff(), false; got != want {
   723  		t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want)
   724  	}
   725  }