github.com/danielpfeifer02/quic-go-prio-packs@v0.41.0-28/integrationtests/self/http_test.go (about) 1 package self_test 2 3 import ( 4 "bufio" 5 "bytes" 6 "compress/gzip" 7 "context" 8 "errors" 9 "fmt" 10 "io" 11 "net" 12 "net/http" 13 "os" 14 "strconv" 15 "time" 16 17 "golang.org/x/sync/errgroup" 18 19 "github.com/danielpfeifer02/quic-go-prio-packs" 20 "github.com/danielpfeifer02/quic-go-prio-packs/http3" 21 22 . "github.com/onsi/ginkgo/v2" 23 . "github.com/onsi/gomega" 24 "github.com/onsi/gomega/gbytes" 25 ) 26 27 type neverEnding byte 28 29 func (b neverEnding) Read(p []byte) (n int, err error) { 30 for i := range p { 31 p[i] = byte(b) 32 } 33 return len(p), nil 34 } 35 36 const deadlineDelay = 250 * time.Millisecond 37 38 var _ = Describe("HTTP tests", func() { 39 var ( 40 mux *http.ServeMux 41 client *http.Client 42 rt *http3.RoundTripper 43 server *http3.Server 44 stoppedServing chan struct{} 45 port int 46 ) 47 48 BeforeEach(func() { 49 mux = http.NewServeMux() 50 mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { 51 defer GinkgoRecover() 52 io.WriteString(w, "Hello, World!\n") // don't check the error here. Stream may be reset. 53 }) 54 55 mux.HandleFunc("/prdata", func(w http.ResponseWriter, r *http.Request) { 56 defer GinkgoRecover() 57 sl := r.URL.Query().Get("len") 58 if sl != "" { 59 var err error 60 l, err := strconv.Atoi(sl) 61 Expect(err).NotTo(HaveOccurred()) 62 w.Write(GeneratePRData(l)) // don't check the error here. Stream may be reset. 63 } else { 64 w.Write(PRData) // don't check the error here. Stream may be reset. 65 } 66 }) 67 68 mux.HandleFunc("/prdatalong", func(w http.ResponseWriter, r *http.Request) { 69 defer GinkgoRecover() 70 w.Write(PRDataLong) // don't check the error here. Stream may be reset. 71 }) 72 73 mux.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) { 74 defer GinkgoRecover() 75 body, err := io.ReadAll(r.Body) 76 Expect(err).NotTo(HaveOccurred()) 77 w.Write(body) // don't check the error here. Stream may be reset. 78 }) 79 80 mux.HandleFunc("/remoteAddr", func(w http.ResponseWriter, r *http.Request) { 81 defer GinkgoRecover() 82 w.Header().Set("X-RemoteAddr", r.RemoteAddr) 83 w.WriteHeader(http.StatusOK) 84 }) 85 86 server = &http3.Server{ 87 Handler: mux, 88 TLSConfig: getTLSConfig(), 89 QuicConfig: getQuicConfig(nil), 90 } 91 92 addr, err := net.ResolveUDPAddr("udp", "0.0.0.0:0") 93 Expect(err).NotTo(HaveOccurred()) 94 conn, err := net.ListenUDP("udp", addr) 95 Expect(err).NotTo(HaveOccurred()) 96 port = conn.LocalAddr().(*net.UDPAddr).Port 97 98 stoppedServing = make(chan struct{}) 99 100 go func() { 101 defer GinkgoRecover() 102 server.Serve(conn) 103 close(stoppedServing) 104 }() 105 }) 106 107 AfterEach(func() { 108 Expect(rt.Close()).NotTo(HaveOccurred()) 109 Expect(server.Close()).NotTo(HaveOccurred()) 110 Eventually(stoppedServing).Should(BeClosed()) 111 }) 112 113 BeforeEach(func() { 114 rt = &http3.RoundTripper{ 115 TLSClientConfig: getTLSClientConfigWithoutServerName(), 116 DisableCompression: true, 117 QuicConfig: getQuicConfig(&quic.Config{MaxIdleTimeout: 10 * time.Second}), 118 } 119 client = &http.Client{Transport: rt} 120 }) 121 122 It("downloads a hello", func() { 123 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/hello", port)) 124 Expect(err).ToNot(HaveOccurred()) 125 Expect(resp.StatusCode).To(Equal(200)) 126 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second)) 127 Expect(err).ToNot(HaveOccurred()) 128 Expect(string(body)).To(Equal("Hello, World!\n")) 129 }) 130 131 It("sets content-length for small response", func() { 132 mux.HandleFunc("/small", func(w http.ResponseWriter, r *http.Request) { 133 defer GinkgoRecover() 134 w.Write([]byte("foobar")) 135 }) 136 137 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/small", port)) 138 Expect(err).ToNot(HaveOccurred()) 139 Expect(resp.StatusCode).To(Equal(200)) 140 Expect(resp.Header.Get("Content-Length")).To(Equal(strconv.Itoa(len("foobar")))) 141 }) 142 143 It("detects stream errors when server panics when writing response", func() { 144 respChan := make(chan struct{}) 145 mux.HandleFunc("/writing_and_panicking", func(w http.ResponseWriter, r *http.Request) { 146 // no recover here as it will interfere with the handler 147 w.Write([]byte("foobar")) 148 w.(http.Flusher).Flush() 149 // wait for the client to receive the response 150 <-respChan 151 panic(http.ErrAbortHandler) 152 }) 153 154 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/writing_and_panicking", port)) 155 close(respChan) 156 Expect(err).ToNot(HaveOccurred()) 157 body, err := io.ReadAll(resp.Body) 158 Expect(err).To(HaveOccurred()) 159 // the body will be a prefix of what's written 160 Expect(bytes.HasPrefix([]byte("foobar"), body)).To(BeTrue()) 161 }) 162 163 It("requests to different servers with the same udpconn", func() { 164 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/remoteAddr", port)) 165 Expect(err).ToNot(HaveOccurred()) 166 Expect(resp.StatusCode).To(Equal(200)) 167 addr1 := resp.Header.Get("X-RemoteAddr") 168 Expect(addr1).ToNot(Equal("")) 169 resp, err = client.Get(fmt.Sprintf("https://127.0.0.1:%d/remoteAddr", port)) 170 Expect(err).ToNot(HaveOccurred()) 171 Expect(resp.StatusCode).To(Equal(200)) 172 addr2 := resp.Header.Get("X-RemoteAddr") 173 Expect(addr2).ToNot(Equal("")) 174 Expect(addr1).To(Equal(addr2)) 175 }) 176 177 It("downloads concurrently", func() { 178 group, ctx := errgroup.WithContext(context.Background()) 179 for i := 0; i < 2; i++ { 180 group.Go(func() error { 181 req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://localhost:%d/hello", port), nil) 182 Expect(err).ToNot(HaveOccurred()) 183 resp, err := client.Do(req) 184 Expect(err).ToNot(HaveOccurred()) 185 Expect(resp.StatusCode).To(Equal(200)) 186 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second)) 187 Expect(err).ToNot(HaveOccurred()) 188 Expect(string(body)).To(Equal("Hello, World!\n")) 189 190 return nil 191 }) 192 } 193 194 err := group.Wait() 195 Expect(err).ToNot(HaveOccurred()) 196 }) 197 198 It("sets and gets request headers", func() { 199 handlerCalled := make(chan struct{}) 200 mux.HandleFunc("/headers/request", func(w http.ResponseWriter, r *http.Request) { 201 defer GinkgoRecover() 202 Expect(r.Header.Get("foo")).To(Equal("bar")) 203 Expect(r.Header.Get("lorem")).To(Equal("ipsum")) 204 close(handlerCalled) 205 }) 206 207 req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/headers/request", port), nil) 208 Expect(err).ToNot(HaveOccurred()) 209 req.Header.Set("foo", "bar") 210 req.Header.Set("lorem", "ipsum") 211 resp, err := client.Do(req) 212 Expect(err).ToNot(HaveOccurred()) 213 Expect(resp.StatusCode).To(Equal(200)) 214 Eventually(handlerCalled).Should(BeClosed()) 215 }) 216 217 It("sets and gets response headers", func() { 218 mux.HandleFunc("/headers/response", func(w http.ResponseWriter, r *http.Request) { 219 defer GinkgoRecover() 220 w.Header().Set("foo", "bar") 221 w.Header().Set("lorem", "ipsum") 222 }) 223 224 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/headers/response", port)) 225 Expect(err).ToNot(HaveOccurred()) 226 Expect(resp.StatusCode).To(Equal(200)) 227 Expect(resp.Header.Get("foo")).To(Equal("bar")) 228 Expect(resp.Header.Get("lorem")).To(Equal("ipsum")) 229 }) 230 231 It("downloads a small file", func() { 232 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/prdata", port)) 233 Expect(err).ToNot(HaveOccurred()) 234 Expect(resp.StatusCode).To(Equal(200)) 235 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second)) 236 Expect(err).ToNot(HaveOccurred()) 237 Expect(body).To(Equal(PRData)) 238 }) 239 240 It("downloads a large file", func() { 241 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/prdatalong", port)) 242 Expect(err).ToNot(HaveOccurred()) 243 Expect(resp.StatusCode).To(Equal(200)) 244 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 20*time.Second)) 245 Expect(err).ToNot(HaveOccurred()) 246 Expect(body).To(Equal(PRDataLong)) 247 }) 248 249 It("downloads many hellos", func() { 250 const num = 150 251 252 for i := 0; i < num; i++ { 253 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/hello", port)) 254 Expect(err).ToNot(HaveOccurred()) 255 Expect(resp.StatusCode).To(Equal(200)) 256 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second)) 257 Expect(err).ToNot(HaveOccurred()) 258 Expect(string(body)).To(Equal("Hello, World!\n")) 259 } 260 }) 261 262 It("downloads many files, if the response is not read", func() { 263 const num = 150 264 265 for i := 0; i < num; i++ { 266 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/prdata", port)) 267 Expect(err).ToNot(HaveOccurred()) 268 Expect(resp.StatusCode).To(Equal(200)) 269 Expect(resp.Body.Close()).To(Succeed()) 270 } 271 }) 272 273 It("posts a small message", func() { 274 resp, err := client.Post( 275 fmt.Sprintf("https://localhost:%d/echo", port), 276 "text/plain", 277 bytes.NewReader([]byte("Hello, world!")), 278 ) 279 Expect(err).ToNot(HaveOccurred()) 280 Expect(resp.StatusCode).To(Equal(200)) 281 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second)) 282 Expect(err).ToNot(HaveOccurred()) 283 Expect(body).To(Equal([]byte("Hello, world!"))) 284 }) 285 286 It("uploads a file", func() { 287 resp, err := client.Post( 288 fmt.Sprintf("https://localhost:%d/echo", port), 289 "text/plain", 290 bytes.NewReader(PRData), 291 ) 292 Expect(err).ToNot(HaveOccurred()) 293 Expect(resp.StatusCode).To(Equal(200)) 294 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second)) 295 Expect(err).ToNot(HaveOccurred()) 296 Expect(body).To(Equal(PRData)) 297 }) 298 299 It("uses gzip compression", func() { 300 mux.HandleFunc("/gzipped/hello", func(w http.ResponseWriter, r *http.Request) { 301 defer GinkgoRecover() 302 Expect(r.Header.Get("Accept-Encoding")).To(Equal("gzip")) 303 w.Header().Set("Content-Encoding", "gzip") 304 w.Header().Set("foo", "bar") 305 306 gw := gzip.NewWriter(w) 307 defer gw.Close() 308 gw.Write([]byte("Hello, World!\n")) 309 }) 310 311 client.Transport.(*http3.RoundTripper).DisableCompression = false 312 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/gzipped/hello", port)) 313 Expect(err).ToNot(HaveOccurred()) 314 Expect(resp.StatusCode).To(Equal(200)) 315 Expect(resp.Uncompressed).To(BeTrue()) 316 317 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second)) 318 Expect(err).ToNot(HaveOccurred()) 319 Expect(string(body)).To(Equal("Hello, World!\n")) 320 }) 321 322 It("handles context cancellations", func() { 323 mux.HandleFunc("/cancel", func(w http.ResponseWriter, r *http.Request) { 324 <-r.Context().Done() 325 }) 326 327 ctx, cancel := context.WithCancel(context.Background()) 328 req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://localhost:%d/cancel", port), nil) 329 Expect(err).ToNot(HaveOccurred()) 330 time.AfterFunc(50*time.Millisecond, cancel) 331 332 _, err = client.Do(req) 333 Expect(err).To(HaveOccurred()) 334 Expect(err).To(MatchError(context.Canceled)) 335 }) 336 337 It("cancels requests", func() { 338 handlerCalled := make(chan struct{}) 339 mux.HandleFunc("/cancel", func(w http.ResponseWriter, r *http.Request) { 340 defer GinkgoRecover() 341 defer close(handlerCalled) 342 for { 343 if _, err := w.Write([]byte("foobar")); err != nil { 344 Expect(r.Context().Done()).To(BeClosed()) 345 var http3Err *http3.Error 346 Expect(errors.As(err, &http3Err)).To(BeTrue()) 347 Expect(http3Err.ErrorCode).To(Equal(http3.ErrCode(0x10c))) 348 Expect(http3Err.Error()).To(Equal("H3_REQUEST_CANCELLED")) 349 return 350 } 351 } 352 }) 353 354 req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/cancel", port), nil) 355 Expect(err).ToNot(HaveOccurred()) 356 ctx, cancel := context.WithCancel(context.Background()) 357 req = req.WithContext(ctx) 358 resp, err := client.Do(req) 359 Expect(err).ToNot(HaveOccurred()) 360 Expect(resp.StatusCode).To(Equal(200)) 361 cancel() 362 Eventually(handlerCalled).Should(BeClosed()) 363 _, err = resp.Body.Read([]byte{0}) 364 var http3Err *http3.Error 365 Expect(errors.As(err, &http3Err)).To(BeTrue()) 366 Expect(http3Err.ErrorCode).To(Equal(http3.ErrCode(0x10c))) 367 Expect(http3Err.Error()).To(Equal("H3_REQUEST_CANCELLED (local)")) 368 }) 369 370 It("allows streamed HTTP requests", func() { 371 done := make(chan struct{}) 372 mux.HandleFunc("/echoline", func(w http.ResponseWriter, r *http.Request) { 373 defer GinkgoRecover() 374 defer close(done) 375 w.WriteHeader(200) 376 w.(http.Flusher).Flush() 377 reader := bufio.NewReader(r.Body) 378 for { 379 msg, err := reader.ReadString('\n') 380 if err != nil { 381 return 382 } 383 _, err = w.Write([]byte(msg)) 384 Expect(err).ToNot(HaveOccurred()) 385 w.(http.Flusher).Flush() 386 } 387 }) 388 389 r, w := io.Pipe() 390 req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("https://localhost:%d/echoline", port), r) 391 Expect(err).ToNot(HaveOccurred()) 392 rsp, err := client.Do(req) 393 Expect(err).ToNot(HaveOccurred()) 394 Expect(rsp.StatusCode).To(Equal(200)) 395 396 reader := bufio.NewReader(rsp.Body) 397 for i := 0; i < 5; i++ { 398 msg := fmt.Sprintf("Hello world, %d!\n", i) 399 fmt.Fprint(w, msg) 400 msgRcvd, err := reader.ReadString('\n') 401 Expect(err).ToNot(HaveOccurred()) 402 Expect(msgRcvd).To(Equal(msg)) 403 } 404 Expect(req.Body.Close()).To(Succeed()) 405 Eventually(done).Should(BeClosed()) 406 }) 407 408 It("allows taking over the stream", func() { 409 mux.HandleFunc("/httpstreamer", func(w http.ResponseWriter, r *http.Request) { 410 defer GinkgoRecover() 411 w.WriteHeader(200) 412 w.(http.Flusher).Flush() 413 414 str := r.Body.(http3.HTTPStreamer).HTTPStream() 415 str.Write([]byte("foobar")) 416 417 // Do this in a Go routine, so that the handler returns early. 418 // This way, we can also check that the HTTP/3 doesn't close the stream. 419 go func() { 420 defer GinkgoRecover() 421 _, err := io.Copy(str, str) 422 Expect(err).ToNot(HaveOccurred()) 423 Expect(str.Close()).To(Succeed()) 424 }() 425 }) 426 427 req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/httpstreamer", port), nil) 428 Expect(err).ToNot(HaveOccurred()) 429 rsp, err := client.Transport.(*http3.RoundTripper).RoundTripOpt(req, http3.RoundTripOpt{DontCloseRequestStream: true}) 430 Expect(err).ToNot(HaveOccurred()) 431 Expect(rsp.StatusCode).To(Equal(200)) 432 433 str := rsp.Body.(http3.HTTPStreamer).HTTPStream() 434 b := make([]byte, 6) 435 _, err = io.ReadFull(str, b) 436 Expect(err).ToNot(HaveOccurred()) 437 Expect(b).To(Equal([]byte("foobar"))) 438 439 data := GeneratePRData(8 * 1024) 440 _, err = str.Write(data) 441 Expect(err).ToNot(HaveOccurred()) 442 Expect(str.Close()).To(Succeed()) 443 repl, err := io.ReadAll(str) 444 Expect(err).ToNot(HaveOccurred()) 445 Expect(repl).To(Equal(data)) 446 }) 447 448 It("serves other QUIC connections", func() { 449 tlsConf := getTLSConfig() 450 tlsConf.NextProtos = []string{http3.NextProtoH3} 451 ln, err := quic.ListenAddr("localhost:0", tlsConf, nil) 452 Expect(err).ToNot(HaveOccurred()) 453 defer ln.Close() 454 done := make(chan struct{}) 455 go func() { 456 defer GinkgoRecover() 457 defer close(done) 458 conn, err := ln.Accept(context.Background()) 459 Expect(err).ToNot(HaveOccurred()) 460 Expect(server.ServeQUICConn(conn)).To(Succeed()) 461 }() 462 463 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/hello", ln.Addr().(*net.UDPAddr).Port)) 464 Expect(err).ToNot(HaveOccurred()) 465 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 466 client.Transport.(io.Closer).Close() 467 Eventually(done).Should(BeClosed()) 468 }) 469 470 It("supports read deadlines", func() { 471 mux.HandleFunc("/read-deadline", func(w http.ResponseWriter, r *http.Request) { 472 defer GinkgoRecover() 473 rc := http.NewResponseController(w) 474 Expect(rc.SetReadDeadline(time.Now().Add(deadlineDelay))).To(Succeed()) 475 476 body, err := io.ReadAll(r.Body) 477 Expect(err).To(MatchError(os.ErrDeadlineExceeded)) 478 Expect(body).To(ContainSubstring("aa")) 479 480 w.Write([]byte("ok")) 481 }) 482 483 expectedEnd := time.Now().Add(deadlineDelay) 484 resp, err := client.Post( 485 fmt.Sprintf("https://localhost:%d/read-deadline", port), 486 "text/plain", 487 neverEnding('a'), 488 ) 489 Expect(err).ToNot(HaveOccurred()) 490 Expect(resp.StatusCode).To(Equal(200)) 491 492 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay)) 493 Expect(err).ToNot(HaveOccurred()) 494 Expect(time.Now().After(expectedEnd)).To(BeTrue()) 495 Expect(string(body)).To(Equal("ok")) 496 }) 497 498 It("supports write deadlines", func() { 499 mux.HandleFunc("/write-deadline", func(w http.ResponseWriter, r *http.Request) { 500 defer GinkgoRecover() 501 rc := http.NewResponseController(w) 502 Expect(rc.SetWriteDeadline(time.Now().Add(deadlineDelay))).To(Succeed()) 503 504 _, err := io.Copy(w, neverEnding('a')) 505 Expect(err).To(MatchError(os.ErrDeadlineExceeded)) 506 }) 507 508 expectedEnd := time.Now().Add(deadlineDelay) 509 510 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/write-deadline", port)) 511 Expect(err).ToNot(HaveOccurred()) 512 Expect(resp.StatusCode).To(Equal(200)) 513 514 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay)) 515 Expect(err).ToNot(HaveOccurred()) 516 Expect(time.Now().After(expectedEnd)).To(BeTrue()) 517 Expect(string(body)).To(ContainSubstring("aa")) 518 }) 519 520 It("sets remote address", func() { 521 mux.HandleFunc("/remote-addr", func(w http.ResponseWriter, r *http.Request) { 522 defer GinkgoRecover() 523 _, ok := r.Context().Value(http3.RemoteAddrContextKey).(net.Addr) 524 Expect(ok).To(BeTrue()) 525 }) 526 527 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/remote-addr", port)) 528 Expect(err).ToNot(HaveOccurred()) 529 Expect(resp.StatusCode).To(Equal(200)) 530 }) 531 532 It("sets conn context", func() { 533 type ctxKey int 534 server.ConnContext = func(ctx context.Context, c quic.Connection) context.Context { 535 serv, ok := ctx.Value(http3.ServerContextKey).(*http3.Server) 536 Expect(ok).To(BeTrue()) 537 Expect(serv).To(Equal(server)) 538 539 ctx = context.WithValue(ctx, ctxKey(0), "Hello") 540 ctx = context.WithValue(ctx, ctxKey(1), c) 541 return ctx 542 } 543 mux.HandleFunc("/conn-context", func(w http.ResponseWriter, r *http.Request) { 544 defer GinkgoRecover() 545 v, ok := r.Context().Value(ctxKey(0)).(string) 546 Expect(ok).To(BeTrue()) 547 Expect(v).To(Equal("Hello")) 548 549 c, ok := r.Context().Value(ctxKey(1)).(quic.Connection) 550 Expect(ok).To(BeTrue()) 551 Expect(c).ToNot(BeNil()) 552 553 serv, ok := r.Context().Value(http3.ServerContextKey).(*http3.Server) 554 Expect(ok).To(BeTrue()) 555 Expect(serv).To(Equal(server)) 556 }) 557 558 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/conn-context", port)) 559 Expect(err).ToNot(HaveOccurred()) 560 Expect(resp.StatusCode).To(Equal(200)) 561 }) 562 })