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

Comment From: arvenil

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.

Comment From: bradclawsie

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.

Comment From: montanaflynn

I thnk this would be something that could be built into gin, like how go stdlib has TimeoutHandler

Comment From: stxml

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

Comment From: bharatkrishna

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.

Comment From: ricardolonga

I would like to intercept the server timeout in a middleware and return 504 in the response. Any idea if this is possible?

Comment From: gnuletik

@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.

Comment From: montanaflynn

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

Comment From: Jim-Lambert-Bose

thats a great solution I may use it in my proxy. Ty.

Comment From: fnsne

@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?

Comment From: montanaflynn

@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

Comment From: dongshimou

same question. Is there any solution?

Comment From: github-zjh

same question. Is there any solution?

you can try this middleware : https://github.com/gin-contrib/timeout, it works for me.

Comment From: Flgado

Its good practice to use gin as a handler inside httpServer instead of using gin.Run() ? Thanks :)