1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-28 21:08:39 +02:00
echo/middleware/timeout.go

94 lines
2.4 KiB
Go

// +build go1.13
package middleware
import (
"context"
"fmt"
"github.com/labstack/echo/v4"
"time"
)
type (
// TimeoutConfig defines the config for Timeout middleware.
TimeoutConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// ErrorHandler defines a function which is executed for a timeout
// It can be used to define a custom timeout error
ErrorHandler TimeoutErrorHandlerWithContext
// Timeout configures a timeout for the middleware, defaults to 0 for no timeout
Timeout time.Duration
}
// TimeoutErrorHandlerWithContext is an error handler that is used with the timeout middleware so we can
// handle the error as we see fit
TimeoutErrorHandlerWithContext func(error, echo.Context) error
)
var (
// DefaultTimeoutConfig is the default Timeout middleware config.
DefaultTimeoutConfig = TimeoutConfig{
Skipper: DefaultSkipper,
Timeout: 0,
ErrorHandler: nil,
}
)
// Timeout returns a middleware which recovers from panics anywhere in the chain
// and handles the control to the centralized HTTPErrorHandler.
func Timeout() echo.MiddlewareFunc {
return TimeoutWithConfig(DefaultTimeoutConfig)
}
// TimeoutWithConfig returns a Timeout middleware with config.
// See: `Timeout()`.
func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultTimeoutConfig.Skipper
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) || config.Timeout == 0 {
return next(c)
}
ctx, cancel := context.WithTimeout(c.Request().Context(), config.Timeout)
defer cancel()
// this does a deep clone of the context, wondering if there is a better way to do this?
c.SetRequest(c.Request().Clone(ctx))
done := make(chan error, 1)
go func() {
defer func() {
if r := recover(); r != nil {
err, ok := r.(error)
if !ok {
err = fmt.Errorf("panic recovered in timeout middleware: %v", r)
}
c.Logger().Error(err)
done <- err
}
}()
// This goroutine will keep running even if this middleware times out and
// will be stopped when ctx.Done() is called down the next(c) call chain
done <- next(c)
}()
select {
case <-ctx.Done():
if config.ErrorHandler != nil {
return config.ErrorHandler(ctx.Err(), c)
}
return ctx.Err()
case err := <-done:
return err
}
}
}
}