Repository: labstack/echo (go)

Issue:
Timeout middleware should propagate context.Context on echo.Context http Request
### Issue Description
When the Timeout middleware is used and the timeout is reached, the http.Request Context propagated isn't properly cancelled.

### Expected behavior

With the server example below this logs should be seen

```bash
{"time":"2021-07-06T00:05:08.341357-03:00","level":"INFO","prefix":"echo","file":"main.go","line":"23","message":"Start Log Job..."}
{"time":"2021-07-06T00:05:10.342116-03:00","level":"WARN","prefix":"echo","file":"main.go","line":"28","message":"Long Job Cancelled!!!"}
{"time":"2021-07-06T00:05:16.342187-03:00","level":"INFO","prefix":"echo","file":"main.go","line":"40","message":"Previous Long Middleware Finished!!!"}
```

### Actual behavior

With the server example below this logs are seen

```bash
{"time":"2021-07-06T00:07:50.818216-03:00","level":"INFO","prefix":"echo","file":"main.go","line":"22","message":"Start Log Job..."}
{"time":"2021-07-06T00:07:54.819956-03:00","level":"INFO","prefix":"echo","file":"main.go","line":"25","message":"Long Job Finished!!!"}
{"time":"2021-07-06T00:07:58.819891-03:00","level":"INFO","prefix":"echo","file":"main.go","line":"39","message":"Previous Long Middleware Finished!!!"}
```

### Steps to reproduce

Perform this request to the server example below

```bash
curl --request GET --url http://127.0.0.1:8080/ --header 'content-type: application/json'
Timeout!!!
```

### Working code to debug

```go
package main

import (
	"net/http"
	"time"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

const longJobFinished string = "Long Job Finished!!!"

func main() {
	// Echo instance
	e := echo.New()
	e.Debug = true

	// Middleware
	e.Use(middleware.Recover())

	handlerFunc := func(c echo.Context) error {
		e.Logger.Info("Start Log Job...")
		select {
		case <-time.After(4 * time.Second):
			e.Logger.Info(longJobFinished)
		case <-c.Request().Context().Done():
			e.Logger.Warn("Long Job Cancelled!!!")
			return nil
		}
		return c.String(http.StatusOK, longJobFinished)
	}

	// This middleware that sleeps is needed because to avoid the original Request Context gets cancelled by Go stdlib
	// See https://golang.org/pkg/net/http/#Request.Context
	previousLongMiddleware := func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			next(c)
			time.Sleep(6 * time.Second)
			e.Logger.Info("Previous Long Middleware Finished!!!")
			return nil
		}
	}

	// Timeout handler middleware
	middlewareFunc := middleware.TimeoutWithConfig(middleware.TimeoutConfig{
		Timeout:      2 * time.Second,
		ErrorMessage: "Timeout!!!",
	})
	// Handler with timeout middleware
	e.GET("/", handlerFunc, previousLongMiddleware, middlewareFunc)

	// Start server
	e.Logger.Fatal(e.Start(":8080"))
}
```

### Version/commit
4.3.0

Pull request:
Fixing Timeout middleware Context propagation
This will let middlewares/handler later on the chain to properly handle the Timeout middleware Context cancellation.

Fixes #1909

Patch:
diff --git a/middleware/timeout.go b/middleware/timeout.go
index fb8ae42..7311365 100644
--- a/middleware/timeout.go
+++ b/middleware/timeout.go
@@ -2,9 +2,10 @@ package middleware
 
 import (
 	"context"
-	"github.com/labstack/echo/v4"
 	"net/http"
 	"time"
+
+	"github.com/labstack/echo/v4"
 )
 
 type (
@@ -87,6 +88,10 @@ type echoHandlerFuncWrapper struct {
 }
 
 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
 	// `writes by h to its ResponseWriter will return ErrHandlerTimeout.`
 	originalWriter := t.ctx.Response().Writer


Test patch:
diff --git a/middleware/timeout_test.go b/middleware/timeout_test.go
index f9f1826..80891e8 100644
--- a/middleware/timeout_test.go
+++ b/middleware/timeout_test.go
@@ -2,8 +2,6 @@ package middleware
 
 import (
 	"errors"
-	"github.com/labstack/echo/v4"
-	"github.com/stretchr/testify/assert"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -11,6 +9,9 @@ import (
 	"strings"
 	"testing"
 	"time"
+
+	"github.com/labstack/echo/v4"
+	"github.com/stretchr/testify/assert"
 )
 
 func TestTimeoutSkipper(t *testing.T) {
@@ -273,3 +274,42 @@ func TestTimeoutWithDefaultErrorMessage(t *testing.T) {
 	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())
 }
+
+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)
+}