589 lines
16 KiB
Go
589 lines
16 KiB
Go
package nghttp2
|
|
|
|
import (
|
|
"encoding/json"
|
|
"github.com/tatsuhiro-t/spdy"
|
|
"golang.org/x/net/http2/hpack"
|
|
"net/http"
|
|
"testing"
|
|
)
|
|
|
|
// TestS3H1PlainGET tests whether simple SPDY GET request works.
|
|
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)
|
|
}
|
|
}
|
|
|
|
// TestS3H1BadRequestCL tests that server rejects request whose
|
|
// content-length header field value does not match its request body
|
|
// size.
|
|
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)
|
|
}
|
|
}
|
|
|
|
// TestS3H1MultipleRequestCL tests that server rejects request with
|
|
// multiple Content-Length request header fields.
|
|
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", "1"),
|
|
},
|
|
})
|
|
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)
|
|
}
|
|
}
|
|
|
|
// TestS3H1InvalidRequestCL tests that server rejects request with
|
|
// Content-Length which cannot be parsed as a number.
|
|
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)
|
|
}
|
|
}
|
|
|
|
// TestS3H1GenerateVia tests that server generates Via header field to and
|
|
// from backend server.
|
|
func TestS3H1GenerateVia(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
if got, want := r.Header.Get("Via"), "1.1 nghttpx"; got != want {
|
|
t.Errorf("Via: %v; want %v", got, want)
|
|
}
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1GenerateVia",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want {
|
|
t.Errorf("Via: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H1AppendVia tests that server adds value to existing Via
|
|
// header field to and from backend server.
|
|
func TestS3H1AppendVia(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
if got, want := r.Header.Get("Via"), "foo, 1.1 nghttpx"; got != want {
|
|
t.Errorf("Via: %v; want %v", got, want)
|
|
}
|
|
w.Header().Add("Via", "bar")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1AppendVia",
|
|
header: []hpack.HeaderField{
|
|
pair("via", "foo"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want {
|
|
t.Errorf("Via: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H1NoVia tests that server does not add value to existing Via
|
|
// header field to and from backend server.
|
|
func TestS3H1NoVia(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--no-via"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
if got, want := r.Header.Get("Via"), "foo"; got != want {
|
|
t.Errorf("Via: %v; want %v", got, want)
|
|
}
|
|
w.Header().Add("Via", "bar")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1NoVia",
|
|
header: []hpack.HeaderField{
|
|
pair("via", "foo"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
if got, want := res.header.Get("Via"), "bar"; got != want {
|
|
t.Errorf("Via: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H1HeaderFieldBuffer tests that request with header fields
|
|
// larger than configured buffer size is rejected.
|
|
func TestS3H1HeaderFieldBuffer(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--header-field-buffer=10"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatal("execution path should not be here")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1HeaderFieldBuffer",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
if got, want := res.spdyRstErrCode, spdy.InternalError; got != want {
|
|
t.Errorf("res.spdyRstErrCode: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H1HeaderFields tests that request with header fields more
|
|
// than configured number is rejected.
|
|
func TestS3H1HeaderFields(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--max-header-fields=1"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatal("execution path should not be here")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1HeaderFields",
|
|
// we have at least 5 pseudo-header fields sent, and
|
|
// that ensures that buffer limit exceeds.
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
if got, want := res.spdyRstErrCode, spdy.InternalError; got != want {
|
|
t.Errorf("res.spdyRstErrCode: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H1InvalidMethod tests that server rejects invalid method with
|
|
// 501.
|
|
func TestS3H1InvalidMethod(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 this request")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1InvalidMethod",
|
|
method: "get",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
if got, want := res.status, 501; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H1BadHost tests that server rejects request including bad
|
|
// character in :host header field.
|
|
func TestS3H1BadHost(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 this request")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1BadHost",
|
|
authority: `foo\bar`,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
if got, want := res.status, 400; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H1BadScheme tests that server rejects request including bad
|
|
// character in :scheme header field.
|
|
func TestS3H1BadScheme(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 this request")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1BadScheme",
|
|
scheme: `http*`,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
if got, want := res.status, 400; got != want {
|
|
t.Errorf("status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H1ReqPhaseSetHeader tests mruby request phase hook
|
|
// modifies request header fields.
|
|
func TestS3H1ReqPhaseSetHeader(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/req-set-header.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
if got, want := r.Header.Get("User-Agent"), "mruby"; got != want {
|
|
t.Errorf("User-Agent = %v; want %v", got, want)
|
|
}
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1ReqPhaseSetHeader",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
|
|
if got, want := res.status, 200; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H1ReqPhaseReturn tests mruby request phase hook returns
|
|
// custom response.
|
|
func TestS3H1ReqPhaseReturn(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/req-return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatalf("request should not be forwarded")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1ReqPhaseReturn",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
|
|
if got, want := res.status, 404; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
|
|
hdtests := []struct {
|
|
k, v string
|
|
}{
|
|
{"content-length", "20"},
|
|
{"from", "mruby"},
|
|
}
|
|
for _, tt := range hdtests {
|
|
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
|
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
|
}
|
|
}
|
|
|
|
if got, want := string(res.body), "Hello World from req"; got != want {
|
|
t.Errorf("body = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H1RespPhaseSetHeader tests mruby response phase hook modifies
|
|
// response header fields.
|
|
func TestS3H1RespPhaseSetHeader(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/resp-set-header.rb"}, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1RespPhaseSetHeader",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
|
|
if got, want := res.status, 200; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
|
|
if got, want := res.header.Get("alpha"), "bravo"; got != want {
|
|
t.Errorf("alpha = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H1RespPhaseReturn tests mruby response phase hook returns
|
|
// custom response.
|
|
func TestS3H1RespPhaseReturn(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/resp-return.rb"}, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H1RespPhaseReturn",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
|
|
if got, want := res.status, 404; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
|
|
hdtests := []struct {
|
|
k, v string
|
|
}{
|
|
{"content-length", "21"},
|
|
{"from", "mruby"},
|
|
}
|
|
for _, tt := range hdtests {
|
|
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
|
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
|
}
|
|
}
|
|
|
|
if got, want := string(res.body), "Hello World from resp"; got != want {
|
|
t.Errorf("body = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// // TestS3H2ConnectFailure tests that server handles the situation that
|
|
// // connection attempt to HTTP/2 backend failed.
|
|
// 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)
|
|
// }
|
|
// }
|
|
|
|
// TestS3H2ReqPhaseReturn tests mruby request phase hook returns
|
|
// custom response.
|
|
func TestS3H2ReqPhaseReturn(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge", "--mruby-file=" + testDir + "/req-return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatalf("request should not be forwarded")
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H2ReqPhaseReturn",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
|
|
if got, want := res.status, 404; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
|
|
hdtests := []struct {
|
|
k, v string
|
|
}{
|
|
{"content-length", "20"},
|
|
{"from", "mruby"},
|
|
}
|
|
for _, tt := range hdtests {
|
|
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
|
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
|
}
|
|
}
|
|
|
|
if got, want := string(res.body), "Hello World from req"; got != want {
|
|
t.Errorf("body = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3H2RespPhaseReturn tests mruby response phase hook returns
|
|
// custom response.
|
|
func TestS3H2RespPhaseReturn(t *testing.T) {
|
|
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge", "--mruby-file=" + testDir + "/resp-return.rb"}, t, noopHandler)
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3H2RespPhaseReturn",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
|
|
if got, want := res.status, 404; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
|
|
hdtests := []struct {
|
|
k, v string
|
|
}{
|
|
{"content-length", "21"},
|
|
{"from", "mruby"},
|
|
}
|
|
for _, tt := range hdtests {
|
|
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
|
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
|
}
|
|
}
|
|
|
|
if got, want := string(res.body), "Hello World from resp"; got != want {
|
|
t.Errorf("body = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3APIBackendReplace exercise backend/replace API endpoint
|
|
// routine for successful case.
|
|
func TestS3APIBackendReplace(t *testing.T) {
|
|
st := newServerTesterTLSConnectPort([]string{"--npn-list=spdy/3.1", "-f127.0.0.1,3010;api"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatalf("request should not be forwarded")
|
|
}, 3010)
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3APIBackendReplace",
|
|
path: "/api/v1beta1/backend/replace",
|
|
method: "PUT",
|
|
body: []byte(`# comment
|
|
backend=127.0.0.1,3011
|
|
|
|
`),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
if got, want := res.status, 200; got != want {
|
|
t.Errorf("res.status: %v; want %v", got, want)
|
|
}
|
|
|
|
var apiResp APIResponse
|
|
err = json.Unmarshal(res.body, &apiResp)
|
|
if err != nil {
|
|
t.Fatalf("Error unmarshaling API response: %v", err)
|
|
}
|
|
if got, want := apiResp.Status, "Success"; got != want {
|
|
t.Errorf("apiResp.Status: %v; want %v", got, want)
|
|
}
|
|
if got, want := apiResp.Code, 200; got != want {
|
|
t.Errorf("apiResp.Status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3APIBackendReplaceBadMethod exercise backend/replace API
|
|
// endpoint routine with bad method.
|
|
func TestS3APIBackendReplaceBadMethod(t *testing.T) {
|
|
st := newServerTesterTLSConnectPort([]string{"--npn-list=spdy/3.1", "-f127.0.0.1,3010;api"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatalf("request should not be forwarded")
|
|
}, 3010)
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3APIBackendReplaceBadMethod",
|
|
path: "/api/v1beta1/backend/replace",
|
|
method: "GET",
|
|
body: []byte(`# comment
|
|
backend=127.0.0.1,3011
|
|
|
|
`),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
if got, want := res.status, 405; got != want {
|
|
t.Errorf("res.status: %v; want %v", got, want)
|
|
}
|
|
|
|
var apiResp APIResponse
|
|
err = json.Unmarshal(res.body, &apiResp)
|
|
if err != nil {
|
|
t.Fatalf("Error unmarshaling API response: %v", err)
|
|
}
|
|
if got, want := apiResp.Status, "Failure"; got != want {
|
|
t.Errorf("apiResp.Status: %v; want %v", got, want)
|
|
}
|
|
if got, want := apiResp.Code, 405; got != want {
|
|
t.Errorf("apiResp.Status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestS3APINotFound exercise backend/replace API endpoint routine
|
|
// when API endpoint is not found.
|
|
func TestS3APINotFound(t *testing.T) {
|
|
st := newServerTesterTLSConnectPort([]string{"--npn-list=spdy/3.1", "-f127.0.0.1,3010;api"}, t, func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatalf("request should not be forwarded")
|
|
}, 3010)
|
|
defer st.Close()
|
|
|
|
res, err := st.spdy(requestParam{
|
|
name: "TestS3APINotFound",
|
|
path: "/api/notfound",
|
|
method: "GET",
|
|
body: []byte(`# comment
|
|
backend=127.0.0.1,3011
|
|
|
|
`),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.spdy() = %v", err)
|
|
}
|
|
if got, want := res.status, 404; got != want {
|
|
t.Errorf("res.status: %v; want %v", got, want)
|
|
}
|
|
|
|
var apiResp APIResponse
|
|
err = json.Unmarshal(res.body, &apiResp)
|
|
if err != nil {
|
|
t.Fatalf("Error unmarshaling API response: %v", err)
|
|
}
|
|
if got, want := apiResp.Status, "Failure"; got != want {
|
|
t.Errorf("apiResp.Status: %v; want %v", got, want)
|
|
}
|
|
if got, want := apiResp.Code, 404; got != want {
|
|
t.Errorf("apiResp.Status: %v; want %v", got, want)
|
|
}
|
|
}
|