-
-
Save montanaflynn/ef9e7b9cd21b355cfe8332b4f20163c1 to your computer and use it in GitHub Desktop.
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")) | |
} |
can you please tell the purpose of adding timeout in middleware?
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.
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 infunc 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?
doneChan := make(chan responseData, 1)
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"},
}
}()
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?