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