github.com/apernet/quic-go@v0.43.1-0.20240515053213-5e9e635fd9f0/http3/request_writer_test.go (about)

     1  package http3
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"net/http"
     7  
     8  	mockquic "github.com/apernet/quic-go/internal/mocks/quic"
     9  
    10  	"github.com/quic-go/qpack"
    11  	"go.uber.org/mock/gomock"
    12  
    13  	. "github.com/onsi/ginkgo/v2"
    14  	. "github.com/onsi/gomega"
    15  )
    16  
    17  var _ = Describe("Request Writer", func() {
    18  	var (
    19  		rw     *requestWriter
    20  		str    *mockquic.MockStream
    21  		strBuf *bytes.Buffer
    22  	)
    23  
    24  	decode := func(str io.Reader) map[string]string {
    25  		frame, err := parseNextFrame(str, nil)
    26  		ExpectWithOffset(1, err).ToNot(HaveOccurred())
    27  		ExpectWithOffset(1, frame).To(BeAssignableToTypeOf(&headersFrame{}))
    28  		headersFrame := frame.(*headersFrame)
    29  		data := make([]byte, headersFrame.Length)
    30  		_, err = io.ReadFull(str, data)
    31  		ExpectWithOffset(1, err).ToNot(HaveOccurred())
    32  		decoder := qpack.NewDecoder(nil)
    33  		hfs, err := decoder.DecodeFull(data)
    34  		ExpectWithOffset(1, err).ToNot(HaveOccurred())
    35  		values := make(map[string]string)
    36  		for _, hf := range hfs {
    37  			values[hf.Name] = hf.Value
    38  		}
    39  		return values
    40  	}
    41  
    42  	BeforeEach(func() {
    43  		rw = newRequestWriter()
    44  		strBuf = &bytes.Buffer{}
    45  		str = mockquic.NewMockStream(mockCtrl)
    46  		str.EXPECT().Write(gomock.Any()).DoAndReturn(strBuf.Write).AnyTimes()
    47  	})
    48  
    49  	It("writes a GET request", func() {
    50  		req, err := http.NewRequest(http.MethodGet, "https://quic.clemente.io/index.html?foo=bar", nil)
    51  		Expect(err).ToNot(HaveOccurred())
    52  		Expect(rw.WriteRequestHeader(str, req, false)).To(Succeed())
    53  		headerFields := decode(strBuf)
    54  		Expect(headerFields).To(HaveKeyWithValue(":authority", "quic.clemente.io"))
    55  		Expect(headerFields).To(HaveKeyWithValue(":method", "GET"))
    56  		Expect(headerFields).To(HaveKeyWithValue(":path", "/index.html?foo=bar"))
    57  		Expect(headerFields).To(HaveKeyWithValue(":scheme", "https"))
    58  		Expect(headerFields).ToNot(HaveKey("accept-encoding"))
    59  	})
    60  
    61  	It("rejects invalid host headers", func() {
    62  		req, err := http.NewRequest(http.MethodGet, "https://quic.clemente.io/index.html?foo=bar", nil)
    63  		Expect(err).ToNot(HaveOccurred())
    64  		req.Host = "foo@bar" // @ is invalid
    65  		Expect(rw.WriteRequestHeader(str, req, false)).To(MatchError("http3: invalid Host header"))
    66  	})
    67  
    68  	It("sends cookies", func() {
    69  		req, err := http.NewRequest(http.MethodGet, "https://quic.clemente.io/", nil)
    70  		Expect(err).ToNot(HaveOccurred())
    71  		cookie1 := &http.Cookie{
    72  			Name:  "Cookie #1",
    73  			Value: "Value #1",
    74  		}
    75  		cookie2 := &http.Cookie{
    76  			Name:  "Cookie #2",
    77  			Value: "Value #2",
    78  		}
    79  		req.AddCookie(cookie1)
    80  		req.AddCookie(cookie2)
    81  		Expect(rw.WriteRequestHeader(str, req, false)).To(Succeed())
    82  		headerFields := decode(strBuf)
    83  		Expect(headerFields).To(HaveKeyWithValue("cookie", `Cookie #1="Value #1"; Cookie #2="Value #2"`))
    84  	})
    85  
    86  	It("adds the header for gzip support", func() {
    87  		req, err := http.NewRequest(http.MethodGet, "https://quic.clemente.io/", nil)
    88  		Expect(err).ToNot(HaveOccurred())
    89  		Expect(rw.WriteRequestHeader(str, req, true)).To(Succeed())
    90  		headerFields := decode(strBuf)
    91  		Expect(headerFields).To(HaveKeyWithValue("accept-encoding", "gzip"))
    92  	})
    93  
    94  	It("writes a CONNECT request", func() {
    95  		req, err := http.NewRequest(http.MethodConnect, "https://quic.clemente.io/", nil)
    96  		Expect(err).ToNot(HaveOccurred())
    97  		Expect(rw.WriteRequestHeader(str, req, false)).To(Succeed())
    98  		headerFields := decode(strBuf)
    99  		Expect(headerFields).To(HaveKeyWithValue(":method", "CONNECT"))
   100  		Expect(headerFields).To(HaveKeyWithValue(":authority", "quic.clemente.io"))
   101  		Expect(headerFields).ToNot(HaveKey(":path"))
   102  		Expect(headerFields).ToNot(HaveKey(":scheme"))
   103  		Expect(headerFields).ToNot(HaveKey(":protocol"))
   104  	})
   105  
   106  	It("writes an Extended CONNECT request", func() {
   107  		req, err := http.NewRequest(http.MethodConnect, "https://quic.clemente.io/foobar", nil)
   108  		Expect(err).ToNot(HaveOccurred())
   109  		req.Proto = "webtransport"
   110  		Expect(rw.WriteRequestHeader(str, req, false)).To(Succeed())
   111  		headerFields := decode(strBuf)
   112  		Expect(headerFields).To(HaveKeyWithValue(":authority", "quic.clemente.io"))
   113  		Expect(headerFields).To(HaveKeyWithValue(":method", "CONNECT"))
   114  		Expect(headerFields).To(HaveKeyWithValue(":path", "/foobar"))
   115  		Expect(headerFields).To(HaveKeyWithValue(":scheme", "https"))
   116  		Expect(headerFields).To(HaveKeyWithValue(":protocol", "webtransport"))
   117  	})
   118  })