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  }