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

View as plain text