1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-24 20:14:31 +02:00

Fixing Timeout middleware Context propagation (#1910)

This will let middlewares/handler later on the chain to properly handle
the Timeout middleware Context cancellation.

Fixes #1909
This commit is contained in:
Pablo Andres Fuente 2021-07-09 23:36:03 -03:00 committed by GitHub
parent 5e791b0787
commit 02de901d7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 48 additions and 3 deletions

View File

@ -2,9 +2,10 @@ package middleware
import ( import (
"context" "context"
"github.com/labstack/echo/v4"
"net/http" "net/http"
"time" "time"
"github.com/labstack/echo/v4"
) )
type ( type (
@ -87,6 +88,10 @@ type echoHandlerFuncWrapper struct {
} }
func (t echoHandlerFuncWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request) { func (t echoHandlerFuncWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
// replace echo.Context Request with the one provided by TimeoutHandler to let later middlewares/handler on the chain
// handle properly it's cancellation
t.ctx.SetRequest(r)
// replace writer with TimeoutHandler custom one. This will guarantee that // replace writer with TimeoutHandler custom one. This will guarantee that
// `writes by h to its ResponseWriter will return ErrHandlerTimeout.` // `writes by h to its ResponseWriter will return ErrHandlerTimeout.`
originalWriter := t.ctx.Response().Writer originalWriter := t.ctx.Response().Writer

View File

@ -2,8 +2,6 @@ package middleware
import ( import (
"errors" "errors"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -11,6 +9,9 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
) )
func TestTimeoutSkipper(t *testing.T) { func TestTimeoutSkipper(t *testing.T) {
@ -273,3 +274,42 @@ func TestTimeoutWithDefaultErrorMessage(t *testing.T) {
assert.Equal(t, http.StatusServiceUnavailable, rec.Code) assert.Equal(t, http.StatusServiceUnavailable, rec.Code)
assert.Equal(t, `<html><head><title>Timeout</title></head><body><h1>Timeout</h1></body></html>`, rec.Body.String()) assert.Equal(t, `<html><head><title>Timeout</title></head><body><h1>Timeout</h1></body></html>`, rec.Body.String())
} }
func TestTimeoutCanHandleContextDeadlineOnNextHandler(t *testing.T) {
t.Parallel()
timeout := 1 * time.Millisecond
m := TimeoutWithConfig(TimeoutConfig{
Timeout: timeout,
ErrorMessage: "Timeout! change me",
})
handlerFinishedExecution := make(chan bool)
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
e := echo.New()
c := e.NewContext(req, rec)
stopChan := make(chan struct{})
err := m(func(c echo.Context) error {
// NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
// the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
// difference over 500microseconds (0.5millisecond) response seems to be reliable
<-stopChan
// The Request Context should have a Deadline set by http.TimeoutHandler
if _, ok := c.Request().Context().Deadline(); !ok {
assert.Fail(t, "No timeout set on Request Context")
}
handlerFinishedExecution <- c.Request().Context().Err() == nil
return c.String(http.StatusOK, "Hello, World!")
})(c)
stopChan <- struct{}{}
assert.NoError(t, err)
assert.Equal(t, http.StatusServiceUnavailable, rec.Code)
assert.Equal(t, "Timeout! change me", rec.Body.String())
assert.False(t, <-handlerFinishedExecution)
}