blob: e54091f3b8118225f514200429280bd968171996 [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.
// Tests that use both the client & server, in both HTTP/1 and HTTP/2 mode.
package http_test
import (
"bytes"
"compress/gzip"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"log"
. "net/http"
"net/http/httptest"
"os"
"reflect"
"strings"
"sync"
"testing"
)
type clientServerTest struct {
t *testing.T
h2 bool
h Handler
ts *httptest.Server
tr *Transport
c *Client
}
func (t *clientServerTest) close() {
t.tr.CloseIdleConnections()
t.ts.Close()
}
const (
h1Mode = false
h2Mode = true
)
func newClientServerTest(t *testing.T, h2 bool, h Handler) *clientServerTest {
cst := &clientServerTest{
t: t,
h2: h2,
h: h,
tr: &Transport{},
}
cst.c = &Client{Transport: cst.tr}
if !h2 {
cst.ts = httptest.NewServer(h)
return cst
}
cst.ts = httptest.NewUnstartedServer(h)
ExportHttp2ConfigureServer(cst.ts.Config, nil)
cst.ts.TLS = cst.ts.Config.TLSConfig
cst.ts.StartTLS()
cst.tr.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
if err := ExportHttp2ConfigureTransport(cst.tr); err != nil {
t.Fatal(err)
}
return cst
}
// Testing the newClientServerTest helper itself.
func TestNewClientServerTest(t *testing.T) {
var got struct {
sync.Mutex
log []string
}
h := HandlerFunc(func(w ResponseWriter, r *Request) {
got.Lock()
defer got.Unlock()
got.log = append(got.log, r.Proto)
})
for _, v := range [2]bool{false, true} {
cst := newClientServerTest(t, v, h)
if _, err := cst.c.Head(cst.ts.URL); err != nil {
t.Fatal(err)
}
cst.close()
}
got.Lock() // no need to unlock
if want := []string{"HTTP/1.1", "HTTP/2.0"}; !reflect.DeepEqual(got.log, want) {
t.Errorf("got %q; want %q", got.log, want)
}
}
func TestChunkedResponseHeaders_h1(t *testing.T) { testChunkedResponseHeaders(t, h1Mode) }
func TestChunkedResponseHeaders_h2(t *testing.T) { testChunkedResponseHeaders(t, h2Mode) }
func testChunkedResponseHeaders(t *testing.T, h2 bool) {
defer afterTest(t)
log.SetOutput(ioutil.Discard) // is noisy otherwise
defer log.SetOutput(os.Stderr)
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
w.Header().Set("Content-Length", "intentional gibberish") // we check that this is deleted
w.(Flusher).Flush()
fmt.Fprintf(w, "I am a chunked response.")
}))
defer cst.close()
res, err := cst.c.Get(cst.ts.URL)
if err != nil {
t.Fatalf("Get error: %v", err)
}
defer res.Body.Close()
if g, e := res.ContentLength, int64(-1); g != e {
t.Errorf("expected ContentLength of %d; got %d", e, g)
}
wantTE := []string{"chunked"}
if h2 {
wantTE = nil
}
if !reflect.DeepEqual(res.TransferEncoding, wantTE) {
t.Errorf("TransferEncoding = %v; want %v", res.TransferEncoding, wantTE)
}
if got, haveCL := res.Header["Content-Length"]; haveCL {
t.Errorf("Unexpected Content-Length: %q", got)
}
}
type reqFunc func(c *Client, url string) (*Response, error)
// h12Compare is a test that compares HTTP/1 and HTTP/2 behavior
// against each other.
type h12Compare struct {
Handler func(ResponseWriter, *Request) // required
ReqFunc reqFunc // optional
CheckResponse func(proto string, res *Response) // optional
}
func (tt h12Compare) reqFunc() reqFunc {
if tt.ReqFunc == nil {
return (*Client).Get
}
return tt.ReqFunc
}
func (tt h12Compare) run(t *testing.T) {
cst1 := newClientServerTest(t, false, HandlerFunc(tt.Handler))
defer cst1.close()
cst2 := newClientServerTest(t, true, HandlerFunc(tt.Handler))
defer cst2.close()
res1, err := tt.reqFunc()(cst1.c, cst1.ts.URL)
if err != nil {
t.Errorf("HTTP/1 request: %v", err)
return
}
res2, err := tt.reqFunc()(cst2.c, cst2.ts.URL)
if err != nil {
t.Errorf("HTTP/2 request: %v", err)
return
}
tt.normalizeRes(t, res1, "HTTP/1.1")
tt.normalizeRes(t, res2, "HTTP/2.0")
res1body, res2body := res1.Body, res2.Body
eres1 := mostlyCopy(res1)
eres2 := mostlyCopy(res2)
if !reflect.DeepEqual(eres1, eres2) {
t.Errorf("Response headers to handler differed:\nhttp/1 (%v):\n\t%#v\nhttp/2 (%v):\n\t%#v",
cst1.ts.URL, eres1, cst2.ts.URL, eres2)
}
if !reflect.DeepEqual(res1body, res2body) {
t.Errorf("Response bodies to handler differed.\nhttp1: %v\nhttp2: %v\n", res1body, res2body)
}
if fn := tt.CheckResponse; fn != nil {
res1.Body, res2.Body = res1body, res2body
fn("HTTP/1.1", res1)
fn("HTTP/2.0", res2)
}
}
func mostlyCopy(r *Response) *Response {
c := *r
c.Body = nil
c.TransferEncoding = nil
c.TLS = nil
c.Request = nil
return &c
}
type slurpResult struct {
io.ReadCloser
body []byte
err error
}
func (sr slurpResult) String() string { return fmt.Sprintf("body %q; err %v", sr.body, sr.err) }
func (tt h12Compare) normalizeRes(t *testing.T, res *Response, wantProto string) {
if res.Proto == wantProto {
res.Proto, res.ProtoMajor, res.ProtoMinor = "", 0, 0
} else {
t.Errorf("got %q response; want %q", res.Proto, wantProto)
}
slurp, err := ioutil.ReadAll(res.Body)
res.Body.Close()
res.Body = slurpResult{
ReadCloser: ioutil.NopCloser(bytes.NewReader(slurp)),
body: slurp,
err: err,
}
for i, v := range res.Header["Date"] {
res.Header["Date"][i] = strings.Repeat("x", len(v))
}
if res.Request == nil {
t.Errorf("for %s, no request", wantProto)
}
if (res.TLS != nil) != (wantProto == "HTTP/2.0") {
t.Errorf("TLS set = %v; want %v", res.TLS != nil, res.TLS == nil)
}
}
// Issue 13532
func TestH12_HeadContentLengthNoBody(t *testing.T) {
h12Compare{
ReqFunc: (*Client).Head,
Handler: func(w ResponseWriter, r *Request) {
},
}.run(t)
}
func TestH12_HeadContentLengthSmallBody(t *testing.T) {
h12Compare{
ReqFunc: (*Client).Head,
Handler: func(w ResponseWriter, r *Request) {
io.WriteString(w, "small")
},
}.run(t)
}
func TestH12_HeadContentLengthLargeBody(t *testing.T) {
h12Compare{
ReqFunc: (*Client).Head,
Handler: func(w ResponseWriter, r *Request) {
chunk := strings.Repeat("x", 512<<10)
for i := 0; i < 10; i++ {
io.WriteString(w, chunk)
}
},
}.run(t)
}
func TestH12_200NoBody(t *testing.T) {
h12Compare{Handler: func(w ResponseWriter, r *Request) {}}.run(t)
}
func TestH2_204NoBody(t *testing.T) { testH12_noBody(t, 204) }
func TestH2_304NoBody(t *testing.T) { testH12_noBody(t, 304) }
func TestH2_404NoBody(t *testing.T) { testH12_noBody(t, 404) }
func testH12_noBody(t *testing.T, status int) {
h12Compare{Handler: func(w ResponseWriter, r *Request) {
w.WriteHeader(status)
}}.run(t)
}
func TestH12_SmallBody(t *testing.T) {
h12Compare{Handler: func(w ResponseWriter, r *Request) {
io.WriteString(w, "small body")
}}.run(t)
}
func TestH12_ExplicitContentLength(t *testing.T) {
h12Compare{Handler: func(w ResponseWriter, r *Request) {
w.Header().Set("Content-Length", "3")
io.WriteString(w, "foo")
}}.run(t)
}
func TestH12_FlushBeforeBody(t *testing.T) {
h12Compare{Handler: func(w ResponseWriter, r *Request) {
w.(Flusher).Flush()
io.WriteString(w, "foo")
}}.run(t)
}
func TestH12_FlushMidBody(t *testing.T) {
h12Compare{Handler: func(w ResponseWriter, r *Request) {
io.WriteString(w, "foo")
w.(Flusher).Flush()
io.WriteString(w, "bar")
}}.run(t)
}
func TestH12_Head_ExplicitLen(t *testing.T) {
h12Compare{
ReqFunc: (*Client).Head,
Handler: func(w ResponseWriter, r *Request) {
if r.Method != "HEAD" {
t.Errorf("unexpected method %q", r.Method)
}
w.Header().Set("Content-Length", "1235")
},
}.run(t)
}
func TestH12_Head_ImplicitLen(t *testing.T) {
h12Compare{
ReqFunc: (*Client).Head,
Handler: func(w ResponseWriter, r *Request) {
if r.Method != "HEAD" {
t.Errorf("unexpected method %q", r.Method)
}
io.WriteString(w, "foo")
},
}.run(t)
}
func TestH12_HandlerWritesTooLittle(t *testing.T) {
h12Compare{
Handler: func(w ResponseWriter, r *Request) {
w.Header().Set("Content-Length", "3")
io.WriteString(w, "12") // one byte short
},
CheckResponse: func(proto string, res *Response) {
sr, ok := res.Body.(slurpResult)
if !ok {
t.Errorf("%s body is %T; want slurpResult", proto, res.Body)
return
}
if sr.err != io.ErrUnexpectedEOF {
t.Errorf("%s read error = %v; want io.ErrUnexpectedEOF", proto, sr.err)
}
if string(sr.body) != "12" {
t.Errorf("%s body = %q; want %q", proto, sr.body, "12")
}
},
}.run(t)
}
// Tests that the HTTP/1 and HTTP/2 servers prevent handlers from
// writing more than they declared. This test does not test whether
// the transport deals with too much data, though, since the server
// doesn't make it possible to send bogus data. For those tests, see
// transport_test.go (for HTTP/1) or x/net/http2/transport_test.go
// (for HTTP/2).
func TestH12_HandlerWritesTooMuch(t *testing.T) {
h12Compare{
Handler: func(w ResponseWriter, r *Request) {
w.Header().Set("Content-Length", "3")
w.(Flusher).Flush()
io.WriteString(w, "123")
w.(Flusher).Flush()
n, err := io.WriteString(w, "x") // too many
if n > 0 || err == nil {
t.Errorf("for proto %q, final write = %v, %v; want 0, some error", r.Proto, n, err)
}
},
}.run(t)
}
// Verify that both our HTTP/1 and HTTP/2 request and auto-decompress gzip.
// Some hosts send gzip even if you don't ask for it; see golang.org/issue/13298
func TestH12_AutoGzip(t *testing.T) {
h12Compare{
Handler: func(w ResponseWriter, r *Request) {
if ae := r.Header.Get("Accept-Encoding"); ae != "gzip" {
t.Errorf("%s Accept-Encoding = %q; want gzip", r.Proto, ae)
}
w.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(w)
io.WriteString(gz, "I am some gzipped content. Go go go go go go go go go go go go should compress well.")
gz.Close()
},
}.run(t)
}
// Test304Responses verifies that 304s don't declare that they're
// chunking in their response headers and aren't allowed to produce
// output.
func Test304Responses_h1(t *testing.T) { test304Responses(t, h1Mode) }
func Test304Responses_h2(t *testing.T) { test304Responses(t, h2Mode) }
func test304Responses(t *testing.T, h2 bool) {
defer afterTest(t)
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
w.WriteHeader(StatusNotModified)
_, err := w.Write([]byte("illegal body"))
if err != ErrBodyNotAllowed {
t.Errorf("on Write, expected ErrBodyNotAllowed, got %v", err)
}
}))
defer cst.close()
res, err := cst.c.Get(cst.ts.URL)
if err != nil {
t.Fatal(err)
}
if len(res.TransferEncoding) > 0 {
t.Errorf("expected no TransferEncoding; got %v", res.TransferEncoding)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error(err)
}
if len(body) > 0 {
t.Errorf("got unexpected body %q", string(body))
}
}
func TestH12_ServerEmptyContentLength(t *testing.T) {
h12Compare{
Handler: func(w ResponseWriter, r *Request) {
w.Header()["Content-Type"] = []string{""}
io.WriteString(w, "<html><body>hi</body></html>")
},
}.run(t)
}
// Tests that closing the Request.Cancel channel also while still
// reading the response body. Issue 13159.
func TestCancelRequestMidBody_h1(t *testing.T) { testCancelRequestMidBody(t, h1Mode) }
func TestCancelRequestMidBody_h2(t *testing.T) { testCancelRequestMidBody(t, h2Mode) }
func testCancelRequestMidBody(t *testing.T, h2 bool) {
defer afterTest(t)
unblock := make(chan bool)
didFlush := make(chan bool, 1)
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
io.WriteString(w, "Hello")
w.(Flusher).Flush()
didFlush <- true
<-unblock
io.WriteString(w, ", world.")
}))
defer cst.close()
defer close(unblock)
req, _ := NewRequest("GET", cst.ts.URL, nil)
cancel := make(chan struct{})
req.Cancel = cancel
res, err := cst.c.Do(req)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
<-didFlush
// Read a bit before we cancel. (Issue 13626)
// We should have "Hello" at least sitting there.
firstRead := make([]byte, 10)
n, err := res.Body.Read(firstRead)
if err != nil {
t.Fatal(err)
}
firstRead = firstRead[:n]
close(cancel)
rest, err := ioutil.ReadAll(res.Body)
all := string(firstRead) + string(rest)
if all != "Hello" {
t.Errorf("Read %q (%q + %q); want Hello", all, firstRead, rest)
}
if !reflect.DeepEqual(err, ExportErrRequestCanceled) {
t.Errorf("ReadAll error = %v; want %v", err, ExportErrRequestCanceled)
}
}