blob: b7385d6570391b353243251b500124aa7a5b4eca [file] [log] [blame]
// Copyright 2015 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 http2
import (
"crypto/tls"
"flag"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"os"
"reflect"
"strings"
"sync"
"testing"
"time"
)
var (
extNet = flag.Bool("extnet", false, "do external network tests")
transportHost = flag.String("transporthost", "http2.golang.org", "hostname to use for TestTransport")
insecure = flag.Bool("insecure", false, "insecure TLS dials") // TODO: dead code. remove?
)
var tlsConfigInsecure = &tls.Config{InsecureSkipVerify: true}
func TestTransportExternal(t *testing.T) {
if !*extNet {
t.Skip("skipping external network test")
}
req, _ := http.NewRequest("GET", "https://"+*transportHost+"/", nil)
rt := &Transport{TLSClientConfig: tlsConfigInsecure}
res, err := rt.RoundTrip(req)
if err != nil {
t.Fatalf("%v", err)
}
res.Write(os.Stdout)
}
func TestTransport(t *testing.T) {
const body = "sup"
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, body)
}, optOnlyServer)
defer st.Close()
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
defer tr.CloseIdleConnections()
req, err := http.NewRequest("GET", st.ts.URL, nil)
if err != nil {
t.Fatal(err)
}
res, err := tr.RoundTrip(req)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
t.Logf("Got res: %+v", res)
if g, w := res.StatusCode, 200; g != w {
t.Errorf("StatusCode = %v; want %v", g, w)
}
if g, w := res.Status, "200 OK"; g != w {
t.Errorf("Status = %q; want %q", g, w)
}
wantHeader := http.Header{
"Content-Length": []string{"3"},
"Content-Type": []string{"text/plain; charset=utf-8"},
}
if !reflect.DeepEqual(res.Header, wantHeader) {
t.Errorf("res Header = %v; want %v", res.Header, wantHeader)
}
if res.Request != req {
t.Errorf("Response.Request = %p; want %p", res.Request, req)
}
if res.TLS == nil {
t.Error("Response.TLS = nil; want non-nil")
}
slurp, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Errorf("Body read: %v", err)
} else if string(slurp) != body {
t.Errorf("Body = %q; want %q", slurp, body)
}
}
func TestTransportReusesConns(t *testing.T) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, r.RemoteAddr)
}, optOnlyServer)
defer st.Close()
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
defer tr.CloseIdleConnections()
get := func() string {
req, err := http.NewRequest("GET", st.ts.URL, nil)
if err != nil {
t.Fatal(err)
}
res, err := tr.RoundTrip(req)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
slurp, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatalf("Body read: %v", err)
}
addr := strings.TrimSpace(string(slurp))
if addr == "" {
t.Fatalf("didn't get an addr in response")
}
return addr
}
first := get()
second := get()
if first != second {
t.Errorf("first and second responses were on different connections: %q vs %q", first, second)
}
}
func TestTransportAbortClosesPipes(t *testing.T) {
shutdown := make(chan struct{})
st := newServerTester(t,
func(w http.ResponseWriter, r *http.Request) {
w.(http.Flusher).Flush()
<-shutdown
},
optOnlyServer,
)
defer st.Close()
defer close(shutdown) // we must shutdown before st.Close() to avoid hanging
done := make(chan struct{})
requestMade := make(chan struct{})
go func() {
defer close(done)
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
req, err := http.NewRequest("GET", st.ts.URL, nil)
if err != nil {
t.Fatal(err)
}
res, err := tr.RoundTrip(req)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
close(requestMade)
_, err = ioutil.ReadAll(res.Body)
if err == nil {
t.Error("expected error from res.Body.Read")
}
}()
<-requestMade
// Now force the serve loop to end, via closing the connection.
st.closeConn()
// deadlock? that's a bug.
select {
case <-done:
case <-time.After(3 * time.Second):
t.Fatal("timeout")
}
}
// TODO: merge this with TestTransportBody to make TestTransportRequest? This
// could be a table-driven test with extra goodies.
func TestTransportPath(t *testing.T) {
gotc := make(chan *url.URL, 1)
st := newServerTester(t,
func(w http.ResponseWriter, r *http.Request) {
gotc <- r.URL
},
optOnlyServer,
)
defer st.Close()
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
defer tr.CloseIdleConnections()
const (
path = "/testpath"
query = "q=1"
)
surl := st.ts.URL + path + "?" + query
req, err := http.NewRequest("POST", surl, nil)
if err != nil {
t.Fatal(err)
}
c := &http.Client{Transport: tr}
res, err := c.Do(req)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
got := <-gotc
if got.Path != path {
t.Errorf("Read Path = %q; want %q", got.Path, path)
}
if got.RawQuery != query {
t.Errorf("Read RawQuery = %q; want %q", got.RawQuery, query)
}
}
func randString(n int) string {
rnd := rand.New(rand.NewSource(int64(n)))
b := make([]byte, n)
for i := range b {
b[i] = byte(rnd.Intn(256))
}
return string(b)
}
var bodyTests = []struct {
body string
noContentLen bool
}{
{body: "some message"},
{body: "some message", noContentLen: true},
{body: ""},
{body: "", noContentLen: true},
{body: strings.Repeat("a", 1<<20), noContentLen: true},
{body: strings.Repeat("a", 1<<20)},
{body: randString(16<<10 - 1)},
{body: randString(16 << 10)},
{body: randString(16<<10 + 1)},
{body: randString(512<<10 - 1)},
{body: randString(512 << 10)},
{body: randString(512<<10 + 1)},
{body: randString(1<<20 - 1)},
{body: randString(1 << 20)},
{body: randString(1<<20 + 2)},
}
func TestTransportBody(t *testing.T) {
gotc := make(chan interface{}, 1)
st := newServerTester(t,
func(w http.ResponseWriter, r *http.Request) {
slurp, err := ioutil.ReadAll(r.Body)
if err != nil {
gotc <- err
} else {
gotc <- string(slurp)
}
},
optOnlyServer,
)
defer st.Close()
for i, tt := range bodyTests {
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
defer tr.CloseIdleConnections()
var body io.Reader = strings.NewReader(tt.body)
if tt.noContentLen {
body = struct{ io.Reader }{body} // just a Reader, hiding concrete type and other methods
}
req, err := http.NewRequest("POST", st.ts.URL, body)
if err != nil {
t.Fatalf("#%d: %v", i, err)
}
c := &http.Client{Transport: tr}
res, err := c.Do(req)
if err != nil {
t.Fatalf("#%d: %v", i, err)
}
defer res.Body.Close()
got := <-gotc
if err, ok := got.(error); ok {
t.Fatalf("#%d: %v", i, err)
} else if got.(string) != tt.body {
got := got.(string)
t.Errorf("#%d: Read body mismatch.\n got: %q (len %d)\nwant: %q (len %d)", i, shortString(got), len(got), shortString(tt.body), len(tt.body))
}
}
}
func shortString(v string) string {
const maxLen = 100
if len(v) <= maxLen {
return v
}
return fmt.Sprintf("%v[...%d bytes omitted...]%v", v[:maxLen/2], len(v)-maxLen, v[len(v)-maxLen/2:])
}
func TestTransportDialTLS(t *testing.T) {
var mu sync.Mutex // guards following
var gotReq, didDial bool
ts := newServerTester(t,
func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
gotReq = true
mu.Unlock()
},
optOnlyServer,
)
defer ts.Close()
tr := &Transport{
DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {
mu.Lock()
didDial = true
mu.Unlock()
cfg.InsecureSkipVerify = true
c, err := tls.Dial(netw, addr, cfg)
if err != nil {
return nil, err
}
return c, c.Handshake()
},
}
defer tr.CloseIdleConnections()
client := &http.Client{Transport: tr}
res, err := client.Get(ts.ts.URL)
if err != nil {
t.Fatal(err)
}
res.Body.Close()
mu.Lock()
if !gotReq {
t.Error("didn't get request")
}
if !didDial {
t.Error("didn't use dial hook")
}
}
func TestConfigureTransport(t *testing.T) {
t1 := &http.Transport{}
err := ConfigureTransport(t1)
if err == errTransportVersion {
t.Skip(err)
}
if err != nil {
t.Fatal(err)
}
if got := fmt.Sprintf("%#v", *t1); !strings.Contains(got, `"h2"`) {
// Laziness, to avoid buildtags.
t.Errorf("stringification of HTTP/1 transport didn't contain \"h2\": %v", got)
}
if t1.TLSClientConfig == nil {
t.Errorf("nil t1.TLSClientConfig")
} else if !reflect.DeepEqual(t1.TLSClientConfig.NextProtos, []string{"h2"}) {
t.Errorf("TLSClientConfig.NextProtos = %q; want just 'h2'", t1.TLSClientConfig.NextProtos)
}
if err := ConfigureTransport(t1); err == nil {
t.Error("unexpected success on second call to ConfigureTransport")
}
// And does it work?
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, r.Proto)
}, optOnlyServer)
defer st.Close()
t1.TLSClientConfig.InsecureSkipVerify = true
c := &http.Client{Transport: t1}
res, err := c.Get(st.ts.URL)
if err != nil {
t.Fatal(err)
}
slurp, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err)
}
if got, want := string(slurp), "HTTP/2.0"; got != want {
t.Errorf("body = %q; want %q", got, want)
}
}