mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-18 14:52:18 +08:00
Merge branch 'master' into utree
This commit is contained in:
commit
b4789d32c1
@ -15,6 +15,10 @@ matrix:
|
|||||||
- go: 1.14.x
|
- go: 1.14.x
|
||||||
env:
|
env:
|
||||||
- TESTTAGS=nomsgpack
|
- TESTTAGS=nomsgpack
|
||||||
|
- go: 1.15.x
|
||||||
|
- go: 1.15.x
|
||||||
|
env:
|
||||||
|
- TESTTAGS=nomsgpack
|
||||||
- go: master
|
- go: master
|
||||||
|
|
||||||
git:
|
git:
|
||||||
|
@ -8,14 +8,14 @@
|
|||||||
|
|
||||||
## Gin v1.6.2
|
## Gin v1.6.2
|
||||||
|
|
||||||
### BUFIXES
|
### BUGFIXES
|
||||||
* fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305)
|
* fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305)
|
||||||
### ENHANCEMENTS
|
### ENHANCEMENTS
|
||||||
* Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306)
|
* Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306)
|
||||||
|
|
||||||
## Gin v1.6.1
|
## Gin v1.6.1
|
||||||
|
|
||||||
### BUFIXES
|
### BUGFIXES
|
||||||
* Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294)
|
* Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294)
|
||||||
|
|
||||||
## Gin v1.6.0
|
## Gin v1.6.0
|
||||||
@ -25,7 +25,7 @@
|
|||||||
* drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148)
|
* drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148)
|
||||||
* Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615)
|
* Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615)
|
||||||
### FEATURES
|
### FEATURES
|
||||||
* add yaml negotitation [#2220](https://github.com/gin-gonic/gin/pull/2220)
|
* add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220)
|
||||||
* FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112)
|
* FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112)
|
||||||
### BUGFIXES
|
### BUGFIXES
|
||||||
* Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280)
|
* Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280)
|
||||||
|
46
README.md
46
README.md
@ -5,7 +5,7 @@
|
|||||||
[](https://travis-ci.org/gin-gonic/gin)
|
[](https://travis-ci.org/gin-gonic/gin)
|
||||||
[](https://codecov.io/gh/gin-gonic/gin)
|
[](https://codecov.io/gh/gin-gonic/gin)
|
||||||
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
||||||
[](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc)
|
[](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc)
|
||||||
[](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
[](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
|
[](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
|
||||||
[](https://www.codetriage.com/gin-gonic/gin)
|
[](https://www.codetriage.com/gin-gonic/gin)
|
||||||
@ -496,6 +496,39 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Custom Recovery behavior
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
// Creates a router without any middleware by default
|
||||||
|
r := gin.New()
|
||||||
|
|
||||||
|
// Global middleware
|
||||||
|
// Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
|
||||||
|
// By default gin.DefaultWriter = os.Stdout
|
||||||
|
r.Use(gin.Logger())
|
||||||
|
|
||||||
|
// Recovery middleware recovers from any panics and writes a 500 if there was one.
|
||||||
|
r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
|
||||||
|
if err, ok := recovered.(string); ok {
|
||||||
|
c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
|
||||||
|
}
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
}))
|
||||||
|
|
||||||
|
r.GET("/panic", func(c *gin.Context) {
|
||||||
|
// panic with a string -- the custom middleware could save this to a database or report it to the user
|
||||||
|
panic("foo")
|
||||||
|
})
|
||||||
|
|
||||||
|
r.GET("/", func(c *gin.Context) {
|
||||||
|
c.String(http.StatusOK, "ohai")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen and serve on 0.0.0.0:8080
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### How to write log file
|
### How to write log file
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -725,12 +758,12 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
"gopkg.in/go-playground/validator.v10"
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Booking contains binded and validated data.
|
// Booking contains binded and validated data.
|
||||||
type Booking struct {
|
type Booking struct {
|
||||||
CheckIn time.Time `form:"check_in" binding:"required" time_format:"2006-01-02"`
|
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
|
||||||
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
|
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -767,11 +800,14 @@ func getBookable(c *gin.Context) {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"
|
$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17"
|
||||||
{"message":"Booking dates are valid!"}
|
{"message":"Booking dates are valid!"}
|
||||||
|
|
||||||
$ curl "localhost:8085/bookable?check_in=2018-03-10&check_out=2018-03-09"
|
$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
|
||||||
{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
|
{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
|
||||||
|
|
||||||
|
$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
|
||||||
|
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%
|
||||||
```
|
```
|
||||||
|
|
||||||
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
|
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
|
||||||
|
@ -270,7 +270,7 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
|||||||
|
|
||||||
switch tf := strings.ToLower(timeFormat); tf {
|
switch tf := strings.ToLower(timeFormat); tf {
|
||||||
case "unix", "unixnano":
|
case "unix", "unixnano":
|
||||||
tv, err := strconv.ParseInt(val, 10, 0)
|
tv, err := strconv.ParseInt(val, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,7 @@ func TestMappingTime(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMapiingTimeDuration(t *testing.T) {
|
func TestMappingTimeDuration(t *testing.T) {
|
||||||
var s struct {
|
var s struct {
|
||||||
D time.Duration
|
D time.Duration
|
||||||
}
|
}
|
||||||
|
@ -416,7 +416,11 @@ func (c *Context) QueryArray(key string) []string {
|
|||||||
|
|
||||||
func (c *Context) initQueryCache() {
|
func (c *Context) initQueryCache() {
|
||||||
if c.queryCache == nil {
|
if c.queryCache == nil {
|
||||||
c.queryCache = c.Request.URL.Query()
|
if c.Request != nil {
|
||||||
|
c.queryCache = c.Request.URL.Query()
|
||||||
|
} else {
|
||||||
|
c.queryCache = url.Values{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,6 +410,21 @@ func TestContextQuery(t *testing.T) {
|
|||||||
assert.Empty(t, c.PostForm("foo"))
|
assert.Empty(t, c.PostForm("foo"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextDefaultQueryOnEmptyRequest(t *testing.T) {
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder()) // here c.Request == nil
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
value, ok := c.GetQuery("NoKey")
|
||||||
|
assert.False(t, ok)
|
||||||
|
assert.Empty(t, value)
|
||||||
|
})
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
assert.Equal(t, "nada", c.DefaultQuery("NoKey", "nada"))
|
||||||
|
})
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
assert.Empty(t, c.Query("NoKey"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextQueryAndPostForm(t *testing.T) {
|
func TestContextQueryAndPostForm(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second")
|
body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second")
|
||||||
@ -940,7 +955,7 @@ func TestContextRenderNoContentHTMLString(t *testing.T) {
|
|||||||
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestContextData tests that the response can be written from `bytesting`
|
// TestContextData tests that the response can be written from `bytestring`
|
||||||
// with specified MIME type
|
// with specified MIME type
|
||||||
func TestContextRenderData(t *testing.T) {
|
func TestContextRenderData(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -7,6 +7,7 @@ package gin
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@ -64,6 +65,18 @@ func TestDebugPrintRoutes(t *testing.T) {
|
|||||||
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re)
|
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDebugPrintRouteFunc(t *testing.T) {
|
||||||
|
DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
|
||||||
|
fmt.Fprintf(DefaultWriter, "[GIN-debug] %-6s %-40s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
|
||||||
|
}
|
||||||
|
re := captureOutput(t, func() {
|
||||||
|
SetMode(DebugMode)
|
||||||
|
debugPrintRoute("GET", "/path/to/route/:param1/:param2", HandlersChain{func(c *Context) {}, handlerNameTest})
|
||||||
|
SetMode(TestMode)
|
||||||
|
})
|
||||||
|
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param1/:param2 --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDebugPrintLoadTemplate(t *testing.T) {
|
func TestDebugPrintLoadTemplate(t *testing.T) {
|
||||||
re := captureOutput(t, func() {
|
re := captureOutput(t, func() {
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
|
20
errors.go
20
errors.go
@ -55,7 +55,7 @@ func (msg *Error) SetMeta(data interface{}) *Error {
|
|||||||
|
|
||||||
// JSON creates a properly formatted JSON
|
// JSON creates a properly formatted JSON
|
||||||
func (msg *Error) JSON() interface{} {
|
func (msg *Error) JSON() interface{} {
|
||||||
json := H{}
|
jsonData := H{}
|
||||||
if msg.Meta != nil {
|
if msg.Meta != nil {
|
||||||
value := reflect.ValueOf(msg.Meta)
|
value := reflect.ValueOf(msg.Meta)
|
||||||
switch value.Kind() {
|
switch value.Kind() {
|
||||||
@ -63,16 +63,16 @@ func (msg *Error) JSON() interface{} {
|
|||||||
return msg.Meta
|
return msg.Meta
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
for _, key := range value.MapKeys() {
|
for _, key := range value.MapKeys() {
|
||||||
json[key.String()] = value.MapIndex(key).Interface()
|
jsonData[key.String()] = value.MapIndex(key).Interface()
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
json["meta"] = msg.Meta
|
jsonData["meta"] = msg.Meta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := json["error"]; !ok {
|
if _, ok := jsonData["error"]; !ok {
|
||||||
json["error"] = msg.Error()
|
jsonData["error"] = msg.Error()
|
||||||
}
|
}
|
||||||
return json
|
return jsonData
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaller interface.
|
// MarshalJSON implements the json.Marshaller interface.
|
||||||
@ -135,17 +135,17 @@ func (a errorMsgs) Errors() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a errorMsgs) JSON() interface{} {
|
func (a errorMsgs) JSON() interface{} {
|
||||||
switch len(a) {
|
switch length := len(a); length {
|
||||||
case 0:
|
case 0:
|
||||||
return nil
|
return nil
|
||||||
case 1:
|
case 1:
|
||||||
return a.Last().JSON()
|
return a.Last().JSON()
|
||||||
default:
|
default:
|
||||||
json := make([]interface{}, len(a))
|
jsonData := make([]interface{}, length)
|
||||||
for i, err := range a {
|
for i, err := range a {
|
||||||
json[i] = err.JSON()
|
jsonData[i] = err.JSON()
|
||||||
}
|
}
|
||||||
return json
|
return jsonData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
fs.go
6
fs.go
@ -9,7 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type onlyfilesFS struct {
|
type onlyFilesFS struct {
|
||||||
fs http.FileSystem
|
fs http.FileSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,11 +26,11 @@ func Dir(root string, listDirectory bool) http.FileSystem {
|
|||||||
if listDirectory {
|
if listDirectory {
|
||||||
return fs
|
return fs
|
||||||
}
|
}
|
||||||
return &onlyfilesFS{fs}
|
return &onlyFilesFS{fs}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open conforms to http.Filesystem.
|
// Open conforms to http.Filesystem.
|
||||||
func (fs onlyfilesFS) Open(name string) (http.File, error) {
|
func (fs onlyFilesFS) Open(name string) (http.File, error) {
|
||||||
f, err := fs.fs.Open(name)
|
f, err := fs.fs.Open(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
27
recovery.go
27
recovery.go
@ -26,13 +26,29 @@ var (
|
|||||||
slash = []byte("/")
|
slash = []byte("/")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RecoveryFunc defines the function passable to CustomRecovery.
|
||||||
|
type RecoveryFunc func(c *Context, err interface{})
|
||||||
|
|
||||||
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
||||||
func Recovery() HandlerFunc {
|
func Recovery() HandlerFunc {
|
||||||
return RecoveryWithWriter(DefaultErrorWriter)
|
return RecoveryWithWriter(DefaultErrorWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
|
||||||
|
func CustomRecovery(handle RecoveryFunc) HandlerFunc {
|
||||||
|
return RecoveryWithWriter(DefaultErrorWriter, handle)
|
||||||
|
}
|
||||||
|
|
||||||
// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
|
// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
|
||||||
func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
|
||||||
|
if len(recovery) > 0 {
|
||||||
|
return CustomRecoveryWithWriter(out, recovery[0])
|
||||||
|
}
|
||||||
|
return CustomRecoveryWithWriter(out, defaultHandleRecovery)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
|
||||||
|
func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
||||||
var logger *log.Logger
|
var logger *log.Logger
|
||||||
if out != nil {
|
if out != nil {
|
||||||
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
|
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
|
||||||
@ -70,13 +86,12 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
|||||||
timeFormat(time.Now()), err, stack, reset)
|
timeFormat(time.Now()), err, stack, reset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the connection is dead, we can't write a status to it.
|
|
||||||
if brokenPipe {
|
if brokenPipe {
|
||||||
|
// If the connection is dead, we can't write a status to it.
|
||||||
c.Error(err.(error)) // nolint: errcheck
|
c.Error(err.(error)) // nolint: errcheck
|
||||||
c.Abort()
|
c.Abort()
|
||||||
} else {
|
} else {
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
handle(c, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -84,6 +99,10 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func defaultHandleRecovery(c *Context, err interface{}) {
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
// stack returns a nicely formatted stack frame, skipping skip frames.
|
// stack returns a nicely formatted stack frame, skipping skip frames.
|
||||||
func stack(skip int) []byte {
|
func stack(skip int) []byte {
|
||||||
buf := new(bytes.Buffer) // the returned data
|
buf := new(bytes.Buffer) // the returned data
|
||||||
|
106
recovery_test.go
106
recovery_test.go
@ -62,7 +62,7 @@ func TestPanicInHandler(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "panic recovered")
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem")
|
assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem")
|
||||||
assert.Contains(t, buffer.String(), "TestPanicInHandler")
|
assert.Contains(t, buffer.String(), t.Name())
|
||||||
assert.NotContains(t, buffer.String(), "GET /recovery")
|
assert.NotContains(t, buffer.String(), "GET /recovery")
|
||||||
|
|
||||||
// Debug mode prints the request
|
// Debug mode prints the request
|
||||||
@ -144,3 +144,107 @@ func TestPanicWithBrokenPipe(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCustomRecoveryWithWriter(t *testing.T) {
|
||||||
|
errBuffer := new(bytes.Buffer)
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
router := New()
|
||||||
|
handleRecovery := func(c *Context, err interface{}) {
|
||||||
|
errBuffer.WriteString(err.(string))
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
router.Use(CustomRecoveryWithWriter(buffer, handleRecovery))
|
||||||
|
router.GET("/recovery", func(_ *Context) {
|
||||||
|
panic("Oupps, Houston, we have a problem")
|
||||||
|
})
|
||||||
|
// RUN
|
||||||
|
w := performRequest(router, "GET", "/recovery")
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
|
assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem")
|
||||||
|
assert.Contains(t, buffer.String(), t.Name())
|
||||||
|
assert.NotContains(t, buffer.String(), "GET /recovery")
|
||||||
|
|
||||||
|
// Debug mode prints the request
|
||||||
|
SetMode(DebugMode)
|
||||||
|
// RUN
|
||||||
|
w = performRequest(router, "GET", "/recovery")
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
|
|
||||||
|
assert.Equal(t, strings.Repeat("Oupps, Houston, we have a problem", 2), errBuffer.String())
|
||||||
|
|
||||||
|
SetMode(TestMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomRecovery(t *testing.T) {
|
||||||
|
errBuffer := new(bytes.Buffer)
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
router := New()
|
||||||
|
DefaultErrorWriter = buffer
|
||||||
|
handleRecovery := func(c *Context, err interface{}) {
|
||||||
|
errBuffer.WriteString(err.(string))
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
router.Use(CustomRecovery(handleRecovery))
|
||||||
|
router.GET("/recovery", func(_ *Context) {
|
||||||
|
panic("Oupps, Houston, we have a problem")
|
||||||
|
})
|
||||||
|
// RUN
|
||||||
|
w := performRequest(router, "GET", "/recovery")
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
|
assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem")
|
||||||
|
assert.Contains(t, buffer.String(), t.Name())
|
||||||
|
assert.NotContains(t, buffer.String(), "GET /recovery")
|
||||||
|
|
||||||
|
// Debug mode prints the request
|
||||||
|
SetMode(DebugMode)
|
||||||
|
// RUN
|
||||||
|
w = performRequest(router, "GET", "/recovery")
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
|
|
||||||
|
assert.Equal(t, strings.Repeat("Oupps, Houston, we have a problem", 2), errBuffer.String())
|
||||||
|
|
||||||
|
SetMode(TestMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) {
|
||||||
|
errBuffer := new(bytes.Buffer)
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
router := New()
|
||||||
|
DefaultErrorWriter = buffer
|
||||||
|
handleRecovery := func(c *Context, err interface{}) {
|
||||||
|
errBuffer.WriteString(err.(string))
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
router.Use(RecoveryWithWriter(DefaultErrorWriter, handleRecovery))
|
||||||
|
router.GET("/recovery", func(_ *Context) {
|
||||||
|
panic("Oupps, Houston, we have a problem")
|
||||||
|
})
|
||||||
|
// RUN
|
||||||
|
w := performRequest(router, "GET", "/recovery")
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, buffer.String(), "panic recovered")
|
||||||
|
assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem")
|
||||||
|
assert.Contains(t, buffer.String(), t.Name())
|
||||||
|
assert.NotContains(t, buffer.String(), "GET /recovery")
|
||||||
|
|
||||||
|
// Debug mode prints the request
|
||||||
|
SetMode(DebugMode)
|
||||||
|
// RUN
|
||||||
|
w = performRequest(router, "GET", "/recovery")
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
|
|
||||||
|
assert.Equal(t, strings.Repeat("Oupps, Houston, we have a problem", 2), errBuffer.String())
|
||||||
|
|
||||||
|
SetMode(TestMode)
|
||||||
|
}
|
||||||
|
@ -187,7 +187,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
|
|||||||
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
|
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
|
||||||
|
|
||||||
return func(c *Context) {
|
return func(c *Context) {
|
||||||
if _, nolisting := fs.(*onlyfilesFS); nolisting {
|
if _, noListing := fs.(*onlyFilesFS); noListing {
|
||||||
c.Writer.WriteHeader(http.StatusNotFound)
|
c.Writer.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user