Skip to content
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 #103

Merged
merged 87 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
e06c940
[Feature] User can custom recover when a request panic
KlassnayaAfrodita Nov 15, 2024
09245fe
Update middleware_timeout.go
KlassnayaAfrodita Nov 15, 2024
5168d1e
Update route.go
KlassnayaAfrodita Nov 15, 2024
32ee690
Update middleware_timeout_test.go
KlassnayaAfrodita Nov 15, 2024
29d36ee
Update middleware_timeout_test.go
KlassnayaAfrodita Nov 15, 2024
88daede
Update context_request.go
KlassnayaAfrodita Nov 15, 2024
e82b340
Update view.go
KlassnayaAfrodita Nov 15, 2024
f3efb50
Update gin.go
KlassnayaAfrodita Nov 15, 2024
61b20aa
Update context_request_test.go
KlassnayaAfrodita Nov 15, 2024
a771bce
Update service_provider.go
KlassnayaAfrodita Nov 15, 2024
3b77bd0
Update view.go
KlassnayaAfrodita Nov 15, 2024
cc7a261
Update gin.go
KlassnayaAfrodita Nov 15, 2024
21125ef
Update context_request_test.go
KlassnayaAfrodita Nov 16, 2024
f3c666a
Update context_request.go
KlassnayaAfrodita Nov 18, 2024
adf6a8c
Update context_request_test.go
KlassnayaAfrodita Nov 18, 2024
0a3998c
Update gin.go
KlassnayaAfrodita Nov 18, 2024
d001e4d
Update middleware_timeout.go
KlassnayaAfrodita Nov 18, 2024
28af043
Update middleware_timeout_test.go
KlassnayaAfrodita Nov 18, 2024
b0a62cd
Update route.go
KlassnayaAfrodita Nov 18, 2024
59ae234
Update service_provider.go
KlassnayaAfrodita Nov 18, 2024
5745c8f
Update view.go
KlassnayaAfrodita Nov 18, 2024
fae6fbe
Update view.go
KlassnayaAfrodita Nov 18, 2024
98f64a5
Update route.go
KlassnayaAfrodita Nov 18, 2024
076581a
Update route.go
KlassnayaAfrodita Nov 21, 2024
d5d8a93
Update middleware_timeout.go
KlassnayaAfrodita Nov 21, 2024
998e3c4
Update middleware_timeout.go
KlassnayaAfrodita Nov 21, 2024
9a3074c
Update route.go
KlassnayaAfrodita Nov 21, 2024
a019972
Update middleware_timeout.go
KlassnayaAfrodita Nov 21, 2024
3f2edef
Update middleware_timeout.go
KlassnayaAfrodita Nov 21, 2024
f7a0ac6
Update context_request.go
KlassnayaAfrodita Nov 21, 2024
36978b1
Update middleware_timeout.go
KlassnayaAfrodita Nov 21, 2024
92c38d4
Update middleware_timeout_test.go
KlassnayaAfrodita Nov 21, 2024
f66dadd
Update context_request_test.go
KlassnayaAfrodita Nov 21, 2024
3915909
Update middleware_timeout.go
KlassnayaAfrodita Nov 21, 2024
faf902e
Update gin.go
KlassnayaAfrodita Nov 22, 2024
cf0b756
Update middleware_timeout_test.go
KlassnayaAfrodita Nov 22, 2024
0023e51
Update middleware_timeout_test.go
KlassnayaAfrodita Nov 22, 2024
3c5f3bc
Update view.go
KlassnayaAfrodita Nov 26, 2024
fb1faf6
Update middleware_timeout_test.go
KlassnayaAfrodita Nov 26, 2024
8f27f62
Update middleware_timeout_test.go
KlassnayaAfrodita Nov 26, 2024
49f6b21
Update route.go
KlassnayaAfrodita Dec 13, 2024
0e34013
Update middleware_timeout.go
KlassnayaAfrodita Dec 13, 2024
99e06c8
Merge branch 'master' into patch-3
KlassnayaAfrodita Dec 13, 2024
c6d79c8
Update middleware_timeout.go
KlassnayaAfrodita Dec 13, 2024
e0727a7
Update context_request.go
KlassnayaAfrodita Dec 13, 2024
8a0c44e
Update context_request.go
KlassnayaAfrodita Dec 17, 2024
173e471
Update middleware_timeout.go
KlassnayaAfrodita Dec 17, 2024
e4a1360
Update service_provider.go
KlassnayaAfrodita Dec 17, 2024
f30e2f4
Update middleware_timeout.go
KlassnayaAfrodita Dec 17, 2024
6217114
Update route.go
KlassnayaAfrodita Dec 17, 2024
35e0182
Update view.go
KlassnayaAfrodita Dec 17, 2024
ace488a
Update gin.go
KlassnayaAfrodita Dec 17, 2024
f7f209e
Update route.go
KlassnayaAfrodita Dec 17, 2024
440ba87
Update context_request.go
KlassnayaAfrodita Dec 17, 2024
ca0c57f
Update service_provider.go
KlassnayaAfrodita Dec 17, 2024
4e8ddfb
Update route.go
KlassnayaAfrodita Dec 17, 2024
dfa4bd0
Update route.go
KlassnayaAfrodita Dec 17, 2024
44b41f6
Update route.go
KlassnayaAfrodita Dec 17, 2024
4346508
Update route.go
KlassnayaAfrodita Dec 17, 2024
1682fe7
Update middleware_timeout.go
KlassnayaAfrodita Dec 17, 2024
60320c1
Merge branch 'master' into patch-3
KlassnayaAfrodita Dec 17, 2024
28dcae8
Update view.go
KlassnayaAfrodita Dec 18, 2024
4aceba8
Update middleware_timeout.go
KlassnayaAfrodita Dec 19, 2024
601dbd0
Update middleware_timeout.go
KlassnayaAfrodita Dec 19, 2024
a79cae7
Update middleware_timeout.go
KlassnayaAfrodita Dec 19, 2024
a581045
Update middleware_timeout.go
KlassnayaAfrodita Dec 19, 2024
5a0101e
Update middleware_timeout.go
KlassnayaAfrodita Dec 19, 2024
1c07943
Update middleware_timeout_test.go
KlassnayaAfrodita Dec 19, 2024
f020930
Update middleware_timeout_test.go
KlassnayaAfrodita Dec 19, 2024
a0b8834
Update view.go
KlassnayaAfrodita Dec 19, 2024
47c53e7
Update view.go
KlassnayaAfrodita Dec 19, 2024
343d493
Update gin.go
KlassnayaAfrodita Dec 23, 2024
41d57a1
Update route.go
KlassnayaAfrodita Dec 23, 2024
057f865
Update service_provider.go
KlassnayaAfrodita Dec 23, 2024
4d82e79
Update view.go
KlassnayaAfrodita Dec 23, 2024
e6fb55f
Update route.go
KlassnayaAfrodita Dec 23, 2024
5df7701
Update route_test.go
KlassnayaAfrodita Dec 24, 2024
aa56ca1
Update route_test.go
KlassnayaAfrodita Dec 24, 2024
66e139e
Update route.go
KlassnayaAfrodita Dec 24, 2024
46fc772
Update route_test.go
KlassnayaAfrodita Dec 26, 2024
48144d5
Update route_test.go
KlassnayaAfrodita Dec 26, 2024
72a5064
Update route.go
KlassnayaAfrodita Dec 26, 2024
e7eef7b
Update route.go
KlassnayaAfrodita Dec 26, 2024
ee526b3
Update route_test.go
KlassnayaAfrodita Dec 26, 2024
4d7e660
Update route_test.go
KlassnayaAfrodita Dec 26, 2024
5371bd7
Update middleware_timeout_test.go
KlassnayaAfrodita Dec 26, 2024
cdf7d2f
Merge branch 'master' into patch-3
hwbrzzl Dec 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions context_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/gookit/validate"
contractsfilesystem "github.com/goravel/framework/contracts/filesystem"
contractshttp "github.com/goravel/framework/contracts/http"
"github.com/goravel/framework/support/color"
"github.com/goravel/framework/contracts/log"
contractsession "github.com/goravel/framework/contracts/session"
contractsvalidate "github.com/goravel/framework/contracts/validation"
Expand All @@ -34,9 +35,12 @@ type ContextRequest struct {
func NewContextRequest(ctx *Context, log log.Log, validation contractsvalidate.Validation) contractshttp.ContextRequest {
httpBody, err := getHttpBody(ctx)
if err != nil {
LogFacade.Error(fmt.Sprintf("%+v", errors.Unwrap(err)))
if LogFacade != nil {
hwbrzzl marked this conversation as resolved.
Show resolved Hide resolved
LogFacade.Error(fmt.Sprintf("%+v", errors.Unwrap(err)))
} else {
color.Errorf("LogFacade is nil, error: %+v\n", errors.Unwrap(err))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the optimization, however, I removed the nilaway check, and LogFacade shouldn't be nil, so we can keep the original code.

}

return &ContextRequest{ctx: ctx, instance: ctx.instance, httpBody: httpBody, log: log, validation: validation}
}

Expand Down
6 changes: 4 additions & 2 deletions context_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ func (s *ContextRequestSuite) SetupTest() {
ValidationFacade = validation.NewValidation()

var err error
s.route, err = NewRoute(s.mockConfig, nil)
s.Require().Nil(err)
route, err := NewRoute(s.mockConfig, nil)
s.Require().NotNil(route)
s.Require().NoError(err)
s.route = route
}

func (s *ContextRequestSuite) TearDownTest() {
Expand Down
5 changes: 4 additions & 1 deletion facades/gin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import (
"log"

"github.com/goravel/framework/contracts/route"

hwbrzzl marked this conversation as resolved.
Show resolved Hide resolved
"github.com/goravel/gin"
)

func Route(driver string) route.Route {
if gin.App == nil {
panic("App is nil")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

instance, err := gin.App.MakeWith(gin.RouteBinding, map[string]any{
"driver": driver,
})

if err != nil {
log.Fatalln(err)
return nil
Expand Down
24 changes: 9 additions & 15 deletions middleware_timeout.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,15 @@ func Timeout(timeout time.Duration) contractshttp.Middleware {
done := make(chan struct{})

go func() {
defer func() {
if r := recover(); r != nil {
if LogFacade != nil {
LogFacade.Request(ctx.Request()).Error(r)
}

// TODO can be customized in https://github.com/goravel/goravel/issues/521
_ = ctx.Response().Status(http.StatusInternalServerError).String("Internal Server Error").Render()
}

close(done)
}()

ctx.Request().Next()
}()
defer func() {
if r := recover(); r != nil {
ctx.Request().AbortWithStatusJson(http.StatusInternalServerError, map[string]interface{}{"error": "Internal Server Error"})
}
close(done)
}()
Copy link
Contributor

@hwbrzzl hwbrzzl Nov 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not use the global recoverFunc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that a panic that occurs in Timeout or any other middleware is passed down the call stack back to the global Recover handler, which is higher in the chain.
Am I right or wrong?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can write tests and check this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the global recovery can't catch the panic that was thrown in goroutine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, can you tell me how to add a global Recover to a goroutine?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can save the custom recovery to a variable in func (r *Route) Recover(callback func(ctx context.Context, err any)), then you can use the custom recovery here and func (r *Route) GlobalMiddleware(middlewares ...httpcontract.Middleware).

Copy link
Contributor Author

@KlassnayaAfrodita KlassnayaAfrodita Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that for the proposed option you need to change the Timeout structure:

func Timeout(route *Route, timeout time.Duration) contractshttp.Middleware {
...
}

or

func (r *Route) Timeout(timeout time.Duration) contracthttp.Middleware {
...
}

and add the recoverCallback field to Route

type Route struct {
    recoverCallback func(ctx context.Context, err any)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unnecessary, a global variable is enough.


ctx.Request().Next()
}()

select {
case <-done:
Expand Down
5 changes: 1 addition & 4 deletions middleware_timeout_test.go
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test case to test the custom recover as well.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
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"
)

Expand Down Expand Up @@ -54,11 +53,9 @@ func TestTimeoutMiddleware(t *testing.T) {
require.NoError(t, err)

mockLog := mockslog.NewLog(t)
mockLog.EXPECT().Request(mock.Anything).Return(mockLog).Once()
mockLog.EXPECT().Error(mock.Anything).Once()
LogFacade = mockLog

route.ServeHTTP(w, req)
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Equal(t, "Internal Server Error", w.Body.String())
assert.JSONEq(t, `{"error":"Internal Server Error"}`, w.Body.String())
}
18 changes: 17 additions & 1 deletion route.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func NewRoute(config config.Config, parameters map[string]any) (*Route, error) {
gin.DisableBindValidation()
engine := gin.New()
engine.MaxMultipartMemory = int64(config.GetInt("http.drivers.gin.body_limit", 4096)) << 10
engine.Use(gin.Recovery()) // recovery middleware
// engine.Use(gin.Recovery()) // recovery middleware
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can keep this line, it will be used when the user doesn't set the global recover and the timeout middleware.


if debugLog := getDebugLog(config); debugLog != nil {
engine.Use(debugLog)
Expand Down Expand Up @@ -88,6 +88,22 @@ func (r *Route) GlobalMiddleware(middlewares ...httpcontract.Middleware) {
r.setMiddlewares(middlewares)
}

// the Recoverer must come first
func (r *Route) Recover(callback func(ctx context.Context, err any)) {
hwbrzzl marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, mistake, should be:

Suggested change
func (r *Route) Recover(callback func(ctx context.Context, err any)) {
func (r *Route) Recover(callback func(ctx httpcontract.Context, err any)) {

Please modify https://github.com/goravel/framework/pull/723/files#diff-37b488415e77368d5640d20902b15e5603864dc8c5c1e715bc36e2a8510b81f8R19 synchronously.

r.instance.Use(func(ctx *gin.Context) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a CustomRecovery method in Gin, we can use it directly.

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will we end up using gin.CustomRecovery?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it can be used directly, yes, we can.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is CustomRecovery suitable, please?

defer func() {
if err := recover(); err != nil {
if callback != nil {
callback(ctx, err)
} else {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"})
}
}
}()
ctx.Next()
})
}

func (r *Route) Run(host ...string) error {
if len(host) == 0 {
defaultHost := r.config.GetString("http.host")
Expand Down
10 changes: 10 additions & 0 deletions service_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/goravel/framework/contracts/validation"
"github.com/goravel/framework/errors"
"github.com/goravel/framework/support/color"
"os"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort it:

system packages

third packages

internal packages

)

const RouteBinding = "goravel.gin.route"
Expand Down Expand Up @@ -36,12 +37,16 @@ func (receiver *ServiceProvider) Boot(app foundation.Application) {
if ConfigFacade = app.MakeConfig(); ConfigFacade == nil {
color.Errorln(errors.ConfigFacadeNotSet.SetModule(module))
}

if LogFacade = app.MakeLog(); LogFacade == nil {
color.Errorln(errors.LogFacadeNotSet.SetModule(module))
shutdownOnCriticalError("LogFacade is not set")
}

if ValidationFacade = app.MakeValidation(); ValidationFacade == nil {
color.Errorln(errors.New("validation facade is not initialized").SetModule(module))
}

if ViewFacade = app.MakeView(); ViewFacade == nil {
color.Errorln(errors.New("view facade is not initialized").SetModule(module))
}
Expand All @@ -50,3 +55,8 @@ func (receiver *ServiceProvider) Boot(app foundation.Application) {
"config/cors.go": app.ConfigPath("cors.go"),
})
}

func shutdownOnCriticalError(message string) {
color.Errorln("Critical error:", message)
os.Exit(1) // Force exit to ensure the application does not run with missing dependencies
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove it, it's unnecessary.

16 changes: 12 additions & 4 deletions view.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ func NewView(instance *gin.Context) *View {
}

func (receive *View) Make(view string, data ...any) contractshttp.Response {
shared := ViewFacade.GetShared()
var shared map[string]any
hwbrzzl marked this conversation as resolved.
Show resolved Hide resolved
if ViewFacade == nil {
panic("ViewFacade is nil")
}
shared = ViewFacade.GetShared()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the optimization, however, I removed the nilaway check, and ViewFacade shouldn't be nil, so we can keep the original code.



hwbrzzl marked this conversation as resolved.
Show resolved Hide resolved
if len(data) == 0 {
return &HtmlResponse{shared, receive.instance, view}
} else {
Expand All @@ -28,11 +34,9 @@ func (receive *View) Make(view string, data ...any) contractshttp.Response {
for key, value := range dataMap {
shared[key] = value
}

return &HtmlResponse{shared, receive.instance, view}
case reflect.Map:
fillShared(data[0], shared)

return &HtmlResponse{data[0], receive.instance, view}
default:
panic(fmt.Sprintf("make %s view failed, data must be map or struct", view))
Expand All @@ -42,8 +46,10 @@ func (receive *View) Make(view string, data ...any) contractshttp.Response {

func (receive *View) First(views []string, data ...any) contractshttp.Response {
for _, view := range views {
if ViewFacade.Exists(view) {
if ViewFacade != nil && ViewFacade.Exists(view) {
return receive.Make(view, data...)
} else {
fmt.Println("ViewFacade is nil or view does not exist")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

}

Expand Down Expand Up @@ -82,6 +88,7 @@ func structToMap(data any) map[string]any {
func fillShared(data any, shared map[string]any) {
dataValue := reflect.ValueOf(data)
keys := dataValue.MapKeys()

for key, value := range shared {
exist := false
for _, k := range keys {
Expand All @@ -90,6 +97,7 @@ func fillShared(data any, shared map[string]any) {
break
}
}

if !exist {
dataValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(value))
}
Expand Down
Loading