-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: [#521] User can custom recover when a request panic #132
Changes from 16 commits
4c5f9b8
f356447
736add3
fcee6dd
7b81bde
5fa4bea
348cccb
f889ae8
84f834a
33094fc
3d748c5
581d296
62a3a17
ef469db
693a723
d355f39
8ba93a3
2065746
d6ce4aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,17 +1,16 @@ | ||||||
package fiber | ||||||
|
||||||
import ( | ||||||
"fmt" | ||||||
"io" | ||||||
"net/http" | ||||||
"testing" | ||||||
"time" | ||||||
|
||||||
"github.com/gofiber/fiber/v2" | ||||||
contractshttp "github.com/goravel/framework/contracts/http" | ||||||
mocksconfig "github.com/goravel/framework/mocks/config" | ||||||
mockslog "github.com/goravel/framework/mocks/log" | ||||||
"github.com/stretchr/testify/assert" | ||||||
"github.com/stretchr/testify/mock" | ||||||
"github.com/stretchr/testify/require" | ||||||
) | ||||||
|
||||||
|
@@ -26,48 +25,66 @@ func TestTimeoutMiddleware(t *testing.T) { | |||||
|
||||||
route.Middleware(Timeout(1*time.Second)).Get("/timeout", func(ctx contractshttp.Context) contractshttp.Response { | ||||||
time.Sleep(2 * time.Second) | ||||||
|
||||||
return ctx.Response().Success().String("timeout") | ||||||
return nil | ||||||
}) | ||||||
|
||||||
route.Middleware(Timeout(1*time.Second)).Get("/normal", func(ctx contractshttp.Context) contractshttp.Response { | ||||||
return ctx.Response().Success().String("normal") | ||||||
}) | ||||||
route.Middleware(Timeout(1*time.Second)).Get("/panic", func(ctx contractshttp.Context) contractshttp.Response { | ||||||
panic(1) | ||||||
|
||||||
route.Middleware(Timeout(5*time.Second)).Get("/panic", func(ctx contractshttp.Context) contractshttp.Response { | ||||||
panic("test panic") | ||||||
}) | ||||||
|
||||||
req, err := http.NewRequest("GET", "/timeout", nil) | ||||||
require.NoError(t, err) | ||||||
globalRecover := func(ctx contractshttp.Context, err any) { | ||||||
ctx.Request().AbortWithStatusJson(http.StatusInternalServerError, fiber.Map{"error": "Internal Panic"}) | ||||||
} | ||||||
route.Recover(globalRecover) | ||||||
|
||||||
mockLog := mockslog.NewLog(t) | ||||||
|
||||||
resp, err := route.instance.Test(req, -1) | ||||||
assert.NoError(t, err) | ||||||
assert.Equal(t, http.StatusGatewayTimeout, resp.StatusCode) | ||||||
t.Run("timeout", func(t *testing.T) { | ||||||
req, err := http.NewRequest("GET", "/timeout", nil) | ||||||
require.NoError(t, err) | ||||||
|
||||||
req, err = http.NewRequest("GET", "/normal", nil) | ||||||
require.NoError(t, err) | ||||||
resp, err := route.instance.Test(req, -1) | ||||||
require.NoError(t, err) | ||||||
require.NotNil(t, resp) | ||||||
|
||||||
resp, err = route.Test(req) | ||||||
assert.NoError(t, err) | ||||||
assert.Equal(t, http.StatusOK, resp.StatusCode) | ||||||
assert.Equal(t, http.StatusGatewayTimeout, resp.StatusCode) | ||||||
|
||||||
body, err := io.ReadAll(resp.Body) | ||||||
assert.NoError(t, err) | ||||||
assert.Equal(t, "normal", string(body)) | ||||||
body, err := io.ReadAll(resp.Body) | ||||||
assert.NoError(t, err) | ||||||
assert.JSONEq(t, `{"error":"Request Timeout"}`, string(body)) | ||||||
}) | ||||||
|
||||||
req, err = http.NewRequest("GET", "/panic", nil) | ||||||
require.NoError(t, err) | ||||||
t.Run("normal", func(t *testing.T) { | ||||||
req, err := http.NewRequest("GET", "/normal", nil) | ||||||
require.NoError(t, err) | ||||||
|
||||||
mockLog := mockslog.NewLog(t) | ||||||
mockLog.EXPECT().Request(mock.Anything).Return(mockLog).Once() | ||||||
mockLog.EXPECT().Error(mock.Anything).Once() | ||||||
LogFacade = mockLog | ||||||
|
||||||
resp, err = route.instance.Test(req) | ||||||
fmt.Printf("resp: %+v\n", resp) | ||||||
assert.NoError(t, err) | ||||||
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) | ||||||
|
||||||
body, err = io.ReadAll(resp.Body) | ||||||
assert.NoError(t, err) | ||||||
assert.Equal(t, "Internal Server Error", string(body)) | ||||||
resp, err := route.instance.Test(req, -1) | ||||||
assert.NoError(t, err) | ||||||
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode) | ||||||
|
||||||
body, err := io.ReadAll(resp.Body) | ||||||
assert.NoError(t, err) | ||||||
assert.Equal(t, "normal", string(body)) | ||||||
}) | ||||||
|
||||||
t.Run("panic", func(t *testing.T) { | ||||||
req, err := http.NewRequest("GET", "/panic", nil) | ||||||
require.NoError(t, err) | ||||||
|
||||||
resp, err := route.instance.Test(req, -1) | ||||||
require.NoError(t, err) | ||||||
|
||||||
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) | ||||||
|
||||||
body, err := io.ReadAll(resp.Body) | ||||||
require.NoError(t, err) | ||||||
assert.JSONEq(t, `{"error":"Internal Panic"}`, string(body)) | ||||||
}) | ||||||
|
||||||
mockLog.AssertExpectations(t) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can be removed since we are using
Suggested change
|
||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -14,7 +14,7 @@ import ( | |||||
|
||||||
"github.com/gofiber/fiber/v2" | ||||||
"github.com/gofiber/fiber/v2/middleware/logger" | ||||||
"github.com/gofiber/fiber/v2/middleware/recover" | ||||||
fiberrecover "github.com/gofiber/fiber/v2/middleware/recover" | ||||||
"github.com/gofiber/template/html/v2" | ||||||
"github.com/goravel/framework/contracts/config" | ||||||
httpcontract "github.com/goravel/framework/contracts/http" | ||||||
|
@@ -26,6 +26,8 @@ import ( | |||||
"github.com/savioxavier/termlink" | ||||||
) | ||||||
|
||||||
var globalRecoverCallback func(ctx httpcontract.Context, err any) | ||||||
|
||||||
// Route fiber route | ||||||
// Route fiber 路由 | ||||||
type Route struct { | ||||||
|
@@ -109,7 +111,7 @@ func (r *Route) GlobalMiddleware(middlewares ...httpcontract.Middleware) { | |||||
debug := r.config.GetBool("app.debug", false) | ||||||
timeout := time.Duration(r.config.GetInt("http.request_timeout", 3)) * time.Second | ||||||
fiberHandlers := []fiber.Handler{ | ||||||
recover.New(recover.Config{ | ||||||
fiberrecover.New(fiberrecover.Config{ | ||||||
EnableStackTrace: debug, | ||||||
}), | ||||||
} | ||||||
|
@@ -130,6 +132,24 @@ func (r *Route) GlobalMiddleware(middlewares ...httpcontract.Middleware) { | |||||
r.setMiddlewares(fiberHandlers) | ||||||
} | ||||||
|
||||||
func (r *Route) Recover(callback func(ctx httpcontract.Context, err any)) { | ||||||
globalRecoverCallback = callback | ||||||
middleware := middlewaresToFiberHandlers([]httpcontract.Middleware{ | ||||||
func(ctx httpcontract.Context) { | ||||||
defer func() { | ||||||
if err := recover(); err != nil { | ||||||
if callback != nil { | ||||||
callback(ctx, err) | ||||||
} else { | ||||||
ctx.Request().AbortWithStatusJson(http.StatusInternalServerError, fiber.Map{"error": "Internal Server Error"}) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The callback should not be
Suggested change
|
||||||
} | ||||||
} | ||||||
}() | ||||||
ctx.Request().Next() | ||||||
}, | ||||||
}) | ||||||
r.setMiddlewares(middleware) | ||||||
} | ||||||
// Listen listen server | ||||||
// Listen 监听服务器 | ||||||
func (r *Route) Listen(l net.Listener) error { | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -8,6 +8,7 @@ import ( | |||||
"mime/multipart" | ||||||
"net" | ||||||
"net/http" | ||||||
"net/http/httptest" | ||||||
"sync" | ||||||
"sync/atomic" | ||||||
"testing" | ||||||
|
@@ -21,6 +22,64 @@ import ( | |||||
"github.com/stretchr/testify/assert" | ||||||
) | ||||||
|
||||||
func TestRecoverWithDefaultCallback(t *testing.T) { | ||||||
globalRecoverCallback = nil | ||||||
mockConfig := configmocks.NewConfig(t) | ||||||
mockConfig.On("GetBool", "http.drivers.fiber.prefork", false).Return(false).Once() | ||||||
mockConfig.On("GetInt", "http.drivers.fiber.body_limit", 4096).Return(4096).Once() | ||||||
mockConfig.On("GetInt", "http.drivers.fiber.header_limit", 4096).Return(4096).Once() | ||||||
|
||||||
route, err := NewRoute(mockConfig, nil) | ||||||
assert.Nil(t, err) | ||||||
route.Recover(globalRecoverCallback) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line is unnecessary in this case, we don't need to pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in goravel/gin we used gin.Recovery() and the default recovery test covered this case. Is it necessary to test the default recoverer in goravel/fiber? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can initially set globalRecoverCallback There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is default recovery as well: fiberHandlers := []fiber.Handler{
fiberrecover.New(fiberrecover.Config{
EnableStackTrace: debug,
}),
} Setting a default |
||||||
route.Get("/recover", func(ctx contractshttp.Context) contractshttp.Response { | ||||||
panic(1) | ||||||
}) | ||||||
|
||||||
req := httptest.NewRequest("GET", "/recover", nil) | ||||||
resp, err := route.Test(req) | ||||||
assert.Nil(t, err) | ||||||
|
||||||
body, err := io.ReadAll(resp.Body) | ||||||
assert.Nil(t, err) | ||||||
assert.Equal(t, "{\"error\":\"Internal Server Error\"}", string(body)) | ||||||
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) | ||||||
|
||||||
mockConfig.AssertExpectations(t) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
func TestRecoverWithCustomCallback(t *testing.T) { | ||||||
mockConfig := configmocks.NewConfig(t) | ||||||
|
||||||
mockConfig.On("GetBool", "http.drivers.fiber.prefork", false).Return(false).Once() | ||||||
mockConfig.On("GetInt", "http.drivers.fiber.body_limit", 4096).Return(4096).Once() | ||||||
mockConfig.On("GetInt", "http.drivers.fiber.header_limit", 4096).Return(4096).Once() | ||||||
|
||||||
route, err := NewRoute(mockConfig, nil) | ||||||
assert.Nil(t, err) | ||||||
|
||||||
globalRecoverCallback := func(ctx contractshttp.Context, err any) { | ||||||
ctx.Request().AbortWithStatusJson(http.StatusInternalServerError, fiber.Map{"error": "Internal Panic"}) | ||||||
} | ||||||
|
||||||
route.Recover(globalRecoverCallback) | ||||||
|
||||||
route.Get("/recover", func(ctx contractshttp.Context) contractshttp.Response { | ||||||
panic(1) | ||||||
}) | ||||||
|
||||||
req := httptest.NewRequest("GET", "/recover", nil) | ||||||
resp, err := route.Test(req) | ||||||
assert.Nil(t, err) | ||||||
|
||||||
body, err := io.ReadAll(resp.Body) | ||||||
assert.Nil(t, err) | ||||||
assert.Equal(t, "{\"error\":\"Internal Panic\"}", string(body)) | ||||||
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) | ||||||
|
||||||
mockConfig.AssertExpectations(t) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
func TestFallback(t *testing.T) { | ||||||
mockConfig := configmocks.NewConfig(t) | ||||||
mockConfig.EXPECT().GetBool("http.drivers.fiber.prefork", false).Return(false).Once() | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The log is required, please keep it. I saw it was removed in gin as well, please add it back.