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