gcc/libgo/go/net/http/client_test.go
Ian Lance Taylor f8d9fa9e80 libgo, compiler: Upgrade libgo to Go 1.4, except for runtime.
This upgrades all of libgo other than the runtime package to
the Go 1.4 release.  In Go 1.4 much of the runtime was
rewritten into Go.  Merging that code will take more time and
will not change the API, so I'm putting it off for now.

There are a few runtime changes anyhow, to accomodate other
packages that rely on minor modifications to the runtime
support.

The compiler changes slightly to add a one-bit flag to each
type descriptor kind that is stored directly in an interface,
which for gccgo is currently only pointer types.  Another
one-bit flag (gcprog) is reserved because it is used by the gc
compiler, but gccgo does not currently use it.

There is another error check in the compiler since I ran
across it during testing.

gotools/:
	* Makefile.am (go_cmd_go_files): Sort entries.  Add generate.go.
	* Makefile.in: Rebuild.

From-SVN: r219627
2015-01-15 00:27:56 +00:00

1076 lines
28 KiB
Go

// Copyright 2009 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.
// Tests for client.go
package http_test
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net"
. "net/http"
"net/http/httptest"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"testing"
"time"
)
var robotsTxtHandler = HandlerFunc(func(w ResponseWriter, r *Request) {
w.Header().Set("Last-Modified", "sometime")
fmt.Fprintf(w, "User-agent: go\nDisallow: /something/")
})
// pedanticReadAll works like ioutil.ReadAll but additionally
// verifies that r obeys the documented io.Reader contract.
func pedanticReadAll(r io.Reader) (b []byte, err error) {
var bufa [64]byte
buf := bufa[:]
for {
n, err := r.Read(buf)
if n == 0 && err == nil {
return nil, fmt.Errorf("Read: n=0 with err=nil")
}
b = append(b, buf[:n]...)
if err == io.EOF {
n, err := r.Read(buf)
if n != 0 || err != io.EOF {
return nil, fmt.Errorf("Read: n=%d err=%#v after EOF", n, err)
}
return b, nil
}
if err != nil {
return b, err
}
}
}
type chanWriter chan string
func (w chanWriter) Write(p []byte) (n int, err error) {
w <- string(p)
return len(p), nil
}
func TestClient(t *testing.T) {
defer afterTest(t)
ts := httptest.NewServer(robotsTxtHandler)
defer ts.Close()
r, err := Get(ts.URL)
var b []byte
if err == nil {
b, err = pedanticReadAll(r.Body)
r.Body.Close()
}
if err != nil {
t.Error(err)
} else if s := string(b); !strings.HasPrefix(s, "User-agent:") {
t.Errorf("Incorrect page body (did not begin with User-agent): %q", s)
}
}
func TestClientHead(t *testing.T) {
defer afterTest(t)
ts := httptest.NewServer(robotsTxtHandler)
defer ts.Close()
r, err := Head(ts.URL)
if err != nil {
t.Fatal(err)
}
if _, ok := r.Header["Last-Modified"]; !ok {
t.Error("Last-Modified header not found.")
}
}
type recordingTransport struct {
req *Request
}
func (t *recordingTransport) RoundTrip(req *Request) (resp *Response, err error) {
t.req = req
return nil, errors.New("dummy impl")
}
func TestGetRequestFormat(t *testing.T) {
defer afterTest(t)
tr := &recordingTransport{}
client := &Client{Transport: tr}
url := "http://dummy.faketld/"
client.Get(url) // Note: doesn't hit network
if tr.req.Method != "GET" {
t.Errorf("expected method %q; got %q", "GET", tr.req.Method)
}
if tr.req.URL.String() != url {
t.Errorf("expected URL %q; got %q", url, tr.req.URL.String())
}
if tr.req.Header == nil {
t.Errorf("expected non-nil request Header")
}
}
func TestPostRequestFormat(t *testing.T) {
defer afterTest(t)
tr := &recordingTransport{}
client := &Client{Transport: tr}
url := "http://dummy.faketld/"
json := `{"key":"value"}`
b := strings.NewReader(json)
client.Post(url, "application/json", b) // Note: doesn't hit network
if tr.req.Method != "POST" {
t.Errorf("got method %q, want %q", tr.req.Method, "POST")
}
if tr.req.URL.String() != url {
t.Errorf("got URL %q, want %q", tr.req.URL.String(), url)
}
if tr.req.Header == nil {
t.Fatalf("expected non-nil request Header")
}
if tr.req.Close {
t.Error("got Close true, want false")
}
if g, e := tr.req.ContentLength, int64(len(json)); g != e {
t.Errorf("got ContentLength %d, want %d", g, e)
}
}
func TestPostFormRequestFormat(t *testing.T) {
defer afterTest(t)
tr := &recordingTransport{}
client := &Client{Transport: tr}
urlStr := "http://dummy.faketld/"
form := make(url.Values)
form.Set("foo", "bar")
form.Add("foo", "bar2")
form.Set("bar", "baz")
client.PostForm(urlStr, form) // Note: doesn't hit network
if tr.req.Method != "POST" {
t.Errorf("got method %q, want %q", tr.req.Method, "POST")
}
if tr.req.URL.String() != urlStr {
t.Errorf("got URL %q, want %q", tr.req.URL.String(), urlStr)
}
if tr.req.Header == nil {
t.Fatalf("expected non-nil request Header")
}
if g, e := tr.req.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; g != e {
t.Errorf("got Content-Type %q, want %q", g, e)
}
if tr.req.Close {
t.Error("got Close true, want false")
}
// Depending on map iteration, body can be either of these.
expectedBody := "foo=bar&foo=bar2&bar=baz"
expectedBody1 := "bar=baz&foo=bar&foo=bar2"
if g, e := tr.req.ContentLength, int64(len(expectedBody)); g != e {
t.Errorf("got ContentLength %d, want %d", g, e)
}
bodyb, err := ioutil.ReadAll(tr.req.Body)
if err != nil {
t.Fatalf("ReadAll on req.Body: %v", err)
}
if g := string(bodyb); g != expectedBody && g != expectedBody1 {
t.Errorf("got body %q, want %q or %q", g, expectedBody, expectedBody1)
}
}
func TestClientRedirects(t *testing.T) {
defer afterTest(t)
var ts *httptest.Server
ts = httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
n, _ := strconv.Atoi(r.FormValue("n"))
// Test Referer header. (7 is arbitrary position to test at)
if n == 7 {
if g, e := r.Referer(), ts.URL+"/?n=6"; e != g {
t.Errorf("on request ?n=7, expected referer of %q; got %q", e, g)
}
}
if n < 15 {
Redirect(w, r, fmt.Sprintf("/?n=%d", n+1), StatusFound)
return
}
fmt.Fprintf(w, "n=%d", n)
}))
defer ts.Close()
c := &Client{}
_, err := c.Get(ts.URL)
if e, g := "Get /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g {
t.Errorf("with default client Get, expected error %q, got %q", e, g)
}
// HEAD request should also have the ability to follow redirects.
_, err = c.Head(ts.URL)
if e, g := "Head /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g {
t.Errorf("with default client Head, expected error %q, got %q", e, g)
}
// Do should also follow redirects.
greq, _ := NewRequest("GET", ts.URL, nil)
_, err = c.Do(greq)
if e, g := "Get /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g {
t.Errorf("with default client Do, expected error %q, got %q", e, g)
}
var checkErr error
var lastVia []*Request
c = &Client{CheckRedirect: func(_ *Request, via []*Request) error {
lastVia = via
return checkErr
}}
res, err := c.Get(ts.URL)
if err != nil {
t.Fatalf("Get error: %v", err)
}
res.Body.Close()
finalUrl := res.Request.URL.String()
if e, g := "<nil>", fmt.Sprintf("%v", err); e != g {
t.Errorf("with custom client, expected error %q, got %q", e, g)
}
if !strings.HasSuffix(finalUrl, "/?n=15") {
t.Errorf("expected final url to end in /?n=15; got url %q", finalUrl)
}
if e, g := 15, len(lastVia); e != g {
t.Errorf("expected lastVia to have contained %d elements; got %d", e, g)
}
checkErr = errors.New("no redirects allowed")
res, err = c.Get(ts.URL)
if urlError, ok := err.(*url.Error); !ok || urlError.Err != checkErr {
t.Errorf("with redirects forbidden, expected a *url.Error with our 'no redirects allowed' error inside; got %#v (%q)", err, err)
}
if res == nil {
t.Fatalf("Expected a non-nil Response on CheckRedirect failure (http://golang.org/issue/3795)")
}
res.Body.Close()
if res.Header.Get("Location") == "" {
t.Errorf("no Location header in Response")
}
}
func TestPostRedirects(t *testing.T) {
defer afterTest(t)
var log struct {
sync.Mutex
bytes.Buffer
}
var ts *httptest.Server
ts = httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
log.Lock()
fmt.Fprintf(&log.Buffer, "%s %s ", r.Method, r.RequestURI)
log.Unlock()
if v := r.URL.Query().Get("code"); v != "" {
code, _ := strconv.Atoi(v)
if code/100 == 3 {
w.Header().Set("Location", ts.URL)
}
w.WriteHeader(code)
}
}))
defer ts.Close()
tests := []struct {
suffix string
want int // response code
}{
{"/", 200},
{"/?code=301", 301},
{"/?code=302", 200},
{"/?code=303", 200},
{"/?code=404", 404},
}
for _, tt := range tests {
res, err := Post(ts.URL+tt.suffix, "text/plain", strings.NewReader("Some content"))
if err != nil {
t.Fatal(err)
}
if res.StatusCode != tt.want {
t.Errorf("POST %s: status code = %d; want %d", tt.suffix, res.StatusCode, tt.want)
}
}
log.Lock()
got := log.String()
log.Unlock()
want := "POST / POST /?code=301 POST /?code=302 GET / POST /?code=303 GET / POST /?code=404 "
if got != want {
t.Errorf("Log differs.\n Got: %q\nWant: %q", got, want)
}
}
var expectedCookies = []*Cookie{
{Name: "ChocolateChip", Value: "tasty"},
{Name: "First", Value: "Hit"},
{Name: "Second", Value: "Hit"},
}
var echoCookiesRedirectHandler = HandlerFunc(func(w ResponseWriter, r *Request) {
for _, cookie := range r.Cookies() {
SetCookie(w, cookie)
}
if r.URL.Path == "/" {
SetCookie(w, expectedCookies[1])
Redirect(w, r, "/second", StatusMovedPermanently)
} else {
SetCookie(w, expectedCookies[2])
w.Write([]byte("hello"))
}
})
func TestClientSendsCookieFromJar(t *testing.T) {
tr := &recordingTransport{}
client := &Client{Transport: tr}
client.Jar = &TestJar{perURL: make(map[string][]*Cookie)}
us := "http://dummy.faketld/"
u, _ := url.Parse(us)
client.Jar.SetCookies(u, expectedCookies)
client.Get(us) // Note: doesn't hit network
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
client.Head(us) // Note: doesn't hit network
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
client.Post(us, "text/plain", strings.NewReader("body")) // Note: doesn't hit network
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
client.PostForm(us, url.Values{}) // Note: doesn't hit network
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
req, _ := NewRequest("GET", us, nil)
client.Do(req) // Note: doesn't hit network
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
req, _ = NewRequest("POST", us, nil)
client.Do(req) // Note: doesn't hit network
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
}
// Just enough correctness for our redirect tests. Uses the URL.Host as the
// scope of all cookies.
type TestJar struct {
m sync.Mutex
perURL map[string][]*Cookie
}
func (j *TestJar) SetCookies(u *url.URL, cookies []*Cookie) {
j.m.Lock()
defer j.m.Unlock()
if j.perURL == nil {
j.perURL = make(map[string][]*Cookie)
}
j.perURL[u.Host] = cookies
}
func (j *TestJar) Cookies(u *url.URL) []*Cookie {
j.m.Lock()
defer j.m.Unlock()
return j.perURL[u.Host]
}
func TestRedirectCookiesJar(t *testing.T) {
defer afterTest(t)
var ts *httptest.Server
ts = httptest.NewServer(echoCookiesRedirectHandler)
defer ts.Close()
c := &Client{
Jar: new(TestJar),
}
u, _ := url.Parse(ts.URL)
c.Jar.SetCookies(u, []*Cookie{expectedCookies[0]})
resp, err := c.Get(ts.URL)
if err != nil {
t.Fatalf("Get: %v", err)
}
resp.Body.Close()
matchReturnedCookies(t, expectedCookies, resp.Cookies())
}
func matchReturnedCookies(t *testing.T, expected, given []*Cookie) {
if len(given) != len(expected) {
t.Logf("Received cookies: %v", given)
t.Errorf("Expected %d cookies, got %d", len(expected), len(given))
}
for _, ec := range expected {
foundC := false
for _, c := range given {
if ec.Name == c.Name && ec.Value == c.Value {
foundC = true
break
}
}
if !foundC {
t.Errorf("Missing cookie %v", ec)
}
}
}
func TestJarCalls(t *testing.T) {
defer afterTest(t)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
pathSuffix := r.RequestURI[1:]
if r.RequestURI == "/nosetcookie" {
return // dont set cookies for this path
}
SetCookie(w, &Cookie{Name: "name" + pathSuffix, Value: "val" + pathSuffix})
if r.RequestURI == "/" {
Redirect(w, r, "http://secondhost.fake/secondpath", 302)
}
}))
defer ts.Close()
jar := new(RecordingJar)
c := &Client{
Jar: jar,
Transport: &Transport{
Dial: func(_ string, _ string) (net.Conn, error) {
return net.Dial("tcp", ts.Listener.Addr().String())
},
},
}
_, err := c.Get("http://firsthost.fake/")
if err != nil {
t.Fatal(err)
}
_, err = c.Get("http://firsthost.fake/nosetcookie")
if err != nil {
t.Fatal(err)
}
got := jar.log.String()
want := `Cookies("http://firsthost.fake/")
SetCookie("http://firsthost.fake/", [name=val])
Cookies("http://secondhost.fake/secondpath")
SetCookie("http://secondhost.fake/secondpath", [namesecondpath=valsecondpath])
Cookies("http://firsthost.fake/nosetcookie")
`
if got != want {
t.Errorf("Got Jar calls:\n%s\nWant:\n%s", got, want)
}
}
// RecordingJar keeps a log of calls made to it, without
// tracking any cookies.
type RecordingJar struct {
mu sync.Mutex
log bytes.Buffer
}
func (j *RecordingJar) SetCookies(u *url.URL, cookies []*Cookie) {
j.logf("SetCookie(%q, %v)\n", u, cookies)
}
func (j *RecordingJar) Cookies(u *url.URL) []*Cookie {
j.logf("Cookies(%q)\n", u)
return nil
}
func (j *RecordingJar) logf(format string, args ...interface{}) {
j.mu.Lock()
defer j.mu.Unlock()
fmt.Fprintf(&j.log, format, args...)
}
func TestStreamingGet(t *testing.T) {
defer afterTest(t)
say := make(chan string)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
w.(Flusher).Flush()
for str := range say {
w.Write([]byte(str))
w.(Flusher).Flush()
}
}))
defer ts.Close()
c := &Client{}
res, err := c.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
var buf [10]byte
for _, str := range []string{"i", "am", "also", "known", "as", "comet"} {
say <- str
n, err := io.ReadFull(res.Body, buf[0:len(str)])
if err != nil {
t.Fatalf("ReadFull on %q: %v", str, err)
}
if n != len(str) {
t.Fatalf("Receiving %q, only read %d bytes", str, n)
}
got := string(buf[0:n])
if got != str {
t.Fatalf("Expected %q, got %q", str, got)
}
}
close(say)
_, err = io.ReadFull(res.Body, buf[0:1])
if err != io.EOF {
t.Fatalf("at end expected EOF, got %v", err)
}
}
type writeCountingConn struct {
net.Conn
count *int
}
func (c *writeCountingConn) Write(p []byte) (int, error) {
*c.count++
return c.Conn.Write(p)
}
// TestClientWrites verifies that client requests are buffered and we
// don't send a TCP packet per line of the http request + body.
func TestClientWrites(t *testing.T) {
defer afterTest(t)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
}))
defer ts.Close()
writes := 0
dialer := func(netz string, addr string) (net.Conn, error) {
c, err := net.Dial(netz, addr)
if err == nil {
c = &writeCountingConn{c, &writes}
}
return c, err
}
c := &Client{Transport: &Transport{Dial: dialer}}
_, err := c.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
if writes != 1 {
t.Errorf("Get request did %d Write calls, want 1", writes)
}
writes = 0
_, err = c.PostForm(ts.URL, url.Values{"foo": {"bar"}})
if err != nil {
t.Fatal(err)
}
if writes != 1 {
t.Errorf("Post request did %d Write calls, want 1", writes)
}
}
func TestClientInsecureTransport(t *testing.T) {
defer afterTest(t)
ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
w.Write([]byte("Hello"))
}))
errc := make(chanWriter, 10) // but only expecting 1
ts.Config.ErrorLog = log.New(errc, "", 0)
defer ts.Close()
// TODO(bradfitz): add tests for skipping hostname checks too?
// would require a new cert for testing, and probably
// redundant with these tests.
for _, insecure := range []bool{true, false} {
tr := &Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure,
},
}
defer tr.CloseIdleConnections()
c := &Client{Transport: tr}
res, err := c.Get(ts.URL)
if (err == nil) != insecure {
t.Errorf("insecure=%v: got unexpected err=%v", insecure, err)
}
if res != nil {
res.Body.Close()
}
}
select {
case v := <-errc:
if !strings.Contains(v, "TLS handshake error") {
t.Errorf("expected an error log message containing 'TLS handshake error'; got %q", v)
}
case <-time.After(5 * time.Second):
t.Errorf("timeout waiting for logged error")
}
}
func TestClientErrorWithRequestURI(t *testing.T) {
defer afterTest(t)
req, _ := NewRequest("GET", "http://localhost:1234/", nil)
req.RequestURI = "/this/field/is/illegal/and/should/error/"
_, err := DefaultClient.Do(req)
if err == nil {
t.Fatalf("expected an error")
}
if !strings.Contains(err.Error(), "RequestURI") {
t.Errorf("wanted error mentioning RequestURI; got error: %v", err)
}
}
func newTLSTransport(t *testing.T, ts *httptest.Server) *Transport {
certs := x509.NewCertPool()
for _, c := range ts.TLS.Certificates {
roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1])
if err != nil {
t.Fatalf("error parsing server's root cert: %v", err)
}
for _, root := range roots {
certs.AddCert(root)
}
}
return &Transport{
TLSClientConfig: &tls.Config{RootCAs: certs},
}
}
func TestClientWithCorrectTLSServerName(t *testing.T) {
defer afterTest(t)
ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
if r.TLS.ServerName != "127.0.0.1" {
t.Errorf("expected client to set ServerName 127.0.0.1, got: %q", r.TLS.ServerName)
}
}))
defer ts.Close()
c := &Client{Transport: newTLSTransport(t, ts)}
if _, err := c.Get(ts.URL); err != nil {
t.Fatalf("expected successful TLS connection, got error: %v", err)
}
}
func TestClientWithIncorrectTLSServerName(t *testing.T) {
defer afterTest(t)
ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {}))
defer ts.Close()
errc := make(chanWriter, 10) // but only expecting 1
ts.Config.ErrorLog = log.New(errc, "", 0)
trans := newTLSTransport(t, ts)
trans.TLSClientConfig.ServerName = "badserver"
c := &Client{Transport: trans}
_, err := c.Get(ts.URL)
if err == nil {
t.Fatalf("expected an error")
}
if !strings.Contains(err.Error(), "127.0.0.1") || !strings.Contains(err.Error(), "badserver") {
t.Errorf("wanted error mentioning 127.0.0.1 and badserver; got error: %v", err)
}
select {
case v := <-errc:
if !strings.Contains(v, "TLS handshake error") {
t.Errorf("expected an error log message containing 'TLS handshake error'; got %q", v)
}
case <-time.After(5 * time.Second):
t.Errorf("timeout waiting for logged error")
}
}
// Test for golang.org/issue/5829; the Transport should respect TLSClientConfig.ServerName
// when not empty.
//
// tls.Config.ServerName (non-empty, set to "example.com") takes
// precedence over "some-other-host.tld" which previously incorrectly
// took precedence. We don't actually connect to (or even resolve)
// "some-other-host.tld", though, because of the Transport.Dial hook.
//
// The httptest.Server has a cert with "example.com" as its name.
func TestTransportUsesTLSConfigServerName(t *testing.T) {
defer afterTest(t)
ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
w.Write([]byte("Hello"))
}))
defer ts.Close()
tr := newTLSTransport(t, ts)
tr.TLSClientConfig.ServerName = "example.com" // one of httptest's Server cert names
tr.Dial = func(netw, addr string) (net.Conn, error) {
return net.Dial(netw, ts.Listener.Addr().String())
}
defer tr.CloseIdleConnections()
c := &Client{Transport: tr}
res, err := c.Get("https://some-other-host.tld/")
if err != nil {
t.Fatal(err)
}
res.Body.Close()
}
func TestResponseSetsTLSConnectionState(t *testing.T) {
defer afterTest(t)
ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
w.Write([]byte("Hello"))
}))
defer ts.Close()
tr := newTLSTransport(t, ts)
tr.TLSClientConfig.CipherSuites = []uint16{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA}
tr.Dial = func(netw, addr string) (net.Conn, error) {
return net.Dial(netw, ts.Listener.Addr().String())
}
defer tr.CloseIdleConnections()
c := &Client{Transport: tr}
res, err := c.Get("https://example.com/")
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
if res.TLS == nil {
t.Fatal("Response didn't set TLS Connection State.")
}
if got, want := res.TLS.CipherSuite, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA; got != want {
t.Errorf("TLS Cipher Suite = %d; want %d", got, want)
}
}
// Verify Response.ContentLength is populated. http://golang.org/issue/4126
func TestClientHeadContentLength(t *testing.T) {
defer afterTest(t)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
if v := r.FormValue("cl"); v != "" {
w.Header().Set("Content-Length", v)
}
}))
defer ts.Close()
tests := []struct {
suffix string
want int64
}{
{"/?cl=1234", 1234},
{"/?cl=0", 0},
{"", -1},
}
for _, tt := range tests {
req, _ := NewRequest("HEAD", ts.URL+tt.suffix, nil)
res, err := DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
if res.ContentLength != tt.want {
t.Errorf("Content-Length = %d; want %d", res.ContentLength, tt.want)
}
bs, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
if len(bs) != 0 {
t.Errorf("Unexpected content: %q", bs)
}
}
}
func TestEmptyPasswordAuth(t *testing.T) {
defer afterTest(t)
gopher := "gopher"
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
auth := r.Header.Get("Authorization")
if strings.HasPrefix(auth, "Basic ") {
encoded := auth[6:]
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
t.Fatal(err)
}
expected := gopher + ":"
s := string(decoded)
if expected != s {
t.Errorf("Invalid Authorization header. Got %q, wanted %q", s, expected)
}
} else {
t.Errorf("Invalid auth %q", auth)
}
}))
defer ts.Close()
c := &Client{}
req, err := NewRequest("GET", ts.URL, nil)
if err != nil {
t.Fatal(err)
}
req.URL.User = url.User(gopher)
resp, err := c.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
}
func TestBasicAuth(t *testing.T) {
defer afterTest(t)
tr := &recordingTransport{}
client := &Client{Transport: tr}
url := "http://My%20User:My%20Pass@dummy.faketld/"
expected := "My User:My Pass"
client.Get(url)
if tr.req.Method != "GET" {
t.Errorf("got method %q, want %q", tr.req.Method, "GET")
}
if tr.req.URL.String() != url {
t.Errorf("got URL %q, want %q", tr.req.URL.String(), url)
}
if tr.req.Header == nil {
t.Fatalf("expected non-nil request Header")
}
auth := tr.req.Header.Get("Authorization")
if strings.HasPrefix(auth, "Basic ") {
encoded := auth[6:]
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
t.Fatal(err)
}
s := string(decoded)
if expected != s {
t.Errorf("Invalid Authorization header. Got %q, wanted %q", s, expected)
}
} else {
t.Errorf("Invalid auth %q", auth)
}
}
func TestClientTimeout(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
defer afterTest(t)
sawRoot := make(chan bool, 1)
sawSlow := make(chan bool, 1)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
if r.URL.Path == "/" {
sawRoot <- true
Redirect(w, r, "/slow", StatusFound)
return
}
if r.URL.Path == "/slow" {
w.Write([]byte("Hello"))
w.(Flusher).Flush()
sawSlow <- true
time.Sleep(2 * time.Second)
return
}
}))
defer ts.Close()
const timeout = 500 * time.Millisecond
c := &Client{
Timeout: timeout,
}
res, err := c.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
select {
case <-sawRoot:
// good.
default:
t.Fatal("handler never got / request")
}
select {
case <-sawSlow:
// good.
default:
t.Fatal("handler never got /slow request")
}
errc := make(chan error, 1)
go func() {
_, err := ioutil.ReadAll(res.Body)
errc <- err
res.Body.Close()
}()
const failTime = timeout * 2
select {
case err := <-errc:
if err == nil {
t.Error("expected error from ReadAll")
}
// Expected error.
case <-time.After(failTime):
t.Errorf("timeout after %v waiting for timeout of %v", failTime, timeout)
}
}
func TestClientRedirectEatsBody(t *testing.T) {
defer afterTest(t)
saw := make(chan string, 2)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
saw <- r.RemoteAddr
if r.URL.Path == "/" {
Redirect(w, r, "/foo", StatusFound) // which includes a body
}
}))
defer ts.Close()
res, err := Get(ts.URL)
if err != nil {
t.Fatal(err)
}
_, err = ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
res.Body.Close()
var first string
select {
case first = <-saw:
default:
t.Fatal("server didn't see a request")
}
var second string
select {
case second = <-saw:
default:
t.Fatal("server didn't see a second request")
}
if first != second {
t.Fatal("server saw different client ports before & after the redirect")
}
}
// eofReaderFunc is an io.Reader that runs itself, and then returns io.EOF.
type eofReaderFunc func()
func (f eofReaderFunc) Read(p []byte) (n int, err error) {
f()
return 0, io.EOF
}
func TestClientTrailers(t *testing.T) {
defer afterTest(t)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
w.Header().Set("Connection", "close")
w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B")
w.Header().Add("Trailer", "Server-Trailer-C")
var decl []string
for k := range r.Trailer {
decl = append(decl, k)
}
sort.Strings(decl)
slurp, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("Server reading request body: %v", err)
}
if string(slurp) != "foo" {
t.Errorf("Server read request body %q; want foo", slurp)
}
if r.Trailer == nil {
io.WriteString(w, "nil Trailer")
} else {
fmt.Fprintf(w, "decl: %v, vals: %s, %s",
decl,
r.Trailer.Get("Client-Trailer-A"),
r.Trailer.Get("Client-Trailer-B"))
}
// TODO: golang.org/issue/7759: there's no way yet for
// the server to set trailers without hijacking, so do
// that for now, just to test the client. Later, in
// Go 1.4, it should be implicit that any mutations
// to w.Header() after the initial write are the
// trailers to be sent, if and only if they were
// previously declared with w.Header().Set("Trailer",
// ..keys..)
w.(Flusher).Flush()
conn, buf, _ := w.(Hijacker).Hijack()
t := Header{}
t.Set("Server-Trailer-A", "valuea")
t.Set("Server-Trailer-C", "valuec") // skipping B
buf.WriteString("0\r\n") // eof
t.Write(buf)
buf.WriteString("\r\n") // end of trailers
buf.Flush()
conn.Close()
}))
defer ts.Close()
var req *Request
req, _ = NewRequest("POST", ts.URL, io.MultiReader(
eofReaderFunc(func() {
req.Trailer["Client-Trailer-A"] = []string{"valuea"}
}),
strings.NewReader("foo"),
eofReaderFunc(func() {
req.Trailer["Client-Trailer-B"] = []string{"valueb"}
}),
))
req.Trailer = Header{
"Client-Trailer-A": nil, // to be set later
"Client-Trailer-B": nil, // to be set later
}
req.ContentLength = -1
res, err := DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil {
t.Error(err)
}
want := Header{
"Server-Trailer-A": []string{"valuea"},
"Server-Trailer-B": nil,
"Server-Trailer-C": []string{"valuec"},
}
if !reflect.DeepEqual(res.Trailer, want) {
t.Errorf("Response trailers = %#v; want %#v", res.Trailer, want)
}
}
func TestReferer(t *testing.T) {
tests := []struct {
lastReq, newReq string // from -> to URLs
want string
}{
// don't send user:
{"http://gopher@test.com", "http://link.com", "http://test.com"},
{"https://gopher@test.com", "https://link.com", "https://test.com"},
// don't send a user and password:
{"http://gopher:go@test.com", "http://link.com", "http://test.com"},
{"https://gopher:go@test.com", "https://link.com", "https://test.com"},
// nothing to do:
{"http://test.com", "http://link.com", "http://test.com"},
{"https://test.com", "https://link.com", "https://test.com"},
// https to http doesn't send a referer:
{"https://test.com", "http://link.com", ""},
{"https://gopher:go@test.com", "http://link.com", ""},
}
for _, tt := range tests {
l, err := url.Parse(tt.lastReq)
if err != nil {
t.Fatal(err)
}
n, err := url.Parse(tt.newReq)
if err != nil {
t.Fatal(err)
}
r := ExportRefererForURL(l, n)
if r != tt.want {
t.Errorf("refererForURL(%q, %q) = %q; want %q", tt.lastReq, tt.newReq, r, tt.want)
}
}
}