Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@montanaflynn
Last active October 19, 2023 15:27
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save montanaflynn/ef9e7b9cd21b355cfe8332b4f20163c1 to your computer and use it in GitHub Desktop.
Save montanaflynn/ef9e7b9cd21b355cfe8332b4f20163c1 to your computer and use it in GitHub Desktop.
Gin request timeout middleware and handler
package main
import (
"context"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// timeout middleware wraps the request context with a timeout
func timeoutMiddleware(timeout time.Duration) func(c *gin.Context) {
return func(c *gin.Context) {
// wrap the request context with a timeout
ctx, cancel := context.WithTimeout(c.Request.Context(), timeout)
defer func() {
// check if context timeout was reached
if ctx.Err() == context.DeadlineExceeded {
// write response and abort the request
c.Writer.WriteHeader(http.StatusGatewayTimeout)
c.Abort()
}
//cancel to clear resources after finished
cancel()
}()
// replace request with context wrapped request
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
func timedHandler(duration time.Duration) func(c *gin.Context) {
return func(c *gin.Context) {
// get the underlying request context
ctx := c.Request.Context()
// create the response data type to use as a channel type
type responseData struct {
status int
body map[string]interface{}
}
// create a done channel to tell the request it's done
doneChan := make(chan responseData)
// here you put the actual work needed for the request
// and then send the doneChan with the status and body
// to finish the request by writing the response
go func() {
time.Sleep(duration)
doneChan <- responseData{
status: 200,
body: gin.H{"hello": "world"},
}
}()
// non-blocking select on two channels see if the request
// times out or finishes
select {
// if the context is done it timed out or was cancelled
// so don't return anything
case <-ctx.Done():
return
// if the request finished then finish the request by
// writing the response
case res := <-doneChan:
c.JSON(res.status, res.body)
}
}
}
func main() {
// create new gin without any middleware
engine := gin.New()
// add timeout middleware with 2 second duration
engine.Use(timeoutMiddleware(time.Second * 2))
// create a handler that will last 1 seconds
engine.GET("/short", timedHandler(time.Second))
// create a route that will last 5 seconds
engine.GET("/long", timedHandler(time.Second*5))
// run the server
log.Fatal(engine.Run(":8080"))
}
@Zingphoy
Copy link

can you please tell the purpose of adding timeout in middleware?

It semms that adding timeout in middleware is where "timeout feature" taking effect.

The param duration shows in func timedHandler(duration time.Duration) is an example for the elasped-time of business logic.

@leozvoy
Copy link

leozvoy commented Jan 17, 2021

can you please tell the purpose of adding timeout in middleware?

It semms that adding timeout in middleware is where "timeout feature" taking effect.

The param duration shows in func timedHandler(duration time.Duration) is an example for the elasped-time of business logic.

Hi,
If handler returns at line 71 , will "doneChan" at line 58 never close? And will goroutine at line 56 never return?Is this a problem?
Could you please check this for me?

@fufuok
Copy link

fufuok commented Jun 16, 2022

doneChan := make(chan responseData, 1)

@pavan-aeturi
Copy link

pavan-aeturi commented Sep 3, 2022

data race can still happen here if you usec (*gin.Context) in this go routine. which you have to use in most of the real-time use cases. Isn't it?

		go func() {
			time.Sleep(duration)
			doneChan <- responseData{
				status: 200,
				body:   gin.H{"hello": "world"},
			}
		}()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment