github.com/varialus/godfly@v0.0.0-20130904042352-1934f9f095ab/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  	// Unchunked response without Content-Length, Request is nil
   353  	{
   354  		"HTTP/1.0 200 OK\r\n" +
   355  			"Connection: close\r\n" +
   356  			"\r\n" +
   357  			"Body here\n",
   358  
   359  		Response{
   360  			Status:     "200 OK",
   361  			StatusCode: 200,
   362  			Proto:      "HTTP/1.0",
   363  			ProtoMajor: 1,
   364  			ProtoMinor: 0,
   365  			Header: Header{
   366  				"Connection": {"close"}, // TODO(rsc): Delete?
   367  			},
   368  			Close:         true,
   369  			ContentLength: -1,
   370  		},
   371  
   372  		"Body here\n",
   373  	},
   374  }
   375  
   376  func TestReadResponse(t *testing.T) {
   377  	for i, tt := range respTests {
   378  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
   379  		if err != nil {
   380  			t.Errorf("#%d: %v", i, err)
   381  			continue
   382  		}
   383  		rbody := resp.Body
   384  		resp.Body = nil
   385  		diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
   386  		var bout bytes.Buffer
   387  		if rbody != nil {
   388  			_, err = io.Copy(&bout, rbody)
   389  			if err != nil {
   390  				t.Errorf("#%d: %v", i, err)
   391  				continue
   392  			}
   393  			rbody.Close()
   394  		}
   395  		body := bout.String()
   396  		if body != tt.Body {
   397  			t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
   398  		}
   399  	}
   400  }
   401  
   402  func TestWriteResponse(t *testing.T) {
   403  	for i, tt := range respTests {
   404  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
   405  		if err != nil {
   406  			t.Errorf("#%d: %v", i, err)
   407  			continue
   408  		}
   409  		bout := bytes.NewBuffer(nil)
   410  		err = resp.Write(bout)
   411  		if err != nil {
   412  			t.Errorf("#%d: %v", i, err)
   413  			continue
   414  		}
   415  	}
   416  }
   417  
   418  var readResponseCloseInMiddleTests = []struct {
   419  	chunked, compressed bool
   420  }{
   421  	{false, false},
   422  	{true, false},
   423  	{true, true},
   424  }
   425  
   426  // TestReadResponseCloseInMiddle tests that closing a body after
   427  // reading only part of its contents advances the read to the end of
   428  // the request, right up until the next request.
   429  func TestReadResponseCloseInMiddle(t *testing.T) {
   430  	for _, test := range readResponseCloseInMiddleTests {
   431  		fatalf := func(format string, args ...interface{}) {
   432  			args = append([]interface{}{test.chunked, test.compressed}, args...)
   433  			t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
   434  		}
   435  		checkErr := func(err error, msg string) {
   436  			if err == nil {
   437  				return
   438  			}
   439  			fatalf(msg+": %v", err)
   440  		}
   441  		var buf bytes.Buffer
   442  		buf.WriteString("HTTP/1.1 200 OK\r\n")
   443  		if test.chunked {
   444  			buf.WriteString("Transfer-Encoding: chunked\r\n")
   445  		} else {
   446  			buf.WriteString("Content-Length: 1000000\r\n")
   447  		}
   448  		var wr io.Writer = &buf
   449  		if test.chunked {
   450  			wr = newChunkedWriter(wr)
   451  		}
   452  		if test.compressed {
   453  			buf.WriteString("Content-Encoding: gzip\r\n")
   454  			wr = gzip.NewWriter(wr)
   455  		}
   456  		buf.WriteString("\r\n")
   457  
   458  		chunk := bytes.Repeat([]byte{'x'}, 1000)
   459  		for i := 0; i < 1000; i++ {
   460  			if test.compressed {
   461  				// Otherwise this compresses too well.
   462  				_, err := io.ReadFull(rand.Reader, chunk)
   463  				checkErr(err, "rand.Reader ReadFull")
   464  			}
   465  			wr.Write(chunk)
   466  		}
   467  		if test.compressed {
   468  			err := wr.(*gzip.Writer).Close()
   469  			checkErr(err, "compressor close")
   470  		}
   471  		if test.chunked {
   472  			buf.WriteString("0\r\n\r\n")
   473  		}
   474  		buf.WriteString("Next Request Here")
   475  
   476  		bufr := bufio.NewReader(&buf)
   477  		resp, err := ReadResponse(bufr, dummyReq("GET"))
   478  		checkErr(err, "ReadResponse")
   479  		expectedLength := int64(-1)
   480  		if !test.chunked {
   481  			expectedLength = 1000000
   482  		}
   483  		if resp.ContentLength != expectedLength {
   484  			fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
   485  		}
   486  		if resp.Body == nil {
   487  			fatalf("nil body")
   488  		}
   489  		if test.compressed {
   490  			gzReader, err := gzip.NewReader(resp.Body)
   491  			checkErr(err, "gzip.NewReader")
   492  			resp.Body = &readerAndCloser{gzReader, resp.Body}
   493  		}
   494  
   495  		rbuf := make([]byte, 2500)
   496  		n, err := io.ReadFull(resp.Body, rbuf)
   497  		checkErr(err, "2500 byte ReadFull")
   498  		if n != 2500 {
   499  			fatalf("ReadFull only read %d bytes", n)
   500  		}
   501  		if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
   502  			fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
   503  		}
   504  		resp.Body.Close()
   505  
   506  		rest, err := ioutil.ReadAll(bufr)
   507  		checkErr(err, "ReadAll on remainder")
   508  		if e, g := "Next Request Here", string(rest); e != g {
   509  			fatalf("remainder = %q, expected %q", g, e)
   510  		}
   511  	}
   512  }
   513  
   514  func diff(t *testing.T, prefix string, have, want interface{}) {
   515  	hv := reflect.ValueOf(have).Elem()
   516  	wv := reflect.ValueOf(want).Elem()
   517  	if hv.Type() != wv.Type() {
   518  		t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
   519  	}
   520  	for i := 0; i < hv.NumField(); i++ {
   521  		hf := hv.Field(i).Interface()
   522  		wf := wv.Field(i).Interface()
   523  		if !reflect.DeepEqual(hf, wf) {
   524  			t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf)
   525  		}
   526  	}
   527  }
   528  
   529  type responseLocationTest struct {
   530  	location string // Response's Location header or ""
   531  	requrl   string // Response.Request.URL or ""
   532  	want     string
   533  	wantErr  error
   534  }
   535  
   536  var responseLocationTests = []responseLocationTest{
   537  	{"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
   538  	{"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
   539  	{"", "http://bar.com/baz", "", ErrNoLocation},
   540  }
   541  
   542  func TestLocationResponse(t *testing.T) {
   543  	for i, tt := range responseLocationTests {
   544  		res := new(Response)
   545  		res.Header = make(Header)
   546  		res.Header.Set("Location", tt.location)
   547  		if tt.requrl != "" {
   548  			res.Request = &Request{}
   549  			var err error
   550  			res.Request.URL, err = url.Parse(tt.requrl)
   551  			if err != nil {
   552  				t.Fatalf("bad test URL %q: %v", tt.requrl, err)
   553  			}
   554  		}
   555  
   556  		got, err := res.Location()
   557  		if tt.wantErr != nil {
   558  			if err == nil {
   559  				t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
   560  				continue
   561  			}
   562  			if g, e := err.Error(), tt.wantErr.Error(); g != e {
   563  				t.Errorf("%d. err=%q; want %q", i, g, e)
   564  				continue
   565  			}
   566  			continue
   567  		}
   568  		if err != nil {
   569  			t.Errorf("%d. err=%q", i, err)
   570  			continue
   571  		}
   572  		if g, e := got.String(), tt.want; g != e {
   573  			t.Errorf("%d. Location=%q; want %q", i, g, e)
   574  		}
   575  	}
   576  }
   577  
   578  func TestResponseStatusStutter(t *testing.T) {
   579  	r := &Response{
   580  		Status:     "123 some status",
   581  		StatusCode: 123,
   582  		ProtoMajor: 1,
   583  		ProtoMinor: 3,
   584  	}
   585  	var buf bytes.Buffer
   586  	r.Write(&buf)
   587  	if strings.Contains(buf.String(), "123 123") {
   588  		t.Errorf("stutter in status: %s", buf.String())
   589  	}
   590  }
   591  
   592  func TestResponseContentLengthShortBody(t *testing.T) {
   593  	const shortBody = "Short body, not 123 bytes."
   594  	br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" +
   595  		"Content-Length: 123\r\n" +
   596  		"\r\n" +
   597  		shortBody))
   598  	res, err := ReadResponse(br, &Request{Method: "GET"})
   599  	if err != nil {
   600  		t.Fatal(err)
   601  	}
   602  	if res.ContentLength != 123 {
   603  		t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
   604  	}
   605  	var buf bytes.Buffer
   606  	n, err := io.Copy(&buf, res.Body)
   607  	if n != int64(len(shortBody)) {
   608  		t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody)
   609  	}
   610  	if buf.String() != shortBody {
   611  		t.Errorf("Read body %q; want %q", buf.String(), shortBody)
   612  	}
   613  	if err != io.ErrUnexpectedEOF {
   614  		t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
   615  	}
   616  }