summaryrefslogtreecommitdiffstats
path: root/integration-tests/nghttpx_http3_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'integration-tests/nghttpx_http3_test.go')
-rw-r--r--integration-tests/nghttpx_http3_test.go393
1 files changed, 393 insertions, 0 deletions
diff --git a/integration-tests/nghttpx_http3_test.go b/integration-tests/nghttpx_http3_test.go
new file mode 100644
index 0000000..9ea85d7
--- /dev/null
+++ b/integration-tests/nghttpx_http3_test.go
@@ -0,0 +1,393 @@
+//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)
+ }
+
+ if got, want := res.status, http.StatusOK; 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, http.StatusOK; 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")
+ if _, err := w.Write([]byte("foo")); err != nil {
+ t.Fatalf("Error w.Write() = %v", err)
+ }
+ },
+ 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, http.StatusOK; 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, http.StatusOK; 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, http.StatusNotFound; 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, http.StatusNotFound; 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, http.StatusNotFound; 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()
+ if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil {
+ t.Fatalf("Error bufrw.WriteString() = %v", err)
+ }
+ 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")
+ }
+}