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.
24 type respTest
struct {
30 func dummyReq(method
string) *Request
{
31 return &Request
{Method
: method
}
34 func dummyReq11(method
string) *Request
{
35 return &Request
{Method
: method
, Proto
: "HTTP/1.1", ProtoMajor
: 1, ProtoMinor
: 1}
38 var respTests
= []respTest
{
39 // Unchunked response without Content-Length.
41 "HTTP/1.0 200 OK\r\n" +
42 "Connection: close\r\n" +
52 Request
: dummyReq("GET"),
54 "Connection": {"close"}, // TODO(rsc): Delete?
63 // Unchunked HTTP/1.1 response without Content-Length or
64 // Connection headers.
66 "HTTP/1.1 200 OK\r\n" +
77 Request
: dummyReq("GET"),
85 // Unchunked HTTP/1.1 204 response without Content-Length.
87 "HTTP/1.1 204 No Content\r\n" +
89 "Body should not be read!\n",
92 Status
: "204 No Content",
98 Request
: dummyReq("GET"),
106 // Unchunked response with Content-Length.
108 "HTTP/1.0 200 OK\r\n" +
109 "Content-Length: 10\r\n" +
110 "Connection: close\r\n" +
120 Request
: dummyReq("GET"),
122 "Connection": {"close"},
123 "Content-Length": {"10"},
132 // Chunked response without Content-Length.
134 "HTTP/1.1 200 OK\r\n" +
135 "Transfer-Encoding: chunked\r\n" +
150 Request
: dummyReq("GET"),
154 TransferEncoding
: []string{"chunked"},
157 "Body here\ncontinued",
160 // Chunked response with Content-Length.
162 "HTTP/1.1 200 OK\r\n" +
163 "Transfer-Encoding: chunked\r\n" +
164 "Content-Length: 10\r\n" +
177 Request
: dummyReq("GET"),
181 TransferEncoding
: []string{"chunked"},
187 // Chunked response in response to a HEAD request
189 "HTTP/1.1 200 OK\r\n" +
190 "Transfer-Encoding: chunked\r\n" +
199 Request
: dummyReq("HEAD"),
201 TransferEncoding
: []string{"chunked"},
209 // Content-Length in response to a HEAD request
211 "HTTP/1.0 200 OK\r\n" +
212 "Content-Length: 256\r\n" +
221 Request
: dummyReq("HEAD"),
222 Header
: Header
{"Content-Length": {"256"}},
223 TransferEncoding
: nil,
231 // Content-Length in response to a HEAD request with HTTP/1.1
233 "HTTP/1.1 200 OK\r\n" +
234 "Content-Length: 256\r\n" +
243 Request
: dummyReq("HEAD"),
244 Header
: Header
{"Content-Length": {"256"}},
245 TransferEncoding
: nil,
253 // No Content-Length or Chunked in response to a HEAD request
255 "HTTP/1.0 200 OK\r\n" +
264 Request
: dummyReq("HEAD"),
266 TransferEncoding
: nil,
274 // explicit Content-Length of 0.
276 "HTTP/1.1 200 OK\r\n" +
277 "Content-Length: 0\r\n" +
286 Request
: dummyReq("GET"),
288 "Content-Length": {"0"},
297 // Status line without a Reason-Phrase, but trailing space.
298 // (permitted by RFC 2616)
300 "HTTP/1.0 303 \r\n\r\n",
307 Request
: dummyReq("GET"),
316 // Status line without a Reason-Phrase, and no trailing space.
317 // (not permitted by RFC 2616, but we'll accept it anyway)
319 "HTTP/1.0 303\r\n\r\n",
326 Request
: dummyReq("GET"),
335 // golang.org/issue/4767: don't special-case multipart/byteranges responses
337 `HTTP/1.1 206 Partial Content
339 Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
343 Status
: "206 Partial Content",
348 Request
: dummyReq("GET"),
350 "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
359 // Unchunked response without Content-Length, Request is nil
361 "HTTP/1.0 200 OK\r\n" +
362 "Connection: close\r\n" +
373 "Connection": {"close"}, // TODO(rsc): Delete?
382 // 206 Partial Content. golang.org/issue/8923
384 "HTTP/1.1 206 Partial Content\r\n" +
385 "Content-Type: text/plain; charset=utf-8\r\n" +
386 "Accept-Ranges: bytes\r\n" +
387 "Content-Range: bytes 0-5/1862\r\n" +
388 "Content-Length: 6\r\n\r\n" +
392 Status
: "206 Partial Content",
397 Request
: dummyReq("GET"),
399 "Accept-Ranges": []string{"bytes"},
400 "Content-Length": []string{"6"},
401 "Content-Type": []string{"text/plain; charset=utf-8"},
402 "Content-Range": []string{"bytes 0-5/1862"},
410 // Both keep-alive and close, on the same Connection line. (Issue 8840)
412 "HTTP/1.1 200 OK\r\n" +
413 "Content-Length: 256\r\n" +
414 "Connection: keep-alive, close\r\n" +
423 Request
: dummyReq("HEAD"),
425 "Content-Length": {"256"},
427 TransferEncoding
: nil,
435 // Both keep-alive and close, on different Connection lines. (Issue 8840)
437 "HTTP/1.1 200 OK\r\n" +
438 "Content-Length: 256\r\n" +
439 "Connection: keep-alive\r\n" +
440 "Connection: close\r\n" +
449 Request
: dummyReq("HEAD"),
451 "Content-Length": {"256"},
453 TransferEncoding
: nil,
461 // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
462 // Without a Content-Length.
464 "HTTP/1.0 200 OK\r\n" +
465 "Transfer-Encoding: bogus\r\n" +
475 Request
: dummyReq("GET"),
484 // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
485 // With a Content-Length.
487 "HTTP/1.0 200 OK\r\n" +
488 "Transfer-Encoding: bogus\r\n" +
489 "Content-Length: 10\r\n" +
499 Request
: dummyReq("GET"),
501 "Content-Length": {"10"},
511 "HTTP/1.1 200 OK\r\n" +
512 "Content-Encoding: gzip\r\n" +
513 "Content-Length: 23\r\n" +
514 "Connection: keep-alive\r\n" +
515 "Keep-Alive: timeout=7200\r\n\r\n" +
516 "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
523 Request
: dummyReq("GET"),
525 "Content-Length": {"23"},
526 "Content-Encoding": {"gzip"},
527 "Connection": {"keep-alive"},
528 "Keep-Alive": {"timeout=7200"},
533 "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
536 // Issue 19989: two spaces between HTTP version and status.
538 "HTTP/1.0 401 Unauthorized\r\n" +
539 "Content-type: text/html\r\n" +
540 "WWW-Authenticate: Basic realm=\"\"\r\n\r\n" +
541 "Your Authentication failed.\r\n",
543 Status
: "401 Unauthorized",
548 Request
: dummyReq("GET"),
550 "Content-Type": {"text/html"},
551 "Www-Authenticate": {`Basic realm=""`},
556 "Your Authentication failed.\r\n",
560 // tests successful calls to ReadResponse, and inspects the returned Response.
561 // For error cases, see TestReadResponseErrors below.
562 func TestReadResponse(t
*testing
.T
) {
563 for i
, tt
:= range respTests
{
564 resp
, err
:= ReadResponse(bufio
.NewReader(strings
.NewReader(tt
.Raw
)), tt
.Resp
.Request
)
566 t
.Errorf("#%d: %v", i
, err
)
571 diff(t
, fmt
.Sprintf("#%d Response", i
), resp
, &tt
.Resp
)
572 var bout bytes
.Buffer
574 _
, err
= io
.Copy(&bout
, rbody
)
576 t
.Errorf("#%d: %v", i
, err
)
581 body
:= bout
.String()
583 t
.Errorf("#%d: Body = %q want %q", i
, body
, tt
.Body
)
588 func TestWriteResponse(t
*testing
.T
) {
589 for i
, tt
:= range respTests
{
590 resp
, err
:= ReadResponse(bufio
.NewReader(strings
.NewReader(tt
.Raw
)), tt
.Resp
.Request
)
592 t
.Errorf("#%d: %v", i
, err
)
595 err
= resp
.Write(ioutil
.Discard
)
597 t
.Errorf("#%d: %v", i
, err
)
603 var readResponseCloseInMiddleTests
= []struct {
604 chunked
, compressed
bool
611 // TestReadResponseCloseInMiddle tests that closing a body after
612 // reading only part of its contents advances the read to the end of
613 // the request, right up until the next request.
614 func TestReadResponseCloseInMiddle(t
*testing
.T
) {
616 for _
, test
:= range readResponseCloseInMiddleTests
{
617 fatalf
:= func(format
string, args
...interface{}) {
618 args
= append([]interface{}{test
.chunked
, test
.compressed
}, args
...)
619 t
.Fatalf("on test chunked=%v, compressed=%v: "+format
, args
...)
621 checkErr
:= func(err error
, msg
string) {
625 fatalf(msg
+": %v", err
)
628 buf
.WriteString("HTTP/1.1 200 OK\r\n")
630 buf
.WriteString("Transfer-Encoding: chunked\r\n")
632 buf
.WriteString("Content-Length: 1000000\r\n")
634 var wr io
.Writer
= &buf
636 wr
= internal
.NewChunkedWriter(wr
)
639 buf
.WriteString("Content-Encoding: gzip\r\n")
640 wr
= gzip
.NewWriter(wr
)
642 buf
.WriteString("\r\n")
644 chunk
:= bytes
.Repeat([]byte{'x'}, 1000)
645 for i
:= 0; i
< 1000; i
++ {
647 // Otherwise this compresses too well.
648 _
, err
:= io
.ReadFull(rand
.Reader
, chunk
)
649 checkErr(err
, "rand.Reader ReadFull")
654 err
:= wr
.(*gzip
.Writer
).Close()
655 checkErr(err
, "compressor close")
658 buf
.WriteString("0\r\n\r\n")
660 buf
.WriteString("Next Request Here")
662 bufr
:= bufio
.NewReader(&buf
)
663 resp
, err
:= ReadResponse(bufr
, dummyReq("GET"))
664 checkErr(err
, "ReadResponse")
665 expectedLength
:= int64(-1)
667 expectedLength
= 1000000
669 if resp
.ContentLength
!= expectedLength
{
670 fatalf("expected response length %d, got %d", expectedLength
, resp
.ContentLength
)
672 if resp
.Body
== nil {
676 gzReader
, err
:= gzip
.NewReader(resp
.Body
)
677 checkErr(err
, "gzip.NewReader")
678 resp
.Body
= &readerAndCloser
{gzReader
, resp
.Body
}
681 rbuf
:= make([]byte, 2500)
682 n
, err
:= io
.ReadFull(resp
.Body
, rbuf
)
683 checkErr(err
, "2500 byte ReadFull")
685 fatalf("ReadFull only read %d bytes", n
)
687 if test
.compressed
== false && !bytes
.Equal(bytes
.Repeat([]byte{'x'}, 2500), rbuf
) {
688 fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf
))
692 rest
, err
:= ioutil
.ReadAll(bufr
)
693 checkErr(err
, "ReadAll on remainder")
694 if e
, g
:= "Next Request Here", string(rest
); e
!= g
{
695 g
= regexp
.MustCompile(`(xx+)`).ReplaceAllStringFunc(g
, func(match
string) string {
696 return fmt
.Sprintf("x(repeated x%d)", len(match
))
698 fatalf("remainder = %q, expected %q", g
, e
)
703 func diff(t
*testing
.T
, prefix
string, have
, want
interface{}) {
704 hv
:= reflect
.ValueOf(have
).Elem()
705 wv
:= reflect
.ValueOf(want
).Elem()
706 if hv
.Type() != wv
.Type() {
707 t
.Errorf("%s: type mismatch %v want %v", prefix
, hv
.Type(), wv
.Type())
709 for i
:= 0; i
< hv
.NumField(); i
++ {
710 name
:= hv
.Type().Field(i
).Name
711 if !ast
.IsExported(name
) {
714 hf
:= hv
.Field(i
).Interface()
715 wf
:= wv
.Field(i
).Interface()
716 if !reflect
.DeepEqual(hf
, wf
) {
717 t
.Errorf("%s: %s = %v want %v", prefix
, name
, hf
, wf
)
722 type responseLocationTest
struct {
723 location
string // Response's Location header or ""
724 requrl
string // Response.Request.URL or ""
729 var responseLocationTests
= []responseLocationTest
{
730 {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
731 {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
732 {"", "http://bar.com/baz", "", ErrNoLocation
},
733 {"/bar", "", "/bar", nil},
736 func TestLocationResponse(t
*testing
.T
) {
737 for i
, tt
:= range responseLocationTests
{
739 res
.Header
= make(Header
)
740 res
.Header
.Set("Location", tt
.location
)
742 res
.Request
= &Request
{}
744 res
.Request
.URL
, err
= url
.Parse(tt
.requrl
)
746 t
.Fatalf("bad test URL %q: %v", tt
.requrl
, err
)
750 got
, err
:= res
.Location()
751 if tt
.wantErr
!= nil {
753 t
.Errorf("%d. err=nil; want %q", i
, tt
.wantErr
)
756 if g
, e
:= err
.Error(), tt
.wantErr
.Error(); g
!= e
{
757 t
.Errorf("%d. err=%q; want %q", i
, g
, e
)
763 t
.Errorf("%d. err=%q", i
, err
)
766 if g
, e
:= got
.String(), tt
.want
; g
!= e
{
767 t
.Errorf("%d. Location=%q; want %q", i
, g
, e
)
772 func TestResponseStatusStutter(t
*testing
.T
) {
774 Status
: "123 some status",
781 if strings
.Contains(buf
.String(), "123 123") {
782 t
.Errorf("stutter in status: %s", buf
.String())
786 func TestResponseContentLengthShortBody(t
*testing
.T
) {
787 const shortBody
= "Short body, not 123 bytes."
788 br
:= bufio
.NewReader(strings
.NewReader("HTTP/1.1 200 OK\r\n" +
789 "Content-Length: 123\r\n" +
792 res
, err
:= ReadResponse(br
, &Request
{Method
: "GET"})
796 if res
.ContentLength
!= 123 {
797 t
.Fatalf("Content-Length = %d; want 123", res
.ContentLength
)
800 n
, err
:= io
.Copy(&buf
, res
.Body
)
801 if n
!= int64(len(shortBody
)) {
802 t
.Errorf("Copied %d bytes; want %d, len(%q)", n
, len(shortBody
), shortBody
)
804 if buf
.String() != shortBody
{
805 t
.Errorf("Read body %q; want %q", buf
.String(), shortBody
)
807 if err
!= io
.ErrUnexpectedEOF
{
808 t
.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err
)
812 // Test various ReadResponse error cases. (also tests success cases, but mostly
813 // it's about errors). This does not test anything involving the bodies. Only
814 // the return value from ReadResponse itself.
815 func TestReadResponseErrors(t
*testing
.T
) {
816 type testCase
struct {
817 name
string // optional, defaults to in
820 wantErr
interface{} // nil, err value, or string substring
823 status
:= func(s
string, wantErr
interface{}) testCase
{
825 wantErr
= "malformed HTTP status code"
828 name
: fmt
.Sprintf("status %q", s
),
829 in
: "HTTP/1.1 " + s
+ "\r\nFoo: bar\r\n\r\n",
834 version
:= func(s
string, wantErr
interface{}) testCase
{
836 wantErr
= "malformed HTTP version"
839 name
: fmt
.Sprintf("version %q", s
),
840 in
: s
+ " 200 OK\r\n\r\n",
845 contentLength
:= func(status
, body
string, wantErr
interface{}, header Header
) testCase
{
847 name
: fmt
.Sprintf("status %q %q", status
, body
),
848 in
: fmt
.Sprintf("HTTP/1.1 %s\r\n%s", status
, body
),
854 errMultiCL
:= "message cannot contain multiple Content-Length headers"
857 {"", "", nil, io
.ErrUnexpectedEOF
},
858 {"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", nil, io
.ErrUnexpectedEOF
},
859 {"", "HTTP/1.1", nil, "malformed HTTP response"},
860 {"", "HTTP/2.0", nil, "malformed HTTP response"},
861 status("20X Unknown", true),
862 status("abcd Unknown", true),
863 status("二百/两百 OK", true),
864 status(" Unknown", true),
865 status("c8 OK", true),
866 status("0x12d Moved Permanently", true),
867 status("200 OK", nil),
868 status("000 OK", nil),
869 status("001 OK", nil),
870 status("404 NOTFOUND", nil),
871 status("20 OK", true),
872 status("00 OK", true),
873 status("-10 OK", true),
874 status("1000 OK", true),
875 status("999 Done", nil),
876 status("-1 OK", true),
877 status("-200 OK", true),
878 version("HTTP/1.2", nil),
879 version("HTTP/2.0", nil),
880 version("HTTP/1.100000000002", true),
881 version("HTTP/1.-1", true),
882 version("HTTP/A.B", true),
883 version("HTTP/1", true),
884 version("http/1.1", true),
886 contentLength("200 OK", "Content-Length: 10\r\nContent-Length: 7\r\n\r\nGopher hey\r\n", errMultiCL
, nil),
887 contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil, Header
{"Content-Length": {"7"}}),
888 contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL
, nil),
889 contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil, Header
{"Content-Length": {"0"}}),
890 contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil, nil),
891 contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL
, nil),
893 // multiple content-length headers for 204 and 304 should still be checked
894 contentLength("204 OK", "Content-Length: 7\r\nContent-Length: 8\r\n\r\n", errMultiCL
, nil),
895 contentLength("204 OK", "Content-Length: 3\r\nContent-Length: 3\r\n\r\n", nil, nil),
896 contentLength("304 OK", "Content-Length: 880\r\nContent-Length: 1\r\n\r\n", errMultiCL
, nil),
897 contentLength("304 OK", "Content-Length: 961\r\nContent-Length: 961\r\n\r\n", nil, nil),
900 for i
, tt
:= range tests
{
901 br
:= bufio
.NewReader(strings
.NewReader(tt
.in
))
902 _
, rerr
:= ReadResponse(br
, nil)
903 if err
:= matchErr(rerr
, tt
.wantErr
); err
!= nil {
906 name
= fmt
.Sprintf("%d. input %q", i
, tt
.in
)
908 t
.Errorf("%s: %v", name
, err
)
913 // wantErr can be nil, an error value to match exactly, or type string to
914 // match a substring.
915 func matchErr(err error
, wantErr
interface{}) error
{
920 if sub
, ok
:= wantErr
.(string); ok
{
921 return fmt
.Errorf("unexpected success; want error with substring %q", sub
)
923 return fmt
.Errorf("unexpected success; want error %v", wantErr
)
926 return fmt
.Errorf("%v; want success", err
)
928 if sub
, ok
:= wantErr
.(string); ok
{
929 if strings
.Contains(err
.Error(), sub
) {
932 return fmt
.Errorf("error = %v; want an error with substring %q", err
, sub
)
937 return fmt
.Errorf("%v; want %v", err
, wantErr
)
940 func TestNeedsSniff(t
*testing
.T
) {
941 // needsSniff returns true with an empty response.
943 if got
, want
:= r
.needsSniff(), true; got
!= want
{
944 t
.Errorf("needsSniff = %t; want %t", got
, want
)
946 // needsSniff returns false when Content-Type = nil.
947 r
.handlerHeader
= Header
{"Content-Type": nil}
948 if got
, want
:= r
.needsSniff(), false; got
!= want
{
949 t
.Errorf("needsSniff empty Content-Type = %t; want %t", got
, want
)
953 // A response should only write out single Connection: close header. Tests #19499.
954 func TestResponseWritesOnlySingleConnectionClose(t
*testing
.T
) {
955 const connectionCloseHeader
= "Connection: close"
957 res
, err
:= ReadResponse(bufio
.NewReader(strings
.NewReader("HTTP/1.0 200 OK\r\n\r\nAAAA")), nil)
959 t
.Fatalf("ReadResponse failed %v", err
)
962 var buf1 bytes
.Buffer
963 if err
= res
.Write(&buf1
); err
!= nil {
964 t
.Fatalf("Write failed %v", err
)
966 if res
, err
= ReadResponse(bufio
.NewReader(&buf1
), nil); err
!= nil {
967 t
.Fatalf("ReadResponse failed %v", err
)
970 var buf2 bytes
.Buffer
971 if err
= res
.Write(&buf2
); err
!= nil {
972 t
.Fatalf("Write failed %v", err
)
974 if count
:= strings
.Count(buf2
.String(), connectionCloseHeader
); count
!= 1 {
975 t
.Errorf("Found %d %q header", count
, connectionCloseHeader
)