mirror of
https://github.com/labstack/echo.git
synced 2025-12-17 23:49:25 +02:00
deprecate timeout middleware
This commit is contained in:
113
CHANGELOG.md
113
CHANGELOG.md
@@ -1,5 +1,118 @@
|
||||
# Changelog
|
||||
|
||||
## v4.15.0 - TBD
|
||||
|
||||
**DEPRECATION NOTICE** Timeout Middleware Deprecated - Use ContextTimeout Instead
|
||||
|
||||
The `middleware.Timeout` middleware has been **deprecated** due to fundamental architectural issues that cause
|
||||
data races. Use `middleware.ContextTimeout` or `middleware.ContextTimeoutWithConfig` instead.
|
||||
|
||||
**Why is this being deprecated?**
|
||||
|
||||
The Timeout middleware manipulates response writers across goroutine boundaries, which causes data races that
|
||||
cannot be reliably fixed without a complete architectural redesign. The middleware:
|
||||
|
||||
- Swaps the response writer using `http.TimeoutHandler`
|
||||
- Must be the first middleware in the chain (fragile constraint)
|
||||
- Can cause races with other middleware (Logger, metrics, custom middleware)
|
||||
- Has been the source of multiple race condition fixes over the years
|
||||
|
||||
**What should you use instead?**
|
||||
|
||||
The `ContextTimeout` middleware (available since v4.12.0) provides timeout functionality using Go's standard
|
||||
context mechanism. It is:
|
||||
|
||||
- Race-free by design
|
||||
- Can be placed anywhere in the middleware chain
|
||||
- Simpler and more maintainable
|
||||
- Compatible with all other middleware
|
||||
|
||||
**Migration Guide:**
|
||||
|
||||
```go
|
||||
// Before (deprecated):
|
||||
e.Use(middleware.Timeout())
|
||||
|
||||
// After (recommended):
|
||||
e.Use(middleware.ContextTimeout(30 * time.Second))
|
||||
```
|
||||
|
||||
With configuration:
|
||||
```go
|
||||
// Before (deprecated):
|
||||
e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
|
||||
Timeout: 30 * time.Second,
|
||||
Skipper: func(c echo.Context) bool {
|
||||
return c.Path() == "/health"
|
||||
},
|
||||
}))
|
||||
|
||||
// After (recommended):
|
||||
e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{
|
||||
Timeout: 30 * time.Second,
|
||||
Skipper: func(c echo.Context) bool {
|
||||
return c.Path() == "/health"
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
**Important Behavioral Differences:**
|
||||
|
||||
1. **Handler cooperation required**: With ContextTimeout, your handlers must check `context.Done()` for cooperative
|
||||
cancellation. The old Timeout middleware would send a 503 response regardless of handler cooperation, but had
|
||||
data race issues.
|
||||
|
||||
2. **Error handling**: ContextTimeout returns errors through the standard error handling flow. Handlers that receive
|
||||
`context.DeadlineExceeded` should handle it appropriately:
|
||||
|
||||
```go
|
||||
e.GET("/long-task", func(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
|
||||
// Example: database query with context
|
||||
result, err := db.QueryContext(ctx, "SELECT * FROM large_table")
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
// Handle timeout
|
||||
return echo.NewHTTPError(http.StatusServiceUnavailable, "Request timeout")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, result)
|
||||
})
|
||||
```
|
||||
|
||||
3. **Background tasks**: For long-running background tasks, use goroutines with context:
|
||||
|
||||
```go
|
||||
e.GET("/async-task", func(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
|
||||
resultCh := make(chan Result, 1)
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
result, err := performLongTask(ctx)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
resultCh <- result
|
||||
}()
|
||||
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
return c.JSON(http.StatusOK, result)
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return echo.NewHTTPError(http.StatusServiceUnavailable, "Request timeout")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
## v4.14.0 - 2025-12-11
|
||||
|
||||
`middleware.Logger` has been deprecated. For request logging, use `middleware.RequestLogger` or
|
||||
|
||||
@@ -11,6 +11,39 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// ContextTimeout Middleware
|
||||
//
|
||||
// ContextTimeout provides request timeout functionality using Go's context mechanism.
|
||||
// It is the recommended replacement for the deprecated Timeout middleware.
|
||||
//
|
||||
//
|
||||
// Basic Usage:
|
||||
//
|
||||
// e.Use(middleware.ContextTimeout(30 * time.Second))
|
||||
//
|
||||
// With Configuration:
|
||||
//
|
||||
// e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{
|
||||
// Timeout: 30 * time.Second,
|
||||
// Skipper: middleware.DefaultSkipper,
|
||||
// }))
|
||||
//
|
||||
// Handler Example:
|
||||
//
|
||||
// e.GET("/task", func(c echo.Context) error {
|
||||
// ctx := c.Request().Context()
|
||||
//
|
||||
// result, err := performTaskWithContext(ctx)
|
||||
// if err != nil {
|
||||
// if errors.Is(err, context.DeadlineExceeded) {
|
||||
// return echo.NewHTTPError(http.StatusServiceUnavailable, "timeout")
|
||||
// }
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// return c.JSON(http.StatusOK, result)
|
||||
// })
|
||||
|
||||
// ContextTimeoutConfig defines the config for ContextTimeout middleware.
|
||||
type ContextTimeoutConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
|
||||
@@ -59,6 +59,12 @@ import (
|
||||
//
|
||||
|
||||
// TimeoutConfig defines the config for Timeout middleware.
|
||||
//
|
||||
// Deprecated: Use ContextTimeoutConfig with ContextTimeout or ContextTimeoutWithConfig instead.
|
||||
// The Timeout middleware has architectural issues that cause data races due to response writer
|
||||
// manipulation across goroutines. It must be the first middleware in the chain, making it fragile.
|
||||
// The ContextTimeout middleware provides timeout functionality using Go's context mechanism,
|
||||
// which is race-free and can be placed anywhere in the middleware chain.
|
||||
type TimeoutConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
@@ -89,11 +95,38 @@ var DefaultTimeoutConfig = TimeoutConfig{
|
||||
|
||||
// Timeout returns a middleware which returns error (503 Service Unavailable error) to client immediately when handler
|
||||
// call runs for longer than its time limit. NB: timeout does not stop handler execution.
|
||||
//
|
||||
// Deprecated: Use ContextTimeout instead. This middleware has known data race issues due to response writer
|
||||
// manipulation. See https://github.com/labstack/echo/blob/master/middleware/context_timeout.go for the
|
||||
// recommended alternative.
|
||||
//
|
||||
// Example migration:
|
||||
//
|
||||
// // Before:
|
||||
// e.Use(middleware.Timeout())
|
||||
//
|
||||
// // After:
|
||||
// e.Use(middleware.ContextTimeout(30 * time.Second))
|
||||
func Timeout() echo.MiddlewareFunc {
|
||||
return TimeoutWithConfig(DefaultTimeoutConfig)
|
||||
}
|
||||
|
||||
// TimeoutWithConfig returns a Timeout middleware with config or panics on invalid configuration.
|
||||
//
|
||||
// Deprecated: Use ContextTimeoutWithConfig instead. This middleware has architectural data race issues.
|
||||
// See the ContextTimeout middleware for a race-free alternative that uses Go's context mechanism.
|
||||
//
|
||||
// Example migration:
|
||||
//
|
||||
// // Before:
|
||||
// e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
|
||||
// Timeout: 30 * time.Second,
|
||||
// }))
|
||||
//
|
||||
// // After:
|
||||
// e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{
|
||||
// Timeout: 30 * time.Second,
|
||||
// }))
|
||||
func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
|
||||
mw, err := config.ToMiddleware()
|
||||
if err != nil {
|
||||
@@ -103,6 +136,8 @@ func TimeoutWithConfig(config TimeoutConfig) echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
// ToMiddleware converts Config to middleware or returns an error for invalid configuration
|
||||
//
|
||||
// Deprecated: Use ContextTimeoutConfig.ToMiddleware instead.
|
||||
func (config TimeoutConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultTimeoutConfig.Skipper
|
||||
|
||||
Reference in New Issue
Block a user