github.com/tumi8/quic-go@v0.37.4-tum/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/tumi8/quic-go" 20 "github.com/tumi8/quic-go/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 string 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 = strconv.Itoa(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("https://localhost:" + port + "/hello") 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("requests to different servers with the same udpconn", func() { 132 resp, err := client.Get("https://localhost:" + port + "/remoteAddr") 133 Expect(err).ToNot(HaveOccurred()) 134 Expect(resp.StatusCode).To(Equal(200)) 135 addr1 := resp.Header.Get("X-RemoteAddr") 136 Expect(addr1).ToNot(Equal("")) 137 resp, err = client.Get("https://127.0.0.1:" + port + "/remoteAddr") 138 Expect(err).ToNot(HaveOccurred()) 139 Expect(resp.StatusCode).To(Equal(200)) 140 addr2 := resp.Header.Get("X-RemoteAddr") 141 Expect(addr2).ToNot(Equal("")) 142 Expect(addr1).To(Equal(addr2)) 143 }) 144 145 It("downloads concurrently", func() { 146 group, ctx := errgroup.WithContext(context.Background()) 147 for i := 0; i < 2; i++ { 148 group.Go(func() error { 149 req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://localhost:"+port+"/hello", nil) 150 Expect(err).ToNot(HaveOccurred()) 151 resp, err := client.Do(req) 152 Expect(err).ToNot(HaveOccurred()) 153 Expect(resp.StatusCode).To(Equal(200)) 154 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second)) 155 Expect(err).ToNot(HaveOccurred()) 156 Expect(string(body)).To(Equal("Hello, World!\n")) 157 158 return nil 159 }) 160 } 161 162 err := group.Wait() 163 Expect(err).ToNot(HaveOccurred()) 164 }) 165 166 It("sets and gets request headers", func() { 167 handlerCalled := make(chan struct{}) 168 mux.HandleFunc("/headers/request", func(w http.ResponseWriter, r *http.Request) { 169 defer GinkgoRecover() 170 Expect(r.Header.Get("foo")).To(Equal("bar")) 171 Expect(r.Header.Get("lorem")).To(Equal("ipsum")) 172 close(handlerCalled) 173 }) 174 175 req, err := http.NewRequest(http.MethodGet, "https://localhost:"+port+"/headers/request", nil) 176 Expect(err).ToNot(HaveOccurred()) 177 req.Header.Set("foo", "bar") 178 req.Header.Set("lorem", "ipsum") 179 resp, err := client.Do(req) 180 Expect(err).ToNot(HaveOccurred()) 181 Expect(resp.StatusCode).To(Equal(200)) 182 Eventually(handlerCalled).Should(BeClosed()) 183 }) 184 185 It("sets and gets response headers", func() { 186 mux.HandleFunc("/headers/response", func(w http.ResponseWriter, r *http.Request) { 187 defer GinkgoRecover() 188 w.Header().Set("foo", "bar") 189 w.Header().Set("lorem", "ipsum") 190 }) 191 192 resp, err := client.Get("https://localhost:" + port + "/headers/response") 193 Expect(err).ToNot(HaveOccurred()) 194 Expect(resp.StatusCode).To(Equal(200)) 195 Expect(resp.Header.Get("foo")).To(Equal("bar")) 196 Expect(resp.Header.Get("lorem")).To(Equal("ipsum")) 197 }) 198 199 It("downloads a small file", func() { 200 resp, err := client.Get("https://localhost:" + port + "/prdata") 201 Expect(err).ToNot(HaveOccurred()) 202 Expect(resp.StatusCode).To(Equal(200)) 203 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second)) 204 Expect(err).ToNot(HaveOccurred()) 205 Expect(body).To(Equal(PRData)) 206 }) 207 208 It("downloads a large file", func() { 209 resp, err := client.Get("https://localhost:" + port + "/prdatalong") 210 Expect(err).ToNot(HaveOccurred()) 211 Expect(resp.StatusCode).To(Equal(200)) 212 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 20*time.Second)) 213 Expect(err).ToNot(HaveOccurred()) 214 Expect(body).To(Equal(PRDataLong)) 215 }) 216 217 It("downloads many hellos", func() { 218 const num = 150 219 220 for i := 0; i < num; i++ { 221 resp, err := client.Get("https://localhost:" + port + "/hello") 222 Expect(err).ToNot(HaveOccurred()) 223 Expect(resp.StatusCode).To(Equal(200)) 224 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second)) 225 Expect(err).ToNot(HaveOccurred()) 226 Expect(string(body)).To(Equal("Hello, World!\n")) 227 } 228 }) 229 230 It("downloads many files, if the response is not read", func() { 231 const num = 150 232 233 for i := 0; i < num; i++ { 234 resp, err := client.Get("https://localhost:" + port + "/prdata") 235 Expect(err).ToNot(HaveOccurred()) 236 Expect(resp.StatusCode).To(Equal(200)) 237 Expect(resp.Body.Close()).To(Succeed()) 238 } 239 }) 240 241 It("posts a small message", func() { 242 resp, err := client.Post( 243 "https://localhost:"+port+"/echo", 244 "text/plain", 245 bytes.NewReader([]byte("Hello, world!")), 246 ) 247 Expect(err).ToNot(HaveOccurred()) 248 Expect(resp.StatusCode).To(Equal(200)) 249 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second)) 250 Expect(err).ToNot(HaveOccurred()) 251 Expect(body).To(Equal([]byte("Hello, world!"))) 252 }) 253 254 It("uploads a file", func() { 255 resp, err := client.Post( 256 "https://localhost:"+port+"/echo", 257 "text/plain", 258 bytes.NewReader(PRData), 259 ) 260 Expect(err).ToNot(HaveOccurred()) 261 Expect(resp.StatusCode).To(Equal(200)) 262 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 5*time.Second)) 263 Expect(err).ToNot(HaveOccurred()) 264 Expect(body).To(Equal(PRData)) 265 }) 266 267 It("uses gzip compression", func() { 268 mux.HandleFunc("/gzipped/hello", func(w http.ResponseWriter, r *http.Request) { 269 defer GinkgoRecover() 270 Expect(r.Header.Get("Accept-Encoding")).To(Equal("gzip")) 271 w.Header().Set("Content-Encoding", "gzip") 272 w.Header().Set("foo", "bar") 273 274 gw := gzip.NewWriter(w) 275 defer gw.Close() 276 gw.Write([]byte("Hello, World!\n")) 277 }) 278 279 client.Transport.(*http3.RoundTripper).DisableCompression = false 280 resp, err := client.Get("https://localhost:" + port + "/gzipped/hello") 281 Expect(err).ToNot(HaveOccurred()) 282 Expect(resp.StatusCode).To(Equal(200)) 283 Expect(resp.Uncompressed).To(BeTrue()) 284 285 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second)) 286 Expect(err).ToNot(HaveOccurred()) 287 Expect(string(body)).To(Equal("Hello, World!\n")) 288 }) 289 290 It("cancels requests", func() { 291 handlerCalled := make(chan struct{}) 292 mux.HandleFunc("/cancel", func(w http.ResponseWriter, r *http.Request) { 293 defer GinkgoRecover() 294 defer close(handlerCalled) 295 for { 296 if _, err := w.Write([]byte("foobar")); err != nil { 297 Expect(r.Context().Done()).To(BeClosed()) 298 var strErr *quic.StreamError 299 Expect(errors.As(err, &strErr)).To(BeTrue()) 300 Expect(strErr.ErrorCode).To(Equal(quic.StreamErrorCode(0x10c))) 301 return 302 } 303 } 304 }) 305 306 req, err := http.NewRequest(http.MethodGet, "https://localhost:"+port+"/cancel", nil) 307 Expect(err).ToNot(HaveOccurred()) 308 ctx, cancel := context.WithCancel(context.Background()) 309 req = req.WithContext(ctx) 310 resp, err := client.Do(req) 311 Expect(err).ToNot(HaveOccurred()) 312 Expect(resp.StatusCode).To(Equal(200)) 313 cancel() 314 Eventually(handlerCalled).Should(BeClosed()) 315 _, err = resp.Body.Read([]byte{0}) 316 Expect(err).To(HaveOccurred()) 317 }) 318 319 It("allows streamed HTTP requests", func() { 320 done := make(chan struct{}) 321 mux.HandleFunc("/echoline", func(w http.ResponseWriter, r *http.Request) { 322 defer GinkgoRecover() 323 defer close(done) 324 w.WriteHeader(200) 325 w.(http.Flusher).Flush() 326 reader := bufio.NewReader(r.Body) 327 for { 328 msg, err := reader.ReadString('\n') 329 if err != nil { 330 return 331 } 332 _, err = w.Write([]byte(msg)) 333 Expect(err).ToNot(HaveOccurred()) 334 w.(http.Flusher).Flush() 335 } 336 }) 337 338 r, w := io.Pipe() 339 req, err := http.NewRequest("PUT", "https://localhost:"+port+"/echoline", r) 340 Expect(err).ToNot(HaveOccurred()) 341 rsp, err := client.Do(req) 342 Expect(err).ToNot(HaveOccurred()) 343 Expect(rsp.StatusCode).To(Equal(200)) 344 345 reader := bufio.NewReader(rsp.Body) 346 for i := 0; i < 5; i++ { 347 msg := fmt.Sprintf("Hello world, %d!\n", i) 348 fmt.Fprint(w, msg) 349 msgRcvd, err := reader.ReadString('\n') 350 Expect(err).ToNot(HaveOccurred()) 351 Expect(msgRcvd).To(Equal(msg)) 352 } 353 Expect(req.Body.Close()).To(Succeed()) 354 Eventually(done).Should(BeClosed()) 355 }) 356 357 It("allows taking over the stream", func() { 358 mux.HandleFunc("/httpstreamer", func(w http.ResponseWriter, r *http.Request) { 359 defer GinkgoRecover() 360 w.WriteHeader(200) 361 w.(http.Flusher).Flush() 362 363 str := r.Body.(http3.HTTPStreamer).HTTPStream() 364 str.Write([]byte("foobar")) 365 366 // Do this in a Go routine, so that the handler returns early. 367 // This way, we can also check that the HTTP/3 doesn't close the stream. 368 go func() { 369 defer GinkgoRecover() 370 _, err := io.Copy(str, str) 371 Expect(err).ToNot(HaveOccurred()) 372 Expect(str.Close()).To(Succeed()) 373 }() 374 }) 375 376 req, err := http.NewRequest(http.MethodGet, "https://localhost:"+port+"/httpstreamer", nil) 377 Expect(err).ToNot(HaveOccurred()) 378 rsp, err := client.Transport.(*http3.RoundTripper).RoundTripOpt(req, http3.RoundTripOpt{DontCloseRequestStream: true}) 379 Expect(err).ToNot(HaveOccurred()) 380 Expect(rsp.StatusCode).To(Equal(200)) 381 382 str := rsp.Body.(http3.HTTPStreamer).HTTPStream() 383 b := make([]byte, 6) 384 _, err = io.ReadFull(str, b) 385 Expect(err).ToNot(HaveOccurred()) 386 Expect(b).To(Equal([]byte("foobar"))) 387 388 data := GeneratePRData(8 * 1024) 389 _, err = str.Write(data) 390 Expect(err).ToNot(HaveOccurred()) 391 Expect(str.Close()).To(Succeed()) 392 repl, err := io.ReadAll(str) 393 Expect(err).ToNot(HaveOccurred()) 394 Expect(repl).To(Equal(data)) 395 }) 396 397 It("serves other QUIC connections", func() { 398 tlsConf := getTLSConfig() 399 tlsConf.NextProtos = []string{http3.NextProtoH3} 400 ln, err := quic.ListenAddr("localhost:0", tlsConf, nil) 401 Expect(err).ToNot(HaveOccurred()) 402 defer ln.Close() 403 done := make(chan struct{}) 404 go func() { 405 defer GinkgoRecover() 406 defer close(done) 407 conn, err := ln.Accept(context.Background()) 408 Expect(err).ToNot(HaveOccurred()) 409 Expect(server.ServeQUICConn(conn)).To(Succeed()) 410 }() 411 412 resp, err := client.Get(fmt.Sprintf("https://localhost:%d/hello", ln.Addr().(*net.UDPAddr).Port)) 413 Expect(err).ToNot(HaveOccurred()) 414 Expect(resp.StatusCode).To(Equal(http.StatusOK)) 415 client.Transport.(io.Closer).Close() 416 Eventually(done).Should(BeClosed()) 417 }) 418 419 if go120 { 420 It("supports read deadlines", func() { 421 mux.HandleFunc("/read-deadline", func(w http.ResponseWriter, r *http.Request) { 422 defer GinkgoRecover() 423 err := setReadDeadline(w, time.Now().Add(deadlineDelay)) 424 Expect(err).ToNot(HaveOccurred()) 425 426 body, err := io.ReadAll(r.Body) 427 Expect(err).To(MatchError(os.ErrDeadlineExceeded)) 428 Expect(body).To(ContainSubstring("aa")) 429 430 w.Write([]byte("ok")) 431 }) 432 433 expectedEnd := time.Now().Add(deadlineDelay) 434 resp, err := client.Post("https://localhost:"+port+"/read-deadline", "text/plain", neverEnding('a')) 435 Expect(err).ToNot(HaveOccurred()) 436 Expect(resp.StatusCode).To(Equal(200)) 437 438 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay)) 439 Expect(err).ToNot(HaveOccurred()) 440 Expect(time.Now().After(expectedEnd)).To(BeTrue()) 441 Expect(string(body)).To(Equal("ok")) 442 }) 443 444 It("supports write deadlines", func() { 445 mux.HandleFunc("/write-deadline", func(w http.ResponseWriter, r *http.Request) { 446 defer GinkgoRecover() 447 err := setWriteDeadline(w, time.Now().Add(deadlineDelay)) 448 Expect(err).ToNot(HaveOccurred()) 449 450 _, err = io.Copy(w, neverEnding('a')) 451 Expect(err).To(MatchError(os.ErrDeadlineExceeded)) 452 }) 453 454 expectedEnd := time.Now().Add(deadlineDelay) 455 456 resp, err := client.Get("https://localhost:" + port + "/write-deadline") 457 Expect(err).ToNot(HaveOccurred()) 458 Expect(resp.StatusCode).To(Equal(200)) 459 460 body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 2*deadlineDelay)) 461 Expect(err).ToNot(HaveOccurred()) 462 Expect(time.Now().After(expectedEnd)).To(BeTrue()) 463 Expect(string(body)).To(ContainSubstring("aa")) 464 }) 465 } 466 })