github.com/apernet/quic-go@v0.43.1-0.20240515053213-5e9e635fd9f0/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/apernet/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 frame, err := parseNextFrame(str, nil) 38 Expect(err).ToNot(HaveOccurred()) 39 Expect(frame).To(BeAssignableToTypeOf(&headersFrame{})) 40 headersFrame := frame.(*headersFrame) 41 data := make([]byte, headersFrame.Length) 42 _, err = io.ReadFull(str, data) 43 Expect(err).ToNot(HaveOccurred()) 44 hfs, err := decoder.DecodeFull(data) 45 Expect(err).ToNot(HaveOccurred()) 46 for _, p := range hfs { 47 fields[p.Name] = append(fields[p.Name], p.Value) 48 } 49 return fields 50 } 51 52 getData := func(str io.Reader) []byte { 53 frame, err := parseNextFrame(str, nil) 54 Expect(err).ToNot(HaveOccurred()) 55 Expect(frame).To(BeAssignableToTypeOf(&dataFrame{})) 56 df := frame.(*dataFrame) 57 data := make([]byte, df.Length) 58 _, err = io.ReadFull(str, data) 59 Expect(err).ToNot(HaveOccurred()) 60 return data 61 } 62 63 It("writes status", func() { 64 rw.WriteHeader(http.StatusTeapot) 65 fields := decodeHeader(strBuf) 66 Expect(fields).To(HaveLen(2)) 67 Expect(fields).To(HaveKeyWithValue(":status", []string{"418"})) 68 Expect(fields).To(HaveKey("date")) 69 }) 70 71 It("writes headers", func() { 72 rw.Header().Add("content-length", "42") 73 rw.WriteHeader(http.StatusTeapot) 74 fields := decodeHeader(strBuf) 75 Expect(fields).To(HaveKeyWithValue("content-length", []string{"42"})) 76 }) 77 78 It("writes multiple headers with the same name", func() { 79 const cookie1 = "test1=1; Max-Age=7200; path=/" 80 const cookie2 = "test2=2; Max-Age=7200; path=/" 81 rw.Header().Add("set-cookie", cookie1) 82 rw.Header().Add("set-cookie", cookie2) 83 rw.WriteHeader(http.StatusTeapot) 84 fields := decodeHeader(strBuf) 85 Expect(fields).To(HaveKey("set-cookie")) 86 cookies := fields["set-cookie"] 87 Expect(cookies).To(ContainElement(cookie1)) 88 Expect(cookies).To(ContainElement(cookie2)) 89 }) 90 91 It("writes data", func() { 92 n, err := rw.Write([]byte("foobar")) 93 Expect(n).To(Equal(6)) 94 Expect(err).ToNot(HaveOccurred()) 95 // Should have written 200 on the header stream 96 fields := decodeHeader(strBuf) 97 Expect(fields).To(HaveKeyWithValue(":status", []string{"200"})) 98 // And foobar on the data stream 99 Expect(getData(strBuf)).To(Equal([]byte("foobar"))) 100 }) 101 102 It("writes data after WriteHeader is called", func() { 103 rw.WriteHeader(http.StatusTeapot) 104 n, err := rw.Write([]byte("foobar")) 105 Expect(n).To(Equal(6)) 106 Expect(err).ToNot(HaveOccurred()) 107 // Should have written 418 on the header stream 108 fields := decodeHeader(strBuf) 109 Expect(fields).To(HaveKeyWithValue(":status", []string{"418"})) 110 // And foobar on the data stream 111 Expect(getData(strBuf)).To(Equal([]byte("foobar"))) 112 }) 113 114 It("does not WriteHeader() twice", func() { 115 rw.WriteHeader(http.StatusOK) 116 rw.WriteHeader(http.StatusInternalServerError) 117 fields := decodeHeader(strBuf) 118 Expect(fields).To(HaveLen(2)) 119 Expect(fields).To(HaveKeyWithValue(":status", []string{"200"})) 120 Expect(fields).To(HaveKey("date")) 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(4)) 141 Expect(fields).To(HaveKeyWithValue(":status", []string{"200"})) 142 Expect(fields).To(HaveKey("date")) 143 Expect(fields).To(HaveKey("content-type")) 144 Expect(fields).To(HaveKeyWithValue("link", []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script"})) 145 146 Expect(getData(strBuf)).To(Equal([]byte("foobar"))) 147 }) 148 149 It("doesn't allow writes if the status code doesn't allow a body", func() { 150 rw.WriteHeader(304) 151 n, err := rw.Write([]byte("foobar")) 152 Expect(n).To(BeZero()) 153 Expect(err).To(MatchError(http.ErrBodyNotAllowed)) 154 }) 155 156 It("first call to Write sniffs if Content-Type is not set", func() { 157 n, err := rw.Write([]byte("<html></html>")) 158 Expect(n).To(Equal(13)) 159 Expect(err).ToNot(HaveOccurred()) 160 161 fields := decodeHeader(strBuf) 162 Expect(fields).To(HaveKeyWithValue("content-type", []string{"text/html; charset=utf-8"})) 163 }) 164 165 It(`is compatible with "net/http".ResponseController`, func() { 166 Expect(rw.SetReadDeadline(time.Now().Add(1 * time.Second))).To(BeNil()) 167 Expect(rw.SetWriteDeadline(time.Now().Add(1 * time.Second))).To(BeNil()) 168 }) 169 170 It(`checks Content-Length header`, func() { 171 rw.Header().Set("Content-Length", "6") 172 n, err := rw.Write([]byte("foobar")) 173 Expect(n).To(Equal(6)) 174 Expect(err).To(BeNil()) 175 176 n, err = rw.Write([]byte("foobar")) 177 Expect(n).To(Equal(0)) 178 Expect(err).To(Equal(http.ErrContentLength)) 179 }) 180 181 It(`panics when writing invalid status`, func() { 182 Expect(func() { rw.WriteHeader(99) }).To(Panic()) 183 Expect(func() { rw.WriteHeader(1000) }).To(Panic()) 184 }) 185 })