Skip to content

Commit 02de901

Browse files
authored
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
1 parent 5e791b0 commit 02de901

File tree

2 files changed

+48
-3
lines changed

2 files changed

+48
-3
lines changed

middleware/timeout.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ package middleware
22

33
import (
44
"context"
5-
"github.com/labstack/echo/v4"
65
"net/http"
76
"time"
7+
8+
"github.com/labstack/echo/v4"
89
)
910

1011
type (
@@ -87,6 +88,10 @@ type echoHandlerFuncWrapper struct {
8788
}
8889

8990
func (t echoHandlerFuncWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
91+
// replace echo.Context Request with the one provided by TimeoutHandler to let later middlewares/handler on the chain
92+
// handle properly it's cancellation
93+
t.ctx.SetRequest(r)
94+
9095
// replace writer with TimeoutHandler custom one. This will guarantee that
9196
// `writes by h to its ResponseWriter will return ErrHandlerTimeout.`
9297
originalWriter := t.ctx.Response().Writer

middleware/timeout_test.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ package middleware
22

33
import (
44
"errors"
5-
"github.com/labstack/echo/v4"
6-
"github.com/stretchr/testify/assert"
75
"net/http"
86
"net/http/httptest"
97
"net/url"
108
"reflect"
119
"strings"
1210
"testing"
1311
"time"
12+
13+
"github.com/labstack/echo/v4"
14+
"github.com/stretchr/testify/assert"
1415
)
1516

1617
func TestTimeoutSkipper(t *testing.T) {
@@ -273,3 +274,42 @@ func TestTimeoutWithDefaultErrorMessage(t *testing.T) {
273274
assert.Equal(t, http.StatusServiceUnavailable, rec.Code)
274275
assert.Equal(t, `<html><head><title>Timeout</title></head><body><h1>Timeout</h1></body></html>`, rec.Body.String())
275276
}
277+
278+
func TestTimeoutCanHandleContextDeadlineOnNextHandler(t *testing.T) {
279+
t.Parallel()
280+
281+
timeout := 1 * time.Millisecond
282+
m := TimeoutWithConfig(TimeoutConfig{
283+
Timeout: timeout,
284+
ErrorMessage: "Timeout! change me",
285+
})
286+
287+
handlerFinishedExecution := make(chan bool)
288+
289+
req := httptest.NewRequest(http.MethodGet, "/", nil)
290+
rec := httptest.NewRecorder()
291+
292+
e := echo.New()
293+
c := e.NewContext(req, rec)
294+
295+
stopChan := make(chan struct{})
296+
err := m(func(c echo.Context) error {
297+
// NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
298+
// the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
299+
// difference over 500microseconds (0.5millisecond) response seems to be reliable
300+
<-stopChan
301+
302+
// The Request Context should have a Deadline set by http.TimeoutHandler
303+
if _, ok := c.Request().Context().Deadline(); !ok {
304+
assert.Fail(t, "No timeout set on Request Context")
305+
}
306+
handlerFinishedExecution <- c.Request().Context().Err() == nil
307+
return c.String(http.StatusOK, "Hello, World!")
308+
})(c)
309+
stopChan <- struct{}{}
310+
311+
assert.NoError(t, err)
312+
assert.Equal(t, http.StatusServiceUnavailable, rec.Code)
313+
assert.Equal(t, "Timeout! change me", rec.Body.String())
314+
assert.False(t, <-handlerFinishedExecution)
315+
}

0 commit comments

Comments
 (0)