diff options
Diffstat (limited to 'src/pkg/net/http/response_test.go')
-rw-r--r-- | src/pkg/net/http/response_test.go | 645 |
1 files changed, 0 insertions, 645 deletions
diff --git a/src/pkg/net/http/response_test.go b/src/pkg/net/http/response_test.go deleted file mode 100644 index 4b8946f7a..000000000 --- a/src/pkg/net/http/response_test.go +++ /dev/null @@ -1,645 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package http - -import ( - "bufio" - "bytes" - "compress/gzip" - "crypto/rand" - "fmt" - "io" - "io/ioutil" - "net/url" - "reflect" - "regexp" - "strings" - "testing" -) - -type respTest struct { - Raw string - Resp Response - Body string -} - -func dummyReq(method string) *Request { - return &Request{Method: method} -} - -func dummyReq11(method string) *Request { - return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1} -} - -var respTests = []respTest{ - // Unchunked response without Content-Length. - { - "HTTP/1.0 200 OK\r\n" + - "Connection: close\r\n" + - "\r\n" + - "Body here\n", - - Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Request: dummyReq("GET"), - Header: Header{ - "Connection": {"close"}, // TODO(rsc): Delete? - }, - Close: true, - ContentLength: -1, - }, - - "Body here\n", - }, - - // Unchunked HTTP/1.1 response without Content-Length or - // Connection headers. - { - "HTTP/1.1 200 OK\r\n" + - "\r\n" + - "Body here\n", - - Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Header: Header{}, - Request: dummyReq("GET"), - Close: true, - ContentLength: -1, - }, - - "Body here\n", - }, - - // Unchunked HTTP/1.1 204 response without Content-Length. - { - "HTTP/1.1 204 No Content\r\n" + - "\r\n" + - "Body should not be read!\n", - - Response{ - Status: "204 No Content", - StatusCode: 204, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Header: Header{}, - Request: dummyReq("GET"), - Close: false, - ContentLength: 0, - }, - - "", - }, - - // Unchunked response with Content-Length. - { - "HTTP/1.0 200 OK\r\n" + - "Content-Length: 10\r\n" + - "Connection: close\r\n" + - "\r\n" + - "Body here\n", - - Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Request: dummyReq("GET"), - Header: Header{ - "Connection": {"close"}, - "Content-Length": {"10"}, - }, - Close: true, - ContentLength: 10, - }, - - "Body here\n", - }, - - // Chunked response without Content-Length. - { - "HTTP/1.1 200 OK\r\n" + - "Transfer-Encoding: chunked\r\n" + - "\r\n" + - "0a\r\n" + - "Body here\n\r\n" + - "09\r\n" + - "continued\r\n" + - "0\r\n" + - "\r\n", - - Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Request: dummyReq("GET"), - Header: Header{}, - Close: false, - ContentLength: -1, - TransferEncoding: []string{"chunked"}, - }, - - "Body here\ncontinued", - }, - - // Chunked response with Content-Length. - { - "HTTP/1.1 200 OK\r\n" + - "Transfer-Encoding: chunked\r\n" + - "Content-Length: 10\r\n" + - "\r\n" + - "0a\r\n" + - "Body here\n\r\n" + - "0\r\n" + - "\r\n", - - Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Request: dummyReq("GET"), - Header: Header{}, - Close: false, - ContentLength: -1, - TransferEncoding: []string{"chunked"}, - }, - - "Body here\n", - }, - - // Chunked response in response to a HEAD request - { - "HTTP/1.1 200 OK\r\n" + - "Transfer-Encoding: chunked\r\n" + - "\r\n", - - Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Request: dummyReq("HEAD"), - Header: Header{}, - TransferEncoding: []string{"chunked"}, - Close: false, - ContentLength: -1, - }, - - "", - }, - - // Content-Length in response to a HEAD request - { - "HTTP/1.0 200 OK\r\n" + - "Content-Length: 256\r\n" + - "\r\n", - - Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Request: dummyReq("HEAD"), - Header: Header{"Content-Length": {"256"}}, - TransferEncoding: nil, - Close: true, - ContentLength: 256, - }, - - "", - }, - - // Content-Length in response to a HEAD request with HTTP/1.1 - { - "HTTP/1.1 200 OK\r\n" + - "Content-Length: 256\r\n" + - "\r\n", - - Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Request: dummyReq("HEAD"), - Header: Header{"Content-Length": {"256"}}, - TransferEncoding: nil, - Close: false, - ContentLength: 256, - }, - - "", - }, - - // No Content-Length or Chunked in response to a HEAD request - { - "HTTP/1.0 200 OK\r\n" + - "\r\n", - - Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Request: dummyReq("HEAD"), - Header: Header{}, - TransferEncoding: nil, - Close: true, - ContentLength: -1, - }, - - "", - }, - - // explicit Content-Length of 0. - { - "HTTP/1.1 200 OK\r\n" + - "Content-Length: 0\r\n" + - "\r\n", - - Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Request: dummyReq("GET"), - Header: Header{ - "Content-Length": {"0"}, - }, - Close: false, - ContentLength: 0, - }, - - "", - }, - - // Status line without a Reason-Phrase, but trailing space. - // (permitted by RFC 2616) - { - "HTTP/1.0 303 \r\n\r\n", - Response{ - Status: "303 ", - StatusCode: 303, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Request: dummyReq("GET"), - Header: Header{}, - Close: true, - ContentLength: -1, - }, - - "", - }, - - // Status line without a Reason-Phrase, and no trailing space. - // (not permitted by RFC 2616, but we'll accept it anyway) - { - "HTTP/1.0 303\r\n\r\n", - Response{ - Status: "303 ", - StatusCode: 303, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Request: dummyReq("GET"), - Header: Header{}, - Close: true, - ContentLength: -1, - }, - - "", - }, - - // golang.org/issue/4767: don't special-case multipart/byteranges responses - { - `HTTP/1.1 206 Partial Content -Connection: close -Content-Type: multipart/byteranges; boundary=18a75608c8f47cef - -some body`, - Response{ - Status: "206 Partial Content", - StatusCode: 206, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Request: dummyReq("GET"), - Header: Header{ - "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"}, - }, - Close: true, - ContentLength: -1, - }, - - "some body", - }, - - // Unchunked response without Content-Length, Request is nil - { - "HTTP/1.0 200 OK\r\n" + - "Connection: close\r\n" + - "\r\n" + - "Body here\n", - - Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: Header{ - "Connection": {"close"}, // TODO(rsc): Delete? - }, - Close: true, - ContentLength: -1, - }, - - "Body here\n", - }, -} - -func TestReadResponse(t *testing.T) { - for i, tt := range respTests { - resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) - if err != nil { - t.Errorf("#%d: %v", i, err) - continue - } - rbody := resp.Body - resp.Body = nil - diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp) - var bout bytes.Buffer - if rbody != nil { - _, err = io.Copy(&bout, rbody) - if err != nil { - t.Errorf("#%d: %v", i, err) - continue - } - rbody.Close() - } - body := bout.String() - if body != tt.Body { - t.Errorf("#%d: Body = %q want %q", i, body, tt.Body) - } - } -} - -func TestWriteResponse(t *testing.T) { - for i, tt := range respTests { - resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) - if err != nil { - t.Errorf("#%d: %v", i, err) - continue - } - err = resp.Write(ioutil.Discard) - if err != nil { - t.Errorf("#%d: %v", i, err) - continue - } - } -} - -var readResponseCloseInMiddleTests = []struct { - chunked, compressed bool -}{ - {false, false}, - {true, false}, - {true, true}, -} - -// TestReadResponseCloseInMiddle tests that closing a body after -// reading only part of its contents advances the read to the end of -// the request, right up until the next request. -func TestReadResponseCloseInMiddle(t *testing.T) { - for _, test := range readResponseCloseInMiddleTests { - fatalf := func(format string, args ...interface{}) { - args = append([]interface{}{test.chunked, test.compressed}, args...) - t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...) - } - checkErr := func(err error, msg string) { - if err == nil { - return - } - fatalf(msg+": %v", err) - } - var buf bytes.Buffer - buf.WriteString("HTTP/1.1 200 OK\r\n") - if test.chunked { - buf.WriteString("Transfer-Encoding: chunked\r\n") - } else { - buf.WriteString("Content-Length: 1000000\r\n") - } - var wr io.Writer = &buf - if test.chunked { - wr = newChunkedWriter(wr) - } - if test.compressed { - buf.WriteString("Content-Encoding: gzip\r\n") - wr = gzip.NewWriter(wr) - } - buf.WriteString("\r\n") - - chunk := bytes.Repeat([]byte{'x'}, 1000) - for i := 0; i < 1000; i++ { - if test.compressed { - // Otherwise this compresses too well. - _, err := io.ReadFull(rand.Reader, chunk) - checkErr(err, "rand.Reader ReadFull") - } - wr.Write(chunk) - } - if test.compressed { - err := wr.(*gzip.Writer).Close() - checkErr(err, "compressor close") - } - if test.chunked { - buf.WriteString("0\r\n\r\n") - } - buf.WriteString("Next Request Here") - - bufr := bufio.NewReader(&buf) - resp, err := ReadResponse(bufr, dummyReq("GET")) - checkErr(err, "ReadResponse") - expectedLength := int64(-1) - if !test.chunked { - expectedLength = 1000000 - } - if resp.ContentLength != expectedLength { - fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength) - } - if resp.Body == nil { - fatalf("nil body") - } - if test.compressed { - gzReader, err := gzip.NewReader(resp.Body) - checkErr(err, "gzip.NewReader") - resp.Body = &readerAndCloser{gzReader, resp.Body} - } - - rbuf := make([]byte, 2500) - n, err := io.ReadFull(resp.Body, rbuf) - checkErr(err, "2500 byte ReadFull") - if n != 2500 { - fatalf("ReadFull only read %d bytes", n) - } - if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) { - fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf)) - } - resp.Body.Close() - - rest, err := ioutil.ReadAll(bufr) - checkErr(err, "ReadAll on remainder") - if e, g := "Next Request Here", string(rest); e != g { - g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string { - return fmt.Sprintf("x(repeated x%d)", len(match)) - }) - fatalf("remainder = %q, expected %q", g, e) - } - } -} - -func diff(t *testing.T, prefix string, have, want interface{}) { - hv := reflect.ValueOf(have).Elem() - wv := reflect.ValueOf(want).Elem() - if hv.Type() != wv.Type() { - t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type()) - } - for i := 0; i < hv.NumField(); i++ { - hf := hv.Field(i).Interface() - wf := wv.Field(i).Interface() - if !reflect.DeepEqual(hf, wf) { - t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf) - } - } -} - -type responseLocationTest struct { - location string // Response's Location header or "" - requrl string // Response.Request.URL or "" - want string - wantErr error -} - -var responseLocationTests = []responseLocationTest{ - {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil}, - {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil}, - {"", "http://bar.com/baz", "", ErrNoLocation}, -} - -func TestLocationResponse(t *testing.T) { - for i, tt := range responseLocationTests { - res := new(Response) - res.Header = make(Header) - res.Header.Set("Location", tt.location) - if tt.requrl != "" { - res.Request = &Request{} - var err error - res.Request.URL, err = url.Parse(tt.requrl) - if err != nil { - t.Fatalf("bad test URL %q: %v", tt.requrl, err) - } - } - - got, err := res.Location() - if tt.wantErr != nil { - if err == nil { - t.Errorf("%d. err=nil; want %q", i, tt.wantErr) - continue - } - if g, e := err.Error(), tt.wantErr.Error(); g != e { - t.Errorf("%d. err=%q; want %q", i, g, e) - continue - } - continue - } - if err != nil { - t.Errorf("%d. err=%q", i, err) - continue - } - if g, e := got.String(), tt.want; g != e { - t.Errorf("%d. Location=%q; want %q", i, g, e) - } - } -} - -func TestResponseStatusStutter(t *testing.T) { - r := &Response{ - Status: "123 some status", - StatusCode: 123, - ProtoMajor: 1, - ProtoMinor: 3, - } - var buf bytes.Buffer - r.Write(&buf) - if strings.Contains(buf.String(), "123 123") { - t.Errorf("stutter in status: %s", buf.String()) - } -} - -func TestResponseContentLengthShortBody(t *testing.T) { - const shortBody = "Short body, not 123 bytes." - br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" + - "Content-Length: 123\r\n" + - "\r\n" + - shortBody)) - res, err := ReadResponse(br, &Request{Method: "GET"}) - if err != nil { - t.Fatal(err) - } - if res.ContentLength != 123 { - t.Fatalf("Content-Length = %d; want 123", res.ContentLength) - } - var buf bytes.Buffer - n, err := io.Copy(&buf, res.Body) - if n != int64(len(shortBody)) { - t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody) - } - if buf.String() != shortBody { - t.Errorf("Read body %q; want %q", buf.String(), shortBody) - } - if err != io.ErrUnexpectedEOF { - t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err) - } -} - -func TestReadResponseUnexpectedEOF(t *testing.T) { - br := bufio.NewReader(strings.NewReader("HTTP/1.1 301 Moved Permanently\r\n" + - "Location: http://example.com")) - _, err := ReadResponse(br, nil) - if err != io.ErrUnexpectedEOF { - t.Errorf("ReadResponse = %v; want io.ErrUnexpectedEOF", err) - } -} - -func TestNeedsSniff(t *testing.T) { - // needsSniff returns true with an empty response. - r := &response{} - if got, want := r.needsSniff(), true; got != want { - t.Errorf("needsSniff = %t; want %t", got, want) - } - // needsSniff returns false when Content-Type = nil. - r.handlerHeader = Header{"Content-Type": nil} - if got, want := r.needsSniff(), false; got != want { - t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want) - } -} |