首先了解一个概念Deadline
net.Conn为Deadline提供了多个方法,如Set[Read|Write]Deadline(time.Time)。Deadline是一个绝对时间值,当到达这个时间的时候,对应的读或写操作会失败,返回超时(timeout)错误。
这里需要了解两点:
- Deadline不是超时(timeout),这个值不会自动重置,需要每次手动设置。
- 所有的超时的实现都是基于Deadline的。
服务器超时设置
http.ListenAndServe的错误
顺便说一下,net/http包下的http.ListenAndServe、http.ListenAndServeTLS和http.Serve并不适合实现互联网服务器。这些函数默认不启用超时时间,并且你没办法设置启用超时处理。取而代之,你应该创建一个http.Server实例,设置ReadTimeout和WriteTimeout。
使用http.Server设置服务器超时
http.Server有两个设置超时的方法: ReadTimeout
和WriteTimeout
|
|
ReadTimeout的时间计算是从连接被accept到request body完全被读取(如果你不读取body,时间截止到读取完header为止)。它的内部实现是在Accept立即调用SetReadDeadline方法
|
|
WriteTimeout的时间计算正常是从request header的读取结束开始,到response write结束为止,它是通过在readRequest方法结束后调用SetWriteDeadline实现的。
|
|
但是当连接时HTTPS时,SetWriteDeadline会在Accept之后立即调用,所以它的时间计算也包括TLS握手时写的时间。讨厌的是,这就意味着WriteTimeout设置的时间也包含读取Header到读取body第一个字节这段时间。
|
|
当你处理不可信的客户端和网络时,你应该同时设置读写超时,这样客户端就不会因为读慢或者写慢长久的持有这个连接了。
客户端超时设置
client设置超时时间主要是为了防止程序卡住。最简单的方式是使用http.Client的Timeout字段,它的时间计算包括从连接(dial)到读完response body
|
|
http.Get无法设置超时时间,一般不直接使用
更细粒度的超时控制net.Dialer.Timeout
限制建立TCP连接的时间http.Transport.TLSHandshakeTimeout
限制TLS握手的时间htp.Transport.ResponseHeaderTimeout
限制读取response header的时间http.Transport.ExpectContinueTimeout
限制client在发送包含Expect:100-continue的header到收到继续发送body的response之间的时间等待。
|
|
注意,没有办法限制发送request的时间。
读取response body的时间花费可以手工的通过一个time.Timer来实现,读取发生在调用Client.Do之后
最后在Go1.7中,增加了一个http.Transport.IdleConnTimeout,它不控制client request的阻塞阶段,但是可以控制连接池中一个连接可以idle多长时间。
注意Client缺省的可以执行redirect.http.Client.Timeout包含所有的redirect,而细粒度的超时控制参数只针对单词请求有效,因为http.Transport是一个底层的类型,没有redirect的概念
Cancel和Context
net/http
提供了两种方式取消一个client的请求: Request.Cancel
和Go1.7新加的Context
Request.Cancel是一个可选的channel,当设置这个值并且close它的时候,request就会终止,就好像超时了一样
如果选用Context的话取消请求很简单,我们只需得到一个新的Context和它的cancel函数,这是通过context.WithCancel方法得到的,然后创建一个request兵使用Requst.WithContext绑定它。当我们想取消这个请求是,我们调`用cancel()去掉这个Context:
|
|
Context好处还在于如果parent context被取消的时候(在context.WithCancel调用的时候传递进来的),命令可以进行传递,子context也收到取消通知。