391 lines
8.9 KiB
Go
391 lines
8.9 KiB
Go
//go:build quic
|
|
|
|
package nghttp2
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"io"
|
|
"net/http"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"golang.org/x/net/http2/hpack"
|
|
)
|
|
|
|
// TestH3H1PlainGET tests whether simple HTTP/3 GET request works.
|
|
func TestH3H1PlainGET(t *testing.T) {
|
|
st := newServerTester(t, options{
|
|
quic: true,
|
|
})
|
|
defer st.Close()
|
|
|
|
res, err := st.http3(requestParam{
|
|
name: "TestH3H1PlainGET",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http3() = %v", err)
|
|
}
|
|
|
|
want := 200
|
|
if got := res.status; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestH3H1RequestBody tests HTTP/3 request with body works.
|
|
func TestH3H1RequestBody(t *testing.T) {
|
|
body := make([]byte, 3333)
|
|
_, err := rand.Read(body)
|
|
if err != nil {
|
|
t.Fatalf("Unable to create request body: %v", err)
|
|
}
|
|
|
|
opts := options{
|
|
handler: func(w http.ResponseWriter, r *http.Request) {
|
|
buf := make([]byte, 4096)
|
|
buflen := 0
|
|
p := buf
|
|
|
|
for {
|
|
if len(p) == 0 {
|
|
t.Fatal("Request body is too large")
|
|
}
|
|
|
|
n, err := r.Body.Read(p)
|
|
|
|
p = p[n:]
|
|
buflen += n
|
|
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
|
|
t.Fatalf("r.Body.Read() = %v", err)
|
|
}
|
|
}
|
|
|
|
buf = buf[:buflen]
|
|
|
|
if got, want := buf, body; !bytes.Equal(got, want) {
|
|
t.Fatalf("buf = %v; want %v", got, want)
|
|
}
|
|
},
|
|
quic: true,
|
|
}
|
|
st := newServerTester(t, opts)
|
|
defer st.Close()
|
|
|
|
res, err := st.http3(requestParam{
|
|
name: "TestH3H1RequestBody",
|
|
body: body,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http3() = %v", err)
|
|
}
|
|
if got, want := res.status, 200; got != want {
|
|
t.Errorf("res.status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestH3H1GenerateVia tests that server generates Via header field to
|
|
// and from backend server.
|
|
func TestH3H1GenerateVia(t *testing.T) {
|
|
opts := options{
|
|
handler: func(w http.ResponseWriter, r *http.Request) {
|
|
if got, want := r.Header.Get("Via"), "3 nghttpx"; got != want {
|
|
t.Errorf("Via: %v; want %v", got, want)
|
|
}
|
|
},
|
|
quic: true,
|
|
}
|
|
st := newServerTester(t, opts)
|
|
defer st.Close()
|
|
|
|
res, err := st.http3(requestParam{
|
|
name: "TestH3H1GenerateVia",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http3() = %v", err)
|
|
}
|
|
if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want {
|
|
t.Errorf("Via: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestH3H1AppendVia tests that server adds value to existing Via
|
|
// header field to and from backend server.
|
|
func TestH3H1AppendVia(t *testing.T) {
|
|
opts := options{
|
|
handler: func(w http.ResponseWriter, r *http.Request) {
|
|
if got, want := r.Header.Get("Via"), "foo, 3 nghttpx"; got != want {
|
|
t.Errorf("Via: %v; want %v", got, want)
|
|
}
|
|
w.Header().Add("Via", "bar")
|
|
},
|
|
quic: true,
|
|
}
|
|
st := newServerTester(t, opts)
|
|
defer st.Close()
|
|
|
|
res, err := st.http3(requestParam{
|
|
name: "TestH3H1AppendVia",
|
|
header: []hpack.HeaderField{
|
|
pair("via", "foo"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http3() = %v", err)
|
|
}
|
|
if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want {
|
|
t.Errorf("Via: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestH3H1NoVia tests that server does not add value to existing Via
|
|
// header field to and from backend server.
|
|
func TestH3H1NoVia(t *testing.T) {
|
|
opts := options{
|
|
args: []string{"--no-via"},
|
|
handler: 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")
|
|
},
|
|
quic: true,
|
|
}
|
|
st := newServerTester(t, opts)
|
|
defer st.Close()
|
|
|
|
res, err := st.http3(requestParam{
|
|
name: "TestH3H1NoVia",
|
|
header: []hpack.HeaderField{
|
|
pair("via", "foo"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http3() = %v", err)
|
|
}
|
|
if got, want := res.header.Get("Via"), "bar"; got != want {
|
|
t.Errorf("Via: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestH3H1BadResponseCL tests that server returns error when
|
|
// content-length response header field value does not match its
|
|
// response body size.
|
|
func TestH3H1BadResponseCL(t *testing.T) {
|
|
opts := options{
|
|
handler: 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"))
|
|
},
|
|
quic: true,
|
|
}
|
|
st := newServerTester(t, opts)
|
|
defer st.Close()
|
|
|
|
_, err := st.http3(requestParam{
|
|
name: "TestH3H1BadResponseCL",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("st.http3() should fail")
|
|
}
|
|
}
|
|
|
|
// TestH3H1HTTPSRedirect tests that HTTPS redirect should not happen
|
|
// with HTTP/3.
|
|
func TestH3H1HTTPSRedirect(t *testing.T) {
|
|
opts := options{
|
|
args: []string{"--redirect-if-not-tls"},
|
|
quic: true,
|
|
}
|
|
st := newServerTester(t, opts)
|
|
defer st.Close()
|
|
|
|
res, err := st.http3(requestParam{
|
|
name: "TestH3H1HTTPSRedirect",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http3() = %v", err)
|
|
}
|
|
|
|
if got, want := res.status, 200; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestH3H1AffinityCookieTLS tests that affinity cookie is sent back
|
|
// in https.
|
|
func TestH3H1AffinityCookieTLS(t *testing.T) {
|
|
opts := options{
|
|
args: []string{"--affinity-cookie"},
|
|
quic: true,
|
|
}
|
|
st := newServerTester(t, opts)
|
|
defer st.Close()
|
|
|
|
res, err := st.http3(requestParam{
|
|
name: "TestH3H1AffinityCookieTLS",
|
|
scheme: "https",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http3() = %v", err)
|
|
}
|
|
|
|
if got, want := res.status, 200; got != want {
|
|
t.Errorf("status = %v; want %v", got, want)
|
|
}
|
|
|
|
const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar; Secure`
|
|
validCookie := regexp.MustCompile(pattern)
|
|
if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) {
|
|
t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern)
|
|
}
|
|
}
|
|
|
|
// TestH3H2ReqPhaseReturn tests mruby request phase hook returns
|
|
// custom response.
|
|
func TestH3H2ReqPhaseReturn(t *testing.T) {
|
|
opts := options{
|
|
args: []string{
|
|
"--http2-bridge",
|
|
"--mruby-file=" + testDir + "/req-return.rb",
|
|
},
|
|
handler: func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatalf("request should not be forwarded")
|
|
},
|
|
quic: true,
|
|
}
|
|
st := newServerTester(t, opts)
|
|
defer st.Close()
|
|
|
|
res, err := st.http3(requestParam{
|
|
name: "TestH3H2ReqPhaseReturn",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http3() = %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)
|
|
}
|
|
}
|
|
|
|
// TestH3H2RespPhaseReturn tests mruby response phase hook returns
|
|
// custom response.
|
|
func TestH3H2RespPhaseReturn(t *testing.T) {
|
|
opts := options{
|
|
args: []string{
|
|
"--http2-bridge",
|
|
"--mruby-file=" + testDir + "/resp-return.rb",
|
|
},
|
|
quic: true,
|
|
}
|
|
st := newServerTester(t, opts)
|
|
defer st.Close()
|
|
|
|
res, err := st.http3(requestParam{
|
|
name: "TestH3H2RespPhaseReturn",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http3() = %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)
|
|
}
|
|
}
|
|
|
|
// TestH3ResponseBeforeRequestEnd tests the situation where response
|
|
// ends before request body finishes.
|
|
func TestH3ResponseBeforeRequestEnd(t *testing.T) {
|
|
opts := options{
|
|
args: []string{"--mruby-file=" + testDir + "/req-return.rb"},
|
|
handler: func(w http.ResponseWriter, r *http.Request) {
|
|
t.Fatal("request should not be forwarded")
|
|
},
|
|
quic: true,
|
|
}
|
|
st := newServerTester(t, opts)
|
|
defer st.Close()
|
|
|
|
res, err := st.http3(requestParam{
|
|
name: "TestH3ResponseBeforeRequestEnd",
|
|
noEndStream: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error st.http3() = %v", err)
|
|
}
|
|
if got, want := res.status, 404; got != want {
|
|
t.Errorf("res.status: %v; want %v", got, want)
|
|
}
|
|
}
|
|
|
|
// TestH3H1ChunkedEndsPrematurely tests that a stream is reset if the
|
|
// backend chunked encoded response ends prematurely.
|
|
func TestH3H1ChunkedEndsPrematurely(t *testing.T) {
|
|
opts := options{
|
|
handler: func(w http.ResponseWriter, r *http.Request) {
|
|
hj, ok := w.(http.Hijacker)
|
|
if !ok {
|
|
http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
conn, bufrw, err := hj.Hijack()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n")
|
|
bufrw.Flush()
|
|
},
|
|
quic: true,
|
|
}
|
|
st := newServerTester(t, opts)
|
|
defer st.Close()
|
|
|
|
_, err := st.http3(requestParam{
|
|
name: "TestH3H1ChunkedEndsPrematurely",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("st.http3() should fail")
|
|
}
|
|
}
|