Go Transport

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/benben_2015/article/details/84886088

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)
}

猜你喜欢

转载自blog.csdn.net/benben_2015/article/details/84886088