Repository: labstack/echo (go)

Issue:
Recover middleware should not log panic for aborted handler
### Issue Description

Using proxy and recover middleware would print the panic log in cases where the HTTP client aborts the request.
The error that is recovered is `http.ErrAbortHandler`. That one is a special error to signal aborting the HTTP handler and should not be logged.
From `net/http/server.go` https://github.com/golang/go/blob/88be85f18bf0244a2470fdf6719e1b5ca5a5e50a/src/net/http/server.go#L1775: 
```
// ErrAbortHandler is a sentinel panic value to abort a handler.
// While any panic from ServeHTTP aborts the response to the client,
// panicking with ErrAbortHandler also suppresses logging of a stack
// trace to the server's error log.
var ErrAbortHandler = errors.New("net/http: abort Handler")
```
It should be ignored the same way as it is done in `net/http/server.go`.
https://github.com/golang/go/blob/88be85f18bf0244a2470fdf6719e1b5ca5a5e50a/src/net/http/server.go#L1799

See also the comment I found here https://github.com/golang/go/issues/28239#issuecomment-430385295

### Checklist

- [x] Dependencies installed
- [x] No typos
- [x] Searched existing issues and docs


### Expected behaviour
No stack trace for recovery of the `ErrAbortHandler`

### Actual behaviour
I am seeing log such as:
```
"[PANIC RECOVER] net/http: abort Handler goroutine 45 [running]:
github.com/labstack/echo/v4/middleware.RecoverWithConfig.func1.1.1()
    github.com/labstack/echo/v4@v4.7.1/middleware/recover.go:89 +0x11a
panic({0xa18940, 0xc000112ad0})
    runtime/panic.go:1038 +0x215
net/http/httputil.(*ReverseProxy).ServeHTTP(0xc0004c0140, {0xba8d28, 0xc0005baae0}, 0xc0000ac100)
    net/http/httputil/reverseproxy.go:348 +0xeba
github.com/labstack/echo/v4/middleware.ProxyWithConfig.func1.1({0xbc4260, 0xc0000a8500})
    github.com/labstack/echo/v4@v4.7.1/middleware/proxy.go:260 +0x5f0
...
```

### Steps to reproduce

### Version/commit
github.com/labstack/echo/v4 v4.7.1

Pull request description:
Recover middleware should not log panic for aborted handler (fix #2133)
This is the fix for the issue #2133 

The fix is in explicit check in recover middleware defer function to re-throw (panic) the `http.ErrAbortHandler` error.

This specific error is recovered in `net/http/server.go` and per default ignored for logging.
https://github.com/golang/go/blob/88be85f18bf0244a2470fdf6719e1b5ca5a5e50a/src/net/http/server.go#L1799

Patch:
diff --git a/echo.go b/echo.go
index 5b30872..c2f23c1 100644
--- a/echo.go
+++ b/echo.go
@@ -732,7 +732,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
 	return s.Serve(e.Listener)
 }
 
-func (e *Echo) configureServer(s *http.Server) (err error) {
+func (e *Echo) configureServer(s *http.Server) error {
 	// Setup
 	e.colorer.SetOutput(e.Logger.Output())
 	s.ErrorLog = e.StdLogger
@@ -747,10 +747,11 @@ func (e *Echo) configureServer(s *http.Server) (err error) {
 
 	if s.TLSConfig == nil {
 		if e.Listener == nil {
-			e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
+			l, err := newListener(s.Addr, e.ListenerNetwork)
 			if err != nil {
 				return err
 			}
+			e.Listener = l
 		}
 		if !e.HidePort {
 			e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
@@ -791,7 +792,7 @@ func (e *Echo) TLSListenerAddr() net.Addr {
 }
 
 // StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext).
-func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
+func (e *Echo) StartH2CServer(address string, h2s *http2.Server) error {
 	e.startupMutex.Lock()
 	// Setup
 	s := e.Server
@@ -808,11 +809,12 @@ func (e *Echo) StartH2CServer(address string, h2s *http2.Server) (err error) {
 	}
 
 	if e.Listener == nil {
-		e.Listener, err = newListener(s.Addr, e.ListenerNetwork)
+		l, err := newListener(s.Addr, e.ListenerNetwork)
 		if err != nil {
 			e.startupMutex.Unlock()
 			return err
 		}
+		e.Listener = l
 	}
 	if !e.HidePort {
 		e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
diff --git a/middleware/recover.go b/middleware/recover.go
index a621a9e..7b61285 100644
--- a/middleware/recover.go
+++ b/middleware/recover.go
@@ -2,6 +2,7 @@ package middleware
 
 import (
 	"fmt"
+	"net/http"
 	"runtime"
 
 	"github.com/labstack/echo/v4"
@@ -77,6 +78,9 @@ func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
 
 			defer func() {
 				if r := recover(); r != nil {
+					if r == http.ErrAbortHandler {
+						panic(r)
+					}
 					err, ok := r.(error)
 					if !ok {
 						err = fmt.Errorf("%v", r)


Test patch:
diff --git a/middleware/recover_test.go b/middleware/recover_test.go
index 9ac4fee..b27f3b4 100644
--- a/middleware/recover_test.go
+++ b/middleware/recover_test.go
@@ -28,6 +28,35 @@ func TestRecover(t *testing.T) {
 	assert.Contains(t, buf.String(), "PANIC RECOVER")
 }
 
+func TestRecoverErrAbortHandler(t *testing.T) {
+	e := echo.New()
+	buf := new(bytes.Buffer)
+	e.Logger.SetOutput(buf)
+	req := httptest.NewRequest(http.MethodGet, "/", nil)
+	rec := httptest.NewRecorder()
+	c := e.NewContext(req, rec)
+	h := Recover()(echo.HandlerFunc(func(c echo.Context) error {
+		panic(http.ErrAbortHandler)
+	}))
+	defer func() {
+		r := recover()
+		if r == nil {
+			assert.Fail(t, "expecting `http.ErrAbortHandler`, got `nil`")
+		} else {
+			if err, ok := r.(error); ok {
+				assert.ErrorIs(t, err, http.ErrAbortHandler)
+			} else {
+				assert.Fail(t, "not of error type")
+			}
+		}
+	}()
+
+	h(c)
+
+	assert.Equal(t, http.StatusInternalServerError, rec.Code)
+	assert.NotContains(t, buf.String(), "PANIC RECOVER")
+}
+
 func TestRecoverWithConfig_LogLevel(t *testing.T) {
 	tests := []struct {
 		logLevel  log.Lvl