I just implemented a timeout middleware in gin, and it's working, but, in log i get some warnings.
Any idea guys of what can be happening?
func Filter(t time.Duration) func(c *gin.Context) {
return func(c *gin.Context) {
finish := make(chan struct{})
go func() {
c.Next()
finish <- struct{}{}
}()
select {
case <-time.After(t):
c.JSON(504, "timeout")
c.Abort()
case <-finish:
}
}
}
Log output:
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 504 with 200
[GIN-debug] [ERROR] Conn.Write wrote more than the declared Content-Length
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 200 with 500
I bet the issue here is that your request timeouts after t and sends response 504 to browser but then your request in goroutine finally finishes and so it tries to send additional response 200 while headers were already sent. That's why you get Wanted to override status code 504 with 200.
This approach results in a race condition (build with -race) ...the Next() handler and the parent are flagged for unsafe access to the shared Context, which as far as I can tell doesn't provide any locking at all.
I thnk this would be something that could be built into gin, like how go stdlib has TimeoutHandler
You don't need to do this in Middleware, you just have to configure the Server manually instead of using router.Run()
func main() {
router := gin.Default()
s := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
s.ListenAndServe()
}
How would I set the timeout later and not when I call ListenAndServe()? I would like to set response timeout based on a query string in the incoming request.
I am looking at something like response.setTimeout() in node.js.
I would like to intercept the server timeout in a middleware and return 504 in the response. Any idea if this is possible?
@stxml's solution is not doing the same thing as a timeout middleware.
As of the http go doc:
ReadTimeout is the maximum duration for reading the entire request, including the body.
And
WriteTimeout is the maximum duration before timing out writes of the response.
For example, if you put a time.Sleep(20 * time.Second) in a handler, the request will last 20 seconds, even when putting WriteTimeout: 10 * time.Second in your HTTP server.
To write a timeout middleware, you should kill the goroutine from itself as there is no external way to kill it.
As @gnuletik pointed out the ReadTimeout and WriteTimeout is not the same as a handler timeout. Those timeouts are before the headers are read and after the response is written. This issue is for closing long running handlers.
Here is a solution, it's not very pretty or elegant but it works and doesn't have any race conditions:
https://gist.github.com/montanaflynn/ef9e7b9cd21b355cfe8332b4f20163c1
curl -i "localhost:8080/short"
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 07 Apr 2019 10:07:41 GMT
Content-Length: 17
{"hello":"world"}
curl -i "localhost:8080/long"
HTTP/1.1 504 Gateway Timeout
Date: Sun, 07 Apr 2019 10:07:46 GMT
Content-Length: 0
thats a great solution I may use it in my proxy. Ty.
@montanaflynn
I have a problem with this solution.
In this solution, the timeout request actually completely executes in the background. Right?
If there are so many long time request come in.
Will all resources are occupied but the server still accepts to perform requests?
@fnsne yes, the request handler that times out will finish in the goroutine. You could pass the request context to http requests, database operations and other functions inside the handler that use context to stop, but you can't stop the goroutine.
https://golang.org/pkg/net/http/#NewRequestWithContext
https://golang.org/pkg/database/sql/#Conn.QueryContext
same question.
Is there any solution?
Most helpful comment
You don't need to do this in Middleware, you just have to configure the Server manually instead of using router.Run()