github.com/metacubex/quic-go@v0.44.1-0.20240520163451-20b689a59136/http3/http_stream.go (about) 1 package http3 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "net/http" 9 "strconv" 10 11 "github.com/metacubex/quic-go" 12 "github.com/metacubex/quic-go/internal/protocol" 13 14 "github.com/quic-go/qpack" 15 ) 16 17 // A Stream is an HTTP/3 request stream. 18 // When writing to and reading from the stream, data is framed in HTTP/3 DATA frames. 19 type Stream interface { 20 quic.Stream 21 22 SendDatagram([]byte) error 23 ReceiveDatagram(context.Context) ([]byte, error) 24 } 25 26 // A RequestStream is an HTTP/3 request stream. 27 // When writing to and reading from the stream, data is framed in HTTP/3 DATA frames. 28 type RequestStream interface { 29 Stream 30 31 // SendRequestHeader sends the HTTP request. 32 // It is invalid to call it more than once. 33 // It is invalid to call it after Write has been called. 34 SendRequestHeader(req *http.Request) error 35 36 // ReadResponse reads the HTTP response from the stream. 37 // It is invalid to call it more than once. 38 // It doesn't set Response.Request and Response.TLS. 39 // It is invalid to call it after Read has been called. 40 ReadResponse() (*http.Response, error) 41 } 42 43 type stream struct { 44 quic.Stream 45 conn *connection 46 47 buf []byte // used as a temporary buffer when writing the HTTP/3 frame headers 48 49 bytesRemainingInFrame uint64 50 51 datagrams *datagrammer 52 } 53 54 var _ Stream = &stream{} 55 56 func newStream(str quic.Stream, conn *connection, datagrams *datagrammer) *stream { 57 return &stream{ 58 Stream: str, 59 conn: conn, 60 buf: make([]byte, 16), 61 datagrams: datagrams, 62 } 63 } 64 65 func (s *stream) Read(b []byte) (int, error) { 66 fp := &frameParser{ 67 r: s.Stream, 68 conn: s.conn, 69 } 70 if s.bytesRemainingInFrame == 0 { 71 parseLoop: 72 for { 73 frame, err := fp.ParseNext() 74 if err != nil { 75 return 0, err 76 } 77 switch f := frame.(type) { 78 case *headersFrame: 79 // skip HEADERS frames 80 continue 81 case *dataFrame: 82 s.bytesRemainingInFrame = f.Length 83 break parseLoop 84 default: 85 s.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "") 86 // parseNextFrame skips over unknown frame types 87 // Therefore, this condition is only entered when we parsed another known frame type. 88 return 0, fmt.Errorf("peer sent an unexpected frame: %T", f) 89 } 90 } 91 } 92 93 var n int 94 var err error 95 if s.bytesRemainingInFrame < uint64(len(b)) { 96 n, err = s.Stream.Read(b[:s.bytesRemainingInFrame]) 97 } else { 98 n, err = s.Stream.Read(b) 99 } 100 s.bytesRemainingInFrame -= uint64(n) 101 return n, err 102 } 103 104 func (s *stream) hasMoreData() bool { 105 return s.bytesRemainingInFrame > 0 106 } 107 108 func (s *stream) Write(b []byte) (int, error) { 109 s.buf = s.buf[:0] 110 s.buf = (&dataFrame{Length: uint64(len(b))}).Append(s.buf) 111 if _, err := s.Stream.Write(s.buf); err != nil { 112 return 0, err 113 } 114 return s.Stream.Write(b) 115 } 116 117 func (s *stream) writeUnframed(b []byte) (int, error) { 118 return s.Stream.Write(b) 119 } 120 121 func (s *stream) StreamID() protocol.StreamID { 122 return s.Stream.StreamID() 123 } 124 125 // The stream conforms to the quic.Stream interface, but instead of writing to and reading directly 126 // from the QUIC stream, it writes to and reads from the HTTP stream. 127 type requestStream struct { 128 *stream 129 130 responseBody io.ReadCloser // set by ReadResponse 131 132 decoder *qpack.Decoder 133 requestWriter *requestWriter 134 maxHeaderBytes uint64 135 reqDone chan<- struct{} 136 disableCompression bool 137 138 sentRequest bool 139 requestedGzip bool 140 isConnect bool 141 } 142 143 var _ RequestStream = &requestStream{} 144 145 func newRequestStream( 146 str *stream, 147 requestWriter *requestWriter, 148 reqDone chan<- struct{}, 149 decoder *qpack.Decoder, 150 disableCompression bool, 151 maxHeaderBytes uint64, 152 ) *requestStream { 153 return &requestStream{ 154 stream: str, 155 requestWriter: requestWriter, 156 reqDone: reqDone, 157 decoder: decoder, 158 disableCompression: disableCompression, 159 maxHeaderBytes: maxHeaderBytes, 160 } 161 } 162 163 func (s *requestStream) Read(b []byte) (int, error) { 164 if s.responseBody == nil { 165 return 0, errors.New("http3: invalid use of RequestStream.Read: need to call ReadResponse first") 166 } 167 return s.responseBody.Read(b) 168 } 169 170 func (s *requestStream) SendRequestHeader(req *http.Request) error { 171 if s.sentRequest { 172 return errors.New("http3: invalid duplicate use of SendRequestHeader") 173 } 174 if !s.disableCompression && req.Method != http.MethodHead && 175 req.Header.Get("Accept-Encoding") == "" && req.Header.Get("Range") == "" { 176 s.requestedGzip = true 177 } 178 s.isConnect = req.Method == http.MethodConnect 179 s.sentRequest = true 180 return s.requestWriter.WriteRequestHeader(s.Stream, req, s.requestedGzip) 181 } 182 183 func (s *requestStream) ReadResponse() (*http.Response, error) { 184 fp := &frameParser{ 185 r: s.Stream, 186 conn: s.conn, 187 } 188 frame, err := fp.ParseNext() 189 if err != nil { 190 s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeFrameError)) 191 s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeFrameError)) 192 return nil, fmt.Errorf("http3: parsing frame failed: %w", err) 193 } 194 hf, ok := frame.(*headersFrame) 195 if !ok { 196 s.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "expected first frame to be a HEADERS frame") 197 return nil, errors.New("http3: expected first frame to be a HEADERS frame") 198 } 199 if hf.Length > s.maxHeaderBytes { 200 s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeFrameError)) 201 s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeFrameError)) 202 return nil, fmt.Errorf("http3: HEADERS frame too large: %d bytes (max: %d)", hf.Length, s.maxHeaderBytes) 203 } 204 headerBlock := make([]byte, hf.Length) 205 if _, err := io.ReadFull(s.Stream, headerBlock); err != nil { 206 s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeRequestIncomplete)) 207 s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeRequestIncomplete)) 208 return nil, fmt.Errorf("http3: failed to read response headers: %w", err) 209 } 210 hfs, err := s.decoder.DecodeFull(headerBlock) 211 if err != nil { 212 // TODO: use the right error code 213 s.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeGeneralProtocolError), "") 214 return nil, fmt.Errorf("http3: failed to decode response headers: %w", err) 215 } 216 217 res, err := responseFromHeaders(hfs) 218 if err != nil { 219 s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeMessageError)) 220 s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeMessageError)) 221 return nil, fmt.Errorf("http3: invalid response: %w", err) 222 } 223 224 // Check that the server doesn't send more data in DATA frames than indicated by the Content-Length header (if set). 225 // See section 4.1.2 of RFC 9114. 226 contentLength := int64(-1) 227 if _, ok := res.Header["Content-Length"]; ok && res.ContentLength >= 0 { 228 contentLength = res.ContentLength 229 } 230 respBody := newResponseBody(s.stream, contentLength, s.reqDone) 231 232 // Rules for when to set Content-Length are defined in https://tools.ietf.org/html/rfc7230#section-3.3.2. 233 _, hasTransferEncoding := res.Header["Transfer-Encoding"] 234 isInformational := res.StatusCode >= 100 && res.StatusCode < 200 235 isNoContent := res.StatusCode == http.StatusNoContent 236 isSuccessfulConnect := s.isConnect && res.StatusCode >= 200 && res.StatusCode < 300 237 if !hasTransferEncoding && !isInformational && !isNoContent && !isSuccessfulConnect { 238 res.ContentLength = -1 239 if clens, ok := res.Header["Content-Length"]; ok && len(clens) == 1 { 240 if clen64, err := strconv.ParseInt(clens[0], 10, 64); err == nil { 241 res.ContentLength = clen64 242 } 243 } 244 } 245 246 if s.requestedGzip && res.Header.Get("Content-Encoding") == "gzip" { 247 res.Header.Del("Content-Encoding") 248 res.Header.Del("Content-Length") 249 res.ContentLength = -1 250 s.responseBody = newGzipReader(respBody) 251 res.Uncompressed = true 252 } else { 253 s.responseBody = respBody 254 } 255 res.Body = s.responseBody 256 return res, nil 257 } 258 259 func (s *stream) SendDatagram(b []byte) error { 260 // TODO: reject if datagrams are not negotiated (yet) 261 return s.datagrams.Send(b) 262 } 263 264 func (s *stream) ReceiveDatagram(ctx context.Context) ([]byte, error) { 265 // TODO: reject if datagrams are not negotiated (yet) 266 return s.datagrams.Receive(ctx) 267 }