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