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