在Go 对HTTP POST的支持中,简单介绍了客户端的定义。其中结构体Client
有一个字段Transport
,它用来定义单个HTTP
请求的机制,如果为nil
,则使用默认的DefaultTransport
。下面是Transport
的默认实现,由DefaultClient
使用。它根据需要来建立网络连接,然后将其缓存,供后续调用重用。它可以使用HTTP
代理,例如根据$HTTP_PROXY
和$NO_PROXY
环境变量指定。
var DefaultTransport RoundTripper = &Transport{
Proxy: ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
默认的Transport
指定了六个字段,各个字段的意思接下来将会解说。单从这几个字段的字面大致可以看出,设置的多是超时的时间。而这些值默认情况下都比较大,所以有必要自己重新定义一个Transport
。
type Transport struct {
idleMu sync.Mutex
wantIdle bool
idleConn map[connectMethodKey][]*persistConn
idleConnCh map[connectMethodKey]chan *persistConn
idleLRU connLRU
reqMu sync.Mutex
reqCanceler map[*Request]func(error)
altMu sync.Mutex
altProto atomic.Value
connCountMu sync.Mutex
connPerHostCount map[connectMethodKey]int
connPerHostAvailable map[connectMethodKey]chan struct{}
//Proxy指定一个函数来返回给定请求的代理
Proxy func(*Request) (*url.URL, error)
//DialContext指定用于创建未加密的TCP连接的拨号功能,如果值为nil,则传输使用net包拨号。此方法返回一个Conn接口
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
// Dial指定用于创建未加密的TCP连接的拨号功能,现在已经被DialContext取代,通过后者可以在不需要的时候取消拨号。如果这两个字段都设置了,那么DialContext的优先级高于Dial
Dial func(network, addr string) (net.Conn, error)
// DialTLS指定可选的拨号功能,用于为非代理的HTTPS请求创建TLS连接,如果值为nil,那么使用Dial和TLSClientConfig。如果设置了值,那么拨号将忽略TLSClientConfig和TLSHandshakeTimeout,假设返回的net.Conn已经通过TLS握手。
DialTLS func(network, addr string) (net.Conn, error)
TLSClientConfig *tls.Config
// TLSHandshakeTimeout定义TLS握手等待的最大时间,0表示没有超时
TLSHandshakeTimeout time.Duration
//DisableKeepAlives如果为true,则禁用HTTP的keep-alive,并且仅使用与服务器的连接来获取单个HTTP请求
DisableKeepAlives bool
//DisableCompression如果为true,则阻止对响应body进行压缩
DisableCompression bool
//控制所有主机上的最大空闲连接数,零意味着没有限制
MaxIdleConns int
MaxIdleConnsPerHost int
MaxConnsPerHost int
//IdleConnTimeout是长连接在关闭之前,保持空闲的最长时间,零表示没有限制
IdleConnTimeout time.Duration
//ResponseHeaderTimeout如果非零,则指定在完全写入请求之后,等待服务器响应header的时间量,这个时间不包括读取响应body的时间
ResponseHeaderTimeout time.Duration
//ExpectContinueTimeout定义等待服务器的第一个响应headers的时间,0表示没有超时,则body会立刻发送,无需等待服务器批准,这个时间不包括发送请求header的时间
ExpectContinueTimeout time.Duration
TLSNextProto map[string]func(authority string, c *tls.Conn) RoundTripper
//ProxyConnectHeader是可选参数,指定在CONNECT请求期间发送给代理的headers
ProxyConnectHeader Header
//MaxResponseHeaderBytes指定服务器响应头中允许的响应字节数限制,0表示使用默认限制
MaxResponseHeaderBytes int64
nextProtoOnce sync.Once
h2transport h2Transport // non-nil if http2 wired up
}
凡是默认Transport
用到的字段都在上面加了注释,其中DialContext
字段有两种方法。
第一种是采用DefaultTransport
中那样的方式定义,首先定义一个Dialer
结构体,然后再其上调用DialContext
接口。
Dialer
包含连接地址的一些选项,每个字段的零值相当于没有该选项的拨号,因此Dialer
为零值等同于直接调用Dial
功能。
type Dialer struct {
//Timeout是拨号等待连接完成的最长时间,如果还设置了Deadline,那么可能会提前失败,默认是没有超时,使用TCP拨号到多个IP地址的主机名时,可以在它们之间划分超时。即使有或没有Timeout,操作系统也会强加自己的超时时间,例如,TCP超时的时间一般在3s
Timeout time.Duration
//Deadline是拨号失败的绝对时间点,零表示没有截止日期,或者与Timeout选项一样依赖于操作系统
Deadline time.Time
LocalAdddr Addr
//是否启动dual-stack状态,启用下,会优先选择IPv6连线,如果失败,再尝试IPv4连线
DualStack bool
//FallbackDelay指定在启用DualStack时生成回退连接之前等待的时间长度,如果为0,使用默认延迟300ms
FallbackDelay time.Duration
//KeepAlive指定保持活动网络连接的时间,如果为0,则不启用keep-alive,不支持keep-alive的网络协议会忽略此字段
KeepAlive time.Duration
Resolver *Resolver
Cancel <-chan struct{}
//Control如果不是nil,则在创建网络连接后,实际拨号之前调用它
Control func(network, address string, c syscall.RawConn) error
}
下面看Dialer
结构体上的接口,DialContext
使用提供的context
连接到指定网络上的地址。提供的context
必须非nil
,如果上下文在连接完成之前到期,那么返回错误。成功连接后,context
的任何过期都不会影响连接。
func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) {
/*
...
*/
}
标准包中使用的方法,直接在自定义的Dialer
上调用DialContext
接口。
client := &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 3 * time.Second,
KeepAlive: 10 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 5 * time.Second,
TLSHandshakeTimeout: 1 * time.Mill
}
}
另外一种是直接生成函数的方法,但是这种方法不推荐,比较复杂。思路也是先生成自定义的Dialer
结构体,然后在其上调用Dial
方法。实质上也是在Dialer
上调用DialContext
方法。例如:
client := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, err error) {
c, err := net.DialTimeout(network, addr, time.Second * 3)
if err != nil {
return nil, err
}
return c, nil
}
}
}
之所以上面说调用Dial
方法实质上也是在Dialer
上调用DialContext
方法,原因如下:
func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
d := Dialer {
Timeout: timeout,
}
return d.Dial(network, address)
}
来看看Dial
接口的定义
func (d *Dialer) Dial(network, address string) (Conn, error) {
return d.DialContext(context.Background(), network, address)
}