package http
import (
)
var DefaultTransport RoundTripper = &Transport{
Proxy: ProxyFromEnvironment,
DialContext: defaultTransportDialContext(&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}),
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
const DefaultMaxIdleConnsPerHost = 2
type Transport struct {
idleMu sync.Mutex
closeIdle bool
idleConn map[connectMethodKey][]*persistConn
idleConnWait map[connectMethodKey]wantConnQueue
idleLRU connLRU
reqMu sync.Mutex
reqCanceler map[cancelKey]func(error)
altMu sync.Mutex
altProto atomic.Value
connsPerHostMu sync.Mutex
connsPerHost map[connectMethodKey]int
connsPerHostWait map[connectMethodKey]wantConnQueue
Proxy func(*Request) (*url.URL, error)
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
Dial func(network, addr string) (net.Conn, error)
DialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)
DialTLS func(network, addr string) (net.Conn, error)
TLSClientConfig *tls.Config
TLSHandshakeTimeout time.Duration
DisableKeepAlives bool
DisableCompression bool
MaxIdleConns int
MaxIdleConnsPerHost int
MaxConnsPerHost int
IdleConnTimeout time.Duration
ResponseHeaderTimeout time.Duration
ExpectContinueTimeout time.Duration
TLSNextProto map[string]func(authority string, c *tls.Conn) RoundTripper
ProxyConnectHeader Header
GetProxyConnectHeader func(ctx context.Context, proxyURL *url.URL, target string) (Header, error)
MaxResponseHeaderBytes int64
WriteBufferSize int
ReadBufferSize int
nextProtoOnce sync.Once
h2transport h2Transport
tlsNextProtoWasNil bool
ForceAttemptHTTP2 bool
}
type cancelKey struct {
req *Request
}
func ( *Transport) () int {
if .WriteBufferSize > 0 {
return .WriteBufferSize
}
return 4 << 10
}
func ( *Transport) () int {
if .ReadBufferSize > 0 {
return .ReadBufferSize
}
return 4 << 10
}
func ( *Transport) () *Transport {
.nextProtoOnce.Do(.onceSetNextProtoDefaults)
:= &Transport{
Proxy: .Proxy,
DialContext: .DialContext,
Dial: .Dial,
DialTLS: .DialTLS,
DialTLSContext: .DialTLSContext,
TLSHandshakeTimeout: .TLSHandshakeTimeout,
DisableKeepAlives: .DisableKeepAlives,
DisableCompression: .DisableCompression,
MaxIdleConns: .MaxIdleConns,
MaxIdleConnsPerHost: .MaxIdleConnsPerHost,
MaxConnsPerHost: .MaxConnsPerHost,
IdleConnTimeout: .IdleConnTimeout,
ResponseHeaderTimeout: .ResponseHeaderTimeout,
ExpectContinueTimeout: .ExpectContinueTimeout,
ProxyConnectHeader: .ProxyConnectHeader.Clone(),
GetProxyConnectHeader: .GetProxyConnectHeader,
MaxResponseHeaderBytes: .MaxResponseHeaderBytes,
ForceAttemptHTTP2: .ForceAttemptHTTP2,
WriteBufferSize: .WriteBufferSize,
ReadBufferSize: .ReadBufferSize,
}
if .TLSClientConfig != nil {
.TLSClientConfig = .TLSClientConfig.Clone()
}
if !.tlsNextProtoWasNil {
:= map[string]func( string, *tls.Conn) RoundTripper{}
for , := range .TLSNextProto {
[] =
}
.TLSNextProto =
}
return
}
type h2Transport interface {
CloseIdleConnections()
}
func ( *Transport) () bool {
return .DialTLS != nil || .DialTLSContext != nil
}
func ( *Transport) () {
.tlsNextProtoWasNil = (.TLSNextProto == nil)
if godebug.Get("http2client") == "0" {
return
}
, := .altProto.Load().(map[string]RoundTripper)
if := reflect.ValueOf(["https"]); .IsValid() && .Type().Kind() == reflect.Struct && .Type().NumField() == 1 {
if := .Field(0); .CanInterface() {
if , := .Interface().(h2Transport); {
.h2transport =
return
}
}
}
if .TLSNextProto != nil {
return
}
if !.ForceAttemptHTTP2 && (.TLSClientConfig != nil || .Dial != nil || .DialContext != nil || .hasCustomTLSDialer()) {
return
}
if omitBundledHTTP2 {
return
}
, := http2configureTransports()
if != nil {
log.Printf("Error enabling Transport HTTP/2 support: %v", )
return
}
.h2transport =
if := .MaxResponseHeaderBytes; != 0 && .MaxHeaderListSize == 0 {
const = 1<<32 - 1
if >= {
.MaxHeaderListSize =
} else {
.MaxHeaderListSize = uint32()
}
}
}
func ( *Request) (*url.URL, error) {
return envProxyFunc()(.URL)
}
func ( *url.URL) func(*Request) (*url.URL, error) {
return func(*Request) (*url.URL, error) {
return , nil
}
}
type transportRequest struct {
*Request
extra Header
trace *httptrace.ClientTrace
cancelKey cancelKey
mu sync.Mutex
err error
}
func ( *transportRequest) () Header {
if .extra == nil {
.extra = make(Header)
}
return .extra
}
func ( *transportRequest) ( error) {
.mu.Lock()
if .err == nil {
.err =
}
.mu.Unlock()
}
func ( *Transport) ( *Request) bool {
if .URL.Scheme == "https" && .requiresHTTP1() {
return false
}
return true
}
func ( *Transport) ( *Request) RoundTripper {
if !.useRegisteredProtocol() {
return nil
}
, := .altProto.Load().(map[string]RoundTripper)
return [.URL.Scheme]
}
func ( *Transport) ( *Request) (*Response, error) {
.nextProtoOnce.Do(.onceSetNextProtoDefaults)
:= .Context()
:= httptrace.ContextClientTrace()
if .URL == nil {
.closeBody()
return nil, errors.New("http: nil Request.URL")
}
if .Header == nil {
.closeBody()
return nil, errors.New("http: nil Request.Header")
}
:= .URL.Scheme
:= == "http" || == "https"
if {
for , := range .Header {
if !httpguts.ValidHeaderFieldName() {
.closeBody()
return nil, fmt.Errorf("net/http: invalid header field name %q", )
}
for , := range {
if !httpguts.ValidHeaderFieldValue() {
.closeBody()
return nil, fmt.Errorf("net/http: invalid header field value %q for key %v", , )
}
}
}
}
:=
:= cancelKey{}
= setupRewindBody()
if := .alternateRoundTripper(); != nil {
if , := .RoundTrip(); != ErrSkipAltProtocol {
return ,
}
var error
, = rewindBody()
if != nil {
return nil,
}
}
if ! {
.closeBody()
return nil, badStringError("unsupported protocol scheme", )
}
if .Method != "" && !validMethod(.Method) {
.closeBody()
return nil, fmt.Errorf("net/http: invalid method %q", .Method)
}
if .URL.Host == "" {
.closeBody()
return nil, errors.New("http: no Host in request URL")
}
for {
select {
case <-.Done():
.closeBody()
return nil, .Err()
default:
}
:= &transportRequest{Request: , trace: , cancelKey: }
, := .connectMethodForRequest()
if != nil {
.closeBody()
return nil,
}
, := .getConn(, )
if != nil {
.setReqCanceler(, nil)
.closeBody()
return nil,
}
var *Response
if .alt != nil {
.setReqCanceler(, nil)
, = .alt.RoundTrip()
} else {
, = .roundTrip()
}
if == nil {
.Request =
return , nil
}
if http2isNoCachedConnError() {
if .removeIdleConn() {
.decConnsPerHost(.cacheKey)
}
} else if !.shouldRetryRequest(, ) {
if , := .(nothingWrittenError); {
= .error
}
if , := .(transportReadFromServerError); {
= .err
}
return nil,
}
testHookRoundTripRetried()
, = rewindBody()
if != nil {
return nil,
}
}
}
var errCannotRewind = errors.New("net/http: cannot rewind body after connection loss")
type readTrackingBody struct {
io.ReadCloser
didRead bool
didClose bool
}
func ( *readTrackingBody) ( []byte) (int, error) {
.didRead = true
return .ReadCloser.Read()
}
func ( *readTrackingBody) () error {
.didClose = true
return .ReadCloser.Close()
}
func ( *Request) *Request {
if .Body == nil || .Body == NoBody {
return
}
:= *
.Body = &readTrackingBody{ReadCloser: .Body}
return &
}
func ( *Request) ( *Request, error) {
if .Body == nil || .Body == NoBody || (!.Body.(*readTrackingBody).didRead && !.Body.(*readTrackingBody).didClose) {
return , nil
}
if !.Body.(*readTrackingBody).didClose {
.closeBody()
}
if .GetBody == nil {
return nil, errCannotRewind
}
, := .GetBody()
if != nil {
return nil,
}
:= *
.Body = &readTrackingBody{ReadCloser: }
return &, nil
}
func ( *persistConn) ( *Request, error) bool {
if http2isNoCachedConnError() {
return true
}
if == errMissingHost {
return false
}
if !.isReused() {
return false
}
if , := .(nothingWrittenError); {
return .outgoingLength() == 0 || .GetBody != nil
}
if !.isReplayable() {
return false
}
if , := .(transportReadFromServerError); {
return true
}
if == errServerClosedIdle {
return true
}
return false
}
var ErrSkipAltProtocol = errors.New("net/http: skip alternate protocol")
func ( *Transport) ( string, RoundTripper) {
.altMu.Lock()
defer .altMu.Unlock()
, := .altProto.Load().(map[string]RoundTripper)
if , := []; {
panic("protocol " + + " already registered")
}
:= make(map[string]RoundTripper)
for , := range {
[] =
}
[] =
.altProto.Store()
}
func ( *Transport) () {
.nextProtoOnce.Do(.onceSetNextProtoDefaults)
.idleMu.Lock()
:= .idleConn
.idleConn = nil
.closeIdle = true
.idleLRU = connLRU{}
.idleMu.Unlock()
for , := range {
for , := range {
.close(errCloseIdleConns)
}
}
if := .h2transport; != nil {
.CloseIdleConnections()
}
}
func ( *Transport) ( *Request) {
.cancelRequest(cancelKey{}, errRequestCanceled)
}
func ( *Transport) ( cancelKey, error) bool {
.reqMu.Lock()
defer .reqMu.Unlock()
:= .reqCanceler[]
delete(.reqCanceler, )
if != nil {
()
}
return != nil
}
var (
envProxyOnce sync.Once
envProxyFuncValue func(*url.URL) (*url.URL, error)
)
func () func(*url.URL) (*url.URL, error) {
envProxyOnce.Do(func() {
envProxyFuncValue = httpproxy.FromEnvironment().ProxyFunc()
})
return envProxyFuncValue
}
func () {
envProxyOnce = sync.Once{}
envProxyFuncValue = nil
}
func ( *Transport) ( *transportRequest) ( connectMethod, error) {
.targetScheme = .URL.Scheme
.targetAddr = canonicalAddr(.URL)
if .Proxy != nil {
.proxyURL, = .Proxy(.Request)
}
.onlyH1 = .requiresHTTP1()
return ,
}
func ( *connectMethod) () string {
if .proxyURL == nil {
return ""
}
if := .proxyURL.User; != nil {
:= .Username()
, := .Password()
return "Basic " + basicAuth(, )
}
return ""
}
var (
errKeepAlivesDisabled = errors.New("http: putIdleConn: keep alives disabled")
errConnBroken = errors.New("http: putIdleConn: connection is in bad state")
errCloseIdle = errors.New("http: putIdleConn: CloseIdleConnections was called")
errTooManyIdle = errors.New("http: putIdleConn: too many idle connections")
errTooManyIdleHost = errors.New("http: putIdleConn: too many idle connections for host")
errCloseIdleConns = errors.New("http: CloseIdleConnections called")
errReadLoopExiting = errors.New("http: persistConn.readLoop exiting")
errIdleConnTimeout = errors.New("http: idle connection timeout")
errServerClosedIdle = errors.New("http: server closed idle connection")
)
type transportReadFromServerError struct {
err error
}
func ( transportReadFromServerError) () error { return .err }
func ( transportReadFromServerError) () string {
return fmt.Sprintf("net/http: Transport failed to read from server: %v", .err)
}
func ( *Transport) ( *persistConn) {
if := .tryPutIdleConn(); != nil {
.close()
}
}
func ( *Transport) () int {
if := .MaxIdleConnsPerHost; != 0 {
return
}
return DefaultMaxIdleConnsPerHost
}
func ( *Transport) ( *persistConn) error {
if .DisableKeepAlives || .MaxIdleConnsPerHost < 0 {
return errKeepAlivesDisabled
}
if .isBroken() {
return errConnBroken
}
.markReused()
.idleMu.Lock()
defer .idleMu.Unlock()
if .alt != nil && .idleLRU.m[] != nil {
return nil
}
:= .cacheKey
if , := .idleConnWait[]; {
:= false
if .alt == nil {
for .len() > 0 {
:= .popFront()
if .tryDeliver(, nil) {
= true
break
}
}
} else {
for .len() > 0 {
:= .popFront()
.tryDeliver(, nil)
}
}
if .len() == 0 {
delete(.idleConnWait, )
} else {
.idleConnWait[] =
}
if {
return nil
}
}
if .closeIdle {
return errCloseIdle
}
if .idleConn == nil {
.idleConn = make(map[connectMethodKey][]*persistConn)
}
:= .idleConn[]
if len() >= .maxIdleConnsPerHost() {
return errTooManyIdleHost
}
for , := range {
if == {
log.Fatalf("dup idle pconn %p in freelist", )
}
}
.idleConn[] = append(, )
.idleLRU.add()
if .MaxIdleConns != 0 && .idleLRU.len() > .MaxIdleConns {
:= .idleLRU.removeOldest()
.close(errTooManyIdle)
.removeIdleConnLocked()
}
if .IdleConnTimeout > 0 && .alt == nil {
if .idleTimer != nil {
.idleTimer.Reset(.IdleConnTimeout)
} else {
.idleTimer = time.AfterFunc(.IdleConnTimeout, .closeConnIfStillIdle)
}
}
.idleAt = time.Now()
return nil
}
func ( *Transport) ( *wantConn) ( bool) {
if .DisableKeepAlives {
return false
}
.idleMu.Lock()
defer .idleMu.Unlock()
.closeIdle = false
if == nil {
return false
}
var time.Time
if .IdleConnTimeout > 0 {
= time.Now().Add(-.IdleConnTimeout)
}
if , := .idleConn[.key]; {
:= false
:= false
for len() > 0 && ! {
:= [len()-1]
:= !.IsZero() && .idleAt.Round(0).Before()
if {
go .closeConnIfStillIdle()
}
if .isBroken() || {
= [:len()-1]
continue
}
= .tryDeliver(, nil)
if {
if .alt != nil {
} else {
.idleLRU.remove()
= [:len()-1]
}
}
= true
}
if len() > 0 {
.idleConn[.key] =
} else {
delete(.idleConn, .key)
}
if {
return
}
}
if .idleConnWait == nil {
.idleConnWait = make(map[connectMethodKey]wantConnQueue)
}
:= .idleConnWait[.key]
.cleanFront()
.pushBack()
.idleConnWait[.key] =
return false
}
func ( *Transport) ( *persistConn) bool {
.idleMu.Lock()
defer .idleMu.Unlock()
return .removeIdleConnLocked()
}
func ( *Transport) ( *persistConn) bool {
if .idleTimer != nil {
.idleTimer.Stop()
}
.idleLRU.remove()
:= .cacheKey
:= .idleConn[]
var bool
switch len() {
case 0:
case 1:
if [0] == {
delete(.idleConn, )
= true
}
default:
for , := range {
if != {
continue
}
copy([:], [+1:])
.idleConn[] = [:len()-1]
= true
break
}
}
return
}
func ( *Transport) ( cancelKey, func(error)) {
.reqMu.Lock()
defer .reqMu.Unlock()
if .reqCanceler == nil {
.reqCanceler = make(map[cancelKey]func(error))
}
if != nil {
.reqCanceler[] =
} else {
delete(.reqCanceler, )
}
}
func ( *Transport) ( cancelKey, func(error)) bool {
.reqMu.Lock()
defer .reqMu.Unlock()
, := .reqCanceler[]
if ! {
return false
}
if != nil {
.reqCanceler[] =
} else {
delete(.reqCanceler, )
}
return true
}
var zeroDialer net.Dialer
func ( *Transport) ( context.Context, , string) (net.Conn, error) {
if .DialContext != nil {
return .DialContext(, , )
}
if .Dial != nil {
, := .Dial(, )
if == nil && == nil {
= errors.New("net/http: Transport.Dial hook returned (nil, nil)")
}
return ,
}
return zeroDialer.DialContext(, , )
}
type wantConn struct {
cm connectMethod
key connectMethodKey
ctx context.Context
ready chan struct{}
beforeDial func()
afterDial func()
mu sync.Mutex
pc *persistConn
err error
}
func ( *wantConn) () bool {
select {
case <-.ready:
return false
default:
return true
}
}
func ( *wantConn) ( *persistConn, error) bool {
.mu.Lock()
defer .mu.Unlock()
if .pc != nil || .err != nil {
return false
}
.pc =
.err =
if .pc == nil && .err == nil {
panic("net/http: internal error: misuse of tryDeliver")
}
close(.ready)
return true
}
func ( *wantConn) ( *Transport, error) {
.mu.Lock()
if .pc == nil && .err == nil {
close(.ready)
}
:= .pc
.pc = nil
.err =
.mu.Unlock()
if != nil {
.putOrCloseIdleConn()
}
}
type wantConnQueue struct {
head []*wantConn
headPos int
tail []*wantConn
}
func ( *wantConnQueue) () int {
return len(.head) - .headPos + len(.tail)
}
func ( *wantConnQueue) ( *wantConn) {
.tail = append(.tail, )
}
func ( *wantConnQueue) () *wantConn {
if .headPos >= len(.head) {
if len(.tail) == 0 {
return nil
}
.head, .headPos, .tail = .tail, 0, .head[:0]
}
:= .head[.headPos]
.head[.headPos] = nil
.headPos++
return
}
func ( *wantConnQueue) () *wantConn {
if .headPos < len(.head) {
return .head[.headPos]
}
if len(.tail) > 0 {
return .tail[0]
}
return nil
}
func ( *wantConnQueue) () ( bool) {
for {
:= .peekFront()
if == nil || .waiting() {
return
}
.popFront()
= true
}
}
func ( *Transport) ( context.Context, , string) ( net.Conn, error) {
if .DialTLSContext != nil {
, = .DialTLSContext(, , )
} else {
, = .DialTLS(, )
}
if == nil && == nil {
= errors.New("net/http: Transport.DialTLS or DialTLSContext returned (nil, nil)")
}
return
}
func ( *Transport) ( *transportRequest, connectMethod) ( *persistConn, error) {
:= .Request
:= .trace
:= .Context()
if != nil && .GetConn != nil {
.GetConn(.addr())
}
:= &wantConn{
cm: ,
key: .key(),
ctx: ,
ready: make(chan struct{}, 1),
beforeDial: testHookPrePendingDial,
afterDial: testHookPostPendingDial,
}
defer func() {
if != nil {
.cancel(, )
}
}()
if := .queueForIdleConn(); {
:= .pc
if .alt == nil && != nil && .GotConn != nil {
.GotConn(.gotIdleConnTrace(.idleAt))
}
.setReqCanceler(.cancelKey, func(error) {})
return , nil
}
:= make(chan error, 1)
.setReqCanceler(.cancelKey, func( error) { <- })
.queueForDial()
select {
case <-.ready:
if .pc != nil && .pc.alt == nil && != nil && .GotConn != nil {
.GotConn(httptrace.GotConnInfo{Conn: .pc.conn, Reused: .pc.isReused()})
}
if .err != nil {
select {
case <-.Cancel:
return nil, errRequestCanceledConn
case <-.Context().Done():
return nil, .Context().Err()
case := <-:
if == errRequestCanceled {
= errRequestCanceledConn
}
return nil,
default:
}
}
return .pc, .err
case <-.Cancel:
return nil, errRequestCanceledConn
case <-.Context().Done():
return nil, .Context().Err()
case := <-:
if == errRequestCanceled {
= errRequestCanceledConn
}
return nil,
}
}
func ( *Transport) ( *wantConn) {
.beforeDial()
if .MaxConnsPerHost <= 0 {
go .dialConnFor()
return
}
.connsPerHostMu.Lock()
defer .connsPerHostMu.Unlock()
if := .connsPerHost[.key]; < .MaxConnsPerHost {
if .connsPerHost == nil {
.connsPerHost = make(map[connectMethodKey]int)
}
.connsPerHost[.key] = + 1
go .dialConnFor()
return
}
if .connsPerHostWait == nil {
.connsPerHostWait = make(map[connectMethodKey]wantConnQueue)
}
:= .connsPerHostWait[.key]
.cleanFront()
.pushBack()
.connsPerHostWait[.key] =
}
func ( *Transport) ( *wantConn) {
defer .afterDial()
, := .dialConn(.ctx, .cm)
:= .tryDeliver(, )
if == nil && (! || .alt != nil) {
.putOrCloseIdleConn()
}
if != nil {
.decConnsPerHost(.key)
}
}
func ( *Transport) ( connectMethodKey) {
if .MaxConnsPerHost <= 0 {
return
}
.connsPerHostMu.Lock()
defer .connsPerHostMu.Unlock()
:= .connsPerHost[]
if == 0 {
panic("net/http: internal error: connCount underflow")
}
if := .connsPerHostWait[]; .len() > 0 {
:= false
for .len() > 0 {
:= .popFront()
if .waiting() {
go .dialConnFor()
= true
break
}
}
if .len() == 0 {
delete(.connsPerHostWait, )
} else {
.connsPerHostWait[] =
}
if {
return
}
}
if --; == 0 {
delete(.connsPerHost, )
} else {
.connsPerHost[] =
}
}
func ( *persistConn) ( context.Context, string, *httptrace.ClientTrace) error {
:= cloneTLSConfig(.t.TLSClientConfig)
if .ServerName == "" {
.ServerName =
}
if .cacheKey.onlyH1 {
.NextProtos = nil
}
:= .conn
:= tls.Client(, )
:= make(chan error, 2)
var *time.Timer
if := .t.TLSHandshakeTimeout; != 0 {
= time.AfterFunc(, func() {
<- tlsHandshakeTimeoutError{}
})
}
go func() {
if != nil && .TLSHandshakeStart != nil {
.TLSHandshakeStart()
}
:= .HandshakeContext()
if != nil {
.Stop()
}
<-
}()
if := <-; != nil {
.Close()
if != nil && .TLSHandshakeDone != nil {
.TLSHandshakeDone(tls.ConnectionState{}, )
}
return
}
:= .ConnectionState()
if != nil && .TLSHandshakeDone != nil {
.TLSHandshakeDone(, nil)
}
.tlsState = &
.conn =
return nil
}
type erringRoundTripper interface {
RoundTripErr() error
}
func ( *Transport) ( context.Context, connectMethod) ( *persistConn, error) {
= &persistConn{
t: ,
cacheKey: .key(),
reqch: make(chan requestAndChan, 1),
writech: make(chan writeRequest, 1),
closech: make(chan struct{}),
writeErrCh: make(chan error, 1),
writeLoopDone: make(chan struct{}),
}
:= httptrace.ContextClientTrace()
:= func( error) error {
if .proxyURL != nil {
return &net.OpError{Op: "proxyconnect", Net: "tcp", Err: }
}
return
}
if .scheme() == "https" && .hasCustomTLSDialer() {
var error
.conn, = .customDialTLS(, "tcp", .addr())
if != nil {
return nil, ()
}
if , := .conn.(*tls.Conn); {
if != nil && .TLSHandshakeStart != nil {
.TLSHandshakeStart()
}
if := .HandshakeContext(); != nil {
go .conn.Close()
if != nil && .TLSHandshakeDone != nil {
.TLSHandshakeDone(tls.ConnectionState{}, )
}
return nil,
}
:= .ConnectionState()
if != nil && .TLSHandshakeDone != nil {
.TLSHandshakeDone(, nil)
}
.tlsState = &
}
} else {
, := .dial(, "tcp", .addr())
if != nil {
return nil, ()
}
.conn =
if .scheme() == "https" {
var string
if , _, = net.SplitHostPort(.addr()); != nil {
return nil, ()
}
if = .addTLS(, , ); != nil {
return nil, ()
}
}
}
switch {
case .proxyURL == nil:
case .proxyURL.Scheme == "socks5":
:= .conn
:= socksNewDialer("tcp", .RemoteAddr().String())
if := .proxyURL.User; != nil {
:= &socksUsernamePassword{
Username: .Username(),
}
.Password, _ = .Password()
.AuthMethods = []socksAuthMethod{
socksAuthMethodNotRequired,
socksAuthMethodUsernamePassword,
}
.Authenticate = .Authenticate
}
if , := .DialWithConn(, , "tcp", .targetAddr); != nil {
.Close()
return nil,
}
case .targetScheme == "http":
.isProxy = true
if := .proxyAuth(); != "" {
.mutateHeaderFunc = func( Header) {
.Set("Proxy-Authorization", )
}
}
case .targetScheme == "https":
:= .conn
var Header
if .GetProxyConnectHeader != nil {
var error
, = .GetProxyConnectHeader(, .proxyURL, .targetAddr)
if != nil {
.Close()
return nil,
}
} else {
= .ProxyConnectHeader
}
if == nil {
= make(Header)
}
if := .proxyAuth(); != "" {
= .Clone()
.Set("Proxy-Authorization", )
}
:= &Request{
Method: "CONNECT",
URL: &url.URL{Opaque: .targetAddr},
Host: .targetAddr,
Header: ,
}
:=
if .Done() == nil {
, := context.WithTimeout(, 1*time.Minute)
defer ()
=
}
:= make(chan struct{})
var (
*Response
error
)
go func() {
defer close()
= .Write()
if != nil {
return
}
:= bufio.NewReader()
, = ReadResponse(, )
}()
select {
case <-.Done():
.Close()
<-
return nil, .Err()
case <-:
}
if != nil {
.Close()
return nil,
}
if .StatusCode != 200 {
, , := strings.Cut(.Status, " ")
.Close()
if ! {
return nil, errors.New("unknown status code")
}
return nil, errors.New()
}
}
if .proxyURL != nil && .targetScheme == "https" {
if := .addTLS(, .tlsHost(), ); != nil {
return nil,
}
}
if := .tlsState; != nil && .NegotiatedProtocolIsMutual && .NegotiatedProtocol != "" {
if , := .TLSNextProto[.NegotiatedProtocol]; {
:= (.targetAddr, .conn.(*tls.Conn))
if , := .(erringRoundTripper); {
return nil, .RoundTripErr()
}
return &persistConn{t: , cacheKey: .cacheKey, alt: }, nil
}
}
.br = bufio.NewReaderSize(, .readBufferSize())
.bw = bufio.NewWriterSize(persistConnWriter{}, .writeBufferSize())
go .readLoop()
go .writeLoop()
return , nil
}
type persistConnWriter struct {
pc *persistConn
}
func ( persistConnWriter) ( []byte) ( int, error) {
, = .pc.conn.Write()
.pc.nwrite += int64()
return
}
func ( persistConnWriter) ( io.Reader) ( int64, error) {
, = io.Copy(.pc.conn, )
.pc.nwrite +=
return
}
var _ io.ReaderFrom = (*persistConnWriter)(nil)
type connectMethod struct {
_ incomparable
proxyURL *url.URL
targetScheme string
targetAddr string
onlyH1 bool
}
func ( *connectMethod) () connectMethodKey {
:= ""
:= .targetAddr
if .proxyURL != nil {
= .proxyURL.String()
if (.proxyURL.Scheme == "http" || .proxyURL.Scheme == "https") && .targetScheme == "http" {
= ""
}
}
return connectMethodKey{
proxy: ,
scheme: .targetScheme,
addr: ,
onlyH1: .onlyH1,
}
}
func ( *connectMethod) () string {
if .proxyURL != nil {
return .proxyURL.Scheme
}
return .targetScheme
}
func ( *connectMethod) () string {
if .proxyURL != nil {
return canonicalAddr(.proxyURL)
}
return .targetAddr
}
func ( *connectMethod) () string {
:= .targetAddr
if hasPort() {
= [:strings.LastIndex(, ":")]
}
return
}
type connectMethodKey struct {
proxy, scheme, addr string
onlyH1 bool
}
func ( connectMethodKey) () string {
var string
if .onlyH1 {
= ",h1"
}
return fmt.Sprintf("%s|%s%s|%s", .proxy, .scheme, , .addr)
}
type persistConn struct {
alt RoundTripper
t *Transport
cacheKey connectMethodKey
conn net.Conn
tlsState *tls.ConnectionState
br *bufio.Reader
bw *bufio.Writer
nwrite int64
reqch chan requestAndChan
writech chan writeRequest
closech chan struct{}
isProxy bool
sawEOF bool
readLimit int64
writeErrCh chan error
writeLoopDone chan struct{}
idleAt time.Time
idleTimer *time.Timer
mu sync.Mutex
numExpectedResponses int
closed error
canceledErr error
broken bool
reused bool
mutateHeaderFunc func(Header)
}
func ( *persistConn) () int64 {
if := .t.MaxResponseHeaderBytes; != 0 {
return
}
return 10 << 20
}
func ( *persistConn) ( []byte) ( int, error) {
if .readLimit <= 0 {
return 0, fmt.Errorf("read limit of %d bytes exhausted", .maxHeaderResponseSize())
}
if int64(len()) > .readLimit {
= [:.readLimit]
}
, = .conn.Read()
if == io.EOF {
.sawEOF = true
}
.readLimit -= int64()
return
}
func ( *persistConn) () bool {
.mu.Lock()
:= .closed != nil
.mu.Unlock()
return
}
func ( *persistConn) () error {
.mu.Lock()
defer .mu.Unlock()
return .canceledErr
}
func ( *persistConn) () bool {
.mu.Lock()
:= .reused
.mu.Unlock()
return
}
func ( *persistConn) ( time.Time) ( httptrace.GotConnInfo) {
.mu.Lock()
defer .mu.Unlock()
.Reused = .reused
.Conn = .conn
.WasIdle = true
if !.IsZero() {
.IdleTime = time.Since()
}
return
}
func ( *persistConn) ( error) {
.mu.Lock()
defer .mu.Unlock()
.canceledErr =
.closeLocked(errRequestCanceled)
}
func ( *persistConn) () {
:= .t
.idleMu.Lock()
defer .idleMu.Unlock()
if , := .idleLRU.m[]; ! {
return
}
.removeIdleConnLocked()
.close(errIdleConnTimeout)
}
func ( *persistConn) ( *transportRequest, int64, error) error {
if == nil {
return nil
}
<-.writeLoopDone
if := .canceled(); != nil {
return
}
.mu.Lock()
:= .err
.mu.Unlock()
if != nil {
return
}
if == errServerClosedIdle {
return
}
if , := .(transportReadFromServerError); {
if .nwrite == {
return nothingWrittenError{}
}
return
}
if .isBroken() {
if .nwrite == {
return nothingWrittenError{}
}
return fmt.Errorf("net/http: HTTP/1.x transport connection broken: %v", )
}
return
}
var errCallerOwnsConn = errors.New("read loop ending; caller owns writable underlying conn")
func ( *persistConn) () {
:= errReadLoopExiting
defer func() {
.close()
.t.removeIdleConn()
}()
:= func( *httptrace.ClientTrace) bool {
if := .t.tryPutIdleConn(); != nil {
=
if != nil && .PutIdleConn != nil && != errKeepAlivesDisabled {
.PutIdleConn()
}
return false
}
if != nil && .PutIdleConn != nil {
.PutIdleConn(nil)
}
return true
}
:= make(chan struct{})
defer close()
testHookMu.Lock()
:= testHookReadLoopBeforeNextRead
testHookMu.Unlock()
:= true
for {
.readLimit = .maxHeaderResponseSize()
, := .br.Peek(1)
.mu.Lock()
if .numExpectedResponses == 0 {
.readLoopPeekFailLocked()
.mu.Unlock()
return
}
.mu.Unlock()
:= <-.reqch
:= httptrace.ContextClientTrace(.req.Context())
var *Response
if == nil {
, = .readResponse(, )
} else {
= transportReadFromServerError{}
=
}
if != nil {
if .readLimit <= 0 {
= fmt.Errorf("net/http: server response headers exceeded %d bytes; aborted", .maxHeaderResponseSize())
}
select {
case .ch <- responseAndError{err: }:
case <-.callerGone:
return
}
return
}
.readLimit = maxInt64
.mu.Lock()
.numExpectedResponses--
.mu.Unlock()
:= .bodyIsWritable()
:= .req.Method != "HEAD" && .ContentLength != 0
if .Close || .req.Close || .StatusCode <= 199 || {
= false
}
if ! || {
:= .t.replaceReqCanceler(.cancelKey, nil)
= &&
!.sawEOF &&
.wroteRequest() &&
&& ()
if {
= errCallerOwnsConn
}
select {
case .ch <- responseAndError{res: }:
case <-.callerGone:
return
}
()
continue
}
:= make(chan bool, 2)
:= &bodyEOFSignal{
body: .Body,
earlyCloseFn: func() error {
<- false
<-
return nil
},
fn: func( error) error {
:= == io.EOF
<-
if {
<-
} else if != nil {
if := .canceled(); != nil {
return
}
}
return
},
}
.Body =
if .addedGzip && ascii.EqualFold(.Header.Get("Content-Encoding"), "gzip") {
.Body = &gzipReader{body: }
.Header.Del("Content-Encoding")
.Header.Del("Content-Length")
.ContentLength = -1
.Uncompressed = true
}
select {
case .ch <- responseAndError{res: }:
case <-.callerGone:
return
}
select {
case := <-:
:= .t.replaceReqCanceler(.cancelKey, nil)
= &&
&&
!.sawEOF &&
.wroteRequest() &&
&& ()
if {
<- struct{}{}
}
case <-.req.Cancel:
= false
.t.CancelRequest(.req)
case <-.req.Context().Done():
= false
.t.cancelRequest(.cancelKey, .req.Context().Err())
case <-.closech:
= false
}
()
}
}
func ( *persistConn) ( error) {
if .closed != nil {
return
}
if := .br.Buffered(); > 0 {
, := .br.Peek()
if is408Message() {
.closeLocked(errServerClosedIdle)
return
} else {
log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", , )
}
}
if == io.EOF {
.closeLocked(errServerClosedIdle)
} else {
.closeLocked(fmt.Errorf("readLoopPeekFailLocked: %v", ))
}
}
func ( []byte) bool {
if len() < len("HTTP/1.x 408") {
return false
}
if string([:7]) != "HTTP/1." {
return false
}
return string([8:12]) == " 408"
}
func ( *persistConn) ( requestAndChan, *httptrace.ClientTrace) ( *Response, error) {
if != nil && .GotFirstResponseByte != nil {
if , := .br.Peek(1); == nil && len() == 1 {
.GotFirstResponseByte()
}
}
:= 0
const = 5
:= .continueCh
for {
, = ReadResponse(.br, .req)
if != nil {
return
}
:= .StatusCode
if != nil {
if == 100 {
if != nil && .Got100Continue != nil {
.Got100Continue()
}
<- struct{}{}
= nil
} else if >= 200 {
close()
= nil
}
}
:= 100 <= && <= 199
:= && != StatusSwitchingProtocols
if {
++
if > {
return nil, errors.New("net/http: too many 1xx informational responses")
}
.readLimit = .maxHeaderResponseSize()
if != nil && .Got1xxResponse != nil {
if := .Got1xxResponse(, textproto.MIMEHeader(.Header)); != nil {
return nil,
}
}
continue
}
break
}
if .isProtocolSwitch() {
.Body = newReadWriteCloserBody(.br, .conn)
}
.TLS = .tlsState
return
}
func ( *persistConn) ( <-chan struct{}) func() bool {
if == nil {
return nil
}
return func() bool {
:= time.NewTimer(.t.ExpectContinueTimeout)
defer .Stop()
select {
case , := <-:
return
case <-.C:
return true
case <-.closech:
return false
}
}
}
func ( *bufio.Reader, io.ReadWriteCloser) io.ReadWriteCloser {
:= &readWriteCloserBody{ReadWriteCloser: }
if .Buffered() != 0 {
.br =
}
return
}
type readWriteCloserBody struct {
_ incomparable
br *bufio.Reader
io.ReadWriteCloser
}
func ( *readWriteCloserBody) ( []byte) ( int, error) {
if .br != nil {
if := .br.Buffered(); len() > {
= [:]
}
, = .br.Read()
if .br.Buffered() == 0 {
.br = nil
}
return ,
}
return .ReadWriteCloser.Read()
}
type nothingWrittenError struct {
error
}
func ( *persistConn) () {
defer close(.writeLoopDone)
for {
select {
case := <-.writech:
:= .nwrite
:= .req.Request.write(.bw, .isProxy, .req.extra, .waitForContinue(.continueCh))
if , := .(requestBodyReadError); {
= .error
.req.setError()
}
if == nil {
= .bw.Flush()
}
if != nil {
if .nwrite == {
= nothingWrittenError{}
}
}
.writeErrCh <-
.ch <-
if != nil {
.close()
return
}
case <-.closech:
return
}
}
}
const maxWriteWaitBeforeConnReuse = 50 * time.Millisecond
func ( *persistConn) () bool {
select {
case := <-.writeErrCh:
return == nil
default:
:= time.NewTimer(maxWriteWaitBeforeConnReuse)
defer .Stop()
select {
case := <-.writeErrCh:
return == nil
case <-.C:
return false
}
}
}
type responseAndError struct {
_ incomparable
res *Response
err error
}
type requestAndChan struct {
_ incomparable
req *Request
cancelKey cancelKey
ch chan responseAndError
addedGzip bool
continueCh chan<- struct{}
callerGone <-chan struct{}
}
type writeRequest struct {
req *transportRequest
ch chan<- error
continueCh <-chan struct{}
}
type httpError struct {
err string
timeout bool
}
func ( *httpError) () string { return .err }
func ( *httpError) () bool { return .timeout }
func ( *httpError) () bool { return true }
var errTimeout error = &httpError{err: "net/http: timeout awaiting response headers", timeout: true}
var errRequestCanceled = http2errRequestCanceled
var errRequestCanceledConn = errors.New("net/http: request canceled while waiting for connection")
func () {}
var (
testHookEnterRoundTrip = nop
testHookWaitResLoop = nop
testHookRoundTripRetried = nop
testHookPrePendingDial = nop
testHookPostPendingDial = nop
testHookMu sync.Locker = fakeLocker{}
testHookReadLoopBeforeNextRead = nop
)
func ( *persistConn) ( *transportRequest) ( *Response, error) {
testHookEnterRoundTrip()
if !.t.replaceReqCanceler(.cancelKey, .cancelRequest) {
.t.putOrCloseIdleConn()
return nil, errRequestCanceled
}
.mu.Lock()
.numExpectedResponses++
:= .mutateHeaderFunc
.mu.Unlock()
if != nil {
(.extraHeaders())
}
:= false
if !.t.DisableCompression &&
.Header.Get("Accept-Encoding") == "" &&
.Header.Get("Range") == "" &&
.Method != "HEAD" {
= true
.extraHeaders().Set("Accept-Encoding", "gzip")
}
var chan struct{}
if .ProtoAtLeast(1, 1) && .Body != nil && .expectsContinue() {
= make(chan struct{}, 1)
}
if .t.DisableKeepAlives &&
!.wantsClose() &&
!isProtocolSwitchHeader(.Header) {
.extraHeaders().Set("Connection", "close")
}
:= make(chan struct{})
defer close()
defer func() {
if != nil {
.t.setReqCanceler(.cancelKey, nil)
}
}()
const = false
:= .nwrite
:= make(chan error, 1)
.writech <- writeRequest{, , }
:= make(chan responseAndError)
.reqch <- requestAndChan{
req: .Request,
cancelKey: .cancelKey,
ch: ,
addedGzip: ,
continueCh: ,
callerGone: ,
}
var <-chan time.Time
:= .Request.Cancel
:= .Context().Done()
:= .closech
:= false
for {
testHookWaitResLoop()
select {
case := <-:
if {
.logf("writeErrCh resv: %T/%#v", , )
}
if != nil {
.close(fmt.Errorf("write error: %v", ))
return nil, .mapRoundTripError(, , )
}
if := .t.ResponseHeaderTimeout; > 0 {
if {
.logf("starting timer for %v", )
}
:= time.NewTimer()
defer .Stop()
= .C
}
case <-:
= nil
if || .t.replaceReqCanceler(.cancelKey, nil) {
if {
.logf("closech recv: %T %#v", .closed, .closed)
}
return nil, .mapRoundTripError(, , .closed)
}
case <-:
if {
.logf("timeout waiting for response headers.")
}
.close(errTimeout)
return nil, errTimeout
case := <-:
if (.res == nil) == (.err == nil) {
panic(fmt.Sprintf("internal error: exactly one of res or err should be set; nil=%v", .res == nil))
}
if {
.logf("resc recv: %p, %T/%#v", .res, .err, .err)
}
if .err != nil {
return nil, .mapRoundTripError(, , .err)
}
return .res, nil
case <-:
= .t.cancelRequest(.cancelKey, errRequestCanceled)
= nil
case <-:
= .t.cancelRequest(.cancelKey, .Context().Err())
= nil
= nil
}
}
}
type tLogKey struct{}
func ( *transportRequest) ( string, ...any) {
if , := .Request.Context().Value(tLogKey{}).(func(string, ...any)); {
(time.Now().Format(time.RFC3339Nano)+": "+, ...)
}
}
func ( *persistConn) () {
.mu.Lock()
.reused = true
.mu.Unlock()
}
func ( *persistConn) ( error) {
.mu.Lock()
defer .mu.Unlock()
.closeLocked()
}
func ( *persistConn) ( error) {
if == nil {
panic("nil error")
}
.broken = true
if .closed == nil {
.closed =
.t.decConnsPerHost(.cacheKey)
if .alt == nil {
if != errCallerOwnsConn {
.conn.Close()
}
close(.closech)
}
}
.mutateHeaderFunc = nil
}
var portMap = map[string]string{
"http": "80",
"https": "443",
"socks5": "1080",
}
func ( *url.URL) string {
:= .Hostname()
if , := idnaASCII(); == nil {
=
}
:= .Port()
if == "" {
= portMap[.Scheme]
}
return net.JoinHostPort(, )
}
type bodyEOFSignal struct {
body io.ReadCloser
mu sync.Mutex
closed bool
rerr error
fn func(error) error
earlyCloseFn func() error
}
var errReadOnClosedResBody = errors.New("http: read on closed response body")
func ( *bodyEOFSignal) ( []byte) ( int, error) {
.mu.Lock()
, := .closed, .rerr
.mu.Unlock()
if {
return 0, errReadOnClosedResBody
}
if != nil {
return 0,
}
, = .body.Read()
if != nil {
.mu.Lock()
defer .mu.Unlock()
if .rerr == nil {
.rerr =
}
= .condfn()
}
return
}
func ( *bodyEOFSignal) () error {
.mu.Lock()
defer .mu.Unlock()
if .closed {
return nil
}
.closed = true
if .earlyCloseFn != nil && .rerr != io.EOF {
return .earlyCloseFn()
}
:= .body.Close()
return .condfn()
}
func ( *bodyEOFSignal) ( error) error {
if .fn == nil {
return
}
= .fn()
.fn = nil
return
}
type gzipReader struct {
_ incomparable
body *bodyEOFSignal
zr *gzip.Reader
zerr error
}
func ( *gzipReader) ( []byte) ( int, error) {
if .zr == nil {
if .zerr == nil {
.zr, .zerr = gzip.NewReader(.body)
}
if .zerr != nil {
return 0, .zerr
}
}
.body.mu.Lock()
if .body.closed {
= errReadOnClosedResBody
}
.body.mu.Unlock()
if != nil {
return 0,
}
return .zr.Read()
}
func ( *gzipReader) () error {
return .body.Close()
}
type tlsHandshakeTimeoutError struct{}
func (tlsHandshakeTimeoutError) () bool { return true }
func (tlsHandshakeTimeoutError) () bool { return true }
func (tlsHandshakeTimeoutError) () string { return "net/http: TLS handshake timeout" }
type fakeLocker struct{}
func (fakeLocker) () {}
func (fakeLocker) () {}
func ( *tls.Config) *tls.Config {
if == nil {
return &tls.Config{}
}
return .Clone()
}
type connLRU struct {
ll *list.List
m map[*persistConn]*list.Element
}
func ( *connLRU) ( *persistConn) {
if .ll == nil {
.ll = list.New()
.m = make(map[*persistConn]*list.Element)
}
:= .ll.PushFront()
if , := .m[]; {
panic("persistConn was already in LRU")
}
.m[] =
}
func ( *connLRU) () *persistConn {
:= .ll.Back()
:= .Value.(*persistConn)
.ll.Remove()
delete(.m, )
return
}
func ( *connLRU) ( *persistConn) {
if , := .m[]; {
.ll.Remove()
delete(.m, )
}
}
func ( *connLRU) () int {
return len(.m)
}