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

View as plain text