// 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 http

import (
	
	
	
	
	
	
	
)

// 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 genericClientConn

	stateHookMu      sync.Mutex
	userStateHook    func(*ClientConn)
	stateHookRunning bool
	lastAvailable    int
	lastInFlight     int
	lastClosed       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() error
	Err() error
	RoundTrip(req *Request) (*Response, error)
	Reserve() error
	Release()
	Available() int
	InFlight() 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.URL
	if .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.closed
	default:
		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 = true
	return 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
	}
}