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"))
}
@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