Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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"))
}
@leo81122
Copy link

leo81122 commented Dec 4, 2020

Hi,
I have a question.
If handler returns at line 71 , will "doneChan" at line 58 never close? And will goroutine at line 56 never return?

@venkatzgithub
Copy link

venkatzgithub commented Jan 15, 2021

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

@Zingphoy
Copy link

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

@leo81122
Copy link

leo81122 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)

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