• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1//go:build quic
2
3package nghttp2
4
5import (
6	"bytes"
7	"crypto/rand"
8	"io"
9	"net/http"
10	"regexp"
11	"testing"
12
13	"golang.org/x/net/http2/hpack"
14)
15
16// TestH3H1PlainGET tests whether simple HTTP/3 GET request works.
17func TestH3H1PlainGET(t *testing.T) {
18	st := newServerTester(t, options{
19		quic: true,
20	})
21	defer st.Close()
22
23	res, err := st.http3(requestParam{
24		name: "TestH3H1PlainGET",
25	})
26	if err != nil {
27		t.Fatalf("Error st.http3() = %v", err)
28	}
29
30	if got, want := res.status, http.StatusOK; got != want {
31		t.Errorf("status = %v; want %v", got, want)
32	}
33}
34
35// TestH3H1RequestBody tests HTTP/3 request with body works.
36func TestH3H1RequestBody(t *testing.T) {
37	body := make([]byte, 3333)
38	_, err := rand.Read(body)
39	if err != nil {
40		t.Fatalf("Unable to create request body: %v", err)
41	}
42
43	opts := options{
44		handler: func(w http.ResponseWriter, r *http.Request) {
45			buf := make([]byte, 4096)
46			buflen := 0
47			p := buf
48
49			for {
50				if len(p) == 0 {
51					t.Fatal("Request body is too large")
52				}
53
54				n, err := r.Body.Read(p)
55
56				p = p[n:]
57				buflen += n
58
59				if err != nil {
60					if err == io.EOF {
61						break
62					}
63
64					t.Fatalf("r.Body.Read() = %v", err)
65				}
66			}
67
68			buf = buf[:buflen]
69
70			if got, want := buf, body; !bytes.Equal(got, want) {
71				t.Fatalf("buf = %v; want %v", got, want)
72			}
73		},
74		quic: true,
75	}
76	st := newServerTester(t, opts)
77	defer st.Close()
78
79	res, err := st.http3(requestParam{
80		name: "TestH3H1RequestBody",
81		body: body,
82	})
83	if err != nil {
84		t.Fatalf("Error st.http3() = %v", err)
85	}
86	if got, want := res.status, http.StatusOK; got != want {
87		t.Errorf("res.status: %v; want %v", got, want)
88	}
89}
90
91// TestH3H1GenerateVia tests that server generates Via header field to
92// and from backend server.
93func TestH3H1GenerateVia(t *testing.T) {
94	opts := options{
95		handler: func(w http.ResponseWriter, r *http.Request) {
96			if got, want := r.Header.Get("Via"), "3 nghttpx"; got != want {
97				t.Errorf("Via: %v; want %v", got, want)
98			}
99		},
100		quic: true,
101	}
102	st := newServerTester(t, opts)
103	defer st.Close()
104
105	res, err := st.http3(requestParam{
106		name: "TestH3H1GenerateVia",
107	})
108	if err != nil {
109		t.Fatalf("Error st.http3() = %v", err)
110	}
111	if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want {
112		t.Errorf("Via: %v; want %v", got, want)
113	}
114}
115
116// TestH3H1AppendVia tests that server adds value to existing Via
117// header field to and from backend server.
118func TestH3H1AppendVia(t *testing.T) {
119	opts := options{
120		handler: func(w http.ResponseWriter, r *http.Request) {
121			if got, want := r.Header.Get("Via"), "foo, 3 nghttpx"; got != want {
122				t.Errorf("Via: %v; want %v", got, want)
123			}
124			w.Header().Add("Via", "bar")
125		},
126		quic: true,
127	}
128	st := newServerTester(t, opts)
129	defer st.Close()
130
131	res, err := st.http3(requestParam{
132		name: "TestH3H1AppendVia",
133		header: []hpack.HeaderField{
134			pair("via", "foo"),
135		},
136	})
137	if err != nil {
138		t.Fatalf("Error st.http3() = %v", err)
139	}
140	if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want {
141		t.Errorf("Via: %v; want %v", got, want)
142	}
143}
144
145// TestH3H1NoVia tests that server does not add value to existing Via
146// header field to and from backend server.
147func TestH3H1NoVia(t *testing.T) {
148	opts := options{
149		args: []string{"--no-via"},
150		handler: func(w http.ResponseWriter, r *http.Request) {
151			if got, want := r.Header.Get("Via"), "foo"; got != want {
152				t.Errorf("Via: %v; want %v", got, want)
153			}
154			w.Header().Add("Via", "bar")
155		},
156		quic: true,
157	}
158	st := newServerTester(t, opts)
159	defer st.Close()
160
161	res, err := st.http3(requestParam{
162		name: "TestH3H1NoVia",
163		header: []hpack.HeaderField{
164			pair("via", "foo"),
165		},
166	})
167	if err != nil {
168		t.Fatalf("Error st.http3() = %v", err)
169	}
170	if got, want := res.header.Get("Via"), "bar"; got != want {
171		t.Errorf("Via: %v; want %v", got, want)
172	}
173}
174
175// TestH3H1BadResponseCL tests that server returns error when
176// content-length response header field value does not match its
177// response body size.
178func TestH3H1BadResponseCL(t *testing.T) {
179	opts := options{
180		handler: func(w http.ResponseWriter, r *http.Request) {
181			// we set content-length: 1024, but only send 3 bytes.
182			w.Header().Add("Content-Length", "1024")
183			if _, err := w.Write([]byte("foo")); err != nil {
184				t.Fatalf("Error w.Write() = %v", err)
185			}
186		},
187		quic: true,
188	}
189	st := newServerTester(t, opts)
190	defer st.Close()
191
192	_, err := st.http3(requestParam{
193		name: "TestH3H1BadResponseCL",
194	})
195	if err == nil {
196		t.Fatal("st.http3() should fail")
197	}
198}
199
200// TestH3H1HTTPSRedirect tests that HTTPS redirect should not happen
201// with HTTP/3.
202func TestH3H1HTTPSRedirect(t *testing.T) {
203	opts := options{
204		args: []string{"--redirect-if-not-tls"},
205		quic: true,
206	}
207	st := newServerTester(t, opts)
208	defer st.Close()
209
210	res, err := st.http3(requestParam{
211		name: "TestH3H1HTTPSRedirect",
212	})
213	if err != nil {
214		t.Fatalf("Error st.http3() = %v", err)
215	}
216
217	if got, want := res.status, http.StatusOK; got != want {
218		t.Errorf("status = %v; want %v", got, want)
219	}
220}
221
222// TestH3H1AffinityCookieTLS tests that affinity cookie is sent back
223// in https.
224func TestH3H1AffinityCookieTLS(t *testing.T) {
225	opts := options{
226		args: []string{"--affinity-cookie"},
227		quic: true,
228	}
229	st := newServerTester(t, opts)
230	defer st.Close()
231
232	res, err := st.http3(requestParam{
233		name:   "TestH3H1AffinityCookieTLS",
234		scheme: "https",
235	})
236	if err != nil {
237		t.Fatalf("Error st.http3() = %v", err)
238	}
239
240	if got, want := res.status, http.StatusOK; got != want {
241		t.Errorf("status = %v; want %v", got, want)
242	}
243
244	const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar; Secure`
245	validCookie := regexp.MustCompile(pattern)
246	if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) {
247		t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern)
248	}
249}
250
251// TestH3H2ReqPhaseReturn tests mruby request phase hook returns
252// custom response.
253func TestH3H2ReqPhaseReturn(t *testing.T) {
254	opts := options{
255		args: []string{
256			"--http2-bridge",
257			"--mruby-file=" + testDir + "/req-return.rb",
258		},
259		handler: func(w http.ResponseWriter, r *http.Request) {
260			t.Fatalf("request should not be forwarded")
261		},
262		quic: true,
263	}
264	st := newServerTester(t, opts)
265	defer st.Close()
266
267	res, err := st.http3(requestParam{
268		name: "TestH3H2ReqPhaseReturn",
269	})
270	if err != nil {
271		t.Fatalf("Error st.http3() = %v", err)
272	}
273
274	if got, want := res.status, http.StatusNotFound; got != want {
275		t.Errorf("status = %v; want %v", got, want)
276	}
277
278	hdtests := []struct {
279		k, v string
280	}{
281		{"content-length", "20"},
282		{"from", "mruby"},
283	}
284	for _, tt := range hdtests {
285		if got, want := res.header.Get(tt.k), tt.v; got != want {
286			t.Errorf("%v = %v; want %v", tt.k, got, want)
287		}
288	}
289
290	if got, want := string(res.body), "Hello World from req"; got != want {
291		t.Errorf("body = %v; want %v", got, want)
292	}
293}
294
295// TestH3H2RespPhaseReturn tests mruby response phase hook returns
296// custom response.
297func TestH3H2RespPhaseReturn(t *testing.T) {
298	opts := options{
299		args: []string{
300			"--http2-bridge",
301			"--mruby-file=" + testDir + "/resp-return.rb",
302		},
303		quic: true,
304	}
305	st := newServerTester(t, opts)
306	defer st.Close()
307
308	res, err := st.http3(requestParam{
309		name: "TestH3H2RespPhaseReturn",
310	})
311	if err != nil {
312		t.Fatalf("Error st.http3() = %v", err)
313	}
314
315	if got, want := res.status, http.StatusNotFound; got != want {
316		t.Errorf("status = %v; want %v", got, want)
317	}
318
319	hdtests := []struct {
320		k, v string
321	}{
322		{"content-length", "21"},
323		{"from", "mruby"},
324	}
325	for _, tt := range hdtests {
326		if got, want := res.header.Get(tt.k), tt.v; got != want {
327			t.Errorf("%v = %v; want %v", tt.k, got, want)
328		}
329	}
330
331	if got, want := string(res.body), "Hello World from resp"; got != want {
332		t.Errorf("body = %v; want %v", got, want)
333	}
334}
335
336// TestH3ResponseBeforeRequestEnd tests the situation where response
337// ends before request body finishes.
338func TestH3ResponseBeforeRequestEnd(t *testing.T) {
339	opts := options{
340		args: []string{"--mruby-file=" + testDir + "/req-return.rb"},
341		handler: func(w http.ResponseWriter, r *http.Request) {
342			t.Fatal("request should not be forwarded")
343		},
344		quic: true,
345	}
346	st := newServerTester(t, opts)
347	defer st.Close()
348
349	res, err := st.http3(requestParam{
350		name:        "TestH3ResponseBeforeRequestEnd",
351		noEndStream: true,
352	})
353	if err != nil {
354		t.Fatalf("Error st.http3() = %v", err)
355	}
356	if got, want := res.status, http.StatusNotFound; got != want {
357		t.Errorf("res.status: %v; want %v", got, want)
358	}
359}
360
361// TestH3H1ChunkedEndsPrematurely tests that a stream is reset if the
362// backend chunked encoded response ends prematurely.
363func TestH3H1ChunkedEndsPrematurely(t *testing.T) {
364	opts := options{
365		handler: func(w http.ResponseWriter, r *http.Request) {
366			hj, ok := w.(http.Hijacker)
367			if !ok {
368				http.Error(w, "Could not hijack the connection", http.StatusInternalServerError)
369				return
370			}
371			conn, bufrw, err := hj.Hijack()
372			if err != nil {
373				http.Error(w, err.Error(), http.StatusInternalServerError)
374				return
375			}
376			defer conn.Close()
377			if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil {
378				t.Fatalf("Error bufrw.WriteString() = %v", err)
379			}
380			bufrw.Flush()
381		},
382		quic: true,
383	}
384	st := newServerTester(t, opts)
385	defer st.Close()
386
387	_, err := st.http3(requestParam{
388		name: "TestH3H1ChunkedEndsPrematurely",
389	})
390	if err == nil {
391		t.Fatal("st.http3() should fail")
392	}
393}
394