Source file src/net/http/internal/http3/roundtrip.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package http3
     6  
     7  import (
     8  	"errors"
     9  	"io"
    10  	"net/http"
    11  	"net/http/httptrace"
    12  	"net/http/internal/ascii"
    13  	"net/http/internal/httpcommon"
    14  	"net/textproto"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  
    19  	"golang.org/x/net/http/httpguts"
    20  	"golang.org/x/net/quic"
    21  )
    22  
    23  type roundTripState struct {
    24  	cc *clientConn
    25  	st *stream
    26  
    27  	// Request body, provided by the caller.
    28  	onceCloseReqBody sync.Once
    29  	reqBody          io.ReadCloser
    30  
    31  	reqBodyWriter bodyWriter
    32  
    33  	// Response.Body, provided to the caller.
    34  	respBody io.ReadCloser
    35  
    36  	trace       *httptrace.ClientTrace
    37  	headerSize  int64
    38  	trailerSize int64
    39  
    40  	errOnce sync.Once
    41  	err     error
    42  }
    43  
    44  // abort terminates the RoundTrip.
    45  // It returns the first fatal error encountered by the RoundTrip call.
    46  func (rt *roundTripState) abort(err error) error {
    47  	rt.errOnce.Do(func() {
    48  		rt.err = err
    49  
    50  		rt.cc.mu.Lock()
    51  		rt.cc.active--
    52  		rt.cc.mu.Unlock()
    53  		rt.cc.maybeCallStateHook()
    54  
    55  		switch e := err.(type) {
    56  		case *connectionError:
    57  			rt.cc.abort(e)
    58  		case *streamError:
    59  			rt.st.CloseRead(uint64(e.code))
    60  			rt.st.Reset(uint64(e.code))
    61  		default:
    62  			rt.st.CloseRead(uint64(errH3NoError))
    63  			rt.st.Reset(uint64(errH3NoError))
    64  		}
    65  	})
    66  	return rt.err
    67  }
    68  
    69  // closeReqBody closes the Request.Body, at most once.
    70  func (rt *roundTripState) closeReqBody() {
    71  	if rt.reqBody != nil {
    72  		rt.onceCloseReqBody.Do(func() {
    73  			rt.reqBody.Close()
    74  		})
    75  	}
    76  }
    77  
    78  // TODO: Set up the rest of the hooks that might be in rt.trace.
    79  func (rt *roundTripState) maybeCallGot1xxResponse(status int, h http.Header) error {
    80  	if rt.trace == nil || rt.trace.Got1xxResponse == nil {
    81  		return nil
    82  	}
    83  	return rt.trace.Got1xxResponse(status, textproto.MIMEHeader(h))
    84  }
    85  
    86  func (rt *roundTripState) maybeCallGot100Continue() {
    87  	if rt.trace == nil || rt.trace.Got100Continue == nil {
    88  		return
    89  	}
    90  	rt.trace.Got100Continue()
    91  }
    92  
    93  func (rt *roundTripState) maybeCallWait100Continue() {
    94  	if rt.trace == nil || rt.trace.Wait100Continue == nil {
    95  		return
    96  	}
    97  	rt.trace.Wait100Continue()
    98  }
    99  
   100  // RoundTrip sends a request on the connection.
   101  func (cc *clientConn) RoundTrip(req *http.Request) (_ *http.Response, err error) {
   102  	cc.mu.Lock()
   103  	if cc.reserved > 0 {
   104  		cc.reserved--
   105  	}
   106  	cc.active++
   107  	cc.mu.Unlock()
   108  
   109  	// Each request gets its own QUIC stream.
   110  	st, err := newConnStream(req.Context(), cc.qconn, streamTypeRequest)
   111  	if err != nil {
   112  		cc.mu.Lock()
   113  		cc.active--
   114  		cc.mu.Unlock()
   115  		cc.maybeCallStateHook()
   116  		return nil, err
   117  	}
   118  	rt := &roundTripState{
   119  		cc:      cc,
   120  		st:      st,
   121  		trace:   httptrace.ContextClientTrace(req.Context()),
   122  		reqBody: req.Body,
   123  	}
   124  	if rt.reqBody == nil {
   125  		rt.reqBody = http.NoBody
   126  	}
   127  	// wg tracks the writeBodyAndTrailer goroutine, if we start one.
   128  	var wg sync.WaitGroup
   129  	defer func() {
   130  		if err != nil {
   131  			err = rt.abort(err)
   132  
   133  			// Close the request body, and wait for writeBodyAndTrailer to
   134  			// finish with it, before returning.
   135  			//
   136  			// Closing the body here wakes up writeBodyAndTrailer if it is
   137  			// blocked reading from it; abort has already reset the stream,
   138  			// so a blocked write fails rather than hanging.
   139  			//
   140  			// net/http inspects the request body as soon as RoundTrip returns,
   141  			// to see whether it was read from or closed, so the close has to
   142  			// happen before we return rather than concurrently with the
   143  			// caller. The HTTP/2 transport does the same thing;
   144  			// see golang/go#60041.
   145  			rt.closeReqBody()
   146  			wg.Wait()
   147  		}
   148  	}()
   149  
   150  	// Cancel reads/writes on the stream when the request expires.
   151  	st.stream.SetReadContext(req.Context())
   152  	st.stream.SetWriteContext(req.Context())
   153  
   154  	addedGzip := httpcommon.IsRequestGzip(req.Method, req.Header, cc.tr.tr1.DisableCompression)
   155  	headers := cc.enc.encode(func(yield func(itype indexType, name, value string)) {
   156  		_, err = httpcommon.EncodeHeaders(req.Context(), httpcommon.EncodeHeadersParam{
   157  			Request: httpcommon.Request{
   158  				URL:                 req.URL,
   159  				Method:              req.Method,
   160  				Host:                req.Host,
   161  				Header:              req.Header,
   162  				Trailer:             req.Trailer,
   163  				ActualContentLength: actualContentLength(req),
   164  			},
   165  			AddGzipHeader:         addedGzip,
   166  			PeerMaxHeaderListSize: 0,
   167  			DefaultUserAgent:      "Go-http-client/3.0",
   168  		}, func(name, value string) {
   169  			// Issue #71374: Consider supporting never-indexed fields.
   170  			yield(mayIndex, name, value)
   171  		})
   172  	})
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	// Write the HEADERS frame.
   178  	st.writeVarint(int64(frameTypeHeaders))
   179  	st.writeVarint(int64(len(headers)))
   180  	st.Write(headers)
   181  
   182  	is100ContinueReq := httpguts.HeaderValuesContainsToken(req.Header["Expect"], "100-continue")
   183  	hasUnsent100Body := false
   184  	switch {
   185  	case rt.reqBody == http.NoBody && len(req.Trailer) == 0:
   186  		st.CloseWrite()
   187  	case is100ContinueReq:
   188  		if err := st.Flush(); err != nil {
   189  			return nil, err
   190  		}
   191  		hasUnsent100Body = true
   192  		rt.maybeCallWait100Continue()
   193  	default:
   194  		if err := st.Flush(); err != nil {
   195  			return nil, err
   196  		}
   197  		wg.Go(func() { cc.writeBodyAndTrailer(rt, req) })
   198  	}
   199  
   200  	// Read the response headers.
   201  	for {
   202  		ftype, err := st.readFrameHeader()
   203  		if err != nil {
   204  			return nil, err
   205  		}
   206  		switch ftype {
   207  		case frameTypeHeaders:
   208  			statusCode, h, err := rt.handleHeaders()
   209  			if err != nil {
   210  				return nil, err
   211  			}
   212  
   213  			if isInfoStatus(statusCode) {
   214  				if err := rt.maybeCallGot1xxResponse(statusCode, h); err != nil {
   215  					return nil, err
   216  				}
   217  				if statusCode == 100 {
   218  					rt.maybeCallGot100Continue()
   219  					if hasUnsent100Body {
   220  						hasUnsent100Body = false
   221  						wg.Go(func() { cc.writeBodyAndTrailer(rt, req) })
   222  					}
   223  				}
   224  				continue
   225  			}
   226  
   227  			// We have the response headers.
   228  			// Set up the response and return it to the caller.
   229  			contentLength, err := parseResponseContentLength(req.Method, statusCode, h)
   230  			if err != nil {
   231  				return nil, err
   232  			}
   233  
   234  			trailer := make(http.Header)
   235  			extractTrailerFromHeader(h, trailer)
   236  			delete(h, "Trailer")
   237  
   238  			if (contentLength != 0 && req.Method != http.MethodHead) || len(trailer) > 0 {
   239  				rt.respBody = &bodyReader{
   240  					st:             st,
   241  					remain:         contentLength,
   242  					trailer:        trailer,
   243  					maxHeaderBytes: cc.tr.maxResponseHeaderBytes(),
   244  				}
   245  			} else {
   246  				rt.respBody = http.NoBody
   247  			}
   248  			resp := &http.Response{
   249  				Proto:         "HTTP/3.0",
   250  				ProtoMajor:    3,
   251  				Header:        h,
   252  				StatusCode:    statusCode,
   253  				Status:        strconv.Itoa(statusCode) + " " + http.StatusText(statusCode),
   254  				ContentLength: contentLength,
   255  				Trailer:       trailer,
   256  				Body:          (*transportResponseBody)(rt),
   257  			}
   258  			if addedGzip && ascii.EqualFold(h.Get("Content-Encoding"), "gzip") {
   259  				resp.Body = &httpcommon.GzipReader{Body: resp.Body}
   260  				h.Del("Content-Encoding")
   261  				h.Del("Content-Length")
   262  				resp.ContentLength = -1
   263  				resp.Uncompressed = true
   264  			}
   265  			return resp, nil
   266  		case frameTypePushPromise:
   267  			if err := cc.handlePushPromise(st); err != nil {
   268  				return nil, err
   269  			}
   270  		default:
   271  			if err := st.discardUnknownFrame(ftype); err != nil {
   272  				return nil, err
   273  			}
   274  		}
   275  	}
   276  }
   277  
   278  // actualContentLength returns a sanitized version of req.ContentLength,
   279  // where 0 actually means zero (not unknown) and -1 means unknown.
   280  func actualContentLength(req *http.Request) int64 {
   281  	if req.Body == nil || req.Body == http.NoBody {
   282  		return 0
   283  	}
   284  	if req.ContentLength != 0 {
   285  		return req.ContentLength
   286  	}
   287  	return -1
   288  }
   289  
   290  // reqBodyIgnored reports whether err is an error caused by the server
   291  // requesting that the client stop sending the request body. Per RFC 9114
   292  // Section 4.1, a server can send a complete response prior to the client
   293  // sending an entire request if the response does not depend on any portion of
   294  // the unsent request. When this happens, the server will use the H3_NO_ERROR
   295  // code, and the client MUST NOT discard the response.
   296  func reqBodyIgnored(err error) bool {
   297  	if streamErr, ok := errors.AsType[quic.StreamError](err); ok {
   298  		return http3Error(streamErr) == errH3NoError
   299  	}
   300  	return false
   301  }
   302  
   303  // writeBodyAndTrailer handles writing the body and trailer for a given
   304  // request, if any. This function will close the write direction of the stream.
   305  func (cc *clientConn) writeBodyAndTrailer(rt *roundTripState, req *http.Request) {
   306  	defer rt.closeReqBody()
   307  
   308  	declaredTrailer := req.Trailer.Clone()
   309  
   310  	rt.reqBodyWriter.st = rt.st
   311  	rt.reqBodyWriter.remain = actualContentLength(req)
   312  	rt.reqBodyWriter.flush = true
   313  	rt.reqBodyWriter.name = "request"
   314  	rt.reqBodyWriter.trailer = req.Trailer
   315  	rt.reqBodyWriter.enc = &cc.enc
   316  
   317  	if _, err := io.Copy(&rt.reqBodyWriter, rt.reqBody); err != nil {
   318  		if reqBodyIgnored(err) {
   319  			return
   320  		}
   321  		rt.abort(err)
   322  		return
   323  	}
   324  	// Get rid of any trailer that was not declared beforehand, before we
   325  	// close the request body which will cause the trailer headers to be
   326  	// written.
   327  	for name := range req.Trailer {
   328  		if _, ok := declaredTrailer[name]; !ok {
   329  			delete(req.Trailer, name)
   330  		}
   331  	}
   332  	if err := rt.reqBodyWriter.Close(); err != nil {
   333  		rt.abort(err)
   334  	}
   335  }
   336  
   337  // transportResponseBody is the Response.Body returned by RoundTrip.
   338  type transportResponseBody roundTripState
   339  
   340  // Read is Response.Body.Read.
   341  func (b *transportResponseBody) Read(p []byte) (n int, err error) {
   342  	return b.respBody.Read(p)
   343  }
   344  
   345  var errRespBodyClosed = errors.New("response body closed")
   346  
   347  // Close is Response.Body.Close.
   348  // Closing the response body is how the caller signals that they're done with a request.
   349  func (b *transportResponseBody) Close() error {
   350  	rt := (*roundTripState)(b)
   351  	// respBody.Close is responsible for closing the receiving half.
   352  	err := rt.respBody.Close()
   353  	if err == nil {
   354  		err = errRespBodyClosed
   355  	}
   356  	err = rt.abort(err)
   357  	// Close the request body, which should wake up writeBodyAndTrailer if it's
   358  	// currently blocked reading the body.
   359  	rt.closeReqBody()
   360  	if err == errRespBodyClosed {
   361  		// No other errors occurred before closing Response.Body,
   362  		// so consider this a successful request.
   363  		return nil
   364  	}
   365  	return err
   366  }
   367  
   368  func parseResponseContentLength(method string, statusCode int, h http.Header) (int64, error) {
   369  	clens := h["Content-Length"]
   370  	if len(clens) == 0 {
   371  		return -1, nil
   372  	}
   373  
   374  	// We allow duplicate Content-Length headers,
   375  	// but only if they all have the same value.
   376  	for _, v := range clens[1:] {
   377  		if clens[0] != v {
   378  			return -1, &streamError{errH3MessageError, "mismatching Content-Length headers"}
   379  		}
   380  	}
   381  
   382  	// "A server MUST NOT send a Content-Length header field in any response
   383  	// with a status code of 1xx (Informational) or 204 (No Content).
   384  	// A server MUST NOT send a Content-Length header field in any 2xx (Successful)
   385  	// response to a CONNECT request [...]"
   386  	// https://www.rfc-editor.org/rfc/rfc9110#section-8.6-8
   387  	if (statusCode >= 100 && statusCode < 200) ||
   388  		statusCode == 204 ||
   389  		(method == "CONNECT" && statusCode >= 200 && statusCode < 300) {
   390  		// This is a protocol violation, but a fairly harmless one.
   391  		// Just ignore the header.
   392  		return -1, nil
   393  	}
   394  
   395  	contentLen, err := strconv.ParseUint(clens[0], 10, 63)
   396  	if err != nil {
   397  		return -1, &streamError{errH3MessageError, "invalid Content-Length header"}
   398  	}
   399  	return int64(contentLen), nil
   400  }
   401  
   402  func (rt *roundTripState) handleHeaders() (statusCode int, h http.Header, err error) {
   403  	maxHeaderBytes := rt.cc.tr.maxResponseHeaderBytes()
   404  	haveStatus := false
   405  	cookie := ""
   406  	// The QPACK decoder will happily allocate an unbounded buffer, so check
   407  	// the stream read limit prior to decoding.
   408  	if rt.st.lim > maxHeaderBytes-rt.headerSize {
   409  		return 0, nil, errHeadersTooLarge
   410  	}
   411  	// Issue #71374: Consider tracking the never-indexed status of headers
   412  	// with the N bit set in their QPACK encoding.
   413  	err = rt.cc.dec.decode(rt.st, func(_ indexType, name, value string) error {
   414  		if name == "trailer" {
   415  			fieldCount := int64(strings.Count(value, ",") + 1)
   416  			rt.trailerSize += int64(len(value)) - fieldCount + 1 + fieldCount*headerFieldOverhead
   417  		} else {
   418  			rt.headerSize += int64(len(name)) + int64(len(value)) + headerFieldOverhead
   419  		}
   420  		if rt.headerSize > maxHeaderBytes || rt.trailerSize > maxHeaderBytes {
   421  			return errHeadersTooLarge
   422  		}
   423  		if !httpguts.ValidHeaderFieldValue(value) {
   424  			return &streamError{errH3MessageError, "invalid field value"}
   425  		}
   426  		switch {
   427  		case name == ":status":
   428  			if haveStatus {
   429  				return &streamError{errH3MessageError, "duplicate :status"}
   430  			}
   431  			haveStatus = true
   432  			statusCode, err = strconv.Atoi(value)
   433  			if err != nil {
   434  				return &streamError{errH3MessageError, "invalid :status"}
   435  			}
   436  		case name[0] == ':':
   437  			// "Endpoints MUST treat a request or response
   438  			// that contains undefined or invalid
   439  			// pseudo-header fields as malformed."
   440  			// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.3-3
   441  			return &streamError{errH3MessageError, "undefined pseudo-header"}
   442  		case name == "cookie":
   443  			// "If a decompressed field section contains multiple cookie field lines,
   444  			// these MUST be concatenated into a single byte string [...]"
   445  			// using the two-byte delimiter of "; "''
   446  			// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2.1-2
   447  			if cookie == "" {
   448  				cookie = value
   449  			} else {
   450  				cookie += "; " + value
   451  			}
   452  		default:
   453  			if !validWireHeaderFieldName(name) {
   454  				return &streamError{errH3MessageError, "invalid field name"}
   455  			}
   456  			if h == nil {
   457  				h = make(http.Header)
   458  			}
   459  			// TODO: Use a per-connection canonicalization cache as we do in HTTP/2.
   460  			// Maybe we could put this in the QPACK decoder and have it deliver
   461  			// pre-canonicalized headers to us here?
   462  			cname := httpcommon.CanonicalHeader(name)
   463  			// TODO: Consider using a single []string slice for all headers,
   464  			// as we do in the HTTP/1 and HTTP/2 cases.
   465  			// This is a bit tricky, since we don't know the number of headers
   466  			// at the start of decoding. Perhaps it's worth doing a two-pass decode,
   467  			// or perhaps we should just allocate header value slices in
   468  			// reasonably-sized chunks.
   469  			h[cname] = append(h[cname], value)
   470  		}
   471  		return nil
   472  	})
   473  	if err != nil {
   474  		return 0, nil, err
   475  	}
   476  	if !haveStatus {
   477  		// "[The :status] pseudo-header field MUST be included in all responses [...]"
   478  		// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.3.2-1
   479  		return 0, nil, errH3MessageError
   480  	}
   481  	if cookie != "" {
   482  		if h == nil {
   483  			h = make(http.Header)
   484  		}
   485  		h["Cookie"] = []string{cookie}
   486  	}
   487  	if err := rt.st.endFrame(); err != nil {
   488  		return 0, nil, err
   489  	}
   490  	// go.dev/issue/65035.
   491  	if isInfoStatus(statusCode) && rt.trace != nil && rt.trace.Got1xxResponse != nil {
   492  		rt.headerSize = 0
   493  		rt.trailerSize = 0
   494  	}
   495  	return statusCode, h, nil
   496  }
   497  
   498  func (cc *clientConn) handlePushPromise(st *stream) error {
   499  	// "A client MUST treat receipt of a PUSH_PROMISE frame that contains a
   500  	// larger push ID than the client has advertised as a connection error of H3_ID_ERROR."
   501  	// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.5-5
   502  	return &connectionError{
   503  		code:    errH3IDError,
   504  		message: "PUSH_PROMISE received when no MAX_PUSH_ID has been sent",
   505  	}
   506  }
   507  

View as plain text