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