Reputation: 4604
I wondered for the best method to set a timeout for the specific route on the Echo library.
I implemented timeout with context.WithTimeout, but used several functions to encapsulate the context, and I think that was wrong.
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
Is there any middleware or a better method to achieve that?
Upvotes: 2
Views: 4336
Reputation: 697
This is a bit tricky to answer without knowing exactly what you are trying to do. I'll answer first on how to handle context wrapping using WithTimeout
with a middleware.
A middleware can add/modify a request context like so:
func TimeoutMiddleware(timeout time.Duration, next func(w http.ResponseWriter, req *http.Request)) func(w http.ResponseWriter, req *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
// Wrap the existing context from the request
ctx, cancel := context.WithTimeout(req.Context(), timeout)
// Always do this to clean up contexts, otherwise they'll hang out
// and gather since they are blocked go rountines
defer cancel()
// Put the new context into the request
req = req.WithContext(ctx)
// Pass the modified request forward
next(w, req)
// Handle any ctx timeout issues in the general middleware
if err := ctx.Err(); err != nil {
if errors.Is(err, context.DeadlineExceeded) {
log.Println("HTTP Request timed out")
w.Write([]byte("Timed out"))
}
}
}
}
The issue is next(w, req)
requires your http handler to handle a context timeout. If the http handler ignores the context, it will not timeout. Such as this:
func endless(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
// Just a busy loop....
for {
select {
case <-ctx.Done():
// Exit the handler if the context is Done(), even if our function is not.
return
default:
fmt.Println("wait")
time.Sleep(1 * time.Second)
}
}
}
If you are making a database call or something that takes time in a handler, often times the database api accepts a context.Context
to abort early if the context is canceled.
So this solution adds a timeout to the request context going into a handler that you still need to manage.
You can always add the timeouts to your request:
client := http.Client{
Timeout: time.Second,
}
client.Do(...)
Upvotes: 4