github.com/apernet/quic-go@v0.43.1-0.20240515053213-5e9e635fd9f0/integrationtests/self/http_test.go (about) 1 package self_test 2 3 import ( 4 "bufio" 5 "bytes" 6 "compress/gzip" 7 "context" 8 "crypto/tls" 9 "encoding/binary" 10 "errors" 11 "fmt" 12 "io" 13 "net" 14 "net/http" 15 "net/http/httptrace" 16 "net/textproto" 17 "net/url" 18 "os" 19 "strconv" 20 "sync/atomic" 21 "time" 22 23 "golang.org/x/sync/errgroup" 24 25 "github.com/apernet/quic-go" 26 "github.com/apernet/quic-go/http3" 27 quicproxy "github.com/apernet/quic-go/integrationtests/tools/proxy" 28 29 . "github.com/onsi/ginkgo/v2" 30 . "github.com/onsi/gomega" 31 "github.com/onsi/gomega/gbytes" 32 ) 33 34 type neverEnding byte 35 36 func (b neverEnding) Read(p []byte) (n int, err error) { 37 for i := range p { 38 p[i] = byte(b) 39 } 40 return len(p), nil 41 } 42 43 const deadlineDelay = 250 * time.Millisecond 44 45 var _ = Describe("HTTP tests", func() { 46 var ( 47 mux *http.ServeMux 48 client *http.Client 49 rt *http3.RoundTripper 50 server *http3.Server 51 stoppedServing chan struct{} 52 port int 53 ) 54 55 BeforeEach(func() { 56 mux = http.NewServeMux() 57 mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { 58 defer GinkgoRecover() 59 io.WriteString(w, "Hello, World!\n") // don't check the error here. Stream may be reset. 60 }) 61 62 mux.HandleFunc("/prdata", func(w http.ResponseWriter, r *http.Request) { 63 defer GinkgoRecover() 64 sl := r.URL.Query().Get("len") 65 if sl != "" { 66 var err error 67 l, err := strconv.Atoi(sl) 68 Expect(err).NotTo(HaveOccurred()) 69 w.Write(GeneratePRData(l)) // don't check the error here. Stream may be reset. 70 } else { 71 w.Write(PRData) // don't check the error here. Stream may be reset. 72 } 73 }) 74 75 mux.HandleFunc("/prdatalong", func(w http.ResponseWriter, r *http.Request) { 76 defer GinkgoRecover() 77 w.Write(PRDataLong) // don't check the error here. Stream may be reset. 78 }) 79 80 mux.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) { 81 defer GinkgoRecover() 82 body, err := io.ReadAll(r.Body) 83 Expect(err).NotTo(HaveOccurred()) 84 w.Write(body) // don't check the error here. Stream may be reset. 85 }) 86 87 mux.HandleFunc("/remoteAddr", func(w http.ResponseWriter, r *http.Request) { 88 defer GinkgoRecover() 89 w.Header().Set("X-RemoteAddr", r.RemoteAddr) 90 w.WriteHeader(http.StatusOK) 91 }) 92 93 server = &http3.Server{ 94 Handler: mux, 95 TLSConfig: getTLSConfig(), 96 QUICConfig: getQuicConfig(&quic.Config{Allow0RTT: true, EnableDatagrams: true}), 97 } 98 99 addr, err := net.ResolveUDPAddr("udp", "0.0.0.0:0") 100 Expect(err).NotTo(HaveOccurred()) 101 conn, err := net.ListenUDP("udp", addr) 102 Expect(err).NotTo(HaveOccurred()) 103 port = conn.LocalAddr().(*net.UDPAddr).Port 104 105 stoppedServing = make(chan struct{}) 106 107 go func() { 108 defer GinkgoRecover() 109 server.Serve(conn) 110 close(stoppedServing) 111 }() 112 }) 113 114 AfterEach(func() { 115 Expect(rt.Close()).NotTo(HaveOccurred()) 116 Expect(server.Close()).NotTo(HaveOccurred()) 117 Eventually(stoppedServing).Should(BeClosed()) 118 }) 119 120 BeforeEach(func() { 121 rt = &http3.RoundTripper{ 122 TLSClientConfig: getTLSClientConfigWithoutServerName(), 123 QUICConfig: getQuicConfig(&quic.Config{ 124 MaxIdleTimeout: 10 * time.Second, 125 }), 126 DisableCompression: true, 127 } 128 client = &http.Client{Transport: rt} 129 }) 130 131 It("downloads a hello", func() { 132 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/hello", port)) 133 Expect(err).ToNot(HaveOccurred()) 134 Expect(resp.StatusCode).To(Equal(200)) 135 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second)) 136 Expect(err).ToNot(HaveOccurred()) 137 Expect(string(body)).To(Equal("Hello, World!\n")) 138 }) 139 140 It("sets content-length for small response", func() { 141 mux.HandleFunc("/small", func(w http.ResponseWriter, r *http.Request) { 142 defer GinkgoRecover() 143 w.Write([]byte("foo")) 144 w.Write([]byte("bar")) 145 }) 146 147 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/small", port)) 148 Expect(err).ToNot(HaveOccurred()) 149 Expect(resp.StatusCode).To(Equal(200)) 150 Expect(resp.Header.Get("Content-Length")).To(Equal("6")) 151 }) 152 153 It("detects stream errors when server panics when writing response", func() { 154 respChan := make(chan struct{}) 155 mux.HandleFunc("/writing_and_panicking", func(w http.ResponseWriter, r *http.Request) { 156 // no recover here as it will interfere with the handler 157 w.Write([]byte("foobar")) 158 w.(http.Flusher).Flush() 159 // wait for the client to receive the response 160 <-respChan 161 panic(http.ErrAbortHandler) 162 }) 163 164 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/writing_and_panicking", port)) 165 close(respChan) 166 Expect(err).ToNot(HaveOccurred()) 167 body, err := io.ReadAll(resp.Body) 168 Expect(err).To(HaveOccurred()) 169 // the body will be a prefix of what's written 170 Expect(bytes.HasPrefix([]byte("foobar"), body)).To(BeTrue()) 171 }) 172 173 It("requests to different servers with the same udpconn", func() { 174 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/remoteAddr", port)) 175 Expect(err).ToNot(HaveOccurred()) 176 Expect(resp.StatusCode).To(Equal(200)) 177 addr1 := resp.Header.Get("X-RemoteAddr") 178 Expect(addr1).ToNot(Equal("")) 179 resp, err = client.Get(fmt.Sprintf("https://127.0.0.1:%d/remoteAddr", port)) 180 Expect(err).ToNot(HaveOccurred()) 181 Expect(resp.StatusCode).To(Equal(200)) 182 addr2 := resp.Header.Get("X-RemoteAddr") 183 Expect(addr2).ToNot(Equal("")) 184 Expect(addr1).To(Equal(addr2)) 185 }) 186 187 It("downloads concurrently", func() { 188 group, ctx := errgroup.WithContext(context.Background()) 189 for i := 0; i < 2; i++ { 190 group.Go(func() error { 191 defer GinkgoRecover() 192 req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://localhost:%d/hello", port), nil) 193 Expect(err).ToNot(HaveOccurred()) 194 resp, err := client.Do(req) 195 Expect(err).ToNot(HaveOccurred()) 196 Expect(resp.StatusCode).To(Equal(200)) 197 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second)) 198 Expect(err).ToNot(HaveOccurred()) 199 Expect(string(body)).To(Equal("Hello, World!\n")) 200 201 return nil 202 }) 203 } 204 205 err := group.Wait() 206 Expect(err).ToNot(HaveOccurred()) 207 }) 208 209 It("sets and gets request headers", func() { 210 handlerCalled := make(chan struct{}) 211 mux.HandleFunc("/headers/request", func(w http.ResponseWriter, r *http.Request) { 212 defer GinkgoRecover() 213 Expect(r.Header.Get("foo")).To(Equal("bar")) 214 Expect(r.Header.Get("lorem")).To(Equal("ipsum")) 215 close(handlerCalled) 216 }) 217 218 req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/headers/request", port), nil) 219 Expect(err).ToNot(HaveOccurred()) 220 req.Header.Set("foo", "bar") 221 req.Header.Set("lorem", "ipsum") 222 resp, err := client.Do(req) 223 Expect(err).ToNot(HaveOccurred()) 224 Expect(resp.StatusCode).To(Equal(200)) 225 Eventually(handlerCalled).Should(BeClosed()) 226 }) 227 228 It("sets and gets response headers", func() { 229 mux.HandleFunc("/headers/response", func(w http.ResponseWriter, r *http.Request) { 230 defer GinkgoRecover() 231 w.Header().Set("foo", "bar") 232 w.Header().Set("lorem", "ipsum") 233 }) 234 235 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/headers/response", port)) 236 Expect(err).ToNot(HaveOccurred()) 237 Expect(resp.StatusCode).To(Equal(200)) 238 Expect(resp.Header.Get("foo")).To(Equal("bar")) 239 Expect(resp.Header.Get("lorem")).To(Equal("ipsum")) 240 }) 241 242 It("downloads a small file", func() { 243 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/prdata", port)) 244 Expect(err).ToNot(HaveOccurred()) 245 Expect(resp.StatusCode).To(Equal(200)) 246 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second)) 247 Expect(err).ToNot(HaveOccurred()) 248 Expect(body).To(Equal(PRData)) 249 }) 250 251 It("downloads a large file", func() { 252 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/prdatalong", port)) 253 Expect(err).ToNot(HaveOccurred()) 254 Expect(resp.StatusCode).To(Equal(200)) 255 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 20*time.Second)) 256 Expect(err).ToNot(HaveOccurred()) 257 Expect(body).To(Equal(PRDataLong)) 258 }) 259 260 It("downloads many hellos", func() { 261 const num = 150 262 263 for i := 0; i < num; i++ { 264 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/hello", port)) 265 Expect(err).ToNot(HaveOccurred()) 266 Expect(resp.StatusCode).To(Equal(200)) 267 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second)) 268 Expect(err).ToNot(HaveOccurred()) 269 Expect(string(body)).To(Equal("Hello, World!\n")) 270 } 271 }) 272 273 It("downloads many files, if the response is not read", func() { 274 const num = 150 275 276 for i := 0; i < num; i++ { 277 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/prdata", port)) 278 Expect(err).ToNot(HaveOccurred()) 279 Expect(resp.StatusCode).To(Equal(200)) 280 Expect(resp.Body.Close()).To(Succeed()) 281 } 282 }) 283 284 It("posts a small message", func() { 285 resp, err := client.Post( 286 fmt.Sprintf("https://localhost:%d/echo", port), 287 "text/plain", 288 bytes.NewReader([]byte("Hello, world!")), 289 ) 290 Expect(err).ToNot(HaveOccurred()) 291 Expect(resp.StatusCode).To(Equal(200)) 292 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second)) 293 Expect(err).ToNot(HaveOccurred()) 294 Expect(body).To(Equal([]byte("Hello, world!"))) 295 }) 296 297 It("uploads a file", func() { 298 resp, err := client.Post( 299 fmt.Sprintf("https://localhost:%d/echo", port), 300 "text/plain", 301 bytes.NewReader(PRData), 302 ) 303 Expect(err).ToNot(HaveOccurred()) 304 Expect(resp.StatusCode).To(Equal(200)) 305 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second)) 306 Expect(err).ToNot(HaveOccurred()) 307 Expect(body).To(Equal(PRData)) 308 }) 309 310 It("uses gzip compression", func() { 311 mux.HandleFunc("/gzipped/hello", func(w http.ResponseWriter, r *http.Request) { 312 defer GinkgoRecover() 313 Expect(r.Header.Get("Accept-Encoding")).To(Equal("gzip")) 314 w.Header().Set("Content-Encoding", "gzip") 315 w.Header().Set("foo", "bar") 316 317 gw := gzip.NewWriter(w) 318 defer gw.Close() 319 gw.Write([]byte("Hello, World!\n")) 320 }) 321 322 client.Transport.(*http3.RoundTripper).DisableCompression = false 323 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/gzipped/hello", port)) 324 Expect(err).ToNot(HaveOccurred()) 325 Expect(resp.StatusCode).To(Equal(200)) 326 Expect(resp.Uncompressed).To(BeTrue()) 327 328 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second)) 329 Expect(err).ToNot(HaveOccurred()) 330 Expect(string(body)).To(Equal("Hello, World!\n")) 331 }) 332 333 It("handles context cancellations", func() { 334 mux.HandleFunc("/cancel", func(w http.ResponseWriter, r *http.Request) { 335 <-r.Context().Done() 336 }) 337 338 ctx, cancel := context.WithCancel(context.Background()) 339 req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://localhost:%d/cancel", port), nil) 340 Expect(err).ToNot(HaveOccurred()) 341 time.AfterFunc(50*time.Millisecond, cancel) 342 343 _, err = client.Do(req) 344 Expect(err).To(HaveOccurred()) 345 Expect(err).To(MatchError(context.Canceled)) 346 }) 347 348 It("cancels requests", func() { 349 handlerCalled := make(chan struct{}) 350 mux.HandleFunc("/cancel", func(w http.ResponseWriter, r *http.Request) { 351 defer GinkgoRecover() 352 defer close(handlerCalled) 353 for { 354 if _, err := w.Write([]byte("foobar")); err != nil { 355 Expect(r.Context().Done()).To(BeClosed()) 356 var http3Err *http3.Error 357 Expect(errors.As(err, &http3Err)).To(BeTrue()) 358 Expect(http3Err.ErrorCode).To(Equal(http3.ErrCode(0x10c))) 359 Expect(http3Err.Error()).To(Equal("H3_REQUEST_CANCELLED")) 360 return 361 } 362 } 363 }) 364 365 req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/cancel", port), nil) 366 Expect(err).ToNot(HaveOccurred()) 367 ctx, cancel := context.WithCancel(context.Background()) 368 req = req.WithContext(ctx) 369 resp, err := client.Do(req) 370 Expect(err).ToNot(HaveOccurred()) 371 Expect(resp.StatusCode).To(Equal(200)) 372 cancel() 373 Eventually(handlerCalled).Should(BeClosed()) 374 _, err = resp.Body.Read([]byte{0}) 375 var http3Err *http3.Error 376 Expect(errors.As(err, &http3Err)).To(BeTrue()) 377 Expect(http3Err.ErrorCode).To(Equal(http3.ErrCode(0x10c))) 378 Expect(http3Err.Error()).To(Equal("H3_REQUEST_CANCELLED (local)")) 379 }) 380 381 It("allows streamed HTTP requests", func() { 382 done := make(chan struct{}) 383 mux.HandleFunc("/echoline", func(w http.ResponseWriter, r *http.Request) { 384 defer GinkgoRecover() 385 defer close(done) 386 w.WriteHeader(200) 387 w.(http.Flusher).Flush() 388 reader := bufio.NewReader(r.Body) 389 for { 390 msg, err := reader.ReadString('\n') 391 if err != nil { 392 return 393 } 394 _, err = w.Write([]byte(msg)) 395 Expect(err).ToNot(HaveOccurred()) 396 w.(http.Flusher).Flush() 397 } 398 }) 399 400 r, w := io.Pipe() 401 req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("https://localhost:%d/echoline", port), r) 402 Expect(err).ToNot(HaveOccurred()) 403 rsp, err := client.Do(req) 404 Expect(err).ToNot(HaveOccurred()) 405 Expect(rsp.StatusCode).To(Equal(200)) 406 407 reader := bufio.NewReader(rsp.Body) 408 for i := 0; i < 5; i++ { 409 msg := fmt.Sprintf("Hello world, %d!\n", i) 410 fmt.Fprint(w, msg) 411 msgRcvd, err := reader.ReadString('\n') 412 Expect(err).ToNot(HaveOccurred()) 413 Expect(msgRcvd).To(Equal(msg)) 414 } 415 Expect(req.Body.Close()).To(Succeed()) 416 Eventually(done).Should(BeClosed()) 417 }) 418 419 It("allows taking over the stream", func() { 420 handlerCalled := make(chan struct{}) 421 mux.HandleFunc("/httpstreamer", func(w http.ResponseWriter, r *http.Request) { 422 defer GinkgoRecover() 423 close(handlerCalled) 424 w.WriteHeader(http.StatusOK) 425 426 str := w.(http3.HTTPStreamer).HTTPStream() 427 str.Write([]byte("foobar")) 428 429 // Do this in a Go routine, so that the handler returns early. 430 // This way, we can also check that the HTTP/3 doesn't close the stream. 431 go func() { 432 defer GinkgoRecover() 433 _, err := io.Copy(str, str) 434 Expect(err).ToNot(HaveOccurred()) 435 Expect(str.Close()).To(Succeed()) 436 }() 437 }) 438 439 req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/httpstreamer", port), nil) 440 Expect(err).ToNot(HaveOccurred()) 441 tlsConf := getTLSClientConfigWithoutServerName() 442 tlsConf.NextProtos = []string{http3.NextProtoH3} 443 conn, err := quic.DialAddr( 444 context.Background(), 445 fmt.Sprintf("localhost:%d", port), 446 tlsConf, 447 getQuicConfig(nil), 448 ) 449 Expect(err).ToNot(HaveOccurred()) 450 defer conn.CloseWithError(0, "") 451 rt := http3.SingleDestinationRoundTripper{Connection: conn} 452 str, err := rt.OpenRequestStream(context.Background()) 453 Expect(err).ToNot(HaveOccurred()) 454 Expect(str.SendRequestHeader(req)).To(Succeed()) 455 // make sure the request is received (and not stuck in some buffer, for example) 456 Eventually(handlerCalled).Should(BeClosed()) 457 458 rsp, err := str.ReadResponse() 459 Expect(err).ToNot(HaveOccurred()) 460 Expect(rsp.StatusCode).To(Equal(200)) 461 462 b := make([]byte, 6) 463 _, err = io.ReadFull(str, b) 464 Expect(err).ToNot(HaveOccurred()) 465 Expect(b).To(Equal([]byte("foobar"))) 466 467 data := GeneratePRData(8 * 1024) 468 _, err = str.Write(data) 469 Expect(err).ToNot(HaveOccurred()) 470 Expect(str.Close()).To(Succeed()) 471 repl, err := io.ReadAll(str) 472 Expect(err).ToNot(HaveOccurred()) 473 Expect(repl).To(Equal(data)) 474 }) 475 476 It("serves QUIC connections", func() { 477 tlsConf := getTLSConfig() 478 tlsConf.NextProtos = []string{http3.NextProtoH3} 479 ln, err := quic.ListenAddr("localhost:0", tlsConf, getQuicConfig(nil)) 480 Expect(err).ToNot(HaveOccurred()) 481 defer ln.Close() 482 done := make(chan struct{}) 483 go func() { 484 defer GinkgoRecover() 485 defer close(done) 486 conn, err := ln.Accept(context.Background()) 487 Expect(err).ToNot(HaveOccurred()) 488 server.ServeQUICConn(conn) // returns once the client closes 489 }() 490 491 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/hello", ln.Addr().(*net.UDPAddr).Port)) 492 Expect(err).ToNot(HaveOccurred()) 493 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 494 client.Transport.(io.Closer).Close() 495 Eventually(done).Should(BeClosed()) 496 }) 497 498 It("supports read deadlines", func() { 499 mux.HandleFunc("/read-deadline", func(w http.ResponseWriter, r *http.Request) { 500 defer GinkgoRecover() 501 rc := http.NewResponseController(w) 502 Expect(rc.SetReadDeadline(time.Now().Add(deadlineDelay))).To(Succeed()) 503 504 body, err := io.ReadAll(r.Body) 505 Expect(err).To(MatchError(os.ErrDeadlineExceeded)) 506 Expect(body).To(ContainSubstring("aa")) 507 508 w.Write([]byte("ok")) 509 }) 510 511 expectedEnd := time.Now().Add(deadlineDelay) 512 resp, err := client.Post( 513 fmt.Sprintf("https://localhost:%d/read-deadline", port), 514 "text/plain", 515 neverEnding('a'), 516 ) 517 Expect(err).ToNot(HaveOccurred()) 518 Expect(resp.StatusCode).To(Equal(200)) 519 520 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay)) 521 Expect(err).ToNot(HaveOccurred()) 522 Expect(time.Now().After(expectedEnd)).To(BeTrue()) 523 Expect(string(body)).To(Equal("ok")) 524 }) 525 526 It("supports write deadlines", func() { 527 mux.HandleFunc("/write-deadline", func(w http.ResponseWriter, r *http.Request) { 528 defer GinkgoRecover() 529 rc := http.NewResponseController(w) 530 Expect(rc.SetWriteDeadline(time.Now().Add(deadlineDelay))).To(Succeed()) 531 532 _, err := io.Copy(w, neverEnding('a')) 533 Expect(err).To(MatchError(os.ErrDeadlineExceeded)) 534 }) 535 536 expectedEnd := time.Now().Add(deadlineDelay) 537 538 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/write-deadline", port)) 539 Expect(err).ToNot(HaveOccurred()) 540 Expect(resp.StatusCode).To(Equal(200)) 541 542 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay)) 543 Expect(err).ToNot(HaveOccurred()) 544 Expect(time.Now().After(expectedEnd)).To(BeTrue()) 545 Expect(string(body)).To(ContainSubstring("aa")) 546 }) 547 548 It("sets remote address", func() { 549 mux.HandleFunc("/remote-addr", func(w http.ResponseWriter, r *http.Request) { 550 defer GinkgoRecover() 551 _, ok := r.Context().Value(http3.RemoteAddrContextKey).(net.Addr) 552 Expect(ok).To(BeTrue()) 553 }) 554 555 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/remote-addr", port)) 556 Expect(err).ToNot(HaveOccurred()) 557 Expect(resp.StatusCode).To(Equal(200)) 558 }) 559 560 It("sets conn context", func() { 561 type ctxKey int 562 var tracingID quic.ConnectionTracingID 563 server.ConnContext = func(ctx context.Context, c quic.Connection) context.Context { 564 serv, ok := ctx.Value(http3.ServerContextKey).(*http3.Server) 565 Expect(ok).To(BeTrue()) 566 Expect(serv).To(Equal(server)) 567 568 ctx = context.WithValue(ctx, ctxKey(0), "Hello") 569 ctx = context.WithValue(ctx, ctxKey(1), c) 570 tracingID = c.Context().Value(quic.ConnectionTracingKey).(quic.ConnectionTracingID) 571 return ctx 572 } 573 mux.HandleFunc("/conn-context", func(w http.ResponseWriter, r *http.Request) { 574 defer GinkgoRecover() 575 v, ok := r.Context().Value(ctxKey(0)).(string) 576 Expect(ok).To(BeTrue()) 577 Expect(v).To(Equal("Hello")) 578 579 c, ok := r.Context().Value(ctxKey(1)).(quic.Connection) 580 Expect(ok).To(BeTrue()) 581 Expect(c).ToNot(BeNil()) 582 583 serv, ok := r.Context().Value(http3.ServerContextKey).(*http3.Server) 584 Expect(ok).To(BeTrue()) 585 Expect(serv).To(Equal(server)) 586 587 id, ok := r.Context().Value(quic.ConnectionTracingKey).(quic.ConnectionTracingID) 588 Expect(ok).To(BeTrue()) 589 Expect(id).To(Equal(tracingID)) 590 }) 591 592 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/conn-context", port)) 593 Expect(err).ToNot(HaveOccurred()) 594 Expect(resp.StatusCode).To(Equal(200)) 595 }) 596 597 It("checks the server's settings", func() { 598 tlsConf := tlsClientConfigWithoutServerName.Clone() 599 tlsConf.NextProtos = []string{http3.NextProtoH3} 600 conn, err := quic.DialAddr( 601 context.Background(), 602 fmt.Sprintf("localhost:%d", port), 603 tlsConf, 604 getQuicConfig(nil), 605 ) 606 Expect(err).ToNot(HaveOccurred()) 607 defer conn.CloseWithError(0, "") 608 rt := http3.SingleDestinationRoundTripper{Connection: conn} 609 hconn := rt.Start() 610 Eventually(hconn.ReceivedSettings(), 5*time.Second, 10*time.Millisecond).Should(BeClosed()) 611 settings := hconn.Settings() 612 Expect(settings.EnableExtendedConnect).To(BeTrue()) 613 Expect(settings.EnableDatagrams).To(BeFalse()) 614 Expect(settings.Other).To(BeEmpty()) 615 }) 616 617 It("receives the client's settings", func() { 618 settingsChan := make(chan *http3.Settings, 1) 619 mux.HandleFunc("/settings", func(w http.ResponseWriter, r *http.Request) { 620 defer GinkgoRecover() 621 conn := w.(http3.Hijacker).Connection() 622 Eventually(conn.ReceivedSettings(), 5*time.Second, 10*time.Millisecond).Should(BeClosed()) 623 settingsChan <- conn.Settings() 624 w.WriteHeader(http.StatusOK) 625 }) 626 627 rt = &http3.RoundTripper{ 628 TLSClientConfig: getTLSClientConfigWithoutServerName(), 629 QUICConfig: getQuicConfig(&quic.Config{ 630 MaxIdleTimeout: 10 * time.Second, 631 EnableDatagrams: true, 632 }), 633 EnableDatagrams: true, 634 AdditionalSettings: map[uint64]uint64{1337: 42}, 635 } 636 req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/settings", port), nil) 637 Expect(err).ToNot(HaveOccurred()) 638 639 _, err = rt.RoundTrip(req) 640 Expect(err).ToNot(HaveOccurred()) 641 var settings *http3.Settings 642 Expect(settingsChan).To(Receive(&settings)) 643 Expect(settings).ToNot(BeNil()) 644 Expect(settings.EnableDatagrams).To(BeTrue()) 645 Expect(settings.EnableExtendedConnect).To(BeFalse()) 646 Expect(settings.Other).To(HaveKeyWithValue(uint64(1337), uint64(42))) 647 }) 648 649 It("processes 1xx response", func() { 650 header1 := "</style.css>; rel=preload; as=style" 651 header2 := "</script.js>; rel=preload; as=script" 652 data := "1xx-test-data" 653 mux.HandleFunc("/103-early-data", func(w http.ResponseWriter, r *http.Request) { 654 defer GinkgoRecover() 655 w.Header().Add("Link", header1) 656 w.Header().Add("Link", header2) 657 w.WriteHeader(http.StatusEarlyHints) 658 n, err := w.Write([]byte(data)) 659 Expect(err).NotTo(HaveOccurred()) 660 Expect(n).To(Equal(len(data))) 661 w.WriteHeader(http.StatusOK) 662 }) 663 664 var ( 665 cnt int 666 status int 667 hdr textproto.MIMEHeader 668 ) 669 ctx := httptrace.WithClientTrace(context.Background(), &httptrace.ClientTrace{ 670 Got1xxResponse: func(code int, header textproto.MIMEHeader) error { 671 hdr = header 672 status = code 673 cnt++ 674 return nil 675 }, 676 }) 677 678 req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://localhost:%d/103-early-data", port), nil) 679 Expect(err).ToNot(HaveOccurred()) 680 resp, err := client.Do(req) 681 Expect(err).ToNot(HaveOccurred()) 682 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 683 body, err := io.ReadAll(resp.Body) 684 Expect(err).ToNot(HaveOccurred()) 685 Expect(string(body)).To(Equal(data)) 686 Expect(status).To(Equal(http.StatusEarlyHints)) 687 Expect(hdr).To(HaveKeyWithValue("Link", []string{header1, header2})) 688 Expect(cnt).To(Equal(1)) 689 Expect(resp.Header).To(HaveKeyWithValue("Link", []string{header1, header2})) 690 Expect(resp.Body.Close()).To(Succeed()) 691 }) 692 693 It("processes 1xx terminal response", func() { 694 mux.HandleFunc("/101-switch-protocols", func(w http.ResponseWriter, r *http.Request) { 695 defer GinkgoRecover() 696 w.Header().Add("Connection", "upgrade") 697 w.Header().Add("Upgrade", "proto") 698 w.WriteHeader(http.StatusSwitchingProtocols) 699 }) 700 701 var ( 702 cnt int 703 status int 704 ) 705 ctx := httptrace.WithClientTrace(context.Background(), &httptrace.ClientTrace{ 706 Got1xxResponse: func(code int, header textproto.MIMEHeader) error { 707 status = code 708 cnt++ 709 return nil 710 }, 711 }) 712 713 req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://localhost:%d/101-switch-protocols", port), nil) 714 Expect(err).ToNot(HaveOccurred()) 715 resp, err := client.Do(req) 716 Expect(err).ToNot(HaveOccurred()) 717 Expect(resp.StatusCode).To(Equal(http.StatusSwitchingProtocols)) 718 Expect(resp.Header).To(HaveKeyWithValue("Connection", []string{"upgrade"})) 719 Expect(resp.Header).To(HaveKeyWithValue("Upgrade", []string{"proto"})) 720 Expect(status).To(Equal(0)) 721 Expect(cnt).To(Equal(0)) 722 }) 723 724 Context("HTTP datagrams", func() { 725 It("sends an receives HTTP datagrams", func() { 726 errChan := make(chan error, 1) 727 const num = 5 728 datagramChan := make(chan struct{}, num) 729 mux.HandleFunc("/datagrams", func(w http.ResponseWriter, r *http.Request) { 730 defer GinkgoRecover() 731 Expect(r.Method).To(Equal(http.MethodConnect)) 732 conn := w.(http3.Hijacker).Connection() 733 Eventually(conn.ReceivedSettings()).Should(BeClosed()) 734 Expect(conn.Settings().EnableDatagrams).To(BeTrue()) 735 w.WriteHeader(http.StatusOK) 736 737 str := w.(http3.HTTPStreamer).HTTPStream() 738 go str.Read([]byte{0}) // need to continue reading from stream to observe state transitions 739 740 for { 741 if _, err := str.ReceiveDatagram(context.Background()); err != nil { 742 errChan <- err 743 return 744 } 745 datagramChan <- struct{}{} 746 } 747 }) 748 749 tlsConf := getTLSClientConfigWithoutServerName() 750 tlsConf.NextProtos = []string{http3.NextProtoH3} 751 conn, err := quic.DialAddr( 752 context.Background(), 753 fmt.Sprintf("localhost:%d", port), 754 tlsConf, 755 getQuicConfig(&quic.Config{EnableDatagrams: true}), 756 ) 757 Expect(err).ToNot(HaveOccurred()) 758 defer conn.CloseWithError(0, "") 759 rt := &http3.SingleDestinationRoundTripper{ 760 Connection: conn, 761 EnableDatagrams: true, 762 } 763 str, err := rt.OpenRequestStream(context.Background()) 764 Expect(err).ToNot(HaveOccurred()) 765 u, err := url.Parse(fmt.Sprintf("https://localhost:%d/datagrams", port)) 766 Expect(err).ToNot(HaveOccurred()) 767 req := &http.Request{ 768 Method: http.MethodConnect, 769 Proto: "datagrams", 770 Host: u.Host, 771 URL: u, 772 } 773 Expect(str.SendRequestHeader(req)).To(Succeed()) 774 775 rsp, err := str.ReadResponse() 776 Expect(err).ToNot(HaveOccurred()) 777 Expect(rsp.StatusCode).To(Equal(http.StatusOK)) 778 779 for i := 0; i < num; i++ { 780 b := make([]byte, 8) 781 binary.BigEndian.PutUint64(b, uint64(i)) 782 Expect(str.SendDatagram(bytes.Repeat(b, 100))).To(Succeed()) 783 } 784 var count int 785 loop: 786 for { 787 select { 788 case <-datagramChan: 789 count++ 790 if count >= num*4/5 { 791 break loop 792 } 793 case err := <-errChan: 794 Fail(fmt.Sprintf("receiving datagrams failed: %s", err)) 795 } 796 } 797 str.CancelWrite(42) 798 799 var resetErr error 800 Eventually(errChan).Should(Receive(&resetErr)) 801 Expect(resetErr.(*quic.StreamError).ErrorCode).To(BeEquivalentTo(42)) 802 }) 803 }) 804 805 Context("0-RTT", func() { 806 runCountingProxy := func(serverPort int, rtt time.Duration) (*quicproxy.QuicProxy, *atomic.Uint32) { 807 var num0RTTPackets atomic.Uint32 808 proxy, err := quicproxy.NewQuicProxy("localhost:0", &quicproxy.Opts{ 809 RemoteAddr: fmt.Sprintf("localhost:%d", serverPort), 810 DelayPacket: func(_ quicproxy.Direction, data []byte) time.Duration { 811 if contains0RTTPacket(data) { 812 num0RTTPackets.Add(1) 813 } 814 return rtt / 2 815 }, 816 }) 817 Expect(err).ToNot(HaveOccurred()) 818 return proxy, &num0RTTPackets 819 } 820 821 It("sends 0-RTT GET requests", func() { 822 proxy, num0RTTPackets := runCountingProxy(port, scaleDuration(50*time.Millisecond)) 823 defer proxy.Close() 824 825 tlsConf := getTLSClientConfigWithoutServerName() 826 puts := make(chan string, 10) 827 tlsConf.ClientSessionCache = newClientSessionCache(tls.NewLRUClientSessionCache(10), nil, puts) 828 rt := &http3.RoundTripper{ 829 TLSClientConfig: tlsConf, 830 QUICConfig: getQuicConfig(&quic.Config{ 831 MaxIdleTimeout: 10 * time.Second, 832 }), 833 DisableCompression: true, 834 } 835 defer rt.Close() 836 837 mux.HandleFunc("/0rtt", func(w http.ResponseWriter, r *http.Request) { 838 w.Write([]byte(strconv.FormatBool(!r.TLS.HandshakeComplete))) 839 }) 840 req, err := http.NewRequest(http3.MethodGet0RTT, fmt.Sprintf("https://localhost:%d/0rtt", proxy.LocalPort()), nil) 841 Expect(err).ToNot(HaveOccurred()) 842 rsp, err := rt.RoundTrip(req) 843 Expect(err).ToNot(HaveOccurred()) 844 Expect(rsp.StatusCode).To(BeEquivalentTo(200)) 845 data, err := io.ReadAll(rsp.Body) 846 Expect(err).ToNot(HaveOccurred()) 847 Expect(string(data)).To(Equal("false")) 848 Expect(num0RTTPackets.Load()).To(BeZero()) 849 Eventually(puts).Should(Receive()) 850 851 rt2 := &http3.RoundTripper{ 852 TLSClientConfig: rt.TLSClientConfig, 853 QUICConfig: rt.QUICConfig, 854 DisableCompression: true, 855 } 856 defer rt2.Close() 857 rsp, err = rt2.RoundTrip(req) 858 Expect(err).ToNot(HaveOccurred()) 859 Expect(rsp.StatusCode).To(BeEquivalentTo(200)) 860 data, err = io.ReadAll(rsp.Body) 861 Expect(err).ToNot(HaveOccurred()) 862 Expect(string(data)).To(Equal("true")) 863 Expect(num0RTTPackets.Load()).To(BeNumerically(">", 0)) 864 }) 865 }) 866 })