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 statusCode == 101 {
   215  					return nil, &streamError{errH3MessageError, "unsupported 101 response"}
   216  				}
   217  				if err := rt.maybeCallGot1xxResponse(statusCode, h); err != nil {
   218  					return nil, err
   219  				}
   220  				if statusCode == 100 {
   221  					rt.maybeCallGot100Continue()
   222  					if hasUnsent100Body {
   223  						hasUnsent100Body = false
   224  						wg.Go(func() { cc.writeBodyAndTrailer(rt, req) })
   225  					}
   226  				}
   227  				continue
   228  			}
   229  
   230  			// We have the response headers.
   231  			// Set up the response and return it to the caller.
   232  			contentLength, err := parseResponseContentLength(req.Method, statusCode, h)
   233  			if err != nil {
   234  				return nil, err
   235  			}
   236  
   237  			trailer := make(http.Header)
   238  			extractTrailerFromHeader(h, trailer)
   239  			delete(h, "Trailer")
   240  
   241  			if (contentLength != 0 && req.Method != http.MethodHead) || len(trailer) > 0 {
   242  				rt.respBody = &bodyReader{
   243  					st:             st,
   244  					remain:         contentLength,
   245  					trailer:        trailer,
   246  					maxHeaderBytes: cc.tr.maxResponseHeaderBytes(),
   247  				}
   248  			} else {
   249  				rt.respBody = http.NoBody
   250  			}
   251  			resp := &http.Response{
   252  				Proto:         "HTTP/3.0",
   253  				ProtoMajor:    3,
   254  				Header:        h,
   255  				StatusCode:    statusCode,
   256  				Status:        strconv.Itoa(statusCode) + " " + http.StatusText(statusCode),
   257  				ContentLength: contentLength,
   258  				Trailer:       trailer,
   259  				Body:          (*transportResponseBody)(rt),
   260  			}
   261  			if addedGzip && ascii.EqualFold(h.Get("Content-Encoding"), "gzip") {
   262  				resp.Body = &httpcommon.GzipReader{Body: resp.Body}
   263  				h.Del("Content-Encoding")
   264  				h.Del("Content-Length")
   265  				resp.ContentLength = -1
   266  				resp.Uncompressed = true
   267  			}
   268  			return resp, nil
   269  		case frameTypePushPromise:
   270  			if err := cc.handlePushPromise(st); err != nil {
   271  				return nil, err
   272  			}
   273  		default:
   274  			if err := st.discardUnknownFrame(ftype); err != nil {
   275  				return nil, err
   276  			}
   277  		}
   278  	}
   279  }
   280  
   281  // actualContentLength returns a sanitized version of req.ContentLength,
   282  // where 0 actually means zero (not unknown) and -1 means unknown.
   283  func actualContentLength(req *http.Request) int64 {
   284  	if req.Body == nil || req.Body == http.NoBody {
   285  		return 0
   286  	}
   287  	if req.ContentLength != 0 {
   288  		return req.ContentLength
   289  	}
   290  	return -1
   291  }
   292  
   293  // reqBodyIgnored reports whether err is an error caused by the server
   294  // requesting that the client stop sending the request body. Per RFC 9114
   295  // Section 4.1, a server can send a complete response prior to the client
   296  // sending an entire request if the response does not depend on any portion of
   297  // the unsent request. When this happens, the server will use the H3_NO_ERROR
   298  // code, and the client MUST NOT discard the response.
   299  func reqBodyIgnored(err error) bool {
   300  	if streamErr, ok := errors.AsType[quic.StreamError](err); ok {
   301  		return http3Error(streamErr) == errH3NoError
   302  	}
   303  	return false
   304  }
   305  
   306  // writeBodyAndTrailer handles writing the body and trailer for a given
   307  // request, if any. This function will close the write direction of the stream.
   308  func (cc *clientConn) writeBodyAndTrailer(rt *roundTripState, req *http.Request) {
   309  	defer rt.closeReqBody()
   310  
   311  	declaredTrailer := req.Trailer.Clone()
   312  
   313  	rt.reqBodyWriter.st = rt.st
   314  	rt.reqBodyWriter.remain = actualContentLength(req)
   315  	rt.reqBodyWriter.flush = true
   316  	rt.reqBodyWriter.name = "request"
   317  	rt.reqBodyWriter.trailer = req.Trailer
   318  	rt.reqBodyWriter.enc = &cc.enc
   319  
   320  	if _, err := io.Copy(&rt.reqBodyWriter, rt.reqBody); err != nil {
   321  		if reqBodyIgnored(err) {
   322  			return
   323  		}
   324  		rt.abort(err)
   325  		return
   326  	}
   327  	// Get rid of any trailer that was not declared beforehand, before we
   328  	// close the request body which will cause the trailer headers to be
   329  	// written.
   330  	for name := range req.Trailer {
   331  		if _, ok := declaredTrailer[name]; !ok {
   332  			delete(req.Trailer, name)
   333  		}
   334  	}
   335  	if err := rt.reqBodyWriter.Close(); err != nil {
   336  		rt.abort(err)
   337  	}
   338  }
   339  
   340  // transportResponseBody is the Response.Body returned by RoundTrip.
   341  type transportResponseBody roundTripState
   342  
   343  // Read is Response.Body.Read.
   344  func (b *transportResponseBody) Read(p []byte) (n int, err error) {
   345  	return b.respBody.Read(p)
   346  }
   347  
   348  var errRespBodyClosed = errors.New("response body closed")
   349  
   350  // Close is Response.Body.Close.
   351  // Closing the response body is how the caller signals that they're done with a request.
   352  func (b *transportResponseBody) Close() error {
   353  	rt := (*roundTripState)(b)
   354  	// respBody.Close is responsible for closing the receiving half.
   355  	err := rt.respBody.Close()
   356  	if err == nil {
   357  		err = errRespBodyClosed
   358  	}
   359  	err = rt.abort(err)
   360  	// Close the request body, which should wake up writeBodyAndTrailer if it's
   361  	// currently blocked reading the body.
   362  	rt.closeReqBody()
   363  	if err == errRespBodyClosed {
   364  		// No other errors occurred before closing Response.Body,
   365  		// so consider this a successful request.
   366  		return nil
   367  	}
   368  	return err
   369  }
   370  
   371  func parseResponseContentLength(method string, statusCode int, h http.Header) (int64, error) {
   372  	clens := h["Content-Length"]
   373  	if len(clens) == 0 {
   374  		return -1, nil
   375  	}
   376  
   377  	// We allow duplicate Content-Length headers,
   378  	// but only if they all have the same value.
   379  	for _, v := range clens[1:] {
   380  		if clens[0] != v {
   381  			return -1, &streamError{errH3MessageError, "mismatching Content-Length headers"}
   382  		}
   383  	}
   384  
   385  	// "A server MUST NOT send a Content-Length header field in any response
   386  	// with a status code of 1xx (Informational) or 204 (No Content).
   387  	// A server MUST NOT send a Content-Length header field in any 2xx (Successful)
   388  	// response to a CONNECT request [...]"
   389  	// https://www.rfc-editor.org/rfc/rfc9110#section-8.6-8
   390  	if (statusCode >= 100 && statusCode < 200) ||
   391  		statusCode == 204 ||
   392  		(method == "CONNECT" && statusCode >= 200 && statusCode < 300) {
   393  		// This is a protocol violation, but a fairly harmless one.
   394  		// Just ignore the header.
   395  		return -1, nil
   396  	}
   397  
   398  	contentLen, err := strconv.ParseUint(clens[0], 10, 63)
   399  	if err != nil {
   400  		return -1, &streamError{errH3MessageError, "invalid Content-Length header"}
   401  	}
   402  	return int64(contentLen), nil
   403  }
   404  
   405  func (rt *roundTripState) handleHeaders() (statusCode int, h http.Header, err error) {
   406  	maxHeaderBytes := rt.cc.tr.maxResponseHeaderBytes()
   407  	haveStatus := false
   408  	cookie := ""
   409  	// The QPACK decoder will happily allocate an unbounded buffer, so check
   410  	// the stream read limit prior to decoding.
   411  	if rt.st.lim > maxHeaderBytes-rt.headerSize {
   412  		return 0, nil, errHeadersTooLarge
   413  	}
   414  	// Issue #71374: Consider tracking the never-indexed status of headers
   415  	// with the N bit set in their QPACK encoding.
   416  	err = rt.cc.dec.decode(rt.st, func(_ indexType, name, value string) error {
   417  		if name == "trailer" {
   418  			fieldCount := int64(strings.Count(value, ",") + 1)
   419  			rt.trailerSize += int64(len(value)) - fieldCount + 1 + fieldCount*headerFieldOverhead
   420  		} else {
   421  			rt.headerSize += int64(len(name)) + int64(len(value)) + headerFieldOverhead
   422  		}
   423  		if rt.headerSize > maxHeaderBytes || rt.trailerSize > maxHeaderBytes {
   424  			return errHeadersTooLarge
   425  		}
   426  		if !httpguts.ValidHeaderFieldValue(value) {
   427  			return &streamError{errH3MessageError, "invalid field value"}
   428  		}
   429  		switch {
   430  		case name == ":status":
   431  			if haveStatus {
   432  				return &streamError{errH3MessageError, "duplicate :status"}
   433  			}
   434  			haveStatus = true
   435  			statusCode, err = strconv.Atoi(value)
   436  			if err != nil {
   437  				return &streamError{errH3MessageError, "invalid :status"}
   438  			}
   439  		case name[0] == ':':
   440  			// "Endpoints MUST treat a request or response
   441  			// that contains undefined or invalid
   442  			// pseudo-header fields as malformed."
   443  			// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.3-3
   444  			return &streamError{errH3MessageError, "undefined pseudo-header"}
   445  		case name == "cookie":
   446  			// "If a decompressed field section contains multiple cookie field lines,
   447  			// these MUST be concatenated into a single byte string [...]"
   448  			// using the two-byte delimiter of "; "''
   449  			// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.2.1-2
   450  			if cookie == "" {
   451  				cookie = value
   452  			} else {
   453  				cookie += "; " + value
   454  			}
   455  		default:
   456  			if !validWireHeaderFieldName(name) {
   457  				return &streamError{errH3MessageError, "invalid field name"}
   458  			}
   459  			if h == nil {
   460  				h = make(http.Header)
   461  			}
   462  			// TODO: Use a per-connection canonicalization cache as we do in HTTP/2.
   463  			// Maybe we could put this in the QPACK decoder and have it deliver
   464  			// pre-canonicalized headers to us here?
   465  			cname := httpcommon.CanonicalHeader(name)
   466  			// TODO: Consider using a single []string slice for all headers,
   467  			// as we do in the HTTP/1 and HTTP/2 cases.
   468  			// This is a bit tricky, since we don't know the number of headers
   469  			// at the start of decoding. Perhaps it's worth doing a two-pass decode,
   470  			// or perhaps we should just allocate header value slices in
   471  			// reasonably-sized chunks.
   472  			h[cname] = append(h[cname], value)
   473  		}
   474  		return nil
   475  	})
   476  	if err != nil {
   477  		return 0, nil, err
   478  	}
   479  	if !haveStatus {
   480  		// "[The :status] pseudo-header field MUST be included in all responses [...]"
   481  		// https://www.rfc-editor.org/rfc/rfc9114.html#section-4.3.2-1
   482  		return 0, nil, errH3MessageError
   483  	}
   484  	if cookie != "" {
   485  		if h == nil {
   486  			h = make(http.Header)
   487  		}
   488  		h["Cookie"] = []string{cookie}
   489  	}
   490  	if err := rt.st.endFrame(); err != nil {
   491  		return 0, nil, err
   492  	}
   493  	// go.dev/issue/65035.
   494  	if isInfoStatus(statusCode) && rt.trace != nil && rt.trace.Got1xxResponse != nil {
   495  		rt.headerSize = 0
   496  		rt.trailerSize = 0
   497  	}
   498  	return statusCode, h, nil
   499  }
   500  
   501  func (cc *clientConn) handlePushPromise(st *stream) error {
   502  	// "A client MUST treat receipt of a PUSH_PROMISE frame that contains a
   503  	// larger push ID than the client has advertised as a connection error of H3_ID_ERROR."
   504  	// https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.5-5
   505  	return &connectionError{
   506  		code:    errH3IDError,
   507  		message: "PUSH_PROMISE received when no MAX_PUSH_ID has been sent",
   508  	}
   509  }
   510  

View as plain text