Repository: labstack/echo (go)

Issue:
timeout middleware returns wrong HTTP status code
### Issue Description
When using the timeout middleware function the HTTP status codes do not work as expected. Returning an error in the actual handler function results in the status code `200` independent of the implementation of the error handler.

### Expected behaviour
If the handler function returns an error, the HTTP status code should be handled by the error handler and not modified by the underlying middleware. In case of the `echo.HTTPError` it should also use the associated status code.

### Actual behaviour
Returning an error results in a `200` status code. Writing a result from the handler such as `c.JSON(..)` etc. results in the expected HTTP response but prints an error log to the stdout. (see below for example)

### Steps to reproduce
The following example is a minimum example that can be used to reproduce the behaviour using just echo `v4.2.1`. In this example the timeout does not really matter. It is intentionally set to a very high threshold. For a `/test` call the server raises an error because it just can't find what it's looking for 😃  

```go
package main

import (
	"log"
	"net/http"
	"time"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func main() {
	e := echo.New()
	e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{
		Timeout: 30 * time.Minute, // unnecessary high timeout for test
	}))
	e.GET("/test", func(_ echo.Context) error {
		return echo.NewHTTPError(http.StatusNotFound, "just not there")
	})
	log.Panic(e.Start(":8080"))
}
```
Calling the server with `curl -i http://localhost:8080/test` results in:
```
HTTP/1.1 200 OK
Date: Wed, 10 Mar 2021 13:54:25 GMT
Content-Length: 29
Content-Type: text/plain; charset=utf-8

{"message":"just not there"}
```
The body of the response reflects the error message defined in the handler but the HTTP status code is wrong. Removing the timeout middleware results in the expected behaviour:
```
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=UTF-8
Date: Wed, 10 Mar 2021 13:56:47 GMT
Content-Length: 29

{"message":"just not there"}
```
---
Independent of wrong HTTP status codes in case of an error the log will always print the following error message.
```
echo: http: superfluous response.WriteHeader call from github.com/labstack/echo/v4.(*Response).WriteHeader (response.go:63)
```
#### Analysis

After reading through the code a little bit my first analysis would point to the `http.TimeoutHandler` as the root problem here. Within the handler it writes the status code header whether there is an error or not (https://github.com/golang/go/blob/master/src/net/http/server.go#L3322). The default case for not timed out requests is status code `200`. The middleware catches a potential error and handles it and therefore the `http.TimeoutHandler` will write this status code as header value. Later on echo will try to write the status code from the error handler. This results in the error message printed to the stdout. If the handler function is a success the `http.TimeoutHandler` will be the second one to try and set the status code header. In both cases there are two places in the code that try to write the status code header which is not possible. I hope this was kind of understandable.

My personal opinion is that `http.TimeoutHandler` might not be the best fit for a middleware. It is designed to decorate an existing handler in the standard golang sdk. Also it attempts to write the status code and the body depending on the outcome of the handler function. In the echo environment I feel this is rather uncommon. The current middleware implementation also needs a workaround to report a potential error out of this using the `echoHandlerFuncWrapper`.

### Version/commit

echo version: `v4.2.1`
golang version: `go version go1.16 darwin/amd64`

Pull request:
fix timeout middleware not sending status code when handler returns an error (fix #1804)
fix timeout middleware not sending status code when handler returns an error (fix #1804)

Patch:
diff --git a/middleware/timeout.go b/middleware/timeout.go
index 68f464e..99d436a 100644
--- a/middleware/timeout.go
+++ b/middleware/timeout.go
@@ -106,6 +106,11 @@ func (t echoHandlerFuncWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Reques
 	// so on timeout writer stays what http.TimeoutHandler uses and prevents writing headers/body
 	t.ctx.Response().Writer = originalWriter
 	if err != nil {
+		// call global error handler to write error to the client. This is needed or `http.TimeoutHandler` will send status code by itself
+		// and after that our tries to write status code will not work anymore
+		t.ctx.Error(err)
+		// we pass error from handler to middlewares up in handler chain to act on it if needed. But this means that
+		// global error handler is probably be called twice as `t.ctx.Error` already does that.
 		t.errChan <- err
 	}
 }


Test patch:
diff --git a/middleware/timeout_test.go b/middleware/timeout_test.go
index af4c626..8f8fa30 100644
--- a/middleware/timeout_test.go
+++ b/middleware/timeout_test.go
@@ -71,10 +71,34 @@ func TestTimeoutErrorOutInHandler(t *testing.T) {
 	c := e.NewContext(req, rec)
 
 	err := m(func(c echo.Context) error {
-		return errors.New("err")
+		return echo.NewHTTPError(http.StatusTeapot, "err")
 	})(c)
 
 	assert.Error(t, err)
+	assert.Equal(t, http.StatusTeapot, rec.Code)
+	assert.Equal(t, "{\"message\":\"err\"}\n", rec.Body.String())
+}
+
+func TestTimeoutSuccessfulRequest(t *testing.T) {
+	t.Parallel()
+	m := TimeoutWithConfig(TimeoutConfig{
+		// Timeout has to be defined or the whole flow for timeout middleware will be skipped
+		Timeout: 50 * time.Millisecond,
+	})
+
+	req := httptest.NewRequest(http.MethodGet, "/", nil)
+	rec := httptest.NewRecorder()
+
+	e := echo.New()
+	c := e.NewContext(req, rec)
+
+	err := m(func(c echo.Context) error {
+		return c.JSON(http.StatusCreated, map[string]string{"data": "ok"})
+	})(c)
+
+	assert.NoError(t, err)
+	assert.Equal(t, http.StatusCreated, rec.Code)
+	assert.Equal(t, "{\"data\":\"ok\"}\n", rec.Body.String())
 }
 
 func TestTimeoutOnTimeoutRouteErrorHandler(t *testing.T) {