This commit is contained in:
		
							
								
								
									
										30
									
								
								recovery.go
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								recovery.go
									
									
									
									
									
								
							@ -10,9 +10,12 @@ import (
 | 
				
			|||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/http/httputil"
 | 
						"net/http/httputil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
	"runtime"
 | 
						"runtime"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -37,17 +40,38 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
 | 
				
			|||||||
	return func(c *Context) {
 | 
						return func(c *Context) {
 | 
				
			||||||
		defer func() {
 | 
							defer func() {
 | 
				
			||||||
			if err := recover(); err != nil {
 | 
								if err := recover(); err != nil {
 | 
				
			||||||
 | 
									// Check for a broken connection, as it is not really a
 | 
				
			||||||
 | 
									// condition that warrants a panic stack trace.
 | 
				
			||||||
 | 
									var brokenPipe bool
 | 
				
			||||||
 | 
									if ne, ok := err.(*net.OpError); ok {
 | 
				
			||||||
 | 
										if se, ok := ne.Err.(*os.SyscallError); ok {
 | 
				
			||||||
 | 
											if se.Err == syscall.EPIPE || se.Err == syscall.ECONNRESET {
 | 
				
			||||||
 | 
												brokenPipe = true
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				if logger != nil {
 | 
									if logger != nil {
 | 
				
			||||||
					stack := stack(3)
 | 
										stack := stack(3)
 | 
				
			||||||
					if IsDebugging() {
 | 
					 | 
				
			||||||
					httprequest, _ := httputil.DumpRequest(c.Request, false)
 | 
										httprequest, _ := httputil.DumpRequest(c.Request, false)
 | 
				
			||||||
						logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset)
 | 
										if brokenPipe {
 | 
				
			||||||
 | 
											logger.Printf("%s\n%s%s", err, string(httprequest), reset)
 | 
				
			||||||
 | 
										} else if IsDebugging() {
 | 
				
			||||||
 | 
											logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
 | 
				
			||||||
 | 
												timeFormat(time.Now()), string(httprequest), err, stack, reset)
 | 
				
			||||||
					} else {
 | 
										} else {
 | 
				
			||||||
						logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", timeFormat(time.Now()), err, stack, reset)
 | 
											logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
 | 
				
			||||||
 | 
												timeFormat(time.Now()), err, stack, reset)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// If the connection is dead, we can't write a status to it.
 | 
				
			||||||
 | 
									if brokenPipe {
 | 
				
			||||||
 | 
										c.Error(err.(error))
 | 
				
			||||||
 | 
										c.Abort()
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
					c.AbortWithStatus(http.StatusInternalServerError)
 | 
										c.AbortWithStatus(http.StatusInternalServerError)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}()
 | 
							}()
 | 
				
			||||||
		c.Next()
 | 
							c.Next()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,11 +2,16 @@
 | 
				
			|||||||
// Use of this source code is governed by a MIT style
 | 
					// Use of this source code is governed by a MIT style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// +build go1.7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package gin
 | 
					package gin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
@ -72,3 +77,38 @@ func TestFunction(t *testing.T) {
 | 
				
			|||||||
	bs := function(1)
 | 
						bs := function(1)
 | 
				
			||||||
	assert.Equal(t, []byte("???"), bs)
 | 
						assert.Equal(t, []byte("???"), bs)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestPanicWithBrokenPipe asserts that recovery specifically handles
 | 
				
			||||||
 | 
					// writing responses to broken pipes
 | 
				
			||||||
 | 
					func TestPanicWithBrokenPipe(t *testing.T) {
 | 
				
			||||||
 | 
						const expectCode = 204
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expectMsgs := map[syscall.Errno]string{
 | 
				
			||||||
 | 
							syscall.EPIPE:      "broken pipe",
 | 
				
			||||||
 | 
							syscall.ECONNRESET: "connection reset",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for errno, expectMsg := range expectMsgs {
 | 
				
			||||||
 | 
							t.Run(expectMsg, func(t *testing.T) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var buf bytes.Buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								router := New()
 | 
				
			||||||
 | 
								router.Use(RecoveryWithWriter(&buf))
 | 
				
			||||||
 | 
								router.GET("/recovery", func(c *Context) {
 | 
				
			||||||
 | 
									// Start writing response
 | 
				
			||||||
 | 
									c.Header("X-Test", "Value")
 | 
				
			||||||
 | 
									c.Status(expectCode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Oops. Client connection closed
 | 
				
			||||||
 | 
									e := &net.OpError{Err: &os.SyscallError{Err: errno}}
 | 
				
			||||||
 | 
									panic(e)
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								// RUN
 | 
				
			||||||
 | 
								w := performRequest(router, "GET", "/recovery")
 | 
				
			||||||
 | 
								// TEST
 | 
				
			||||||
 | 
								assert.Equal(t, expectCode, w.Code)
 | 
				
			||||||
 | 
								assert.Contains(t, buf.String(), expectMsg)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user