package http2
import (
mathrand
)
const (
transportDefaultConnFlow = 1 << 30
transportDefaultStreamFlow = 4 << 20
defaultUserAgent = "Go-http-client/2.0"
initialMaxConcurrentStreams = 100
defaultMaxConcurrentStreams = 1000
)
type Transport struct {
t1 TransportConfig
connPool noDialClientConnPool
*transportTestHooks
}
type transportTestHooks struct {
newclientconn func(*ClientConn)
}
func ( *Transport) () uint32 {
:= .t1.MaxHeaderListSize()
if := .t1.MaxResponseHeaderBytes(); != 0 {
=
if > 0 {
= adjustHTTP1MaxHeaderSize()
}
}
if <= 0 {
return 10 << 20
}
if >= 0xffffffff {
return 0
}
return uint32()
}
func ( *Transport) () bool {
return .t1 != nil && .t1.DisableCompression()
}
func ( TransportConfig) *Transport {
:= new(clientConnPool)
:= &Transport{
connPool: noDialClientConnPool{},
t1: ,
}
.t =
return
}
func ( *Transport) (, string, net.Conn) error {
:= authorityAddr(, )
, := .connPool.addConnIfNeeded(, , )
if ! {
go .Close()
}
return
}
type unencryptedTransport Transport
func ( *unencryptedTransport) ( *ClientRequest) (*ClientResponse, error) {
return (*Transport)().RoundTripOpt(, RoundTripOpt{})
}
type ClientConn struct {
t *Transport
tconn net.Conn
tlsState *tls.ConnectionState
atomicReused uint32
singleUse bool
getConnCalled bool
readerDone chan struct{}
readerErr error
idleTimeout time.Duration
idleTimer *time.Timer
mu sync.Mutex
cond *sync.Cond
flow connOutflow
inflow inflow
doNotReuse bool
closing bool
closed bool
closedOnIdle bool
seenSettings bool
seenSettingsChan chan struct{}
wantSettingsAck bool
goAway *GoAwayFrame
goAwayDebug string
streams map[uint32]*clientStream
streamsReserved int
nextStreamID uint32
pendingRequests int
pings map[[8]byte]chan struct{}
br *bufio.Reader
lastActive time.Time
lastIdle time.Time
maxFrameSize uint32
maxConcurrentStreams uint32
peerMaxHeaderListSize uint64
peerMaxHeaderTableSize uint32
initialStreamRecvWindowSize int32
readIdleTimeout time.Duration
pingTimeout time.Duration
extendedConnectAllowed bool
strictMaxConcurrentStreams bool
rstStreamPingsBlocked bool
pendingResets int
readBeforeStreamID uint32
reqHeaderMu chan struct{}
internalStateHook func()
wmu sync.Mutex
bw *bufio.Writer
fr *Framer
werr error
hbuf bytes.Buffer
henc *hpack.Encoder
}
type clientStream struct {
cc *ClientConn
ctx context.Context
reqCancel <-chan struct{}
trace *httptrace.ClientTrace
ID uint32
bufPipe pipe
requestedGzip bool
isHead bool
abortOnce sync.Once
abort chan struct{}
abortErr error
peerClosed chan struct{}
donec chan struct{}
on100 chan struct{}
respHeaderRecv chan struct{}
res *ClientResponse
flow outflow
inflow inflow
bytesRemain int64
readErr error
reqBody io.ReadCloser
reqBodyContentLength int64
reqBodyClosed chan struct{}
sentEndStream bool
sentHeaders bool
firstByte bool
pastHeaders bool
pastTrailers bool
readClosed bool
readAborted bool
totalHeaderSize int64
trailer Header
resTrailer *Header
staticResp ClientResponse
}
var got1xxFuncForTests func(int, textproto.MIMEHeader) error
func ( *clientStream) () func(int, textproto.MIMEHeader) error {
if := got1xxFuncForTests; != nil {
return
}
return traceGot1xxResponseFunc(.trace)
}
func ( *clientStream) ( error) {
.cc.mu.Lock()
defer .cc.mu.Unlock()
.abortStreamLocked()
}
func ( *clientStream) ( error) {
.abortOnce.Do(func() {
.abortErr =
close(.abort)
})
if .reqBody != nil {
.closeReqBodyLocked()
}
if .cc.cond != nil {
.cc.cond.Broadcast()
}
}
func ( *clientStream) () {
:= .cc
.mu.Lock()
defer .mu.Unlock()
if .reqBody != nil && .reqBodyClosed == nil {
.closeReqBodyLocked()
.cond.Broadcast()
}
}
func ( *clientStream) () {
if .reqBodyClosed != nil {
return
}
.reqBodyClosed = make(chan struct{})
:= .reqBodyClosed
go func() {
.reqBody.Close()
close()
}()
}
type stickyErrWriter struct {
conn net.Conn
timeout time.Duration
err *error
}
func ( stickyErrWriter) ( []byte) ( int, error) {
if *.err != nil {
return 0, *.err
}
, = writeWithByteTimeout(.conn, .timeout, )
*.err =
return ,
}
type noCachedConnError struct{}
func (noCachedConnError) () {}
func (noCachedConnError) () string { return "http2: no cached connection was available" }
func ( error) bool {
, := .(interface{ () })
return
}
var ErrNoCachedConn error = noCachedConnError{}
type RoundTripOpt struct {
OnlyCachedConn bool
}
func ( *Transport) ( *ClientRequest) (*ClientResponse, error) {
return .RoundTripOpt(, RoundTripOpt{})
}
func ( string, string) ( string) {
, , := net.SplitHostPort()
if != nil {
=
= ""
}
if == "" {
= "443"
if == "http" {
= "80"
}
}
if , := idna.ToASCII(); == nil {
=
}
if strings.HasPrefix(, "[") && strings.HasSuffix(, "]") {
return + ":" +
}
return net.JoinHostPort(, )
}
func ( *Transport) ( *ClientRequest, RoundTripOpt) (*ClientResponse, error) {
switch .URL.Scheme {
case "https":
case "http":
default:
return nil, errors.New("http2: unsupported scheme")
}
:= authorityAddr(.URL.Scheme, .URL.Host)
for := 0; ; ++ {
, := .connPool.GetClientConn(, )
if != nil {
.vlogf("http2: Transport failed to get client conn for %s: %v", , )
return nil,
}
:= !atomic.CompareAndSwapUint32(&.atomicReused, 0, 1)
traceGotConn(, , )
, := .RoundTrip()
if != nil && <= 6 {
:=
if , = shouldRetryRequest(, ); == nil {
if == 0 {
.vlogf("RoundTrip retrying after failure: %v", )
continue
}
:= float64(uint(1) << (uint() - 1))
+= * (0.1 * mathrand.Float64())
:= time.Second * time.Duration()
:= time.NewTimer()
select {
case <-.C:
.vlogf("RoundTrip retrying after failure: %v", )
continue
case <-.Context.Done():
.Stop()
= .Context.Err()
}
}
}
if == errClientConnNotEstablished {
if .idleTimer != nil {
.idleTimer.Stop()
}
.connPool.MarkDead()
}
if != nil {
.vlogf("RoundTrip failure: %v", )
return nil,
}
return , nil
}
}
func ( *Transport) () []string {
var []string
.connPool.mu.Lock()
defer .connPool.mu.Unlock()
for , := range .connPool.conns {
for , := range {
if .idleState().canTakeNewRequest {
= append(, )
}
}
}
slices.Sort()
return
}
func ( *Transport) () {
.connPool.closeIdleConnections()
}
var (
errClientConnClosed = errors.New("http2: client conn is closed")
errClientConnUnusable = errors.New("http2: client conn not usable")
errClientConnNotEstablished = errors.New("http2: client conn could not be established")
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
errClientConnForceClosed = errors.New("http2: client connection force closed via ClientConn.Close")
)
func ( *ClientRequest, error) (*ClientRequest, error) {
if !canRetryError() {
return nil,
}
if .Body == nil || .Body == NoBody {
return .Clone(), nil
}
if .GetBody != nil {
, := .GetBody()
if != nil {
return nil,
}
:= .Clone()
.Body =
return , nil
}
if == errClientConnUnusable {
return .Clone(), nil
}
return nil, fmt.Errorf("http2: Transport: cannot retry err [%v] after Request.Body was written; define Request.GetBody to avoid this error", )
}
func ( error) bool {
if == errClientConnUnusable || == errClientConnGotGoAway {
return true
}
if , := .(StreamError); {
return .Code == ErrCodeRefusedStream
}
return false
}
func ( *Transport) ( context.Context, string, bool) (*ClientConn, error) {
if .transportTestHooks != nil {
return .newClientConn(nil, , nil)
}
, , := net.SplitHostPort()
if != nil {
return nil,
}
, := .dialTLS(, "tcp", , .newTLSConfig())
if != nil {
return nil,
}
return .newClientConn(, , nil)
}
func ( *Transport) ( string) *tls.Config {
:= new(tls.Config)
if !slices.Contains(.NextProtos, NextProtoTLS) {
.NextProtos = append([]string{NextProtoTLS}, .NextProtos...)
}
if .ServerName == "" {
.ServerName =
}
return
}
func ( *Transport) ( context.Context, , string, *tls.Config) (net.Conn, error) {
, := .dialTLSWithContext(, , , )
if != nil {
return nil,
}
:= .ConnectionState()
if := .NegotiatedProtocol; != NextProtoTLS {
return nil, fmt.Errorf("http2: unexpected ALPN protocol %q; want %q", , NextProtoTLS)
}
if !.NegotiatedProtocolIsMutual {
return nil, errors.New("http2: could not negotiate protocol mutually")
}
return , nil
}
func ( *Transport) () bool {
return .t1 != nil && .t1.DisableKeepAlives()
}
func ( *Transport) () time.Duration {
if .t1 == nil {
return 0
}
return .t1.ExpectContinueTimeout()
}
func ( *Transport) ( net.Conn, func()) (NetHTTPClientConn, error) {
, := .newClientConn(, .disableKeepAlives(), )
if != nil {
return NetHTTPClientConn{},
}
.strictMaxConcurrentStreams = true
return NetHTTPClientConn{}, nil
}
func ( *Transport) ( net.Conn, bool, func()) (*ClientConn, error) {
:= configFromTransport()
:= &ClientConn{
t: ,
tconn: ,
readerDone: make(chan struct{}),
nextStreamID: 1,
maxFrameSize: 16 << 10,
initialStreamRecvWindowSize: int32(.MaxReceiveBufferPerStream),
maxConcurrentStreams: initialMaxConcurrentStreams,
strictMaxConcurrentStreams: .StrictMaxConcurrentRequests,
peerMaxHeaderListSize: 0xffffffffffffffff,
streams: make(map[uint32]*clientStream),
singleUse: ,
seenSettingsChan: make(chan struct{}),
wantSettingsAck: true,
readIdleTimeout: .SendPingTimeout,
pingTimeout: .PingTimeout,
pings: make(map[[8]byte]chan struct{}),
reqHeaderMu: make(chan struct{}, 1),
lastActive: time.Now(),
internalStateHook: ,
}
if .transportTestHooks != nil {
.transportTestHooks.newclientconn()
= .tconn
}
if VerboseLogs {
.vlogf("http2: Transport creating client conn %p to %v", , .RemoteAddr())
}
.cond = sync.NewCond(&.mu)
.flow.init()
.bw = bufio.NewWriter(stickyErrWriter{
conn: ,
timeout: .WriteByteTimeout,
err: &.werr,
})
.br = bufio.NewReader()
.fr = NewFramer(.bw, .br)
.fr.SetMaxReadFrameSize(uint32(.MaxReadFrameSize))
if .CountError != nil {
.fr.countError = .CountError
}
:= uint32(.MaxDecoderHeaderTableSize)
.fr.ReadMetaHeaders = hpack.NewDecoder(, nil)
.fr.MaxHeaderListSize = .maxHeaderListSize()
.henc = hpack.NewEncoder(&.hbuf)
.henc.SetMaxDynamicTableSizeLimit(uint32(.MaxEncoderHeaderTableSize))
.peerMaxHeaderTableSize = initialHeaderTableSize
if , := .(connectionStater); {
:= .ConnectionState()
.tlsState = &
}
:= []Setting{
{ID: SettingEnablePush, Val: 0},
{ID: SettingInitialWindowSize, Val: uint32(.initialStreamRecvWindowSize)},
}
= append(, Setting{ID: SettingMaxFrameSize, Val: uint32(.MaxReadFrameSize)})
if := .maxHeaderListSize(); != 0 {
= append(, Setting{ID: SettingMaxHeaderListSize, Val: })
}
if != initialHeaderTableSize {
= append(, Setting{ID: SettingHeaderTableSize, Val: })
}
.bw.Write(clientPreface)
.fr.WriteSettings(...)
.fr.WriteWindowUpdate(0, uint32(.MaxReceiveBufferPerConnection))
.inflow.init(int32(.MaxReceiveBufferPerConnection) + initialWindowSize)
.bw.Flush()
if .werr != nil {
.Close()
return nil, .werr
}
if := .idleConnTimeout(); != 0 {
.idleTimeout =
.idleTimer = time.AfterFunc(, .onIdleTimeout)
}
go .readLoop()
return , nil
}
func ( *ClientConn) () {
:= .pingTimeout
, := context.WithTimeout(context.Background(), )
defer ()
.vlogf("http2: Transport sending health check")
:= .Ping()
if != nil {
.vlogf("http2: Transport health check failure: %v", )
.closeForLostPing()
} else {
.vlogf("http2: Transport health check success")
}
}
func ( *ClientConn) () {
.mu.Lock()
defer .mu.Unlock()
.doNotReuse = true
}
func ( *ClientConn) () bool {
.mu.Lock()
defer .mu.Unlock()
return .canTakeNewRequestLocked()
}
func ( *ClientConn) () bool {
.mu.Lock()
defer .mu.Unlock()
if := .idleStateLocked(); !.canTakeNewRequest {
return false
}
.streamsReserved++
return true
}
type ClientConnState struct {
Closed bool
Closing bool
StreamsActive int
StreamsReserved int
StreamsPending int
MaxConcurrentStreams uint32
LastIdle time.Time
}
func ( *ClientConn) () ClientConnState {
.wmu.Lock()
:= .maxConcurrentStreams
if !.seenSettings {
= 0
}
.wmu.Unlock()
.mu.Lock()
defer .mu.Unlock()
return ClientConnState{
Closed: .closed,
Closing: .closing || .singleUse || .doNotReuse || .goAway != nil,
StreamsActive: len(.streams) + .pendingResets,
StreamsReserved: .streamsReserved,
StreamsPending: .pendingRequests,
LastIdle: .lastIdle,
MaxConcurrentStreams: ,
}
}
type clientConnIdleState struct {
canTakeNewRequest bool
}
func ( *ClientConn) () clientConnIdleState {
.mu.Lock()
defer .mu.Unlock()
return .idleStateLocked()
}
func ( *ClientConn) () ( clientConnIdleState) {
if .singleUse && .nextStreamID > 1 {
return
}
var bool
if .strictMaxConcurrentStreams {
= true
} else {
= .currentRequestCountLocked() < int(.maxConcurrentStreams)
}
.canTakeNewRequest = && .isUsableLocked()
if .nextStreamID == 1 && .streamsReserved == 0 && .closed && !.closedOnIdle {
.canTakeNewRequest = true
}
return
}
func ( *ClientConn) () bool {
return .goAway == nil &&
!.closed &&
!.closing &&
!.doNotReuse &&
int64(.nextStreamID)+2*int64(.pendingRequests) < math.MaxInt32 &&
!.tooIdleLocked()
}
func ( *ClientConn) () bool {
if .currentRequestCountLocked() >= int(.maxConcurrentStreams) {
return false
}
if !.isUsableLocked() {
return false
}
return true
}
func ( *ClientConn) () int {
return len(.streams) + .streamsReserved + .pendingResets
}
func ( *ClientConn) () bool {
:= .idleStateLocked()
return .canTakeNewRequest
}
func ( *ClientConn) () int {
if !.canTakeNewRequestLocked() {
return 0
}
return max(0, int(.maxConcurrentStreams)-.currentRequestCountLocked())
}
func ( *ClientConn) () bool {
return .idleTimeout != 0 && !.lastIdle.IsZero() && time.Since(.lastIdle.Round(0)) > .idleTimeout
}
func ( *ClientConn) () {
.closeIfIdle()
}
func ( *ClientConn) () {
:= time.AfterFunc(250*time.Millisecond, .forceCloseConn)
defer .Stop()
.tconn.Close()
.maybeCallStateHook()
}
func ( *ClientConn) () {
, := .tconn.(*tls.Conn)
if ! {
return
}
if := .NetConn(); != nil {
.Close()
}
}
func ( *ClientConn) () {
.mu.Lock()
if len(.streams) > 0 || .streamsReserved > 0 {
.mu.Unlock()
return
}
.closed = true
.closedOnIdle = true
:= .nextStreamID
.mu.Unlock()
if VerboseLogs {
.vlogf("http2: Transport closing idle conn %p (forSingleUse=%v, maxStream=%v)", , .singleUse, -2)
}
.closeConn()
}
func ( *ClientConn) () bool {
.mu.Lock()
defer .mu.Unlock()
return .doNotReuse && len(.streams) == 0
}
var shutdownEnterWaitStateHook = func() {}
func ( *ClientConn) ( context.Context) error {
if := .sendGoAway(); != nil {
return
}
:= make(chan struct{})
:= false
go func() {
.mu.Lock()
defer .mu.Unlock()
for {
if len(.streams) == 0 || .closed {
.closed = true
close()
break
}
if {
break
}
.cond.Wait()
}
}()
shutdownEnterWaitStateHook()
select {
case <-:
.closeConn()
return nil
case <-.Done():
.mu.Lock()
= true
.cond.Broadcast()
.mu.Unlock()
return .Err()
}
}
func ( *ClientConn) () error {
.mu.Lock()
:= .closing
.closing = true
:= .nextStreamID
.mu.Unlock()
if {
return nil
}
.wmu.Lock()
defer .wmu.Unlock()
if := .fr.WriteGoAway(, ErrCodeNo, nil); != nil {
return
}
if := .bw.Flush(); != nil {
return
}
return nil
}
func ( *ClientConn) ( error) {
.mu.Lock()
.closed = true
for , := range .streams {
.abortStreamLocked()
}
.cond.Broadcast()
.mu.Unlock()
.closeConn()
}
func ( *ClientConn) ( ErrCode) {
.mu.Lock()
:= .closed
.closing = true
.closed = true
.mu.Unlock()
if {
return
}
if := .fr.countError; != nil {
(fmt.Sprintf("conn_close_error_%s", .stringToken()))
}
:= make(chan struct{})
go func() {
defer close()
.wmu.Lock()
.fr.WriteGoAway(0, , nil)
.bw.Flush()
.wmu.Unlock()
}()
select {
case <-:
case <-time.After(250 * time.Millisecond):
}
.closeForError(fmt.Errorf("http2: closing connection with %v", ))
}
func ( *ClientConn) () error {
.closeForError(errClientConnForceClosed)
return nil
}
func ( *ClientConn) () {
:= errors.New("http2: client connection lost")
if := .fr.countError; != nil {
("conn_close_lost_ping")
}
.closeForError()
}
var errRequestCanceled = internal.ErrRequestCanceled
func ( *ClientConn) () time.Duration {
if .t.t1 != nil {
return .t.t1.ResponseHeaderTimeout()
}
return 0
}
func ( *ClientRequest) int64 {
if .Body == nil || .Body == NoBody {
return 0
}
if .ContentLength != 0 {
return .ContentLength
}
return -1
}
func ( *ClientConn) () {
.mu.Lock()
defer .mu.Unlock()
.decrStreamReservationsLocked()
}
func ( *ClientConn) () {
if .streamsReserved > 0 {
.streamsReserved--
}
}
func ( *ClientConn) ( *ClientRequest) (*ClientResponse, error) {
return .roundTrip(, nil)
}
func ( *ClientConn) ( *ClientRequest, func(*clientStream)) (*ClientResponse, error) {
:= .Context
.stream = clientStream{
cc: ,
ctx: ,
reqCancel: .Cancel,
isHead: .Method == "HEAD",
reqBody: .Body,
reqBodyContentLength: actualContentLength(),
trace: httptrace.ContextClientTrace(),
peerClosed: make(chan struct{}),
abort: make(chan struct{}),
respHeaderRecv: make(chan struct{}),
donec: make(chan struct{}),
resTrailer: .ResTrailer,
}
:= &.stream
.requestedGzip = httpcommon.IsRequestGzip(.Method, .Header, .t.disableCompression())
go .doRequest(, )
:= func() error {
select {
case <-.donec:
return nil
case <-.Done():
return .Err()
case <-.reqCancel:
return errRequestCanceled
}
}
:= func() (*ClientResponse, error) {
:= .res
if .StatusCode > 299 {
.abortRequestBodyWrite()
}
.TLS = .tlsState
if .Body == NoBody && actualContentLength() == 0 {
if := (); != nil {
return nil,
}
}
return , nil
}
:= func( *clientStream, error) error {
.cc.mu.Lock()
:= .reqBodyClosed
.cc.mu.Unlock()
if != nil {
<-
}
return
}
for {
select {
case <-.respHeaderRecv:
return ()
case <-.abort:
select {
case <-.respHeaderRecv:
return ()
default:
()
return nil, .abortErr
}
case <-.Done():
:= .Err()
.abortStream()
return nil, (, )
case <-.reqCancel:
.abortStream(errRequestCanceled)
return nil, (, errRequestCanceled)
}
}
}
func ( *clientStream) ( *ClientRequest, func(*clientStream)) {
:= .writeRequest(, )
.cleanupWriteRequest()
}
var errExtendedConnectNotSupported = errors.New("net/http: extended connect not supported by peer")
func ( *clientStream) ( *ClientRequest, func(*clientStream)) ( error) {
:= .cc
:= .ctx
var bool
if .Method == "CONNECT" && .Header.Get(":protocol") != "" {
= true
}
if .reqHeaderMu == nil {
panic("RoundTrip on uninitialized ClientConn")
}
if {
select {
case <-.reqCancel:
return errRequestCanceled
case <-.Done():
return .Err()
case <-.seenSettingsChan:
if !.extendedConnectAllowed {
return errExtendedConnectNotSupported
}
}
}
select {
case .reqHeaderMu <- struct{}{}:
case <-.reqCancel:
return errRequestCanceled
case <-.Done():
return .Err()
}
.mu.Lock()
if .idleTimer != nil {
.idleTimer.Stop()
}
.decrStreamReservationsLocked()
if := .awaitOpenSlotForStreamLocked(); != nil {
.mu.Unlock()
<-.reqHeaderMu
return
}
.addStreamLocked()
if isConnectionCloseRequest() {
.doNotReuse = true
}
.mu.Unlock()
if != nil {
()
}
:= .t.expectContinueTimeout()
if != 0 {
if !httpguts.HeaderValuesContainsToken(.Header["Expect"], "100-continue") {
= 0
} else {
.on100 = make(chan struct{}, 1)
}
}
= .encodeAndWriteHeaders()
<-.reqHeaderMu
if != nil {
return
}
:= .reqBodyContentLength != 0
if ! {
.sentEndStream = true
} else {
if != 0 {
traceWait100Continue(.trace)
:= time.NewTimer()
select {
case <-.C:
= nil
case <-.on100:
= nil
case <-.abort:
= .abortErr
case <-.Done():
= .Err()
case <-.reqCancel:
= errRequestCanceled
}
.Stop()
if != nil {
traceWroteRequest(.trace, )
return
}
}
if = .writeRequestBody(); != nil {
if != errStopReqBodyWrite {
traceWroteRequest(.trace, )
return
}
} else {
.sentEndStream = true
}
}
traceWroteRequest(.trace, )
var <-chan time.Time
var chan struct{}
if := .responseHeaderTimeout(); != 0 {
:= time.NewTimer()
defer .Stop()
= .C
= .respHeaderRecv
}
for {
select {
case <-.peerClosed:
return nil
case <-:
return errTimeout
case <-:
= nil
= nil
case <-.abort:
return .abortErr
case <-.Done():
return .Err()
case <-.reqCancel:
return errRequestCanceled
}
}
}
func ( *clientStream) ( *ClientRequest) error {
:= .cc
:= .ctx
.wmu.Lock()
defer .wmu.Unlock()
select {
case <-.abort:
return .abortErr
case <-.Done():
return .Err()
case <-.reqCancel:
return errRequestCanceled
default:
}
.hbuf.Reset()
, := encodeRequestHeaders(, .requestedGzip, .peerMaxHeaderListSize, func(, string) {
.writeHeader(, )
})
if != nil {
return fmt.Errorf("http2: %w", )
}
:= .hbuf.Bytes()
:= !.HasBody && !.HasTrailers
.sentHeaders = true
= .writeHeaders(.ID, , int(.maxFrameSize), )
traceWroteHeaders(.trace)
return
}
func ( *ClientRequest, bool, uint64, func(, string)) (httpcommon.EncodeHeadersResult, error) {
return httpcommon.EncodeHeaders(.Context, httpcommon.EncodeHeadersParam{
Request: httpcommon.Request{
Header: .Header,
Trailer: .Trailer,
URL: .URL,
Host: .Host,
Method: .Method,
ActualContentLength: actualContentLength(),
},
AddGzipHeader: ,
PeerMaxHeaderListSize: ,
DefaultUserAgent: defaultUserAgent,
}, )
}
func ( *clientStream) ( error) {
:= .cc
if .ID == 0 {
.decrStreamReservations()
}
.mu.Lock()
:= false
if .reqBody != nil && .reqBodyClosed == nil {
= true
.reqBodyClosed = make(chan struct{})
}
:= .reqBodyClosed
:= .singleUse || .doNotReuse || .t.disableKeepAlives() || .goAway != nil
:= .readBeforeStreamID > .ID
.mu.Unlock()
if {
.reqBody.Close()
close()
}
if != nil {
<-
}
if != nil && .sentEndStream {
select {
case <-.peerClosed:
= nil
default:
}
}
if != nil {
.abortStream()
if , := .(ConnectionError); {
.goAwayAndClose(ErrCode())
} else if .sentHeaders {
if , := .(StreamError); {
if .Cause != errFromPeer {
.writeStreamReset(.ID, .Code, false, )
}
} else {
:= false
if ! && ! {
.mu.Lock()
if !.rstStreamPingsBlocked {
if .pendingResets == 0 {
= true
}
.pendingResets++
}
.mu.Unlock()
}
.writeStreamReset(.ID, ErrCodeCancel, , )
}
}
.bufPipe.CloseWithError()
} else {
if .sentHeaders && !.sentEndStream {
.writeStreamReset(.ID, ErrCodeNo, false, nil)
}
.bufPipe.CloseWithError(errRequestCanceled)
}
if .ID != 0 {
.forgetStreamID(.ID)
}
.wmu.Lock()
:= .werr
.wmu.Unlock()
if != nil {
.Close()
}
close(.donec)
.maybeCallStateHook()
}
func ( *ClientConn) ( *clientStream) error {
for {
if .closed && .nextStreamID == 1 && .streamsReserved == 0 {
return errClientConnNotEstablished
}
.lastActive = time.Now()
if .closed || !.canTakeNewRequestLocked() {
return errClientConnUnusable
}
.lastIdle = time.Time{}
if .currentRequestCountLocked() < int(.maxConcurrentStreams) {
return nil
}
.pendingRequests++
.cond.Wait()
.pendingRequests--
select {
case <-.abort:
return .abortErr
default:
}
}
}
func ( *ClientConn) ( uint32, bool, int, []byte) error {
:= true
for len() > 0 && .werr == nil {
:=
if len() > {
= [:]
}
= [len():]
:= len() == 0
if {
.fr.WriteHeaders(HeadersFrameParam{
StreamID: ,
BlockFragment: ,
EndStream: ,
EndHeaders: ,
})
= false
} else {
.fr.WriteContinuation(, , )
}
}
.bw.Flush()
return .werr
}
var (
errStopReqBodyWrite = errors.New("http2: aborting request body write")
errStopReqBodyWriteAndCancel = errors.New("http2: canceling request")
errReqBodyTooLong = errors.New("http2: request body larger than specified content length")
)
func ( *clientStream) ( int) int {
const = 512 << 10
:= min(int64(), )
if := .reqBodyContentLength; != -1 && +1 < {
= + 1
}
if < 1 {
return 1
}
return int()
}
var bufPools [7]sync.Pool
func ( int) int {
if <= 16384 {
return 0
}
-= 1
:= bits.Len(uint())
:= - 14
if >= len(bufPools) {
return len(bufPools) - 1
}
return
}
func ( *clientStream) ( *ClientRequest) ( error) {
:= .cc
:= .reqBody
:= false
:= .Trailer != nil
:= .reqBodyContentLength
:= != -1
.mu.Lock()
:= int(.maxFrameSize)
.mu.Unlock()
:= .frameScratchBufferLen()
var []byte
:= bufPoolIndex()
if , := bufPools[].Get().(*[]byte); && len(*) >= {
defer bufPools[].Put()
= *
} else {
= make([]byte, )
defer bufPools[].Put(&)
}
var bool
for ! {
, := .Read()
if {
-= int64()
if == 0 && == nil {
var [1]byte
var int
, = .Read([:])
-= int64()
}
if < 0 {
= errReqBodyTooLong
return
}
}
if != nil {
.mu.Lock()
:= .reqBodyClosed != nil
.mu.Unlock()
switch {
case :
return errStopReqBodyWrite
case == io.EOF:
= true
= nil
default:
return
}
}
:= [:]
for len() > 0 && == nil {
var int32
, = .awaitFlowControl(len())
if != nil {
return
}
.wmu.Lock()
:= [:]
= [:]
= && len() == 0 && !
= .fr.WriteData(.ID, , )
if == nil {
= .bw.Flush()
}
.wmu.Unlock()
}
if != nil {
return
}
}
if {
return nil
}
.mu.Lock()
:= .Trailer
= .abortErr
.mu.Unlock()
if != nil {
return
}
.wmu.Lock()
defer .wmu.Unlock()
var []byte
if len() > 0 {
, = .encodeTrailers()
if != nil {
return
}
}
if len() > 0 {
= .writeHeaders(.ID, true, , )
} else {
= .fr.WriteData(.ID, true, nil)
}
if := .bw.Flush(); != nil && == nil {
=
}
return
}
func ( *clientStream) ( int) ( int32, error) {
:= .cc
:= .ctx
.mu.Lock()
defer .mu.Unlock()
for {
if .closed {
return 0, errClientConnClosed
}
if .reqBodyClosed != nil {
return 0, errStopReqBodyWrite
}
select {
case <-.abort:
return 0, .abortErr
case <-.Done():
return 0, .Err()
case <-.reqCancel:
return 0, errRequestCanceled
default:
}
, := .flow.available()
if ! {
return 0, ConnectionError(ErrCodeFlowControl)
}
if > 0 {
:=
if int() > {
= int32()
}
if > int32(.maxFrameSize) {
= int32(.maxFrameSize)
}
.flow.take()
return , nil
}
.cond.Wait()
}
}
func ( *ClientConn) ( Header) ([]byte, error) {
.hbuf.Reset()
:= uint64(0)
for , := range {
for , := range {
:= hpack.HeaderField{Name: , Value: }
+= uint64(.Size())
}
}
if > .peerMaxHeaderListSize {
return nil, errRequestHeaderListSize
}
for , := range {
, := httpcommon.LowerHeader()
if ! {
continue
}
for , := range {
.writeHeader(, )
}
}
return .hbuf.Bytes(), nil
}
func ( *ClientConn) (, string) {
if VerboseLogs {
log.Printf("http2: Transport encoding header %q = %q", , )
}
.henc.WriteField(hpack.HeaderField{Name: , Value: })
}
type resAndError struct {
_ incomparable
res *ClientResponse
err error
}
func ( *ClientConn) ( *clientStream) {
.flow.conn = &.flow
.inflow.init(.initialStreamRecvWindowSize)
.ID = .nextStreamID
.nextStreamID += 2
.streams[.ID] =
if .ID == 0 {
panic("assigned stream ID 0")
}
}
func ( *ClientConn) ( uint32) {
.mu.Lock()
:= len(.streams)
delete(.streams, )
if len(.streams) != -1 {
panic("forgetting unknown stream id")
}
.lastActive = time.Now()
if len(.streams) == 0 && .idleTimer != nil {
.idleTimer.Reset(.idleTimeout)
.lastIdle = time.Now()
}
.cond.Broadcast()
:= .singleUse || .doNotReuse || .t.disableKeepAlives() || .goAway != nil
if && .streamsReserved == 0 && len(.streams) == 0 {
if VerboseLogs {
.vlogf("http2: Transport closing idle conn %p (forSingleUse=%v, maxStream=%v)", , .singleUse, .nextStreamID-2)
}
.closed = true
defer .closeConn()
}
.mu.Unlock()
}
type clientConnReadLoop struct {
_ incomparable
cc *ClientConn
}
func ( *ClientConn) () {
:= &clientConnReadLoop{cc: }
defer .cleanup()
.readerErr = .run()
if , := .readerErr.(ConnectionError); {
.wmu.Lock()
.fr.WriteGoAway(0, ErrCode(), nil)
.wmu.Unlock()
}
}
type GoAwayError struct {
LastStreamID uint32
ErrCode ErrCode
DebugData string
}
func ( GoAwayError) () string {
return fmt.Sprintf("http2: server sent GOAWAY and closed the connection; LastStreamID=%v, ErrCode=%v, debug=%q",
.LastStreamID, .ErrCode, .DebugData)
}
func ( error) bool {
if == io.EOF {
return true
}
, := .(*net.OpError)
return && .Op == "read"
}
func ( *clientConnReadLoop) () {
:= .cc
defer .closeConn()
defer close(.readerDone)
if .idleTimer != nil {
.idleTimer.Stop()
}
:= .readerErr
.mu.Lock()
if .goAway != nil && isEOFOrNetReadError() {
= GoAwayError{
LastStreamID: .goAway.LastStreamID,
ErrCode: .goAway.ErrCode,
DebugData: .goAwayDebug,
}
} else if == io.EOF {
= io.ErrUnexpectedEOF
}
.closed = true
:= 5 * time.Second
if .idleTimeout > 0 && > .idleTimeout {
= .idleTimeout
}
:= time.Now().Sub(.lastActive)
if atomic.LoadUint32(&.atomicReused) == 0 && < && !.closedOnIdle {
.idleTimer = time.AfterFunc(-, func() {
.t.connPool.MarkDead()
})
} else {
.mu.Unlock()
.t.connPool.MarkDead()
.mu.Lock()
}
for , := range .streams {
select {
case <-.peerClosed:
default:
.abortStreamLocked()
}
}
.cond.Broadcast()
.mu.Unlock()
if !.seenSettings {
.extendedConnectAllowed = true
close(.seenSettingsChan)
}
}
func ( *ClientConn) ( error) {
:= .fr.countError
if == nil || == nil {
return
}
if , := .(ConnectionError); {
:= ErrCode()
(fmt.Sprintf("read_frame_conn_error_%s", .stringToken()))
return
}
if errors.Is(, io.EOF) {
("read_frame_eof")
return
}
if errors.Is(, io.ErrUnexpectedEOF) {
("read_frame_unexpected_eof")
return
}
if errors.Is(, ErrFrameTooLarge) {
("read_frame_too_large")
return
}
("read_frame_other")
}
var errStopReadLoop = errors.New("client connection is closing (BUG: this is not user visible)")
func ( *clientConnReadLoop) () error {
:= .cc
:= false
:= .readIdleTimeout
var *time.Timer
if != 0 {
= time.AfterFunc(, .healthCheck)
}
for {
, := .fr.ReadFrame()
if != nil {
.Reset()
}
if != nil {
.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", , , )
}
if , := .(StreamError); {
if := .streamByID(.StreamID, notHeaderOrDataFrame); != nil {
if .Cause == nil {
.Cause = .fr.errDetail
}
.endStreamError(, )
}
continue
} else if != nil {
.countReadFrameError()
return
}
if VerboseLogs {
.vlogf("http2: Transport received %s", summarizeFrame())
}
if ! {
if , := .(*SettingsFrame); ! {
.logf("protocol error: received %T before a SETTINGS frame", )
return ConnectionError(ErrCodeProtocol)
}
= true
}
switch f := .(type) {
case *MetaHeadersFrame:
= .processHeaders()
case *DataFrame:
= .processData()
case *GoAwayFrame:
= .processGoAway()
case *RSTStreamFrame:
= .processResetStream()
case *SettingsFrame:
= .processSettings()
case *PushPromiseFrame:
= .processPushPromise()
case *WindowUpdateFrame:
= .processWindowUpdate()
case *PingFrame:
= .processPing()
default:
.logf("Transport: unhandled response frame type %T", )
}
if != nil {
if VerboseLogs && != errStopReadLoop {
.vlogf("http2: Transport conn %p received error from processing frame %v: %v", , summarizeFrame(), )
}
return
}
}
}
func ( *clientConnReadLoop) ( *MetaHeadersFrame) error {
:= .streamByID(.StreamID, headerOrDataFrame)
if == nil {
return nil
}
if .readClosed {
.endStreamError(, StreamError{
StreamID: .StreamID,
Code: ErrCodeProtocol,
Cause: errors.New("protocol error: headers after END_STREAM"),
})
return nil
}
if !.firstByte {
if .trace != nil {
traceFirstResponseByte(.trace)
}
.firstByte = true
}
if !.pastHeaders {
.pastHeaders = true
} else {
return .processTrailers(, )
}
, := .handleResponse(, )
if != nil {
if , := .(ConnectionError); {
return
}
.endStreamError(, StreamError{
StreamID: .StreamID,
Code: ErrCodeProtocol,
Cause: ,
})
return nil
}
if == nil {
return nil
}
.res =
close(.respHeaderRecv)
if .StreamEnded() {
.endStream()
}
return nil
}
func ( *clientConnReadLoop) ( *clientStream, *MetaHeadersFrame) (*ClientResponse, error) {
if .Truncated {
return nil, errResponseHeaderListSize
}
:= .PseudoValue("status")
if == "" {
return nil, errors.New("malformed response from server: missing status pseudo header")
}
, := strconv.Atoi()
if != nil {
return nil, errors.New("malformed response from server: malformed non-numeric status pseudo header")
}
:= .RegularFields()
:= make([]string, len())
:= make(Header, len())
:= &.staticResp
.staticResp = ClientResponse{
Header: ,
StatusCode: ,
Status: ,
}
for , := range {
:= httpcommon.CanonicalHeader(.Name)
if == "Trailer" {
:= .Trailer
if == nil {
= make(Header)
.Trailer =
}
foreachHeaderElement(.Value, func( string) {
[httpcommon.CanonicalHeader()] = nil
})
} else {
:= []
if == nil && len() > 0 {
, = [:1:1], [1:]
[0] = .Value
[] =
} else {
[] = append(, .Value)
}
}
}
if >= 100 && <= 199 {
if .StreamEnded() {
return nil, errors.New("1xx informational response with END_STREAM flag")
}
if := .get1xxTraceFunc(); != nil {
if := (, textproto.MIMEHeader()); != nil {
return nil,
}
} else {
:= int64(.cc.t.maxHeaderListSize())
if := .cc.t.t1; != nil && .MaxResponseHeaderBytes() > {
= .MaxResponseHeaderBytes()
}
for , := range .Fields {
.totalHeaderSize += int64(.Size())
}
if .totalHeaderSize > {
if VerboseLogs {
log.Printf("http2: 1xx informational responses too large")
}
return nil, errors.New("header list too large")
}
}
if == 100 {
traceGot100Continue(.trace)
select {
case .on100 <- struct{}{}:
default:
}
}
.pastHeaders = false
return nil, nil
}
for , := range connHeaders {
delete(.Header, )
}
.ContentLength = -1
if , := .Header["Content-Length"]; {
:= slices.ContainsFunc([1:], func( string) bool {
return != [0]
})
, := strconv.ParseUint([0], 10, 63)
if || != nil {
delete(.Header, "Content-Length")
} else {
.Header["Content-Length"] = [:1]
.ContentLength = int64()
}
}
if .ContentLength < 0 && .StreamEnded() && !.isHead {
.ContentLength = 0
}
if .isHead {
.Body = NoBody
return , nil
}
if .StreamEnded() {
if .ContentLength > 0 {
.Body = missingBody{}
} else {
.Body = NoBody
}
return , nil
}
.bufPipe.setBuffer(&dataBuffer{expected: .ContentLength})
.bytesRemain = .ContentLength
.Body = transportResponseBody{}
if .requestedGzip && asciiEqualFold(.Header.Get("Content-Encoding"), "gzip") {
.Header.Del("Content-Encoding")
.Header.Del("Content-Length")
.ContentLength = -1
.Body = &gzipReader{body: .Body}
.Uncompressed = true
}
return , nil
}
func ( *clientConnReadLoop) ( *clientStream, *MetaHeadersFrame) error {
if .pastTrailers {
return ConnectionError(ErrCodeProtocol)
}
.pastTrailers = true
if !.StreamEnded() {
return ConnectionError(ErrCodeProtocol)
}
if len(.PseudoFields()) > 0 {
return ConnectionError(ErrCodeProtocol)
}
if .Truncated {
.endStreamError(, StreamError{
StreamID: .StreamID,
Code: ErrCodeProtocol,
Cause: errResponseHeaderListSize,
})
return nil
}
:= make(Header)
for , := range .RegularFields() {
:= httpcommon.CanonicalHeader(.Name)
[] = append([], .Value)
}
.trailer =
.endStream()
return nil
}
type transportResponseBody struct {
cs *clientStream
}
func ( transportResponseBody) ( []byte) ( int, error) {
:= .cs
:= .cc
if .readErr != nil {
return 0, .readErr
}
, = .cs.bufPipe.Read()
if .bytesRemain != -1 {
if int64() > .bytesRemain {
= int(.bytesRemain)
if == nil {
= errors.New("net/http: server replied with more than declared Content-Length; truncated")
.abortStream()
}
.readErr =
return int(.bytesRemain),
}
.bytesRemain -= int64()
if == io.EOF && .bytesRemain > 0 {
= io.ErrUnexpectedEOF
.readErr =
return ,
}
}
if == 0 {
return
}
.mu.Lock()
:= .inflow.add()
var int32
if == nil {
= .inflow.add()
}
.mu.Unlock()
if != 0 || != 0 {
.wmu.Lock()
defer .wmu.Unlock()
if != 0 {
.fr.WriteWindowUpdate(0, mustUint31())
}
if != 0 {
.fr.WriteWindowUpdate(.ID, mustUint31())
}
.bw.Flush()
}
return
}
var errClosedResponseBody = errors.New("http2: response body closed")
func ( transportResponseBody) () error {
:= .cs
:= .cc
.bufPipe.BreakWithError(errClosedResponseBody)
.abortStream(errClosedResponseBody)
:= .bufPipe.Len()
if > 0 {
.mu.Lock()
:= .inflow.add()
.mu.Unlock()
.wmu.Lock()
if > 0 {
.fr.WriteWindowUpdate(0, uint32())
}
.bw.Flush()
.wmu.Unlock()
}
select {
case <-.donec:
case <-.ctx.Done():
return nil
case <-.reqCancel:
return errRequestCanceled
}
return nil
}
func ( *clientConnReadLoop) ( *DataFrame) error {
:= .cc
:= .streamByID(.StreamID, headerOrDataFrame)
:= .Data()
if == nil {
.mu.Lock()
:= .nextStreamID
.mu.Unlock()
if .StreamID >= {
.logf("http2: Transport received unsolicited DATA frame; closing connection")
return ConnectionError(ErrCodeProtocol)
}
if .Length > 0 {
.mu.Lock()
:= .inflow.take(.Length)
:= .inflow.add(int(.Length))
.mu.Unlock()
if ! {
return ConnectionError(ErrCodeFlowControl)
}
if > 0 {
.wmu.Lock()
.fr.WriteWindowUpdate(0, uint32())
.bw.Flush()
.wmu.Unlock()
}
}
return nil
}
if .readClosed {
.logf("protocol error: received DATA after END_STREAM")
.endStreamError(, StreamError{
StreamID: .StreamID,
Code: ErrCodeProtocol,
})
return nil
}
if !.pastHeaders {
.logf("protocol error: received DATA before a HEADERS frame")
.endStreamError(, StreamError{
StreamID: .StreamID,
Code: ErrCodeProtocol,
})
return nil
}
if .Length > 0 {
if .isHead && len() > 0 {
.logf("protocol error: received DATA on a HEAD request")
.endStreamError(, StreamError{
StreamID: .StreamID,
Code: ErrCodeProtocol,
})
return nil
}
.mu.Lock()
if !takeInflows(&.inflow, &.inflow, .Length) {
.mu.Unlock()
return ConnectionError(ErrCodeFlowControl)
}
var int
if := int(.Length) - len(); > 0 {
+=
}
:= false
var error
if len() > 0 {
if _, = .bufPipe.Write(); != nil {
= true
+= len()
}
}
:= .inflow.add()
var int32
if ! {
= .inflow.add()
}
.mu.Unlock()
if > 0 || > 0 {
.wmu.Lock()
if > 0 {
.fr.WriteWindowUpdate(0, uint32())
}
if > 0 {
.fr.WriteWindowUpdate(.ID, uint32())
}
.bw.Flush()
.wmu.Unlock()
}
if != nil {
.endStreamError(, )
return nil
}
}
if .StreamEnded() {
.endStream()
}
return nil
}
func ( *clientConnReadLoop) ( *clientStream) {
if !.readClosed {
.readClosed = true
.cc.mu.Lock()
defer .cc.mu.Unlock()
.bufPipe.closeWithErrorAndCode(io.EOF, .copyTrailers)
close(.peerClosed)
}
}
func ( *clientConnReadLoop) ( *clientStream, error) {
.readAborted = true
.abortStream()
}
func ( *clientConnReadLoop) ( *clientStream, error) {
.readAborted = true
.abortStreamLocked()
}
const (
headerOrDataFrame = true
notHeaderOrDataFrame = false
)
func ( *clientConnReadLoop) ( uint32, bool) *clientStream {
.cc.mu.Lock()
defer .cc.mu.Unlock()
return .streamByIDLocked(, )
}
func ( *clientConnReadLoop) ( uint32, bool) *clientStream {
if {
.cc.rstStreamPingsBlocked = false
}
.cc.readBeforeStreamID = .cc.nextStreamID
:= .cc.streams[]
if != nil && !.readAborted {
return
}
return nil
}
func ( *clientStream) () {
for , := range .trailer {
:= .resTrailer
if * == nil {
* = make(Header)
}
(*)[] =
}
}
func ( *clientConnReadLoop) ( *GoAwayFrame) error {
:= .cc
.t.connPool.MarkDead()
if .ErrCode != 0 {
.vlogf("transport got GOAWAY with error code = %v", .ErrCode)
if := .fr.countError; != nil {
("recv_goaway_" + .ErrCode.stringToken())
}
}
.mu.Lock()
defer .mu.Unlock()
:= .goAway
.goAway =
if .goAwayDebug == "" {
.goAwayDebug = string(.DebugData())
}
if != nil && .ErrCode != ErrCodeNo {
.goAway.ErrCode = .ErrCode
}
:= .LastStreamID
if len(.streams) == 0 {
return errStopReadLoop
}
for , := range .streams {
if <= {
continue
}
if == 1 && .goAway.ErrCode != ErrCodeNo {
.abortStreamLocked(fmt.Errorf("http2: Transport received GOAWAY from server ErrCode:%v", .goAway.ErrCode))
} else {
.abortStreamLocked(errClientConnGotGoAway)
}
}
return nil
}
func ( *clientConnReadLoop) ( *SettingsFrame) error {
:= .cc
.wmu.Lock()
defer .wmu.Unlock()
if := .processSettingsNoWrite(); != nil {
return
}
if !.IsAck() {
.fr.WriteSettingsAck()
.bw.Flush()
}
return nil
}
func ( *clientConnReadLoop) ( *SettingsFrame) error {
:= .cc
defer .maybeCallStateHook()
.mu.Lock()
defer .mu.Unlock()
if .IsAck() {
if .wantSettingsAck {
.wantSettingsAck = false
return nil
}
return ConnectionError(ErrCodeProtocol)
}
var bool
:= .ForeachSetting(func( Setting) error {
if := .Valid(); != nil {
return
}
switch .ID {
case SettingMaxFrameSize:
.maxFrameSize = .Val
case SettingMaxConcurrentStreams:
.maxConcurrentStreams = .Val
= true
case SettingMaxHeaderListSize:
.peerMaxHeaderListSize = uint64(.Val)
case SettingInitialWindowSize:
if !.flow.changeInitialWindowSize(int64(.Val)) {
return ConnectionError(ErrCodeFlowControl)
}
.cond.Broadcast()
case SettingHeaderTableSize:
.henc.SetMaxDynamicTableSize(.Val)
.peerMaxHeaderTableSize = .Val
case SettingEnableConnectProtocol:
if !.seenSettings {
.extendedConnectAllowed = .Val == 1
}
default:
.vlogf("Unhandled Setting: %v", )
}
return nil
})
if != nil {
return
}
if !.seenSettings {
if ! {
.maxConcurrentStreams = defaultMaxConcurrentStreams
}
close(.seenSettingsChan)
.seenSettings = true
}
return nil
}
func ( *clientConnReadLoop) ( *WindowUpdateFrame) error {
:= .cc
.mu.Lock()
defer .mu.Unlock()
if .StreamID == 0 {
if !.flow.add(int32(.Increment)) {
return ConnectionError(ErrCodeFlowControl)
}
} else {
:= .streamByIDLocked(.StreamID, notHeaderOrDataFrame)
if == nil {
return nil
}
if !.flow.add(int32(.Increment)) {
if .flow.conn.flowErr {
return ConnectionError(ErrCodeFlowControl)
}
.endStreamErrorLocked(, StreamError{
StreamID: .StreamID,
Code: ErrCodeFlowControl,
})
return nil
}
}
.cond.Broadcast()
return nil
}
func ( *clientConnReadLoop) ( *RSTStreamFrame) error {
:= .streamByID(.StreamID, notHeaderOrDataFrame)
if == nil {
return nil
}
:= streamError(.ID, .ErrCode)
.Cause = errFromPeer
if .ErrCode == ErrCodeProtocol {
.cc.SetDoNotReuse()
}
if := .cc.fr.countError; != nil {
("recv_rststream_" + .ErrCode.stringToken())
}
.abortStream()
.bufPipe.CloseWithError()
return nil
}
func ( *ClientConn) ( context.Context) error {
:= make(chan struct{})
var [8]byte
for {
if , := rand.Read([:]); != nil {
return
}
.mu.Lock()
if , := .pings[]; ! {
.pings[] =
.mu.Unlock()
break
}
.mu.Unlock()
}
var error
:= make(chan struct{})
go func() {
.wmu.Lock()
defer .wmu.Unlock()
if = .fr.WritePing(false, ); != nil {
close()
return
}
if = .bw.Flush(); != nil {
close()
return
}
}()
select {
case <-:
return nil
case <-:
return
case <-.Done():
return .Err()
case <-.readerDone:
return .readerErr
}
}
func ( *clientConnReadLoop) ( *PingFrame) error {
if .IsAck() {
:= .cc
defer .maybeCallStateHook()
.mu.Lock()
defer .mu.Unlock()
if , := .pings[.Data]; {
close()
delete(.pings, .Data)
}
if .pendingResets > 0 {
.pendingResets = 0
.rstStreamPingsBlocked = true
.cond.Broadcast()
}
return nil
}
:= .cc
.wmu.Lock()
defer .wmu.Unlock()
if := .fr.WritePing(true, .Data); != nil {
return
}
return .bw.Flush()
}
func ( *clientConnReadLoop) ( *PushPromiseFrame) error {
return ConnectionError(ErrCodeProtocol)
}
func ( *ClientConn) ( uint32, ErrCode, bool, error) {
.wmu.Lock()
.fr.WriteRSTStream(, )
if {
var [8]byte
rand.Read([:])
.fr.WritePing(false, )
}
.bw.Flush()
.wmu.Unlock()
}
var (
errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit")
errRequestHeaderListSize = httpcommon.ErrRequestHeaderListSize
)
func ( *ClientConn) ( string, ...any) {
.t.logf(, ...)
}
func ( *ClientConn) ( string, ...any) {
.t.vlogf(, ...)
}
func ( *Transport) ( string, ...any) {
if VerboseLogs {
.logf(, ...)
}
}
func ( *Transport) ( string, ...any) {
log.Printf(, ...)
}
type missingBody struct{}
func (missingBody) () error { return nil }
func (missingBody) ([]byte) (int, error) { return 0, io.ErrUnexpectedEOF }
type erringRoundTripper struct{ err error }
func ( erringRoundTripper) () error { return .err }
func ( erringRoundTripper) (*ClientRequest) (*ClientResponse, error) { return nil, .err }
var errConcurrentReadOnResBody = errors.New("http2: concurrent read on response body")
type gzipReader struct {
_ incomparable
body io.ReadCloser
mu sync.Mutex
zr *gzip.Reader
zerr error
}
type eofReader struct{}
func (eofReader) ([]byte) (int, error) { return 0, io.EOF }
func (eofReader) () (byte, error) { return 0, io.EOF }
var gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }}
func ( io.Reader) (*gzip.Reader, error) {
:= gzipPool.Get().(*gzip.Reader)
if := .Reset(); != nil {
gzipPoolPut()
return nil,
}
return , nil
}
func ( *gzip.Reader) {
var flate.Reader = eofReader{}
.Reset()
gzipPool.Put()
}
func ( *gzipReader) () (*gzip.Reader, error) {
.mu.Lock()
defer .mu.Unlock()
if .zerr != nil {
return nil, .zerr
}
if .zr == nil {
.zerr = errConcurrentReadOnResBody
.mu.Unlock()
, := gzipPoolGet(.body)
.mu.Lock()
if .zerr != errConcurrentReadOnResBody {
if != nil {
gzipPoolPut()
}
return nil, .zerr
}
.zr, .zerr = ,
if .zerr != nil {
return nil, .zerr
}
}
:= .zr
.zr, .zerr = nil, errConcurrentReadOnResBody
return , nil
}
func ( *gzipReader) ( *gzip.Reader) {
.mu.Lock()
defer .mu.Unlock()
if .zerr == errConcurrentReadOnResBody {
.zr, .zerr = , nil
} else {
gzipPoolPut()
}
}
func ( *gzipReader) () {
.mu.Lock()
defer .mu.Unlock()
if .zerr == nil && .zr != nil {
gzipPoolPut(.zr)
.zr = nil
}
.zerr = fs.ErrClosed
}
func ( *gzipReader) ( []byte) ( int, error) {
, := .acquire()
if != nil {
return 0,
}
defer .release()
return .Read()
}
func ( *gzipReader) () error {
.close()
return .body.Close()
}
func ( *ClientRequest) bool {
return .Close || httpguts.HeaderValuesContainsToken(.Header["Connection"], "close")
}
type NetHTTPClientConn struct {
cc *ClientConn
}
func ( NetHTTPClientConn) ( *ClientRequest) (*ClientResponse, error) {
return .cc.RoundTrip()
}
func ( NetHTTPClientConn) () error {
return .cc.Close()
}
func ( NetHTTPClientConn) () error {
.cc.mu.Lock()
defer .cc.mu.Unlock()
if .cc.closed {
return errors.New("connection closed")
}
return nil
}
func ( NetHTTPClientConn) () error {
defer .cc.maybeCallStateHook()
.cc.mu.Lock()
defer .cc.mu.Unlock()
if !.cc.canReserveLocked() {
return errors.New("connection is unavailable")
}
.cc.streamsReserved++
return nil
}
func ( NetHTTPClientConn) () {
defer .cc.maybeCallStateHook()
.cc.mu.Lock()
defer .cc.mu.Unlock()
if .cc.streamsReserved > 0 {
.cc.streamsReserved--
}
}
func ( NetHTTPClientConn) () int {
.cc.mu.Lock()
defer .cc.mu.Unlock()
return .cc.availableLocked()
}
func ( NetHTTPClientConn) () int {
.cc.mu.Lock()
defer .cc.mu.Unlock()
return .cc.currentRequestCountLocked()
}
func ( NetHTTPClientConn) ( context.Context) error {
return .cc.Ping()
}
func ( *ClientConn) () {
if .internalStateHook != nil {
.internalStateHook()
}
}
func ( *Transport) () time.Duration {
if .t1 != nil {
return .t1.IdleConnTimeout()
}
return 0
}
func ( *ClientRequest, string) {
:= httptrace.ContextClientTrace(.Context)
if == nil || .GetConn == nil {
return
}
.GetConn()
}
func ( *ClientRequest, *ClientConn, bool) {
:= httptrace.ContextClientTrace(.Context)
if == nil || .GotConn == nil {
return
}
:= httptrace.GotConnInfo{Conn: .tconn}
.Reused =
.mu.Lock()
.WasIdle = len(.streams) == 0 &&
if .WasIdle && !.lastActive.IsZero() {
.IdleTime = time.Since(.lastActive)
}
.mu.Unlock()
.GotConn()
}
func ( *httptrace.ClientTrace) {
if != nil && .WroteHeaders != nil {
.WroteHeaders()
}
}
func ( *httptrace.ClientTrace) {
if != nil && .Got100Continue != nil {
.Got100Continue()
}
}
func ( *httptrace.ClientTrace) {
if != nil && .Wait100Continue != nil {
.Wait100Continue()
}
}
func ( *httptrace.ClientTrace, error) {
if != nil && .WroteRequest != nil {
.WroteRequest(httptrace.WroteRequestInfo{Err: })
}
}
func ( *httptrace.ClientTrace) {
if != nil && .GotFirstResponseByte != nil {
.GotFirstResponseByte()
}
}
func ( *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error {
if != nil {
return .Got1xxResponse
}
return nil
}
func ( *Transport) ( context.Context, , string, *tls.Config) (*tls.Conn, error) {
:= &tls.Dialer{
Config: ,
}
, := .DialContext(, , )
if != nil {
return nil,
}
:= .(*tls.Conn)
return , nil
}