mirror of
https://github.com/gin-gonic/gin.git
synced 2026-07-14 07:07:03 +08:00
Compare commits
4 Commits
66a5a0dfe7
...
91e2b92283
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91e2b92283 | ||
|
|
3ab698dc51 | ||
|
|
c4e549a738 | ||
|
|
0fe68969cf |
37
recovery.go
37
recovery.go
@ -12,12 +12,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||
@ -57,40 +57,33 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
||||
}
|
||||
return func(c *Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if rec := recover(); rec != 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 {
|
||||
var se *os.SyscallError
|
||||
if errors.As(ne, &se) {
|
||||
seStr := strings.ToLower(se.Error())
|
||||
if strings.Contains(seStr, "broken pipe") ||
|
||||
strings.Contains(seStr, "connection reset by peer") {
|
||||
brokenPipe = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if e, ok := err.(error); ok && errors.Is(e, http.ErrAbortHandler) {
|
||||
brokenPipe = true
|
||||
var isBrokenPipe bool
|
||||
err, ok := rec.(error)
|
||||
if ok {
|
||||
isBrokenPipe = errors.Is(err, syscall.EPIPE) ||
|
||||
errors.Is(err, syscall.ECONNRESET) ||
|
||||
errors.Is(err, http.ErrAbortHandler)
|
||||
}
|
||||
if logger != nil {
|
||||
if brokenPipe {
|
||||
logger.Printf("%s\n%s%s", err, secureRequestDump(c.Request), reset)
|
||||
if isBrokenPipe {
|
||||
logger.Printf("%s\n%s%s", rec, secureRequestDump(c.Request), reset)
|
||||
} else if IsDebugging() {
|
||||
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
|
||||
timeFormat(time.Now()), secureRequestDump(c.Request), err, stack(stackSkip), reset)
|
||||
timeFormat(time.Now()), secureRequestDump(c.Request), rec, stack(stackSkip), reset)
|
||||
} else {
|
||||
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
|
||||
timeFormat(time.Now()), err, stack(stackSkip), reset)
|
||||
timeFormat(time.Now()), rec, stack(stackSkip), reset)
|
||||
}
|
||||
}
|
||||
if brokenPipe {
|
||||
if isBrokenPipe {
|
||||
// If the connection is dead, we can't write a status to it.
|
||||
c.Error(err.(error)) //nolint: errcheck
|
||||
c.Error(err) //nolint: errcheck
|
||||
c.Abort()
|
||||
} else {
|
||||
handle(c, err)
|
||||
handle(c, rec)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@ -98,13 +98,13 @@ func TestFunction(t *testing.T) {
|
||||
func TestPanicWithBrokenPipe(t *testing.T) {
|
||||
const expectCode = 204
|
||||
|
||||
expectMsgs := map[syscall.Errno]string{
|
||||
syscall.EPIPE: "broken pipe",
|
||||
syscall.ECONNRESET: "connection reset by peer",
|
||||
expectErrnos := []syscall.Errno{
|
||||
syscall.EPIPE,
|
||||
syscall.ECONNRESET,
|
||||
}
|
||||
|
||||
for errno, expectMsg := range expectMsgs {
|
||||
t.Run(expectMsg, func(t *testing.T) {
|
||||
for _, errno := range expectErrnos {
|
||||
t.Run("Recovery from "+errno.Error(), func(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
|
||||
router := New()
|
||||
@ -122,7 +122,8 @@ func TestPanicWithBrokenPipe(t *testing.T) {
|
||||
w := PerformRequest(router, http.MethodGet, "/recovery")
|
||||
// TEST
|
||||
assert.Equal(t, expectCode, w.Code)
|
||||
assert.Contains(t, strings.ToLower(buf.String()), expectMsg)
|
||||
assert.Contains(t, strings.ToLower(buf.String()), errno.Error())
|
||||
assert.NotContains(t, strings.ToLower(buf.String()), "[Recovery]")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,10 @@
|
||||
|
||||
package render
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Data contains ContentType and bytes data.
|
||||
type Data struct {
|
||||
@ -15,6 +18,9 @@ type Data struct {
|
||||
// Render (Data) writes data with custom ContentType.
|
||||
func (r Data) Render(w http.ResponseWriter) (err error) {
|
||||
r.WriteContentType(w)
|
||||
if len(r.Data) > 0 {
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(r.Data)))
|
||||
}
|
||||
_, err = w.Write(r.Data)
|
||||
return
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"html/template"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -427,6 +428,36 @@ func TestRenderData(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "#!PNG some raw data", w.Body.String())
|
||||
assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
|
||||
assert.Equal(t, "19", w.Header().Get("Content-Length"))
|
||||
}
|
||||
|
||||
func TestRenderDataContentLength(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
size, err := strconv.Atoi(r.URL.Query().Get("size"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
data := Data{
|
||||
ContentType: "application/octet-stream",
|
||||
Data: make([]byte, size),
|
||||
}
|
||||
assert.NoError(t, data.Render(w))
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
for _, size := range []int{0, 1, 100, 100_000} {
|
||||
t.Run(strconv.Itoa(size), func(t *testing.T) {
|
||||
resp, err := http.Get(srv.URL + "?size=" + strconv.Itoa(size))
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, "application/octet-stream", resp.Header.Get("Content-Type"))
|
||||
assert.EqualValues(t, strconv.Itoa(size), resp.Header.Get("Content-Length"))
|
||||
|
||||
actual, err := io.Copy(io.Discard, resp.Body)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, size, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderString(t *testing.T) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user