Source File
clientconn.go
Belonging Package
net/http
// Copyright 2025 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package httpimport ()// A ClientConn is a client connection to an HTTP server.//// Unlike a [Transport], a ClientConn represents a single connection.// Most users should use a Transport rather than creating client connections directly.type ClientConn struct {cc genericClientConnstateHookMu sync.MutexuserStateHook func(*ClientConn)stateHookRunning boollastAvailable intlastInFlight intlastClosed bool}// newClientConner is the interface implemented by HTTP/2 transports to create new client conns.//// The http package (this package) needs a way to ask the http2 package to// create a client connection.//// Transport.TLSNextProto["h2"] contains a function which appears to do this,// but for historical reasons it does not: The TLSNextProto function adds a// *tls.Conn to the http2.Transport's connection pool and returns a RoundTripper// which is backed by that connection pool. NewClientConn needs a way to get a// single client connection out of the http2 package.//// The http2 package registers a RoundTripper with Transport.RegisterProtocol.// If this RoundTripper implements newClientConner, then Transport.NewClientConn will use// it to create new HTTP/2 client connections.type newClientConner interface {// NewClientConn creates a new client connection from a net.Conn.//// The RoundTripper returned by NewClientConn must implement genericClientConn.// (We don't define NewClientConn as returning genericClientConn,// because either we'd need to make genericClientConn an exported type// or define it as a type alias. Neither is particularly appealing.)//// The state hook passed here is the internal state hook// (ClientConn.maybeRunStateHook). The internal state hook calls// the user state hook (if any), which is set by the user with// ClientConn.SetStateHook.//// The client connection should arrange to call the internal state hook// when the connection closes, when requests complete, and when the// connection concurrency limit changes.//// The client connection must call the internal state hook when the connection state// changes asynchronously, such as when a request completes.//// The internal state hook need not be called after synchronous changes to the state:// Close, Reserve, Release, and RoundTrip calls which don't start a request// do not need to call the hook.//// The general idea is that if we call (for example) Close,// we know that the connection state has probably changed and we// don't need the state hook to tell us that.// However, if the connection closes asynchronously// (because, for example, the other end of the conn closed it),// the state hook needs to inform us.NewClientConn(nc net.Conn, internalStateHook func()) (RoundTripper, error)}// genericClientConn is an interface implemented by HTTP/2 client conns// returned from newClientConner.NewClientConn.//// See the newClientConner doc comment for more information.type genericClientConn interface {Close() errorErr() errorRoundTrip(req *Request) (*Response, error)Reserve() errorRelease()Available() intInFlight() int}// NewClientConn creates a new client connection to the given address.//// If scheme is "http", the connection is unencrypted.// If scheme is "https", the connection uses TLS.//// The protocol used for the new connection is determined by the scheme,// Transport.Protocols configuration field, and protocols supported by the// server. See Transport.Protocols for more details.//// If Transport.Proxy is set and indicates that a request sent to the given// address should use a proxy, the new connection uses that proxy.//// NewClientConn always creates a new connection,// even if the Transport has an existing cached connection to the given host.//// The new connection is not added to the Transport's connection cache,// and will not be used by [Transport.RoundTrip].// It does not count against the MaxIdleConns and MaxConnsPerHost limits.//// The caller is responsible for closing the new connection.func ( *Transport) ( context.Context, , string) (*ClientConn, error) {.nextProtoOnce.Do(.onceSetNextProtoDefaults)switch {case "http", "https":default:return nil, fmt.Errorf("net/http: invalid scheme %q", )}, , := net.SplitHostPort()if != nil {return nil,}if == "" {= schemePort()}var *url.URLif .Proxy != nil {// Transport.Proxy takes a *Request, so create a fake one to pass it.:= &Request{ctx: ,Method: "GET",URL: &url.URL{Scheme: ,Host: ,Path: "/",},Proto: "HTTP/1.1",ProtoMajor: 1,ProtoMinor: 1,Header: make(Header),Body: NoBody,Host: ,}var error, = .Proxy()if != nil {return nil,}}:= connectMethod{targetScheme: ,targetAddr: net.JoinHostPort(, ),proxyURL: ,}// The state hook is a bit tricky:// The persistConn has a state hook which calls ClientConn.maybeRunStateHook,// which in turn calls the user-provided state hook (if any).//// ClientConn.maybeRunStateHook handles debouncing hook calls for both// HTTP/1 and HTTP/2.//// Since there's no need to change the persistConn's hook, we set it at creation time.:= &ClientConn{}const = true, := .dialConn(, , , .maybeRunStateHook)if != nil {return nil,}// Note that cc.maybeRunStateHook may have been called// in the short window between dialConn and now.// This is fine..stateHookMu.Lock()defer .stateHookMu.Unlock()if .alt != nil {// If pconn.alt is set, this is a connection implemented in another package// (probably x/net/http2) or the bundled copy in h2_bundle.go., := .alt.(genericClientConn)if ! {return nil, errors.New("http: NewClientConn returned something that is not a ClientConn")}.cc =.lastAvailable = .Available()} else {// This is an HTTP/1 connection..availch = make(chan struct{}, 1).availch <- struct{}{}.cc = http1ClientConn{}.lastAvailable = 1}return , nil}// Close closes the connection.// Outstanding RoundTrip calls are interrupted.func ( *ClientConn) () error {defer .maybeRunStateHook()return .cc.Close()}// Err reports any fatal connection errors.// It returns nil if the connection is usable.// If it returns non-nil, the connection can no longer be used.func ( *ClientConn) () error {return .cc.Err()}func validateClientConnRequest( *Request) error {if .URL == nil {return errors.New("http: nil Request.URL")}if .Header == nil {return errors.New("http: nil Request.Header")}// Validate the outgoing headers.if := validateHeaders(.Header); != "" {return fmt.Errorf("http: invalid header %s", )}// Validate the outgoing trailers too.if := validateHeaders(.Trailer); != "" {return fmt.Errorf("http: invalid trailer %s", )}if .Method != "" && !validMethod(.Method) {return fmt.Errorf("http: invalid method %q", .Method)}if .URL.Host == "" {return errors.New("http: no Host in request URL")}return nil}// RoundTrip implements the [RoundTripper] interface.//// The request is sent on the client connection,// regardless of the URL being requested or any proxy settings.//// If the connection is at its concurrency limit,// RoundTrip waits for the connection to become available// before sending the request.func ( *ClientConn) ( *Request) (*Response, error) {defer .maybeRunStateHook()if := validateClientConnRequest(); != nil {.Release()return nil,}return .cc.RoundTrip()}// Available reports the number of requests that may be sent// to the connection without blocking.// It returns 0 if the connection is closed.func ( *ClientConn) () int {return .cc.Available()}// InFlight reports the number of requests in flight,// including reserved requests.// It returns 0 if the connection is closed.func ( *ClientConn) () int {return .cc.InFlight()}// Reserve reserves a concurrency slot on the connection.// If Reserve returns nil, one additional RoundTrip call may be made// without waiting for an existing request to complete.//// The reserved concurrency slot is accounted as an in-flight request.// A successful call to RoundTrip will decrement the Available count// and increment the InFlight count.//// Each successful call to Reserve should be followed by exactly one call// to RoundTrip or Release, which will consume or release the reservation.//// If the connection is closed or at its concurrency limit,// Reserve returns an error.func ( *ClientConn) () error {defer .maybeRunStateHook()return .cc.Reserve()}// Release releases an unused concurrency slot reserved by Reserve.// If there are no reserved concurrency slots, it has no effect.func ( *ClientConn) () {defer .maybeRunStateHook().cc.Release()}// shouldRunStateHook returns the user's state hook if we should call it,// or nil if we don't need to call it at this time.func ( *ClientConn) ( bool) func(*ClientConn) {.stateHookMu.Lock()defer .stateHookMu.Unlock()if .cc == nil {return nil}if {.stateHookRunning = false}if .userStateHook == nil {return nil}if .stateHookRunning {return nil}var (= .Available()= .InFlight()= .Err() != nil)var func(*ClientConn)if > .lastAvailable || < .lastInFlight || != .lastClosed {= .userStateHook.stateHookRunning = true}.lastAvailable =.lastInFlight =.lastClosed =return}func ( *ClientConn) () {:= .shouldRunStateHook(false)if == nil {return}// Run the hook synchronously.//// This means that if, for example, the user calls resp.Body.Close to finish a request,// the Close call will synchronously run the hook, giving the hook the chance to// return the ClientConn to a connection pool before the next request is made.()// The connection state may have changed while the hook was running,// in which case we need to run it again.//// If we do need to run the hook again, do so in a new goroutine to avoid blocking// the current goroutine indefinitely.= .shouldRunStateHook(true)if != nil {go func() {for != nil {()= .shouldRunStateHook(true)}}()}}// SetStateHook arranges for f to be called when the state of the connection changes.// At most one call to f is made at a time.// If the connection's state has changed since it was created,// f is called immediately in a separate goroutine.// f may be called synchronously from RoundTrip or Response.Body.Close.//// If SetStateHook is called multiple times, the new hook replaces the old one.// If f is nil, no further calls will be made to f after SetStateHook returns.//// f is called when Available increases (more requests may be sent on the connection),// InFlight decreases (existing requests complete), or Err begins returning non-nil// (the connection is no longer usable).func ( *ClientConn) ( func(*ClientConn)) {.stateHookMu.Lock().userStateHook =.stateHookMu.Unlock().maybeRunStateHook()}// http1ClientConn is a genericClientConn implementation backed by// an HTTP/1 *persistConn (pconn.alt is nil).type http1ClientConn struct {pconn *persistConn}func ( http1ClientConn) ( *Request) (*Response, error) {:= .Context():= httptrace.ContextClientTrace()// Convert Request.Cancel into context cancelation., := context.WithCancelCause(.Context())if .Cancel != nil {go awaitLegacyCancel(, , )}:= &transportRequest{Request: , trace: , ctx: , cancel: }, := .pconn.roundTrip()if != nil {return nil,}.Request =return , nil}func ( http1ClientConn) () error {.pconn.close(errors.New("ClientConn closed"))return nil}func ( http1ClientConn) () error {select {case <-.pconn.closech:return .pconn.closeddefault:return nil}}func ( http1ClientConn) () int {.pconn.mu.Lock()defer .pconn.mu.Unlock()if .pconn.closed != nil || .pconn.reserved || .pconn.inFlight {return 0}return 1}func ( http1ClientConn) () int {.pconn.mu.Lock()defer .pconn.mu.Unlock()if .pconn.closed == nil && (.pconn.reserved || .pconn.inFlight) {return 1}return 0}func ( http1ClientConn) () error {.pconn.mu.Lock()defer .pconn.mu.Unlock()if .pconn.closed != nil {return .pconn.closed}select {case <-.pconn.availch:default:return errors.New("connection is unavailable")}.pconn.reserved = truereturn nil}func ( http1ClientConn) () {.pconn.mu.Lock()defer .pconn.mu.Unlock()if .pconn.reserved {select {case .pconn.availch <- struct{}{}:default:panic("cannot release reservation")}.pconn.reserved = false}}
![]() |
The pages are generated with Golds v0.8.3-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |