667 lines
16 KiB
Go
667 lines
16 KiB
Go
package nghttp2
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"github.com/bradfitz/http2"
|
|
"github.com/bradfitz/http2/hpack"
|
|
"golang.org/x/net/spdy"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"syscall"
|
|
"testing"
|
|
)
|
|
|
|
func TestH1H1PlainGET(t *testing.T) {
|
|
st := newServerTester(nil, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
res, err := st.http1(requestParam{
|
|
name: "TestH1H1PlainGET",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http1() = %v", err)
|
|
}
|
|
|
|
want := 200
|
|
if got := res.status; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH1H1PlainGETClose(t *testing.T) {
|
|
st := newServerTester(nil, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
res, err := st.http1(requestParam{
|
|
name: "TestH1H1PlainGETClose",
|
|
header: []hpack.HeaderField{
|
|
pair("Connection", "close"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http1() = %v", err)
|
|
}
|
|
|
|
want := 200
|
|
if got := res.status; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH1H1MultipleRequestCL(t *testing.T) {
|
|
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Errorf("server should not forward bad request")
|
|
})
|
|
defer st.Close()
|
|
|
|
if _, err := io.WriteString(st.conn, fmt.Sprintf(`GET / HTTP/1.1
|
|
Host: %v
|
|
Test-Case: TestH1H1MultipleRequestCL
|
|
Content-Length: 0
|
|
Content-Length: 1
|
|
|
|
`, st.authority)); err != nil {
|
|
t.Fatalf("Error io.WriteString() = %v", err)
|
|
}
|
|
|
|
resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
|
|
if err != nil {
|
|
t.Fatalf("Error http.ReadResponse() = %v", err)
|
|
}
|
|
|
|
want := 400
|
|
if got := resp.StatusCode; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH1H1ConnectFailure(t *testing.T) {
|
|
st := newServerTester(nil, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
// shutdown backend server to simulate backend connect failure
|
|
st.ts.Close()
|
|
|
|
res, err := st.http1(requestParam{
|
|
name: "TestH1H1ConnectFailure",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http1() = %v", err)
|
|
}
|
|
want := 503
|
|
if got := res.status; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH1H1GracefulShutdown(t *testing.T) {
|
|
st := newServerTester(nil, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
res, err := st.http1(requestParam{
|
|
name: "TestH1H1GracefulShutdown-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http1() = %v", err)
|
|
}
|
|
|
|
if got, want := res.status, 200; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
|
|
st.cmd.Process.Signal(syscall.SIGQUIT)
|
|
|
|
res, err = st.http1(requestParam{
|
|
name: "TestH1H1GracefulShutdown-2",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http1() = %v", err)
|
|
}
|
|
|
|
if got, want := res.status, 200; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
|
|
if got, want := res.connClose, true; got != want {
|
|
t.Errorf("res.connClose: %v; want %v", got, want)
|
|
}
|
|
|
|
want := io.EOF
|
|
if _, err := st.conn.Read(nil); err == nil || err != want {
|
|
t.Errorf("st.conn.Read(): %v; want %v", err, want)
|
|
}
|
|
}
|
|
|
|
func TestH1H2ConnectFailure(t *testing.T) {
|
|
st := newServerTester([]string{"--http2-bridge"}, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
// simulate backend connect attempt failure
|
|
st.ts.Close()
|
|
|
|
res, err := st.http1(requestParam{
|
|
name: "TestH1H2ConnectFailure",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http1() = %v", err)
|
|
}
|
|
want := 503
|
|
if got := res.status; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH1H2NoHost(t *testing.T) {
|
|
st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Errorf("server should not forward bad request")
|
|
})
|
|
defer st.Close()
|
|
|
|
// without Host header field, we expect 400 response
|
|
if _, err := io.WriteString(st.conn, "GET / HTTP/1.1\r\nTest-Case: TestH1H2NoHost\r\n\r\n"); err != nil {
|
|
t.Fatalf("Error io.WriteString() = %v", err)
|
|
}
|
|
|
|
resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
|
|
if err != nil {
|
|
t.Fatalf("Error http.ReadResponse() = %v", err)
|
|
}
|
|
|
|
want := 400
|
|
if got := resp.StatusCode; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH2H1PlainGET(t *testing.T) {
|
|
st := newServerTester(nil, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
res, err := st.http2(requestParam{
|
|
name: "TestH2H1PlainGET",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
|
|
want := 200
|
|
if res.status != want {
|
|
t.Errorf("status = %v; want %v", res.status, want)
|
|
}
|
|
}
|
|
|
|
func TestH2H1AddXff(t *testing.T) {
|
|
st := newServerTester([]string{"--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
xff := r.Header.Get("X-Forwarded-For")
|
|
want := "127.0.0.1"
|
|
if xff != want {
|
|
t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
|
|
}
|
|
})
|
|
defer st.Close()
|
|
|
|
_, err := st.http2(requestParam{
|
|
name: "TestH2H1AddXff",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestH2H1AddXff2(t *testing.T) {
|
|
st := newServerTester([]string{"--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
xff := r.Header.Get("X-Forwarded-For")
|
|
want := "host, 127.0.0.1"
|
|
if xff != want {
|
|
t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
|
|
}
|
|
})
|
|
defer st.Close()
|
|
|
|
_, err := st.http2(requestParam{
|
|
name: "TestH2H1AddXff2",
|
|
header: []hpack.HeaderField{
|
|
pair("x-forwarded-for", "host"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestH2H1StripXff(t *testing.T) {
|
|
st := newServerTester([]string{"--strip-incoming-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
if xff, found := r.Header["X-Forwarded-For"]; found {
|
|
t.Errorf("X-Forwarded-For = %v; want nothing", xff)
|
|
}
|
|
})
|
|
defer st.Close()
|
|
|
|
_, err := st.http2(requestParam{
|
|
name: "TestH2H1StripXff1",
|
|
header: []hpack.HeaderField{
|
|
pair("x-forwarded-for", "host"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestH2H1StripAddXff(t *testing.T) {
|
|
args := []string{
|
|
"--strip-incoming-x-forwarded-for",
|
|
"--add-x-forwarded-for",
|
|
}
|
|
st := newServerTester(args, t, func(w http.ResponseWriter, r *http.Request) {
|
|
xff := r.Header.Get("X-Forwarded-For")
|
|
want := "127.0.0.1"
|
|
if xff != want {
|
|
t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
|
|
}
|
|
})
|
|
defer st.Close()
|
|
|
|
_, err := st.http2(requestParam{
|
|
name: "TestH2H1StripAddXff",
|
|
header: []hpack.HeaderField{
|
|
pair("x-forwarded-for", "host"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestH2H1BadRequestCL(t *testing.T) {
|
|
st := newServerTester(nil, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
// we set content-length: 1024, but the actual request body is
|
|
// 3 bytes.
|
|
res, err := st.http2(requestParam{
|
|
name: "TestH2H1BadRequestCL",
|
|
method: "POST",
|
|
header: []hpack.HeaderField{
|
|
pair("content-length", "1024"),
|
|
},
|
|
body: []byte("foo"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
|
|
want := http2.ErrCodeProtocol
|
|
if res.errCode != want {
|
|
t.Errorf("res.errCode = %v; want %v", res.errCode, want)
|
|
}
|
|
}
|
|
|
|
func TestH2H1BadResponseCL(t *testing.T) {
|
|
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
|
|
// we set content-length: 1024, but only send 3 bytes.
|
|
w.Header().Add("Content-Length", "1024")
|
|
w.Write([]byte("foo"))
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.http2(requestParam{
|
|
name: "TestH2H1BadResponseCL",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
|
|
want := http2.ErrCodeProtocol
|
|
if res.errCode != want {
|
|
t.Errorf("res.errCode = %v; want %v", res.errCode, want)
|
|
}
|
|
}
|
|
|
|
func TestH2H1LocationRewrite(t *testing.T) {
|
|
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
|
|
// TODO we cannot get st.ts's port number here.. 8443
|
|
// is just a place holder. We ignore it on rewrite.
|
|
w.Header().Add("Location", "http://127.0.0.1:8443/p/q?a=b#fragment")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.http2(requestParam{
|
|
name: "TestH2H1LocationRewrite",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
|
|
want := fmt.Sprintf("http://127.0.0.1:%v/p/q?a=b#fragment", serverPort)
|
|
if got := res.header.Get("Location"); got != want {
|
|
t.Errorf("Location: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH2H1ChunkedRequestBody(t *testing.T) {
|
|
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
|
|
want := "[chunked]"
|
|
if got := fmt.Sprint(r.TransferEncoding); got != want {
|
|
t.Errorf("Transfer-Encoding: %v; want %v", got, want)
|
|
}
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Fatalf("Error reading r.body: %v", err)
|
|
}
|
|
want = "foo"
|
|
if got := string(body); got != want {
|
|
t.Errorf("body: %v; want %v", got, want)
|
|
}
|
|
})
|
|
defer st.Close()
|
|
|
|
_, err := st.http2(requestParam{
|
|
name: "TestH2H1ChunkedRequestBody",
|
|
method: "POST",
|
|
body: []byte("foo"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestH2H1MultipleRequestCL(t *testing.T) {
|
|
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Errorf("server should not forward bad request")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.http2(requestParam{
|
|
name: "TestH2H1MultipleRequestCL",
|
|
header: []hpack.HeaderField{
|
|
pair("content-length", "1"),
|
|
pair("content-length", "2"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
want := 400
|
|
if got := res.status; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH2H1InvalidRequestCL(t *testing.T) {
|
|
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Errorf("server should not forward bad request")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.http2(requestParam{
|
|
name: "TestH2H1InvalidRequestCL",
|
|
header: []hpack.HeaderField{
|
|
pair("content-length", ""),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
want := 400
|
|
if got := res.status; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH2H1ConnectFailure(t *testing.T) {
|
|
st := newServerTester(nil, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
// shutdown backend server to simulate backend connect failure
|
|
st.ts.Close()
|
|
|
|
res, err := st.http2(requestParam{
|
|
name: "TestH2H1ConnectFailure",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
want := 503
|
|
if got := res.status; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH2H1GracefulShutdown(t *testing.T) {
|
|
st := newServerTester(nil, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
fmt.Fprint(st.conn, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
|
|
if err := st.fr.WriteSettings(); err != nil {
|
|
t.Fatalf("st.fr.WriteSettings(): %v", err)
|
|
}
|
|
|
|
header := []hpack.HeaderField{
|
|
pair(":method", "GET"),
|
|
pair(":scheme", "http"),
|
|
pair(":authority", st.authority),
|
|
pair(":path", "/"),
|
|
}
|
|
|
|
for _, h := range header {
|
|
_ = st.enc.WriteField(h)
|
|
}
|
|
|
|
if err := st.fr.WriteHeaders(http2.HeadersFrameParam{
|
|
StreamID: 1,
|
|
EndStream: false,
|
|
EndHeaders: true,
|
|
BlockFragment: st.headerBlkBuf.Bytes(),
|
|
}); err != nil {
|
|
t.Fatalf("st.fr.WriteHeaders(): %v", err)
|
|
}
|
|
|
|
// send SIGQUIT signal to nghttpx to perform graceful shutdown
|
|
st.cmd.Process.Signal(syscall.SIGQUIT)
|
|
|
|
// after signal, finish request body
|
|
if err := st.fr.WriteData(1, true, nil); err != nil {
|
|
t.Fatalf("st.fr.WriteData(): %v", err)
|
|
}
|
|
|
|
numGoAway := 0
|
|
|
|
for {
|
|
fr, err := st.readFrame()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
want := 2
|
|
if got := numGoAway; got != want {
|
|
t.Fatalf("numGoAway: %v; want %v", got, want)
|
|
}
|
|
return
|
|
}
|
|
t.Fatalf("st.readFrame(): %v", err)
|
|
}
|
|
switch f := fr.(type) {
|
|
case *http2.GoAwayFrame:
|
|
numGoAway += 1
|
|
want := http2.ErrCodeNo
|
|
if got := f.ErrCode; got != want {
|
|
t.Fatalf("f.ErrCode(%v): %v; want %v", numGoAway, got, want)
|
|
}
|
|
switch numGoAway {
|
|
case 1:
|
|
want := (uint32(1) << 31) - 1
|
|
if got := f.LastStreamID; got != want {
|
|
t.Fatalf("f.LastStreamID(%v): %v; want %v", numGoAway, got, want)
|
|
}
|
|
case 2:
|
|
want := uint32(1)
|
|
if got := f.LastStreamID; got != want {
|
|
t.Fatalf("f.LastStreamID(%v): %v; want %v", numGoAway, got, want)
|
|
}
|
|
case 3:
|
|
t.Fatalf("too many GOAWAYs received")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestH2H2MultipleResponseCL(t *testing.T) {
|
|
st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Add("content-length", "1")
|
|
w.Header().Add("content-length", "2")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.http2(requestParam{
|
|
name: "TestH2H2MultipleResponseCL",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
want := 502
|
|
if got := res.status; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH2H2InvalidResponseCL(t *testing.T) {
|
|
st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Add("content-length", "")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.http2(requestParam{
|
|
name: "TestH2H2InvalidResponseCL",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
want := 502
|
|
if got := res.status; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestH2H2ConnectFailure(t *testing.T) {
|
|
st := newServerTester([]string{"--http2-bridge"}, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
// simulate backend connect attempt failure
|
|
st.ts.Close()
|
|
|
|
res, err := st.http2(requestParam{
|
|
name: "TestH2H2ConnectFailure",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http2() = %v", err)
|
|
}
|
|
want := 503
|
|
if got := res.status; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestS3H1PlainGET(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1PlainGET",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
|
|
want := 200
|
|
if got := res.status; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestS3H1BadRequestCL(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
// we set content-length: 1024, but the actual request body is
|
|
// 3 bytes.
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1BadRequestCL",
|
|
method: "POST",
|
|
header: []hpack.HeaderField{
|
|
pair("content-length", "1024"),
|
|
},
|
|
body: []byte("foo"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
|
|
want := spdy.ProtocolError
|
|
if got := res.spdyRstErrCode; got != want {
|
|
t.Errorf("res.spdyRstErrCode = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestS3H1MultipleRequestCL(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Errorf("server should not forward bad request")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1MultipleRequestCL",
|
|
header: []hpack.HeaderField{
|
|
pair("content-length", "1"),
|
|
pair("content-length", "2"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
want := 400
|
|
if got := res.status; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestS3H1InvalidRequestCL(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Errorf("server should not forward bad request")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1InvalidRequestCL",
|
|
header: []hpack.HeaderField{
|
|
pair("content-length", ""),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
want := 400
|
|
if got := res.status; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestS3H2ConnectFailure(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge"}, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
// simulate backend connect attempt failure
|
|
st.ts.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H2ConnectFailure",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
want := 503
|
|
if got := res.status; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|