github.com/mikelsr/quic-go@v0.36.1-0.20230701132136-1d9415b66898/http3/response_writer_test.go (about) 1 package http3 2 3 import ( 4 "bytes" 5 "io" 6 "net/http" 7 "time" 8 9 mockquic "github.com/mikelsr/quic-go/internal/mocks/quic" 10 "github.com/mikelsr/quic-go/internal/utils" 11 12 "github.com/golang/mock/gomock" 13 "github.com/quic-go/qpack" 14 15 . "github.com/onsi/ginkgo/v2" 16 . "github.com/onsi/gomega" 17 ) 18 19 var _ = Describe("Response Writer", func() { 20 var ( 21 rw *responseWriter 22 strBuf *bytes.Buffer 23 ) 24 25 BeforeEach(func() { 26 strBuf = &bytes.Buffer{} 27 str := mockquic.NewMockStream(mockCtrl) 28 str.EXPECT().Write(gomock.Any()).DoAndReturn(strBuf.Write).AnyTimes() 29 str.EXPECT().SetReadDeadline(gomock.Any()).Return(nil).AnyTimes() 30 str.EXPECT().SetWriteDeadline(gomock.Any()).Return(nil).AnyTimes() 31 rw = newResponseWriter(str, nil, utils.DefaultLogger) 32 }) 33 34 decodeHeader := func(str io.Reader) map[string][]string { 35 rw.Flush() 36 fields := make(map[string][]string) 37 decoder := qpack.NewDecoder(nil) 38 39 frame, err := parseNextFrame(str, nil) 40 Expect(err).ToNot(HaveOccurred()) 41 Expect(frame).To(BeAssignableToTypeOf(&headersFrame{})) 42 headersFrame := frame.(*headersFrame) 43 data := make([]byte, headersFrame.Length) 44 _, err = io.ReadFull(str, data) 45 Expect(err).ToNot(HaveOccurred()) 46 hfs, err := decoder.DecodeFull(data) 47 Expect(err).ToNot(HaveOccurred()) 48 for _, p := range hfs { 49 fields[p.Name] = append(fields[p.Name], p.Value) 50 } 51 return fields 52 } 53 54 getData := func(str io.Reader) []byte { 55 frame, err := parseNextFrame(str, nil) 56 Expect(err).ToNot(HaveOccurred()) 57 Expect(frame).To(BeAssignableToTypeOf(&dataFrame{})) 58 df := frame.(*dataFrame) 59 data := make([]byte, df.Length) 60 _, err = io.ReadFull(str, data) 61 Expect(err).ToNot(HaveOccurred()) 62 return data 63 } 64 65 It("writes status", func() { 66 rw.WriteHeader(http.StatusTeapot) 67 fields := decodeHeader(strBuf) 68 Expect(fields).To(HaveLen(1)) 69 Expect(fields).To(HaveKeyWithValue(":status", []string{"418"})) 70 }) 71 72 It("writes headers", func() { 73 rw.Header().Add("content-length", "42") 74 rw.WriteHeader(http.StatusTeapot) 75 fields := decodeHeader(strBuf) 76 Expect(fields).To(HaveKeyWithValue("content-length", []string{"42"})) 77 }) 78 79 It("writes multiple headers with the same name", func() { 80 const cookie1 = "test1=1; Max-Age=7200; path=/" 81 const cookie2 = "test2=2; Max-Age=7200; path=/" 82 rw.Header().Add("set-cookie", cookie1) 83 rw.Header().Add("set-cookie", cookie2) 84 rw.WriteHeader(http.StatusTeapot) 85 fields := decodeHeader(strBuf) 86 Expect(fields).To(HaveKey("set-cookie")) 87 cookies := fields["set-cookie"] 88 Expect(cookies).To(ContainElement(cookie1)) 89 Expect(cookies).To(ContainElement(cookie2)) 90 }) 91 92 It("writes data", func() { 93 n, err := rw.Write([]byte("foobar")) 94 Expect(n).To(Equal(6)) 95 Expect(err).ToNot(HaveOccurred()) 96 // Should have written 200 on the header stream 97 fields := decodeHeader(strBuf) 98 Expect(fields).To(HaveKeyWithValue(":status", []string{"200"})) 99 // And foobar on the data stream 100 Expect(getData(strBuf)).To(Equal([]byte("foobar"))) 101 }) 102 103 It("writes data after WriteHeader is called", func() { 104 rw.WriteHeader(http.StatusTeapot) 105 n, err := rw.Write([]byte("foobar")) 106 Expect(n).To(Equal(6)) 107 Expect(err).ToNot(HaveOccurred()) 108 // Should have written 418 on the header stream 109 fields := decodeHeader(strBuf) 110 Expect(fields).To(HaveKeyWithValue(":status", []string{"418"})) 111 // And foobar on the data stream 112 Expect(getData(strBuf)).To(Equal([]byte("foobar"))) 113 }) 114 115 It("does not WriteHeader() twice", func() { 116 rw.WriteHeader(http.StatusOK) 117 rw.WriteHeader(http.StatusInternalServerError) 118 fields := decodeHeader(strBuf) 119 Expect(fields).To(HaveLen(1)) 120 Expect(fields).To(HaveKeyWithValue(":status", []string{"200"})) 121 }) 122 123 It("allows calling WriteHeader() several times when using the 103 status code", func() { 124 rw.Header().Add("Link", "</style.css>; rel=preload; as=style") 125 rw.Header().Add("Link", "</script.js>; rel=preload; as=script") 126 rw.WriteHeader(http.StatusEarlyHints) 127 128 n, err := rw.Write([]byte("foobar")) 129 Expect(n).To(Equal(6)) 130 Expect(err).ToNot(HaveOccurred()) 131 132 // Early Hints must have been received 133 fields := decodeHeader(strBuf) 134 Expect(fields).To(HaveLen(2)) 135 Expect(fields).To(HaveKeyWithValue(":status", []string{"103"})) 136 Expect(fields).To(HaveKeyWithValue("link", []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script"})) 137 138 // According to the spec, headers sent in the informational response must also be included in the final response 139 fields = decodeHeader(strBuf) 140 Expect(fields).To(HaveLen(2)) 141 Expect(fields).To(HaveKeyWithValue(":status", []string{"200"})) 142 Expect(fields).To(HaveKeyWithValue("link", []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script"})) 143 144 Expect(getData(strBuf)).To(Equal([]byte("foobar"))) 145 }) 146 147 It("doesn't allow writes if the status code doesn't allow a body", func() { 148 rw.WriteHeader(304) 149 n, err := rw.Write([]byte("foobar")) 150 Expect(n).To(BeZero()) 151 Expect(err).To(MatchError(http.ErrBodyNotAllowed)) 152 }) 153 154 It("first call to Write sniffs if Content-Type is not set", func() { 155 n, err := rw.Write([]byte("<html></html>")) 156 Expect(n).To(Equal(13)) 157 Expect(err).ToNot(HaveOccurred()) 158 159 fields := decodeHeader(strBuf) 160 Expect(fields).To(HaveKeyWithValue("content-type", []string{"text/html; charset=utf-8"})) 161 }) 162 163 It(`is compatible with "net/http".ResponseController`, func() { 164 Expect(rw.SetReadDeadline(time.Now().Add(1 * time.Second))).To(BeNil()) 165 Expect(rw.SetWriteDeadline(time.Now().Add(1 * time.Second))).To(BeNil()) 166 }) 167 })