package http2
import (
)
func ( *http.Transport) error {
, := configureTransports()
return
}
func ( *http.Transport) (*Transport, error) {
:= &Transport{}
.configure()
if .TLSClientConfig == nil {
.TLSClientConfig = &tls.Config{}
}
if .Protocols == nil {
.Protocols = new(http.Protocols)
.Protocols.SetHTTP1(true)
}
.Protocols.SetHTTP2(true)
return , nil
}
type transportConfig struct {
t *Transport
}
func ( transportConfig) ( *http.Transport) {
.t.lazyt1 =
}
func ( transportConfig) () bool {
return .t.DisableCompression
}
func ( transportConfig) () int64 {
return int64(.t.MaxHeaderListSize)
}
func ( transportConfig) () time.Duration {
return .t.IdleConnTimeout
}
func ( transportConfig) () http.HTTP2Config {
return http.HTTP2Config{
StrictMaxConcurrentRequests: .t.StrictMaxConcurrentStreams,
MaxDecoderHeaderTableSize: int(.t.MaxDecoderHeaderTableSize),
MaxEncoderHeaderTableSize: int(.t.MaxEncoderHeaderTableSize),
MaxReadFrameSize: int(.t.MaxReadFrameSize),
SendPingTimeout: .t.ReadIdleTimeout,
PingTimeout: .t.PingTimeout,
WriteByteTimeout: .t.WriteByteTimeout,
CountError: .t.CountError,
}
}
func ( transportConfig) () bool {
return .t.ConnPool != nil
}
func ( transportConfig) ( *http.Request) (*http.Response, error) {
if .t.ConnPool == nil {
return nil, http.ErrSkipAltProtocol
}
return .t.RoundTrip()
}
type netConnContextKey struct{}
func ( transportConfig) ( context.Context) net.Conn {
, := .Value(netConnContextKey{}).(net.Conn)
return
}
type http2TransportContextKey struct{}
func ( transportConfig) ( context.Context, , string) (net.Conn, error) {
, := .Value(http2TransportContextKey{}).(*transportRoundTripState)
if == nil {
return nil, errors.ErrUnsupported
}
return .t.dialForRoundTrip(, , , )
}
type transportInternal struct {
initOnce sync.Once
lazyt1 *http.Transport
dialMu sync.Mutex
dials map[string]*transportDialState
}
type transportDialState struct {
dialc chan struct{}
dialErr error
rtdonec chan struct{}
}
func ( *Transport) () *http.Transport {
.initOnce.Do(func() {
if .lazyt1 != nil {
return
}
:= &http.Transport{}
.configure()
})
return .lazyt1
}
func ( *Transport) ( *http.Transport) {
.RegisterProtocol("http/2", transportConfig{})
if .lazyt1 != {
panic("http2: net/http does not support this version of x/net/http2")
}
}
type transportRoundTripState struct {
mu sync.Mutex
rtdone bool
rtdonec chan struct{}
gotconnc chan struct{}
}
func ( *transportRoundTripState) () ( chan struct{}, bool) {
.mu.Lock()
defer .mu.Unlock()
if .rtdone {
return nil, true
}
if .rtdonec == nil {
.rtdonec = make(chan struct{})
}
.gotconnc = make(chan struct{})
return .gotconnc, false
}
func ( *transportRoundTripState) () {
.mu.Lock()
defer .mu.Unlock()
if .gotconnc != nil {
close(.gotconnc)
.gotconnc = nil
}
}
func ( *transportRoundTripState) () {
.mu.Lock()
defer .mu.Unlock()
.rtdone = true
if .rtdonec != nil {
close(.rtdonec)
}
}
func ( *Transport) ( *http.Request, RoundTripOpt) (*http.Response, error) {
:= .init()
if .URL.Scheme == "http" && !.AllowHTTP {
return nil, errors.New("http2: unencrypted HTTP/2 not enabled")
}
if .ConnPool != nil {
return .roundTripViaPool(, , .ConnPool)
}
:= &transportRoundTripState{}
defer .roundTripDone()
:= context.WithValue(.Context(), http2TransportContextKey{}, )
= httptrace.WithClientTrace(, &httptrace.ClientTrace{
GotConn: func(httptrace.GotConnInfo) {
.gotConn()
},
})
= .WithContext()
return .RoundTrip()
}
var errCoalescedDialAbandoned = errors.New("http2: abandoned coalesced dial")
const coalescedDialRetryTimeout = 50 * time.Millisecond
func ( *Transport) ( context.Context, *transportRoundTripState, , string) (net.Conn, error) {
, := .startDial()
if {
return nil, errCoalescedDialAbandoned
}
var *transportDialState
for {
.dialMu.Lock()
if .dials == nil {
.dials = make(map[string]*transportDialState)
}
:= .dials[]
if == nil {
= &transportDialState{
dialc: make(chan struct{}),
rtdonec: .rtdonec,
}
.dials[] =
}
.dialMu.Unlock()
if == nil {
break
}
var (
= false
= .dialc
= .rtdonec
<-chan time.Time
)
for ! {
if == nil && == nil && == nil {
= time.After(coalescedDialRetryTimeout)
}
select {
case <-:
if .dialErr != nil {
return nil, .dialErr
}
= nil
case <-:
= nil
case <-:
= true
case <-.rtdonec:
return nil, errCoalescedDialAbandoned
case <-:
return nil, errCoalescedDialAbandoned
case <-.Done():
return nil, .Err()
}
}
}
:= .TLSClientConfig
if == nil {
= &tls.Config{}
} else {
= .Clone()
}
if !slices.Contains(.NextProtos, "h2") {
.NextProtos = append([]string{"h2"}, .NextProtos...)
}
if .ServerName == "" {
, , := net.SplitHostPort()
if == nil {
.ServerName =
}
}
, := .dialTLS(, , , )
.dialMu.Lock()
delete(.dials, )
.dialMu.Unlock()
.dialErr =
close(.dialc)
return ,
}
func ( *Transport) () {
:= .init()
.CloseIdleConnections()
}
func ( *Transport) ( net.Conn) (*ClientConn, error) {
:= .init()
:= context.WithValue(context.Background(), netConnContextKey{}, )
, := .NewClientConn(, "http/2", "")
if != nil {
return nil,
}
:= &ClientConn{cc: , tr: , tconn: }
.SetStateHook(.stateHook)
return , nil
}
type ClientConn struct {
cc *http.ClientConn
tconn net.Conn
tr *Transport
doNotReuse bool
mu sync.Mutex
closing bool
closed bool
roundTrips int
reserved int
starting int
pending int
maxConcurrent int
lastIdle time.Time
shutdownc chan struct{}
atomicReused uint32
}
func ( *ClientConn) ( *http.Request) (*http.Response, error) {
, := func() (bool, error) {
.mu.Lock()
defer .mu.Unlock()
if .doNotReuse {
return false, errClientConnUnusable
}
.starting++
.roundTrips++
if .reserved == 0 {
return false, nil
}
.reserved--
return true, nil
}()
if != nil {
return nil,
}
if ! && .cc.Reserve() != nil {
.mu.Lock()
.pending++
.mu.Unlock()
}
, := .cc.RoundTrip()
.mu.Lock()
.starting--
if .pending > 0 {
.pending--
}
.updateStateLocked()
.mu.Unlock()
return ,
}
func ( *ClientConn) () bool {
return .cc.Available() > 0 && !.doNotReuse
}
func ( *ClientConn) () error {
return .cc.Close()
}
func ( *ClientConn) ( context.Context) error {
, := .cc.RoundTrip((&http.Request{
Method: ":ping",
}).WithContext())
return
}
func ( *ClientConn) () bool {
if := .cc.Reserve(); != nil {
return false
}
:= true
.mu.Lock()
if .doNotReuse {
= false
} else {
.reserved++
}
.mu.Unlock()
if ! {
.cc.Release()
}
return
}
func ( *ClientConn) () {
.mu.Lock()
defer .mu.Unlock()
.doNotReuse = true
.closing = true
}
func ( *ClientConn) ( context.Context) error {
.mu.Lock()
:= .cc.InFlight() + .starting
if > 0 && .shutdownc == nil {
.shutdownc = make(chan struct{})
}
:= .shutdownc
.mu.Unlock()
if != nil {
select {
case <-:
case <-.Done():
return .Err()
}
}
.cc.Close()
return nil
}
func ( *ClientConn) () ClientConnState {
.mu.Lock()
defer .mu.Unlock()
.updateStateLocked()
return ClientConnState{
Closed: .closed,
Closing: .closing,
StreamsActive: .cc.InFlight() - .reserved,
StreamsReserved: .reserved,
StreamsPending: .pending,
MaxConcurrentStreams: uint32(min(int64(.maxConcurrent), math.MaxUint32)),
LastIdle: .lastIdle,
}
}
func ( *ClientConn) (*http.ClientConn) {
.mu.Lock()
defer .mu.Unlock()
.updateStateLocked()
}
func ( *ClientConn) () {
if .cc.Err() != nil && !.closed {
.closing = true
.closed = true
if .tr.ConnPool != nil {
go .tr.ConnPool.MarkDead()
}
}
if .cc.InFlight() == 0 && .roundTrips > 0 && .starting == 0 {
.lastIdle = time.Now()
}
if !.closed {
.maxConcurrent = .cc.Available() + .cc.InFlight()
}
if .shutdownc != nil && .cc.InFlight()+.starting == 0 {
close(.shutdownc)
.shutdownc = nil
}
}
func ( *ClientConn) () {}
func ( *http.Request, *ClientConn, bool) {
:= httptrace.ContextClientTrace(.Context())
if == nil || .GotConn == nil {
return
}
:= httptrace.GotConnInfo{Conn: .tconn}
.Reused =
.GotConn()
}