http3: convert RequestStream from an interface to a struct (#5153)

This commit is contained in:
Marten Seemann
2025-05-29 11:08:39 +08:00
committed by GitHub
parent 45e0d5fc7d
commit 2675d0845f
6 changed files with 27 additions and 36 deletions

View File

@@ -127,7 +127,7 @@ func newClientConn(
} }
// OpenRequestStream opens a new request stream on the HTTP/3 connection. // OpenRequestStream opens a new request stream on the HTTP/3 connection.
func (c *ClientConn) OpenRequestStream(ctx context.Context) (RequestStream, error) { func (c *ClientConn) OpenRequestStream(ctx context.Context) (*RequestStream, error) {
return c.openRequestStream(ctx, c.requestWriter, nil, c.disableCompression, c.maxResponseHeaderBytes) return c.openRequestStream(ctx, c.requestWriter, nil, c.disableCompression, c.maxResponseHeaderBytes)
} }
@@ -300,7 +300,7 @@ func (c *ClientConn) sendRequestBody(str Stream, body io.ReadCloser, contentLeng
return err return err
} }
func (c *ClientConn) doRequest(req *http.Request, str *requestStream) (*http.Response, error) { func (c *ClientConn) doRequest(req *http.Request, str *RequestStream) (*http.Response, error) {
trace := httptrace.ContextClientTrace(req.Context()) trace := httptrace.ContextClientTrace(req.Context())
if err := str.SendRequestHeader(req); err != nil { if err := str.SendRequestHeader(req); err != nil {
traceWroteRequest(trace, err) traceWroteRequest(trace, err)

View File

@@ -119,7 +119,7 @@ func (c *connection) openRequestStream(
reqDone chan<- struct{}, reqDone chan<- struct{},
disableCompression bool, disableCompression bool,
maxHeaderBytes uint64, maxHeaderBytes uint64,
) (*requestStream, error) { ) (*RequestStream, error) {
if c.perspective == protocol.PerspectiveClient { if c.perspective == protocol.PerspectiveClient {
c.streamMx.Lock() c.streamMx.Lock()
maxStreamID := c.maxStreamID maxStreamID := c.maxStreamID

View File

@@ -23,23 +23,7 @@ type Stream interface {
ReceiveDatagram(context.Context) ([]byte, error) ReceiveDatagram(context.Context) ([]byte, error)
} }
// A RequestStream is an HTTP/3 request stream.
// When writing to and reading from the stream, data is framed in HTTP/3 DATA frames. // When writing to and reading from the stream, data is framed in HTTP/3 DATA frames.
type RequestStream interface {
Stream
// SendRequestHeader sends the HTTP request.
// It is invalid to call it more than once.
// It is invalid to call it after Write has been called.
SendRequestHeader(req *http.Request) error
// ReadResponse reads the HTTP response from the stream.
// It is invalid to call it more than once.
// It doesn't set Response.Request and Response.TLS.
// It is invalid to call it after Read has been called.
ReadResponse() (*http.Response, error)
}
type stream struct { type stream struct {
quic.Stream quic.Stream
conn *connection conn *connection
@@ -135,9 +119,11 @@ func (s *stream) StreamID() protocol.StreamID {
return s.Stream.StreamID() return s.Stream.StreamID()
} }
// The stream conforms to the quic.Stream interface, but instead of writing to and reading directly // A RequestStream is a low-level abstraction representing an HTTP/3 request stream.
// from the QUIC stream, it writes to and reads from the HTTP stream. // It decouples sending of the HTTP request from reading the HTTP response, allowing
type requestStream struct { // the application to optimistically use the stream (and, for example, send datagrams)
// before receiving the response.
type RequestStream struct {
*stream *stream
responseBody io.ReadCloser // set by ReadResponse responseBody io.ReadCloser // set by ReadResponse
@@ -156,8 +142,6 @@ type requestStream struct {
firstByte bool firstByte bool
} }
var _ RequestStream = &requestStream{}
func newRequestStream( func newRequestStream(
str *stream, str *stream,
requestWriter *requestWriter, requestWriter *requestWriter,
@@ -167,8 +151,8 @@ func newRequestStream(
maxHeaderBytes uint64, maxHeaderBytes uint64,
rsp *http.Response, rsp *http.Response,
trace *httptrace.ClientTrace, trace *httptrace.ClientTrace,
) *requestStream { ) *RequestStream {
return &requestStream{ return &RequestStream{
stream: str, stream: str,
requestWriter: requestWriter, requestWriter: requestWriter,
reqDone: reqDone, reqDone: reqDone,
@@ -180,14 +164,17 @@ func newRequestStream(
} }
} }
func (s *requestStream) Read(b []byte) (int, error) { func (s *RequestStream) Read(b []byte) (int, error) {
if s.responseBody == nil { if s.responseBody == nil {
return 0, errors.New("http3: invalid use of RequestStream.Read: need to call ReadResponse first") return 0, errors.New("http3: invalid use of RequestStream.Read: need to call ReadResponse first")
} }
return s.responseBody.Read(b) return s.responseBody.Read(b)
} }
func (s *requestStream) SendRequestHeader(req *http.Request) error { // SendRequestHeader sends the HTTP request.
// It is invalid to call it more than once.
// It is invalid to call it after Write has been called.
func (s *RequestStream) SendRequestHeader(req *http.Request) error {
if s.sentRequest { if s.sentRequest {
return errors.New("http3: invalid duplicate use of SendRequestHeader") return errors.New("http3: invalid duplicate use of SendRequestHeader")
} }
@@ -200,7 +187,11 @@ func (s *requestStream) SendRequestHeader(req *http.Request) error {
return s.requestWriter.WriteRequestHeader(s.Stream, req, s.requestedGzip) return s.requestWriter.WriteRequestHeader(s.Stream, req, s.requestedGzip)
} }
func (s *requestStream) ReadResponse() (*http.Response, error) { // ReadResponse reads the HTTP response from the stream.
// It is invalid to call it more than once.
// It doesn't set Response.Request and Response.TLS.
// It is invalid to call it after Read has been called.
func (s *RequestStream) ReadResponse() (*http.Response, error) {
fp := &frameParser{ fp := &frameParser{
conn: s.conn, conn: s.conn,
r: &tracingReader{ r: &tracingReader{

View File

@@ -42,10 +42,10 @@ func (m *MockClientConn) EXPECT() *MockClientConnMockRecorder {
} }
// OpenRequestStream mocks base method. // OpenRequestStream mocks base method.
func (m *MockClientConn) OpenRequestStream(arg0 context.Context) (RequestStream, error) { func (m *MockClientConn) OpenRequestStream(arg0 context.Context) (*RequestStream, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OpenRequestStream", arg0) ret := m.ctrl.Call(m, "OpenRequestStream", arg0)
ret0, _ := ret[0].(RequestStream) ret0, _ := ret[0].(*RequestStream)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
@@ -63,19 +63,19 @@ type MockClientConnOpenRequestStreamCall struct {
} }
// Return rewrite *gomock.Call.Return // Return rewrite *gomock.Call.Return
func (c *MockClientConnOpenRequestStreamCall) Return(arg0 RequestStream, arg1 error) *MockClientConnOpenRequestStreamCall { func (c *MockClientConnOpenRequestStreamCall) Return(arg0 *RequestStream, arg1 error) *MockClientConnOpenRequestStreamCall {
c.Call = c.Call.Return(arg0, arg1) c.Call = c.Call.Return(arg0, arg1)
return c return c
} }
// Do rewrite *gomock.Call.Do // Do rewrite *gomock.Call.Do
func (c *MockClientConnOpenRequestStreamCall) Do(f func(context.Context) (RequestStream, error)) *MockClientConnOpenRequestStreamCall { func (c *MockClientConnOpenRequestStreamCall) Do(f func(context.Context) (*RequestStream, error)) *MockClientConnOpenRequestStreamCall {
c.Call = c.Call.Do(f) c.Call = c.Call.Do(f)
return c return c
} }
// DoAndReturn rewrite *gomock.Call.DoAndReturn // DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockClientConnOpenRequestStreamCall) DoAndReturn(f func(context.Context) (RequestStream, error)) *MockClientConnOpenRequestStreamCall { func (c *MockClientConnOpenRequestStreamCall) DoAndReturn(f func(context.Context) (*RequestStream, error)) *MockClientConnOpenRequestStreamCall {
c.Call = c.Call.DoAndReturn(f) c.Call = c.Call.DoAndReturn(f)
return c return c
} }

View File

@@ -39,7 +39,7 @@ type RoundTripOpt struct {
} }
type clientConn interface { type clientConn interface {
OpenRequestStream(context.Context) (RequestStream, error) OpenRequestStream(context.Context) (*RequestStream, error)
RoundTrip(*http.Request) (*http.Response, error) RoundTrip(*http.Request) (*http.Response, error)
} }

View File

@@ -90,7 +90,7 @@ func TestHTTPSettings(t *testing.T) {
}) })
} }
func dialAndOpenHTTPDatagramStream(t *testing.T, addr string) http3.RequestStream { func dialAndOpenHTTPDatagramStream(t *testing.T, addr string) *http3.RequestStream {
t.Helper() t.Helper()
u, err := url.Parse(addr) u, err := url.Parse(addr)