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  })