1 // Copyright 2009 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.
27 var robotsTxtHandler
= HandlerFunc(func(w ResponseWriter
, r
*Request
) {
28 w
.Header().Set("Last-Modified", "sometime")
29 fmt
.Fprintf(w
, "User-agent: go\nDisallow: /something/")
32 // pedanticReadAll works like ioutil.ReadAll but additionally
33 // verifies that r obeys the documented io.Reader contract.
34 func pedanticReadAll(r io
.Reader
) (b
[]byte, err error
) {
39 if n
== 0 && err
== nil {
40 return nil, fmt
.Errorf("Read: n=0 with err=nil")
42 b
= append(b
, buf
[:n
]...)
45 if n
!= 0 || err
!= io
.EOF
{
46 return nil, fmt
.Errorf("Read: n=%d err=%#v after EOF", n
, err
)
56 func TestClient(t
*testing
.T
) {
58 ts
:= httptest
.NewServer(robotsTxtHandler
)
64 b
, err
= pedanticReadAll(r
.Body
)
69 } else if s
:= string(b
); !strings
.HasPrefix(s
, "User-agent:") {
70 t
.Errorf("Incorrect page body (did not begin with User-agent): %q", s
)
74 func TestClientHead(t
*testing
.T
) {
76 ts
:= httptest
.NewServer(robotsTxtHandler
)
79 r
, err
:= Head(ts
.URL
)
83 if _
, ok
:= r
.Header
["Last-Modified"]; !ok
{
84 t
.Error("Last-Modified header not found.")
88 type recordingTransport
struct {
92 func (t
*recordingTransport
) RoundTrip(req
*Request
) (resp
*Response
, err error
) {
94 return nil, errors
.New("dummy impl")
97 func TestGetRequestFormat(t
*testing
.T
) {
99 tr
:= &recordingTransport
{}
100 client
:= &Client
{Transport
: tr
}
101 url
:= "http://dummy.faketld/"
102 client
.Get(url
) // Note: doesn't hit network
103 if tr
.req
.Method
!= "GET" {
104 t
.Errorf("expected method %q; got %q", "GET", tr
.req
.Method
)
106 if tr
.req
.URL
.String() != url
{
107 t
.Errorf("expected URL %q; got %q", url
, tr
.req
.URL
.String())
109 if tr
.req
.Header
== nil {
110 t
.Errorf("expected non-nil request Header")
114 func TestPostRequestFormat(t
*testing
.T
) {
116 tr
:= &recordingTransport
{}
117 client
:= &Client
{Transport
: tr
}
119 url
:= "http://dummy.faketld/"
120 json
:= `{"key":"value"}`
121 b
:= strings
.NewReader(json
)
122 client
.Post(url
, "application/json", b
) // Note: doesn't hit network
124 if tr
.req
.Method
!= "POST" {
125 t
.Errorf("got method %q, want %q", tr
.req
.Method
, "POST")
127 if tr
.req
.URL
.String() != url
{
128 t
.Errorf("got URL %q, want %q", tr
.req
.URL
.String(), url
)
130 if tr
.req
.Header
== nil {
131 t
.Fatalf("expected non-nil request Header")
134 t
.Error("got Close true, want false")
136 if g
, e
:= tr
.req
.ContentLength
, int64(len(json
)); g
!= e
{
137 t
.Errorf("got ContentLength %d, want %d", g
, e
)
141 func TestPostFormRequestFormat(t
*testing
.T
) {
143 tr
:= &recordingTransport
{}
144 client
:= &Client
{Transport
: tr
}
146 urlStr
:= "http://dummy.faketld/"
147 form
:= make(url
.Values
)
148 form
.Set("foo", "bar")
149 form
.Add("foo", "bar2")
150 form
.Set("bar", "baz")
151 client
.PostForm(urlStr
, form
) // Note: doesn't hit network
153 if tr
.req
.Method
!= "POST" {
154 t
.Errorf("got method %q, want %q", tr
.req
.Method
, "POST")
156 if tr
.req
.URL
.String() != urlStr
{
157 t
.Errorf("got URL %q, want %q", tr
.req
.URL
.String(), urlStr
)
159 if tr
.req
.Header
== nil {
160 t
.Fatalf("expected non-nil request Header")
162 if g
, e
:= tr
.req
.Header
.Get("Content-Type"), "application/x-www-form-urlencoded"; g
!= e
{
163 t
.Errorf("got Content-Type %q, want %q", g
, e
)
166 t
.Error("got Close true, want false")
168 // Depending on map iteration, body can be either of these.
169 expectedBody
:= "foo=bar&foo=bar2&bar=baz"
170 expectedBody1
:= "bar=baz&foo=bar&foo=bar2"
171 if g
, e
:= tr
.req
.ContentLength
, int64(len(expectedBody
)); g
!= e
{
172 t
.Errorf("got ContentLength %d, want %d", g
, e
)
174 bodyb
, err
:= ioutil
.ReadAll(tr
.req
.Body
)
176 t
.Fatalf("ReadAll on req.Body: %v", err
)
178 if g
:= string(bodyb
); g
!= expectedBody
&& g
!= expectedBody1
{
179 t
.Errorf("got body %q, want %q or %q", g
, expectedBody
, expectedBody1
)
183 func TestClientRedirects(t
*testing
.T
) {
185 var ts
*httptest
.Server
186 ts
= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
187 n
, _
:= strconv
.Atoi(r
.FormValue("n"))
188 // Test Referer header. (7 is arbitrary position to test at)
190 if g
, e
:= r
.Referer(), ts
.URL
+"/?n=6"; e
!= g
{
191 t
.Errorf("on request ?n=7, expected referer of %q; got %q", e
, g
)
195 Redirect(w
, r
, fmt
.Sprintf("/?n=%d", n
+1), StatusFound
)
198 fmt
.Fprintf(w
, "n=%d", n
)
203 _
, err
:= c
.Get(ts
.URL
)
204 if e
, g
:= "Get /?n=10: stopped after 10 redirects", fmt
.Sprintf("%v", err
); e
!= g
{
205 t
.Errorf("with default client Get, expected error %q, got %q", e
, g
)
208 // HEAD request should also have the ability to follow redirects.
209 _
, err
= c
.Head(ts
.URL
)
210 if e
, g
:= "Head /?n=10: stopped after 10 redirects", fmt
.Sprintf("%v", err
); e
!= g
{
211 t
.Errorf("with default client Head, expected error %q, got %q", e
, g
)
214 // Do should also follow redirects.
215 greq
, _
:= NewRequest("GET", ts
.URL
, nil)
217 if e
, g
:= "Get /?n=10: stopped after 10 redirects", fmt
.Sprintf("%v", err
); e
!= g
{
218 t
.Errorf("with default client Do, expected error %q, got %q", e
, g
)
222 var lastVia
[]*Request
223 c
= &Client
{CheckRedirect
: func(_
*Request
, via
[]*Request
) error
{
227 res
, err
:= c
.Get(ts
.URL
)
229 t
.Fatalf("Get error: %v", err
)
232 finalUrl
:= res
.Request
.URL
.String()
233 if e
, g
:= "<nil>", fmt
.Sprintf("%v", err
); e
!= g
{
234 t
.Errorf("with custom client, expected error %q, got %q", e
, g
)
236 if !strings
.HasSuffix(finalUrl
, "/?n=15") {
237 t
.Errorf("expected final url to end in /?n=15; got url %q", finalUrl
)
239 if e
, g
:= 15, len(lastVia
); e
!= g
{
240 t
.Errorf("expected lastVia to have contained %d elements; got %d", e
, g
)
243 checkErr
= errors
.New("no redirects allowed")
244 res
, err
= c
.Get(ts
.URL
)
245 if urlError
, ok
:= err
.(*url
.Error
); !ok || urlError
.Err
!= checkErr
{
246 t
.Errorf("with redirects forbidden, expected a *url.Error with our 'no redirects allowed' error inside; got %#v (%q)", err
, err
)
249 t
.Fatalf("Expected a non-nil Response on CheckRedirect failure (http://golang.org/issue/3795)")
252 if res
.Header
.Get("Location") == "" {
253 t
.Errorf("no Location header in Response")
257 func TestPostRedirects(t
*testing
.T
) {
263 var ts
*httptest
.Server
264 ts
= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
266 fmt
.Fprintf(&log
.Buffer
, "%s %s ", r
.Method
, r
.RequestURI
)
268 if v
:= r
.URL
.Query().Get("code"); v
!= "" {
269 code
, _
:= strconv
.Atoi(v
)
271 w
.Header().Set("Location", ts
.URL
)
279 want
int // response code
287 for _
, tt
:= range tests
{
288 res
, err
:= Post(ts
.URL
+tt
.suffix
, "text/plain", strings
.NewReader("Some content"))
292 if res
.StatusCode
!= tt
.want
{
293 t
.Errorf("POST %s: status code = %d; want %d", tt
.suffix
, res
.StatusCode
, tt
.want
)
299 want
:= "POST / POST /?code=301 POST /?code=302 GET / POST /?code=303 GET / POST /?code=404 "
301 t
.Errorf("Log differs.\n Got: %q\nWant: %q", got
, want
)
305 var expectedCookies
= []*Cookie
{
306 {Name
: "ChocolateChip", Value
: "tasty"},
307 {Name
: "First", Value
: "Hit"},
308 {Name
: "Second", Value
: "Hit"},
311 var echoCookiesRedirectHandler
= HandlerFunc(func(w ResponseWriter
, r
*Request
) {
312 for _
, cookie
:= range r
.Cookies() {
315 if r
.URL
.Path
== "/" {
316 SetCookie(w
, expectedCookies
[1])
317 Redirect(w
, r
, "/second", StatusMovedPermanently
)
319 SetCookie(w
, expectedCookies
[2])
320 w
.Write([]byte("hello"))
324 func TestClientSendsCookieFromJar(t
*testing
.T
) {
325 tr
:= &recordingTransport
{}
326 client
:= &Client
{Transport
: tr
}
327 client
.Jar
= &TestJar
{perURL
: make(map[string][]*Cookie
)}
328 us
:= "http://dummy.faketld/"
329 u
, _
:= url
.Parse(us
)
330 client
.Jar
.SetCookies(u
, expectedCookies
)
332 client
.Get(us
) // Note: doesn't hit network
333 matchReturnedCookies(t
, expectedCookies
, tr
.req
.Cookies())
335 client
.Head(us
) // Note: doesn't hit network
336 matchReturnedCookies(t
, expectedCookies
, tr
.req
.Cookies())
338 client
.Post(us
, "text/plain", strings
.NewReader("body")) // Note: doesn't hit network
339 matchReturnedCookies(t
, expectedCookies
, tr
.req
.Cookies())
341 client
.PostForm(us
, url
.Values
{}) // Note: doesn't hit network
342 matchReturnedCookies(t
, expectedCookies
, tr
.req
.Cookies())
344 req
, _
:= NewRequest("GET", us
, nil)
345 client
.Do(req
) // Note: doesn't hit network
346 matchReturnedCookies(t
, expectedCookies
, tr
.req
.Cookies())
348 req
, _
= NewRequest("POST", us
, nil)
349 client
.Do(req
) // Note: doesn't hit network
350 matchReturnedCookies(t
, expectedCookies
, tr
.req
.Cookies())
353 // Just enough correctness for our redirect tests. Uses the URL.Host as the
354 // scope of all cookies.
355 type TestJar
struct {
357 perURL
map[string][]*Cookie
360 func (j
*TestJar
) SetCookies(u
*url
.URL
, cookies
[]*Cookie
) {
364 j
.perURL
= make(map[string][]*Cookie
)
366 j
.perURL
[u
.Host
] = cookies
369 func (j
*TestJar
) Cookies(u
*url
.URL
) []*Cookie
{
372 return j
.perURL
[u
.Host
]
375 func TestRedirectCookiesOnRequest(t
*testing
.T
) {
377 var ts
*httptest
.Server
378 ts
= httptest
.NewServer(echoCookiesRedirectHandler
)
381 req
, _
:= NewRequest("GET", ts
.URL
, nil)
382 req
.AddCookie(expectedCookies
[0])
383 // TODO: Uncomment when an implementation of a RFC6265 cookie jar lands.
385 // resp, _ := c.Do(req)
386 // matchReturnedCookies(t, expectedCookies, resp.Cookies())
388 req
, _
= NewRequest("GET", ts
.URL
, nil)
389 // resp, _ = c.Do(req)
390 // matchReturnedCookies(t, expectedCookies[1:], resp.Cookies())
393 func TestRedirectCookiesJar(t
*testing
.T
) {
395 var ts
*httptest
.Server
396 ts
= httptest
.NewServer(echoCookiesRedirectHandler
)
401 u
, _
:= url
.Parse(ts
.URL
)
402 c
.Jar
.SetCookies(u
, []*Cookie
{expectedCookies
[0]})
403 resp
, err
:= c
.Get(ts
.URL
)
405 t
.Fatalf("Get: %v", err
)
408 matchReturnedCookies(t
, expectedCookies
, resp
.Cookies())
411 func matchReturnedCookies(t
*testing
.T
, expected
, given
[]*Cookie
) {
412 t
.Logf("Received cookies: %v", given
)
413 if len(given
) != len(expected
) {
414 t
.Errorf("Expected %d cookies, got %d", len(expected
), len(given
))
416 for _
, ec
:= range expected
{
418 for _
, c
:= range given
{
419 if ec
.Name
== c
.Name
&& ec
.Value
== c
.Value
{
425 t
.Errorf("Missing cookie %v", ec
)
430 func TestJarCalls(t
*testing
.T
) {
432 ts
:= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
433 pathSuffix
:= r
.RequestURI
[1:]
434 if r
.RequestURI
== "/nosetcookie" {
435 return // dont set cookies for this path
437 SetCookie(w
, &Cookie
{Name
: "name" + pathSuffix
, Value
: "val" + pathSuffix
})
438 if r
.RequestURI
== "/" {
439 Redirect(w
, r
, "http://secondhost.fake/secondpath", 302)
443 jar
:= new(RecordingJar
)
446 Transport
: &Transport
{
447 Dial
: func(_
string, _
string) (net
.Conn
, error
) {
448 return net
.Dial("tcp", ts
.Listener
.Addr().String())
452 _
, err
:= c
.Get("http://firsthost.fake/")
456 _
, err
= c
.Get("http://firsthost.fake/nosetcookie")
460 got
:= jar
.log
.String()
461 want
:= `Cookies("http://firsthost.fake/")
462 SetCookie("http://firsthost.fake/", [name=val])
463 Cookies("http://secondhost.fake/secondpath")
464 SetCookie("http://secondhost.fake/secondpath", [namesecondpath=valsecondpath])
465 Cookies("http://firsthost.fake/nosetcookie")
468 t
.Errorf("Got Jar calls:\n%s\nWant:\n%s", got
, want
)
472 // RecordingJar keeps a log of calls made to it, without
473 // tracking any cookies.
474 type RecordingJar
struct {
479 func (j
*RecordingJar
) SetCookies(u
*url
.URL
, cookies
[]*Cookie
) {
480 j
.logf("SetCookie(%q, %v)\n", u
, cookies
)
483 func (j
*RecordingJar
) Cookies(u
*url
.URL
) []*Cookie
{
484 j
.logf("Cookies(%q)\n", u
)
488 func (j
*RecordingJar
) logf(format
string, args
...interface{}) {
491 fmt
.Fprintf(&j
.log
, format
, args
...)
494 func TestStreamingGet(t
*testing
.T
) {
496 say
:= make(chan string)
497 ts
:= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
499 for str
:= range say
{
507 res
, err
:= c
.Get(ts
.URL
)
512 for _
, str
:= range []string{"i", "am", "also", "known", "as", "comet"} {
514 n
, err
:= io
.ReadFull(res
.Body
, buf
[0:len(str
)])
516 t
.Fatalf("ReadFull on %q: %v", str
, err
)
519 t
.Fatalf("Receiving %q, only read %d bytes", str
, n
)
521 got
:= string(buf
[0:n
])
523 t
.Fatalf("Expected %q, got %q", str
, got
)
527 _
, err
= io
.ReadFull(res
.Body
, buf
[0:1])
529 t
.Fatalf("at end expected EOF, got %v", err
)
533 type writeCountingConn
struct {
538 func (c
*writeCountingConn
) Write(p
[]byte) (int, error
) {
540 return c
.Conn
.Write(p
)
543 // TestClientWrites verifies that client requests are buffered and we
544 // don't send a TCP packet per line of the http request + body.
545 func TestClientWrites(t
*testing
.T
) {
547 ts
:= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
552 dialer
:= func(netz
string, addr
string) (net
.Conn
, error
) {
553 c
, err
:= net
.Dial(netz
, addr
)
555 c
= &writeCountingConn
{c
, &writes
}
559 c
:= &Client
{Transport
: &Transport
{Dial
: dialer
}}
561 _
, err
:= c
.Get(ts
.URL
)
566 t
.Errorf("Get request did %d Write calls, want 1", writes
)
570 _
, err
= c
.PostForm(ts
.URL
, url
.Values
{"foo": {"bar"}})
575 t
.Errorf("Post request did %d Write calls, want 1", writes
)
579 func TestClientInsecureTransport(t
*testing
.T
) {
581 ts
:= httptest
.NewTLSServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
582 w
.Write([]byte("Hello"))
586 // TODO(bradfitz): add tests for skipping hostname checks too?
587 // would require a new cert for testing, and probably
588 // redundant with these tests.
589 for _
, insecure
:= range []bool{true, false} {
591 TLSClientConfig
: &tls
.Config
{
592 InsecureSkipVerify
: insecure
,
595 defer tr
.CloseIdleConnections()
596 c
:= &Client
{Transport
: tr
}
597 res
, err
:= c
.Get(ts
.URL
)
598 if (err
== nil) != insecure
{
599 t
.Errorf("insecure=%v: got unexpected err=%v", insecure
, err
)
607 func TestClientErrorWithRequestURI(t
*testing
.T
) {
609 req
, _
:= NewRequest("GET", "http://localhost:1234/", nil)
610 req
.RequestURI
= "/this/field/is/illegal/and/should/error/"
611 _
, err
:= DefaultClient
.Do(req
)
613 t
.Fatalf("expected an error")
615 if !strings
.Contains(err
.Error(), "RequestURI") {
616 t
.Errorf("wanted error mentioning RequestURI; got error: %v", err
)
620 func newTLSTransport(t
*testing
.T
, ts
*httptest
.Server
) *Transport
{
621 certs
:= x509
.NewCertPool()
622 for _
, c
:= range ts
.TLS
.Certificates
{
623 roots
, err
:= x509
.ParseCertificates(c
.Certificate
[len(c
.Certificate
)-1])
625 t
.Fatalf("error parsing server's root cert: %v", err
)
627 for _
, root
:= range roots
{
632 TLSClientConfig
: &tls
.Config
{RootCAs
: certs
},
636 func TestClientWithCorrectTLSServerName(t
*testing
.T
) {
638 ts
:= httptest
.NewTLSServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
639 if r
.TLS
.ServerName
!= "127.0.0.1" {
640 t
.Errorf("expected client to set ServerName 127.0.0.1, got: %q", r
.TLS
.ServerName
)
645 c
:= &Client
{Transport
: newTLSTransport(t
, ts
)}
646 if _
, err
:= c
.Get(ts
.URL
); err
!= nil {
647 t
.Fatalf("expected successful TLS connection, got error: %v", err
)
651 func TestClientWithIncorrectTLSServerName(t
*testing
.T
) {
653 ts
:= httptest
.NewTLSServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {}))
656 trans
:= newTLSTransport(t
, ts
)
657 trans
.TLSClientConfig
.ServerName
= "badserver"
658 c
:= &Client
{Transport
: trans
}
659 _
, err
:= c
.Get(ts
.URL
)
661 t
.Fatalf("expected an error")
663 if !strings
.Contains(err
.Error(), "127.0.0.1") ||
!strings
.Contains(err
.Error(), "badserver") {
664 t
.Errorf("wanted error mentioning 127.0.0.1 and badserver; got error: %v", err
)
668 // Verify Response.ContentLength is populated. http://golang.org/issue/4126
669 func TestClientHeadContentLength(t
*testing
.T
) {
671 ts
:= httptest
.NewServer(HandlerFunc(func(w ResponseWriter
, r
*Request
) {
672 if v
:= r
.FormValue("cl"); v
!= "" {
673 w
.Header().Set("Content-Length", v
)
685 for _
, tt
:= range tests
{
686 req
, _
:= NewRequest("HEAD", ts
.URL
+tt
.suffix
, nil)
687 res
, err
:= DefaultClient
.Do(req
)
691 if res
.ContentLength
!= tt
.want
{
692 t
.Errorf("Content-Length = %d; want %d", res
.ContentLength
, tt
.want
)
694 bs
, err
:= ioutil
.ReadAll(res
.Body
)
699 t
.Errorf("Unexpected content: %q", bs
)