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.
21 type reqWriteTest
struct {
23 Body
interface{} // optional []byte or func() io.ReadCloser to populate Req.Body
25 // Any of these three may be empty to skip that test.
26 WantWrite
string // Request.Write
27 WantProxy
string // Request.WriteProxy
29 WantError error
// wanted error from Request.Write
32 var reqWriteTests
= []reqWriteTest
{
33 // HTTP/1.1 => chunked coding; no body; no trailer
39 Host
: "www.techcrunch.com",
46 "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
47 "Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
48 "Accept-Encoding": {"gzip,deflate"},
49 "Accept-Language": {"en-us,en;q=0.5"},
50 "Keep-Alive": {"300"},
51 "Proxy-Connection": {"keep-alive"},
52 "User-Agent": {"Fake"},
56 Host
: "www.techcrunch.com",
57 Form
: map[string][]string{},
60 WantWrite
: "GET / HTTP/1.1\r\n" +
61 "Host: www.techcrunch.com\r\n" +
62 "User-Agent: Fake\r\n" +
63 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
64 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
65 "Accept-Encoding: gzip,deflate\r\n" +
66 "Accept-Language: en-us,en;q=0.5\r\n" +
67 "Keep-Alive: 300\r\n" +
68 "Proxy-Connection: keep-alive\r\n\r\n",
70 WantProxy
: "GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
71 "Host: www.techcrunch.com\r\n" +
72 "User-Agent: Fake\r\n" +
73 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
74 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
75 "Accept-Encoding: gzip,deflate\r\n" +
76 "Accept-Language: en-us,en;q=0.5\r\n" +
77 "Keep-Alive: 300\r\n" +
78 "Proxy-Connection: keep-alive\r\n\r\n",
80 // HTTP/1.1 => chunked coding; body; empty trailer
86 Host
: "www.google.com",
92 TransferEncoding
: []string{"chunked"},
95 Body
: []byte("abcdef"),
97 WantWrite
: "GET /search HTTP/1.1\r\n" +
98 "Host: www.google.com\r\n" +
99 "User-Agent: Go-http-client/1.1\r\n" +
100 "Transfer-Encoding: chunked\r\n\r\n" +
101 chunk("abcdef") + chunk(""),
103 WantProxy
: "GET http://www.google.com/search HTTP/1.1\r\n" +
104 "Host: www.google.com\r\n" +
105 "User-Agent: Go-http-client/1.1\r\n" +
106 "Transfer-Encoding: chunked\r\n\r\n" +
107 chunk("abcdef") + chunk(""),
109 // HTTP/1.1 POST => chunked coding; body; empty trailer
115 Host
: "www.google.com",
122 TransferEncoding
: []string{"chunked"},
125 Body
: []byte("abcdef"),
127 WantWrite
: "POST /search HTTP/1.1\r\n" +
128 "Host: www.google.com\r\n" +
129 "User-Agent: Go-http-client/1.1\r\n" +
130 "Connection: close\r\n" +
131 "Transfer-Encoding: chunked\r\n\r\n" +
132 chunk("abcdef") + chunk(""),
134 WantProxy
: "POST http://www.google.com/search HTTP/1.1\r\n" +
135 "Host: www.google.com\r\n" +
136 "User-Agent: Go-http-client/1.1\r\n" +
137 "Connection: close\r\n" +
138 "Transfer-Encoding: chunked\r\n\r\n" +
139 chunk("abcdef") + chunk(""),
142 // HTTP/1.1 POST with Content-Length, no chunking
148 Host
: "www.google.com",
158 Body
: []byte("abcdef"),
160 WantWrite
: "POST /search HTTP/1.1\r\n" +
161 "Host: www.google.com\r\n" +
162 "User-Agent: Go-http-client/1.1\r\n" +
163 "Connection: close\r\n" +
164 "Content-Length: 6\r\n" +
168 WantProxy
: "POST http://www.google.com/search HTTP/1.1\r\n" +
169 "Host: www.google.com\r\n" +
170 "User-Agent: Go-http-client/1.1\r\n" +
171 "Connection: close\r\n" +
172 "Content-Length: 6\r\n" +
177 // HTTP/1.1 POST with Content-Length in headers
181 URL
: mustParseURL("http://example.com/"),
184 "Content-Length": []string{"10"}, // ignored
189 Body
: []byte("abcdef"),
191 WantWrite
: "POST / HTTP/1.1\r\n" +
192 "Host: example.com\r\n" +
193 "User-Agent: Go-http-client/1.1\r\n" +
194 "Content-Length: 6\r\n" +
198 WantProxy
: "POST http://example.com/ HTTP/1.1\r\n" +
199 "Host: example.com\r\n" +
200 "User-Agent: Go-http-client/1.1\r\n" +
201 "Content-Length: 6\r\n" +
206 // default to HTTP/1.1
210 URL
: mustParseURL("/search"),
211 Host
: "www.google.com",
214 WantWrite
: "GET /search HTTP/1.1\r\n" +
215 "Host: www.google.com\r\n" +
216 "User-Agent: Go-http-client/1.1\r\n" +
220 // Request with a 0 ContentLength and a 0 byte body.
224 URL
: mustParseURL("/"),
228 ContentLength
: 0, // as if unset by user
231 Body
: func() io
.ReadCloser
{ return ioutil
.NopCloser(io
.LimitReader(strings
.NewReader("xx"), 0)) },
233 WantWrite
: "POST / HTTP/1.1\r\n" +
234 "Host: example.com\r\n" +
235 "User-Agent: Go-http-client/1.1\r\n" +
236 "Transfer-Encoding: chunked\r\n" +
239 WantProxy
: "POST / HTTP/1.1\r\n" +
240 "Host: example.com\r\n" +
241 "User-Agent: Go-http-client/1.1\r\n" +
242 "Transfer-Encoding: chunked\r\n" +
246 // Request with a 0 ContentLength and a nil body.
250 URL
: mustParseURL("/"),
254 ContentLength
: 0, // as if unset by user
257 Body
: func() io
.ReadCloser
{ return nil },
259 WantWrite
: "POST / HTTP/1.1\r\n" +
260 "Host: example.com\r\n" +
261 "User-Agent: Go-http-client/1.1\r\n" +
262 "Content-Length: 0\r\n" +
265 WantProxy
: "POST / HTTP/1.1\r\n" +
266 "Host: example.com\r\n" +
267 "User-Agent: Go-http-client/1.1\r\n" +
268 "Content-Length: 0\r\n" +
272 // Request with a 0 ContentLength and a 1 byte body.
276 URL
: mustParseURL("/"),
280 ContentLength
: 0, // as if unset by user
283 Body
: func() io
.ReadCloser
{ return ioutil
.NopCloser(io
.LimitReader(strings
.NewReader("xx"), 1)) },
285 WantWrite
: "POST / HTTP/1.1\r\n" +
286 "Host: example.com\r\n" +
287 "User-Agent: Go-http-client/1.1\r\n" +
288 "Transfer-Encoding: chunked\r\n\r\n" +
289 chunk("x") + chunk(""),
291 WantProxy
: "POST / HTTP/1.1\r\n" +
292 "Host: example.com\r\n" +
293 "User-Agent: Go-http-client/1.1\r\n" +
294 "Transfer-Encoding: chunked\r\n\r\n" +
295 chunk("x") + chunk(""),
298 // Request with a ContentLength of 10 but a 5 byte body.
302 URL
: mustParseURL("/"),
306 ContentLength
: 10, // but we're going to send only 5 bytes
308 Body
: []byte("12345"),
309 WantError
: errors
.New("http: ContentLength=10 with Body length 5"),
312 // Request with a ContentLength of 4 but an 8 byte body.
316 URL
: mustParseURL("/"),
320 ContentLength
: 4, // but we're going to try to send 8 bytes
322 Body
: []byte("12345678"),
323 WantError
: errors
.New("http: ContentLength=4 with Body length 8"),
326 // Request with a 5 ContentLength and nil body.
330 URL
: mustParseURL("/"),
334 ContentLength
: 5, // but we'll omit the body
336 WantError
: errors
.New("http: Request.ContentLength=5 with nil Body"),
339 // Request with a 0 ContentLength and a body with 1 byte content and an error.
343 URL
: mustParseURL("/"),
347 ContentLength
: 0, // as if unset by user
350 Body
: func() io
.ReadCloser
{
351 err
:= errors
.New("Custom reader error")
352 errReader
:= &errorReader
{err
}
353 return ioutil
.NopCloser(io
.MultiReader(strings
.NewReader("x"), errReader
))
356 WantError
: errors
.New("Custom reader error"),
359 // Request with a 0 ContentLength and a body without content and an error.
363 URL
: mustParseURL("/"),
367 ContentLength
: 0, // as if unset by user
370 Body
: func() io
.ReadCloser
{
371 err
:= errors
.New("Custom reader error")
372 errReader
:= &errorReader
{err
}
373 return ioutil
.NopCloser(errReader
)
376 WantError
: errors
.New("Custom reader error"),
379 // Verify that DumpRequest preserves the HTTP version number, doesn't add a Host,
380 // and doesn't add a User-Agent.
384 URL
: mustParseURL("/foo"),
388 "X-Foo": []string{"X-Bar"},
392 WantWrite
: "GET /foo HTTP/1.1\r\n" +
394 "User-Agent: Go-http-client/1.1\r\n" +
395 "X-Foo: X-Bar\r\n\r\n",
398 // If no Request.Host and no Request.URL.Host, we send
399 // an empty Host header, and don't use
400 // Request.Header["Host"]. This is just testing that
401 // we don't change Go 1.0 behavior.
414 "Host": []string{"bad.example.com"},
418 WantWrite
: "GET /search HTTP/1.1\r\n" +
420 "User-Agent: Go-http-client/1.1\r\n\r\n",
423 // Opaque test #1 from golang.org/issue/4860
429 Host
: "www.google.com",
437 WantWrite
: "GET /%2F/%2F/ HTTP/1.1\r\n" +
438 "Host: www.google.com\r\n" +
439 "User-Agent: Go-http-client/1.1\r\n\r\n",
442 // Opaque test #2 from golang.org/issue/4860
448 Host
: "x.google.com",
449 Opaque
: "//y.google.com/%2F/%2F/",
456 WantWrite
: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" +
457 "Host: x.google.com\r\n" +
458 "User-Agent: Go-http-client/1.1\r\n\r\n",
461 // Testing custom case in header keys. Issue 5022.
467 Host
: "www.google.com",
478 WantWrite
: "GET / HTTP/1.1\r\n" +
479 "Host: www.google.com\r\n" +
480 "User-Agent: Go-http-client/1.1\r\n" +
485 // Request with host header field; IPv6 address with zone identifier
490 Host
: "[fe80::1%en0]",
494 WantWrite
: "GET / HTTP/1.1\r\n" +
495 "Host: [fe80::1]\r\n" +
496 "User-Agent: Go-http-client/1.1\r\n" +
500 // Request with optional host header field; IPv6 address with zone identifier
505 Host
: "www.example.com",
507 Host
: "[fe80::1%en0]:8080",
510 WantWrite
: "GET / HTTP/1.1\r\n" +
511 "Host: [fe80::1]:8080\r\n" +
512 "User-Agent: Go-http-client/1.1\r\n" +
517 func TestRequestWrite(t
*testing
.T
) {
518 for i
:= range reqWriteTests
{
519 tt
:= &reqWriteTests
[i
]
525 switch b
:= tt
.Body
.(type) {
527 tt
.Req
.Body
= ioutil
.NopCloser(bytes
.NewReader(b
))
528 case func() io
.ReadCloser
:
533 if tt
.Req
.Header
== nil {
534 tt
.Req
.Header
= make(Header
)
537 var braw bytes
.Buffer
538 err
:= tt
.Req
.Write(&braw
)
539 if g
, e
:= fmt
.Sprintf("%v", err
), fmt
.Sprintf("%v", tt
.WantError
); g
!= e
{
540 t
.Errorf("writing #%d, err = %q, want %q", i
, g
, e
)
547 if tt
.WantWrite
!= "" {
548 sraw
:= braw
.String()
549 if sraw
!= tt
.WantWrite
{
550 t
.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i
, tt
.WantWrite
, sraw
)
555 if tt
.WantProxy
!= "" {
557 var praw bytes
.Buffer
558 err
= tt
.Req
.WriteProxy(&praw
)
560 t
.Errorf("WriteProxy #%d: %s", i
, err
)
563 sraw
:= praw
.String()
564 if sraw
!= tt
.WantProxy
{
565 t
.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i
, tt
.WantProxy
, sraw
)
572 func TestRequestWriteTransport(t
*testing
.T
) {
575 matchSubstr
:= func(substr
string) func(string) error
{
576 return func(written
string) error
{
577 if !strings
.Contains(written
, substr
) {
578 return fmt
.Errorf("expected substring %q in request: %s", substr
, written
)
584 noContentLengthOrTransferEncoding
:= func(req
string) error
{
585 if strings
.Contains(req
, "Content-Length: ") {
586 return fmt
.Errorf("unexpected Content-Length in request: %s", req
)
588 if strings
.Contains(req
, "Transfer-Encoding: ") {
589 return fmt
.Errorf("unexpected Transfer-Encoding in request: %s", req
)
594 all
:= func(checks
...func(string) error
) func(string) error
{
595 return func(req
string) error
{
596 for _
, c
:= range checks
{
597 if err
:= c(req
); err
!= nil {
605 type testCase
struct {
607 clen
int64 // ContentLength
609 want
func(string) error
619 want
: noContentLengthOrTransferEncoding
,
623 body
: ioutil
.NopCloser(strings
.NewReader("")),
624 want
: noContentLengthOrTransferEncoding
,
629 body
: ioutil
.NopCloser(strings
.NewReader("")),
630 want
: noContentLengthOrTransferEncoding
,
632 // A GET with a body, with explicit content length:
636 body
: ioutil
.NopCloser(strings
.NewReader("foobody")),
637 want
: all(matchSubstr("Content-Length: 7"),
638 matchSubstr("foobody")),
640 // A GET with a body, sniffing the leading "f" from "foobody".
644 body
: ioutil
.NopCloser(strings
.NewReader("foobody")),
645 want
: all(matchSubstr("Transfer-Encoding: chunked"),
646 matchSubstr("\r\n1\r\nf\r\n"),
647 matchSubstr("oobody")),
649 // But a POST request is expected to have a body, so
650 // no sniffing happens:
654 body
: ioutil
.NopCloser(strings
.NewReader("foobody")),
655 want
: all(matchSubstr("Transfer-Encoding: chunked"),
656 matchSubstr("foobody")),
661 body
: ioutil
.NopCloser(strings
.NewReader("")),
662 want
: all(matchSubstr("Transfer-Encoding: chunked")),
664 // Verify that a blocking Request.Body doesn't block forever.
668 init
: func(tt
*testCase
) {
670 tt
.afterReqRead
= func() {
673 tt
.body
= ioutil
.NopCloser(pr
)
675 want
: matchSubstr("Transfer-Encoding: chunked"),
679 for i
, tt
:= range tests
{
689 Header
: make(Header
),
690 ContentLength
: tt
.clen
,
693 got
, err
:= dumpRequestOut(req
, tt
.afterReqRead
)
695 t
.Errorf("test[%d]: %v", i
, err
)
698 if err
:= tt
.want(string(got
)); err
!= nil {
699 t
.Errorf("test[%d]: %v", i
, err
)
704 type closeChecker
struct {
709 func (rc
*closeChecker
) Close() error
{
714 // TestRequestWriteClosesBody tests that Request.Write closes its request.Body.
715 // It also indirectly tests NewRequest and that it doesn't wrap an existing Closer
716 // inside a NopCloser, and that it serializes it correctly.
717 func TestRequestWriteClosesBody(t
*testing
.T
) {
718 rc
:= &closeChecker
{Reader
: strings
.NewReader("my body")}
719 req
, err
:= NewRequest("POST", "http://foo.com/", rc
)
723 buf
:= new(bytes
.Buffer
)
724 if err
:= req
.Write(buf
); err
!= nil {
728 t
.Error("body not closed after write")
730 expected
:= "POST / HTTP/1.1\r\n" +
731 "Host: foo.com\r\n" +
732 "User-Agent: Go-http-client/1.1\r\n" +
733 "Transfer-Encoding: chunked\r\n\r\n" +
736 if buf
.String() != expected
{
737 t
.Errorf("write:\n got: %s\nwant: %s", buf
.String(), expected
)
741 func chunk(s
string) string {
742 return fmt
.Sprintf("%x\r\n%s\r\n", len(s
), s
)
745 func mustParseURL(s
string) *url
.URL
{
746 u
, err
:= url
.Parse(s
)
748 panic(fmt
.Sprintf("Error parsing URL %q: %v", s
, err
))
753 type writerFunc
func([]byte) (int, error
)
755 func (f writerFunc
) Write(p
[]byte) (int, error
) { return f(p
) }
757 // TestRequestWriteError tests the Write err != nil checks in (*Request).write.
758 func TestRequestWriteError(t
*testing
.T
) {
759 failAfter
, writeCount
:= 0, 0
760 errFail
:= errors
.New("fake write failure")
762 // w is the buffered io.Writer to write the request to. It
763 // fails exactly once on its Nth Write call, as controlled by
764 // failAfter. It also tracks the number of calls in
767 io
.ByteWriter
// to avoid being wrapped by a bufio.Writer
771 writerFunc(func(p
[]byte) (n
int, err error
) {
781 req
, _
:= NewRequest("GET", "http://example.com/", nil)
782 const writeCalls
= 4 // number of Write calls in current implementation
784 for n
:= 0; n
<= writeCalls
+2; n
++ {
793 t
.Errorf("for fail-after %d Writes, err = %v; want %v", n
, err
, wantErr
)
798 if writeCount
!= writeCalls
{
799 t
.Fatalf("writeCalls constant is outdated in test")
802 if writeCount
> writeCalls || writeCount
> n
+1 {
803 t
.Errorf("for fail-after %d, saw unexpectedly high (%d) write calls", n
, writeCount
)
807 t
.Fatalf("writeCalls constant is outdated in test")
811 // dumpRequestOut is a modified copy of net/http/httputil.DumpRequestOut.
812 // Unlike the original, this version doesn't mutate the req.Body and
813 // try to restore it. It always dumps the whole body.
814 // And it doesn't support https.
815 func dumpRequestOut(req
*Request
, onReadHeaders
func()) ([]byte, error
) {
817 // Use the actual Transport code to record what we would send
818 // on the wire, but not using TCP. Use a Transport with a
819 // custom dialer that returns a fake net.Conn that waits
820 // for the full input (and recording it), and then responds
821 // with a dummy response.
822 var buf bytes
.Buffer
// records the output
826 dr
:= &delegateReader
{c
: make(chan io
.Reader
)}
829 Dial
: func(net
, addr
string) (net
.Conn
, error
) {
830 return &dumpConn
{io
.MultiWriter(&buf
, pw
), dr
}, nil
833 defer t
.CloseIdleConnections()
835 // Wait for the request before replying with a dummy response:
837 req
, err
:= ReadRequest(bufio
.NewReader(pr
))
839 if onReadHeaders
!= nil {
842 // Ensure all the body is read; otherwise
843 // we'll get a partial dump.
844 io
.Copy(ioutil
.Discard
, req
.Body
)
847 dr
.c
<- strings
.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
850 _
, err
:= t
.RoundTrip(req
)
854 return buf
.Bytes(), nil
857 // delegateReader is a reader that delegates to another reader,
858 // once it arrives on a channel.
859 type delegateReader
struct {
861 r io
.Reader
// nil until received from c
864 func (r
*delegateReader
) Read(p
[]byte) (int, error
) {
871 // dumpConn is a net.Conn that writes to Writer and reads from Reader.
872 type dumpConn
struct {
877 func (c
*dumpConn
) Close() error
{ return nil }
878 func (c
*dumpConn
) LocalAddr() net
.Addr
{ return nil }
879 func (c
*dumpConn
) RemoteAddr() net
.Addr
{ return nil }
880 func (c
*dumpConn
) SetDeadline(t time
.Time
) error
{ return nil }
881 func (c
*dumpConn
) SetReadDeadline(t time
.Time
) error
{ return nil }
882 func (c
*dumpConn
) SetWriteDeadline(t time
.Time
) error
{ return nil }