mirror of
https://github.com/kataras/iris.git
synced 2025-03-13 21:36:28 +01:00
More on Transactions: Fallback on, unexpected, panics and able to send 'silent' error which stills reverts the changes but no output
This commit is contained in:
parent
1da8231abd
commit
88c98bb1e1
|
@ -2,6 +2,9 @@
|
|||
|
||||
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
|
||||
|
||||
## 5.1.1 -> 5.1.3
|
||||
- **More on Transactions vol 3**: Recovery from any (unexpected error) panics inside `context.BeginTransaction` without loud, continue the execution as expected. Next version will have a little cleanup if I see that the transactions code is going very large or hard to understand the flow*
|
||||
|
||||
## 5.1.1 -> 5.1.2
|
||||
|
||||
- **More on Transactions vol 2**: Added **iris.UseTransaction** and **iris.DoneTransaction** to register transactions as you register middleware(handlers). new named type **iris.TransactionFunc**, shortcut of `func(scope *iris.TransactionScope)`, that gives you a function which you can convert a transaction to a normal handler/middleware using its `.ToMiddleware()`, for more see the `test code inside context_test.go:TestTransactionsMiddleware`.
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<br/>
|
||||
|
||||
|
||||
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%205.1.2%20-blue.svg?style=flat-square" alt="Releases"></a>
|
||||
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%205.1.3%20-blue.svg?style=flat-square" alt="Releases"></a>
|
||||
|
||||
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
|
||||
|
||||
|
@ -920,7 +920,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
|
|||
Versioning
|
||||
------------
|
||||
|
||||
Current: **v5.1.2**
|
||||
Current: **v5.1.3**
|
||||
|
||||
Stable: **[v4 LTS](https://github.com/kataras/iris/tree/4.0.0#versioning)**
|
||||
|
||||
|
|
75
context.go
75
context.go
|
@ -1122,6 +1122,23 @@ func (ctx *Context) MaxAge() int64 {
|
|||
return -1
|
||||
}
|
||||
|
||||
// ErrFallback is just an empty error but it is recognised from the TransactionScope.Complete,
|
||||
// it reverts its changes and continue as normal, no error will be shown to the user.
|
||||
//
|
||||
// Usually it is used on recovery from panics (inside .BeginTransaction)
|
||||
// but users can use that also to by-pass the error's response of your custom transaction pipe.
|
||||
type ErrFallback struct{}
|
||||
|
||||
func (ne *ErrFallback) Error() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// NewErrFallback returns a new error wihch contains an empty error,
|
||||
// look .BeginTransaction and context_test.go:TestTransactionRecoveryFromPanic
|
||||
func NewErrFallback() *ErrFallback {
|
||||
return &ErrFallback{}
|
||||
}
|
||||
|
||||
// ErrWithStatus custom error type which is useful
|
||||
// to send an error containing the http status code and a reason
|
||||
type ErrWithStatus struct {
|
||||
|
@ -1131,6 +1148,11 @@ type ErrWithStatus struct {
|
|||
message string // if it's empty then the already registered custom(or default) http error will be fired.
|
||||
}
|
||||
|
||||
// Silent in case the user changed his/her mind and wants to silence this error
|
||||
func (err *ErrWithStatus) Silent() error {
|
||||
return NewErrFallback()
|
||||
}
|
||||
|
||||
// Status sets the http status code of this error
|
||||
// if only status exists but no reason then
|
||||
// custom http error of this staus (if any) will be fired (context.EmitError)
|
||||
|
@ -1215,7 +1237,12 @@ func (r *TransactionScope) Complete(err error) {
|
|||
ctx := r.Context
|
||||
statusCode := StatusInternalServerError // default http status code if not provided
|
||||
reason := err.Error()
|
||||
|
||||
if _, ok := err.(*ErrFallback); ok {
|
||||
// revert without any log or response.
|
||||
r.isFailure = true
|
||||
ctx.Response.Reset()
|
||||
return
|
||||
}
|
||||
if errWstatus, ok := err.(*ErrWithStatus); ok {
|
||||
if errWstatus.statusCode > 0 {
|
||||
// get the status code from the custom error type
|
||||
|
@ -1303,11 +1330,17 @@ func (pipe TransactionFunc) ToMiddleware() HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// non-detailed error log for transacton unexpected panic
|
||||
var errTransactionInterrupted = errors.New("Transaction Interrupted, recovery from panic:\n%s")
|
||||
|
||||
// BeginTransaction starts a request scoped transaction.
|
||||
// Transactions have their own middleware ecosystem also, look iris.go:UseTransaction.
|
||||
//
|
||||
// See https://github.com/iris-contrib/examples/tree/master/transactions for more
|
||||
func (ctx *Context) BeginTransaction(pipe TransactionFunc) {
|
||||
func (ctx *Context) BeginTransaction(pipe func(scope *TransactionScope)) {
|
||||
// SILLY NOTE: use of manual pipe type in order of TransactionFunc
|
||||
// in order to help editors complete the sentence here...
|
||||
|
||||
// do NOT begin a transaction when the previous transaction has been failed
|
||||
// and it was requested scoped or SkipTransactions called manually.
|
||||
if ctx.TransactionsSkipped() {
|
||||
|
@ -1318,24 +1351,34 @@ func (ctx *Context) BeginTransaction(pipe TransactionFunc) {
|
|||
tempCtx := *ctx
|
||||
// get a transaction scope from the pool by passing the temp context/
|
||||
scope := acquireTransactionScope(&tempCtx)
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if ctx.framework.Config.IsDevelopment {
|
||||
ctx.Log(errTransactionInterrupted.Format(err).Error())
|
||||
}
|
||||
// complete (again or not , doesn't matters) the scope without loud
|
||||
scope.Complete(NewErrFallback())
|
||||
// we continue as normal, no need to return here*
|
||||
}
|
||||
|
||||
// if the transaction completed with an error then the transaction itself reverts the changes
|
||||
// and replaces the context's response with an error.
|
||||
// if the transaction completed successfully then we need to pass the temp's context's response to this context.
|
||||
// so we must copy back its context at all cases, no matter the result of the transaction.
|
||||
*ctx = *scope.Context
|
||||
|
||||
// if the scope had lifetime of the whole request and it completed with an error(failure)
|
||||
// then we do not continue to the next transactions.
|
||||
if scope.isRequestScoped && scope.isFailure {
|
||||
ctx.SkipTransactions()
|
||||
}
|
||||
|
||||
// finally, release and put the transaction scope back to the pool.
|
||||
releaseTransactionScope(scope)
|
||||
}()
|
||||
// run the worker with its context inside this scope.
|
||||
pipe(scope)
|
||||
|
||||
// if the transaction completed with an error then the transaction itself reverts the changes
|
||||
// and replaces the context's response with an error.
|
||||
// if the transaction completed successfully then we need to pass the temp's context's response to this context.
|
||||
// so we must copy back its context at all cases, no matter the result of the transaction.
|
||||
*ctx = *scope.Context
|
||||
|
||||
// if the scope had lifetime of the whole request and it completed with an error(failure)
|
||||
// then we do not continue to the next transactions.
|
||||
if scope.isRequestScoped && scope.isFailure {
|
||||
ctx.SkipTransactions()
|
||||
}
|
||||
|
||||
// finally, release and put the transaction scope back to the pool.
|
||||
releaseTransactionScope(scope)
|
||||
}
|
||||
|
||||
// Log logs to the iris defined logger
|
||||
|
|
|
@ -972,5 +972,69 @@ func TestTransactionsMiddleware(t *testing.T) {
|
|||
ContentType("text/html", api.Config.Charset).
|
||||
Body().
|
||||
Equal(expectedResponse)
|
||||
}
|
||||
|
||||
func TestTransactionFailureCompletionButSilently(t *testing.T) {
|
||||
iris.ResetDefault()
|
||||
expectedBody := "I don't care for any unexpected panics, this response should be sent."
|
||||
|
||||
iris.Get("/panic_silent", func(ctx *iris.Context) {
|
||||
ctx.BeginTransaction(func(scope *iris.TransactionScope) {
|
||||
scope.Context.Write("blablabla this should not be shown because of 'unexpected' panic.")
|
||||
panic("OMG, UNEXPECTED ERROR BECAUSE YOU ARE NOT A DISCIPLINED PROGRAMMER, BUT IRIS HAS YOU COVERED!")
|
||||
})
|
||||
|
||||
ctx.WriteString(expectedBody)
|
||||
})
|
||||
|
||||
iris.Get("/expected_error_but_silent_instead_of_send_the_reason", func(ctx *iris.Context) {
|
||||
ctx.BeginTransaction(func(scope *iris.TransactionScope) {
|
||||
scope.Context.Write("this will not be sent.")
|
||||
// complete with a failure ( so revert the changes) but do it silently.
|
||||
scope.Complete(iris.NewErrFallback())
|
||||
})
|
||||
|
||||
ctx.WriteString(expectedBody)
|
||||
})
|
||||
|
||||
iris.Get("/silly_way_expected_error_but_silent_instead_of_send_the_reason", func(ctx *iris.Context) {
|
||||
ctx.BeginTransaction(func(scope *iris.TransactionScope) {
|
||||
scope.Context.Write("this will not be sent.")
|
||||
|
||||
// or if you know the error will be silent from the beggining: err := &iris.ErrFallback{}
|
||||
err := iris.NewErrWithStatus()
|
||||
|
||||
fail := true
|
||||
|
||||
if fail {
|
||||
err.Status(iris.StatusBadRequest).Reason("we dont know but it was expected error")
|
||||
}
|
||||
|
||||
// we change our mind we don't want to send the error to the user, so err.Silent to the .Complete
|
||||
// complete with a failure ( so revert the changes) but do it silently.
|
||||
scope.Complete(err.Silent())
|
||||
})
|
||||
|
||||
ctx.WriteString(expectedBody)
|
||||
})
|
||||
|
||||
e := httptest.New(iris.Default, t)
|
||||
|
||||
e.GET("/panic_silent").Expect().
|
||||
Status(iris.StatusOK).
|
||||
Body().
|
||||
Equal(expectedBody)
|
||||
|
||||
e.GET("/expected_error_but_silent_instead_of_send_the_reason").
|
||||
Expect().
|
||||
Status(iris.StatusOK).
|
||||
Body().
|
||||
Equal(expectedBody)
|
||||
|
||||
e.GET("/silly_way_expected_error_but_silent_instead_of_send_the_reason").
|
||||
Expect().
|
||||
Status(iris.StatusOK).
|
||||
Body().
|
||||
Equal(expectedBody)
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user