This is a creation in Article, where the information may have evolved or changed.
Catalogue [−]
- Setdeadline
- Server-side Timeout settings
- http. Listenandserve's error
- About Flow
- Client timeout settings
- Cancel and Context
English Original source: The Complete Guide to Go net/http Timeouts, author: Filippo Valsorda
When you use go to write HTTP server and client, time-out processing is always the most error-prone and one of the most subtle places. Errors can come from many places, and an error can wait for a long time without results until a network failure or process hangs.
HTTP is a complex, multi-phase (multi-stage) protocol, so there is no universal timeout solution, such as a streaming service, a JSON API, and a comet service that has different requirements for timeouts, often the default value is not what you want.
In this article I will disassemble the stages that require a timeout setting and see what different ways to handle it, including the server side and the client.
Setdeadline
First, you need to know the network primitive (primitive) that the go implementation timed out: Deadline (Deadline).
net.Conn
Several methods are available for deadline Set[Read|Write]Deadline(time.Time)
. Deadline is an absolute time value, and when this time is reached, all I/O operations fail, returning a timeout (timeout) error.
deadline not timed out (timeout). Once they are set, they are permanently active (or until the next call to Setdeadline), regardless of whether the connection is being used and how it is used. So if you want to use SetDeadline
the set up timeout mechanism, you have to Read/Write
call it each time before the operation.
You may not want to invoke it yourself SetDeadline
, but instead let it net/http
call you instead, so you can call the more advanced timeout method. Keep in mind, however, that all timeouts are implemented based on deadline, so they will not reset this value every time they receive or send (so they does not reset every times data is sent or received).
The rain of Jiangnan is correct:
It should be because "deadline is an absolute time value" and not a true timeout mechanism, so the author specifically reminds you that this value is not automatically reset and needs to be set manually each time.
Server-side Timeout settings
For servers exposed to the Web, setting timeouts for client connections is critical, otherwise a giant slow or hidden client could cause the file handle to fail to release, resulting in the server having the following error:
Http:accept error:accept TCP [::]:80:accept4:too many open files; Retrying in 5ms
http.Server
There are two ways to set timeouts: ReadTimeout
and and
writetimeout '. You can set them in the display:
12345 |
SRV: = &http. server{ readtimeout: 5 * time. Second, writetimeout: Ten * time. Second,}log. Println (SRV. Listenandserve ()) |
ReadTimeout
The time calculation is completely read from the connection received (accept) to the request body (if you do not read the body, then the time expires until the header is read). Its internal implementation is Accept
called immediately in the SetReadDeadline
method (line of code).
12345678 |
... if d: = C.server.readtimeout; D! = 0 {c.rwc.setreaddeadline (time. Now (). ADD (d))}if d: = C.server.writetimeout; D! = 0 {c.rwc.setwritedeadline (time. Now (). ADD (d))} ... |
WriteTimeout
The time calculation normally begins at the end of the read of the request header to the end of the response write (that is, the declaration period of the Servehttp method), by invoking the implementation at the readRequest
end of the method SetWriteDeadline
(the line of code).
12345678910111213141516 |
func (c *conn) readrequest (CTX context. Context) (w *response, err error) {if c.hijacked () {returnnil, errhijacked}if d: = c. Server. ReadTimeout; D! = 0 {c.rwc.setreaddeadline (time. Now (). ADD (d))}if d: = C.server.writetimeout; D! = 0 {deferfunc() {C.rwc.setwritedeadline (time). Now (). ADD (d))} ()} ...} |
However, when the connection is HTTPS, SetWriteDeadline
Accept
it is called immediately after (code), so its time calculation also includes the time at which the TLS handshake was written. The annoying thing is that this means (and only in this case) that WriteTimeout
the set time also contains the time to read Headerd to the first byte of the body to read.
12345678 |
if tlsconn, OK: = C.RWC. (*tls. Conn); OK {if d: = c.server.readtimeout; d! = 0 {c.rwc.setreaddeadline (time). Now (). ADD (d))}if d: = C.server.writetimeout; D! = 0 {c.rwc.setwritedeadline (time. Now (). ADD (d))} ... |
When you are dealing with untrusted clients and networks, you should set a read-write timeout at the same time, so that the client does not hold the connection because it is slow to read or slow to write.
Finally, there is one more http.TimeoutHandler
way. It is not a server parameter, but a handler wrapper function that can restrict ServeHTTP
invocation. It caches response and sends 504 Gateway Timeout errors if deadline is exceeded. Note that this function has a problem in 1.6 and is corrected in 1.6.2.
http. Listenandserve's error
Incidentally, the package wraps the net/http
bypass http.Server
function http.ListenAndServe
, http.ListenAndServeTLS
and http.Serve
is not suitable for implementing the Internet server. These functions let the timeout setting be not enabled by default, and you have no way to set enable timeout processing. So if you use them, you will soon find a connection leak, too many file handles. I have made this mistake at least five or six times.
Instead, you should create an http.Server
example, set up ReadTimeout
and WriteTimeout
, as in the example above, use the appropriate method.
About Flow
The thing is, there is no way to ServeHTTP
access the underlying from the net.Conn
, so the stream service is forced not to set WriteTimeout
(this may also be why the default value of these values is always 0). If you cannot access net.Conn
it, you cannot invoke it every time Write
SetWriteDeadline
to implement a correct idle timeout.
Also, there is no way to cancel a blocking ResponseWriter.Write
because ResponseWriter.Close
there is no document stating that it can cancel a blocking concurrent write. There is no way to use a timer to create a Russian-handmade timeout cup with a streaming server that cannot be shielded from slow-read clients. I submitted a [bug] (https://github.com/golang/go/issues/16100), welcome feedback.
Editor 's note: The author's statement here is problematic and can be obtained through hijack net.conn, since it is possible to get net. Conn, we can call its Setwritedeadline method. The code example is as follows:
123456789101112131415161718192021222324252627282930313233343536 |
PackageMainImport("FMT""Log""Net/http")funcMain () {http. Handlefunc ("/hijack",func(W http. Responsewriter, R *http. Request) {HJ, OK: = W. (http. Hijacker)if!ok {http. Error (W,"webserver doesn ' t support hijacking", HTTP. Statusinternalservererror)return}conn, BUFRW, err: = HJ. Hijack ()ifErr! =Nil{http. Error (W, err. Error (), HTTP. Statusinternalservererror)return}//Don ' t forget to close the connection:deferConn. Close () Conn. Setwritedeadline (time. Now (). Add(Ten* Time. Second)) Bufrw. WriteString ("Now we ' re speaking raw TCP. Say Hi: ") BUFRW. Flush () s, err: = BUFRW. ReadString (' \ n ')ifErr! =Nil{log. Printf ("Error reading string:%v", err)return}fmt. fprintf (BUFRW,"You said:%q\nbye.\n", s) bufrw. Flush ()})} |
Client timeout settings
Client side of the time-out is complex and complex, said simple is simple, see how you use, the most important thing is not to have the resources to leak the situation or the program is stuck.
The simplest way is to use http.Client
the Timeout
field. Its time calculation includes the connection (Dial) to the response body after reading.
1234 |
c: = &http. client{ Timeout: * time. SECOND,}RESP, err: = C.get ("https://blog.filippo.io/") |
Just like the server side, http.GET
there is no time-out setting when using the client, so it is also dangerous to use on the Internet.
There are some finer-grained timeout controls:
net.Dialer.Timeout
Restricting the time to establish a TCP connection
http.Transport.TLSHandshakeTimeout
Limit the time of the TLS handshake
http.Transport.ResponseHeaderTimeout
Limit the time to read response headers
http.Transport.ExpectContinueTimeout
Restricts the time that the client waits between sending the included Expect: 100-continue
header to the response that receives the continue send body. Note Setting this value in 1.6 disables HTTP/2 ( DefaultTransport
since 1.6.2 is a special case)
1234567891011 |
c: = &http. client{ Transport: &transport{ Dial: (&net. dialer{ Timeout: * time. Second, KeepAlive: * time. Second, }). Dial, tlshandshaketimeout: Ten * time. Second, responseheadertimeout: Ten * time. Second, expectcontinuetimeout: 1 * time. Second, }} |
As I said, there is no way to limit the time it takes to send a request. Read response body (the original is read the request body, according to understand should be read response can be manually controlled) the time spent can be manually through one time.Timer
to achieve, read occurs after the call Client.do (see the next section).
Finally, in Go 1.7, add one http.Transport.IdleConnTimeout
that does not control the blocking phase of the client request, but can control how long a connection in the connection pool can be idle.
Note that a client default can execute the redirect. http.Client.Timeout
contains all redirect
, while fine-grained timeout control parameters are valid only for a single request, because http.Transport
it is an underlying type, not a redirect
concept.
Cancel and Context
net/http
There are two ways to cancel a client request: Request.Cancel
as well as go 1.7 new plus Context
.
Request.Cancel
is an optional channel, when you set this value and close it, the request terminates, as if it were timed out (the actual implementation is the same, I found a 1.7 bug when writing this article, The error returned by all cancel operations is still timeout error).
We can use Request.Cancel
and time.Timer
to construct a fine-grained timeout control that allows you to defer deadline when reading streaming data:
123456789101112131415161718192021222324252627282930313233343536373839404142 |
PackageMainImport("IO" "Io/ioutil" "Log" "Net/http" "Time")funcMain () {c: = Make(Chan struct{}) Timer: = time. Afterfunc(5*time. Second,func() {Close(c)})//Serve bytes every second.Req, Err: = http. Newrequest ("GET","http://httpbin.org/range/2048?duration=8&chunk_size=256",Nil)ifErr! =Nil{log. Fatal (Err)} req. Cancel = c log. Println ("Sending request ...") RESP, err: = http. Defaultclient.do (req)ifErr! =Nil{log. Fatal (ERR)}deferResp. Body.close () log. Println ("Reading body ...") for{Timer. Reset(2* Time. Second)//Try Instead:timer. Reset (* time.millisecond)_, err = Io. Copyn (Ioutil. Discard, resp. Body, the)ifErr = = Io. EOF { Break}Else ifErr! =Nil{log. Fatal (Err)}}} |
In the example above we set a timeout of 5 seconds for the Do method execution phase, but we spent at least 8 seconds executing 8 times to read the desired body and set a 2-second timeout each time. We can do this for the streaming API to avoid the program dying there. If more than two seconds we did not read from the server to the data, Io. Copyn will return an net/http: request canceled
error.
In 1.7, the context package was upgraded and entered into the standard library. The context has a lot of features to learn, but for the content described in this article, you just need to wait until it can be replaced and thrown away Request.Cancel
.
To cancel the request with the context is simple, we just need to get a new context and its cancel () function, which is passed through the context. The Withcancel method gets, then creates a request and uses Request.WithContext
it to bind it. When we want to cancel this request is, we call cancel()
cancel this context:
12345678910 |
CTX, Cancel: = context. Withcancel (context. TODO ()) timer: = time. Afterfunc(5func() { cancel ()}) req, err: = http. Newrequest ("GET""http://httpbin.org/range/2048?duration=8&chunk_size=256"nil) ifnil { log. Fatal (err)}req = req. Withcontext (CTX) |
The benefit of the context is also that if the parent context is canceled (passed in at the time of the context.WithCancel
call), the sub-context is also canceled and the command is passed.
Well, that's all I want to say, I hope I'm not over your reading deadline.
The author's company, CloudFlare, is recruiting in the UK, USA and Singapore. Cloud startups, quite well known.