mirror of
https://github.com/kataras/iris.git
synced 2025-03-13 21:36:28 +01:00
More on Transactions: iris.UseTransaction and iris.DoneTransaction. See HISTORY.md
This commit is contained in:
parent
48e770dab0
commit
f54dc697cc
|
@ -2,10 +2,14 @@
|
|||
|
||||
**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.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`.
|
||||
|
||||
## 5.1.0 -> 5.1.1
|
||||
Two hours after the previous update,
|
||||
|
||||
- **More on Transactions**: By-default transaction's lifetime is 'per-call/transient' meaning that each transaction has its own scope on the context, rollbacks when `scope.Complete(notNilAndNotEmptyError)` and the rest of transictions in chain are executed as expected, from now and on you have the ability to `skip the rest of the next transictions on first failure` by simply call `scope.RequestScoped(true)`.
|
||||
- **More on Transactions**: By-default transaction's lifetime is 'per-call/transient' meaning that each transaction has its own scope on the context, rollbacks when `scope.Complete(notNilAndNotEmptyError)` and the rest of transactions in chain are executed as expected, from now and on you have the ability to `skip the rest of the next transactions on first failure` by simply call `scope.RequestScoped(true)`.
|
||||
|
||||
Note: `RequestTransactionScope` renamed to ,simply, `TransactionScope`.
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<br/>
|
||||
|
||||
|
||||
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%205.1.1%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.2%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.1**
|
||||
Current: **v5.1.2**
|
||||
|
||||
Stable: **[v4 LTS](https://github.com/kataras/iris/tree/4.0.0#versioning)**
|
||||
|
||||
|
|
35
context.go
35
context.go
|
@ -1161,13 +1161,6 @@ func NewErrWithStatus() *ErrWithStatus {
|
|||
return new(ErrWithStatus)
|
||||
}
|
||||
|
||||
// TransactionValidator used to register global transaction pre-validators
|
||||
type TransactionValidator interface {
|
||||
// ValidateTransaction pre-validates transactions
|
||||
// transaction fails if this returns an error or it's Complete has a non empty error
|
||||
ValidateTransaction(*TransactionScope) error
|
||||
}
|
||||
|
||||
// TransactionScope is the (request) transaction scope of a handler's context
|
||||
// Can't say a lot here because I it will take more than 200 lines to write about.
|
||||
// You can search third-party articles or books on how Business Transaction works (it's quite simple, especialy here).
|
||||
|
@ -1222,7 +1215,9 @@ func (r *TransactionScope) Complete(err error) {
|
|||
return
|
||||
}
|
||||
}
|
||||
} else if reason == "" {
|
||||
}
|
||||
|
||||
if reason == "" {
|
||||
// do nothing empty reason and no status code means that this is not a failure, even if the error is not nil.
|
||||
return
|
||||
}
|
||||
|
@ -1275,10 +1270,28 @@ func (ctx *Context) TransactionsSkipped() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// BeginTransaction starts a request scoped transaction.
|
||||
// TransactionFunc is just a func(scope *TransactionScope)
|
||||
// used to register transaction(s) to a handler's context.
|
||||
type TransactionFunc func(scope *TransactionScope)
|
||||
|
||||
// ToMiddleware wraps/converts a transaction to a handler func
|
||||
// it is not recommended to be used by users because
|
||||
// this can be a little tricky if someone doesn't know how transaction works.
|
||||
//
|
||||
// See more here: https://github.com/iris-contrib/examples/tree/master/transactions
|
||||
func (ctx *Context) BeginTransaction(pipe func(scope *TransactionScope)) {
|
||||
// Note: it auto-calls the ctx.Next() so as I noted, not recommended to use if you don't know the code behind it,
|
||||
// use the .UseTransaction and .DoneTransaction instead
|
||||
func (pipe TransactionFunc) ToMiddleware() HandlerFunc {
|
||||
return func(ctx *Context) {
|
||||
ctx.BeginTransaction(pipe)
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// do NOT begin a transaction when the previous transaction has been failed
|
||||
// and it was requested scoped or SkipTransactions called manually
|
||||
if ctx.TransactionsSkipped() {
|
||||
|
|
114
context_test.go
114
context_test.go
|
@ -783,13 +783,13 @@ func TestTemplatesDisabled(t *testing.T) {
|
|||
|
||||
func TestTransactions(t *testing.T) {
|
||||
iris.ResetDefault()
|
||||
firstTransictionFailureMessage := "Error: Virtual failure!!!"
|
||||
secondTransictionSuccessHTMLMessage := "<h1>This will sent at all cases because it lives on different transaction and it doesn't fails</h1>"
|
||||
firstTransactionFailureMessage := "Error: Virtual failure!!!"
|
||||
secondTransactionSuccessHTMLMessage := "<h1>This will sent at all cases because it lives on different transaction and it doesn't fails</h1>"
|
||||
persistMessage := "<h1>I persist show this message to the client!</h1>"
|
||||
|
||||
maybeFailureTransaction := func(shouldFail bool, isRequestScoped bool) func(scope *iris.TransactionScope) {
|
||||
return func(scope *iris.TransactionScope) {
|
||||
// OPTIONAl, if true then the next transictions will not be executed if this transiction fails
|
||||
// OPTIONAl, if true then the next transactions will not be executed if this transaction fails
|
||||
scope.RequestScoped(isRequestScoped)
|
||||
|
||||
// OPTIONAL STEP:
|
||||
|
@ -815,7 +815,7 @@ func TestTransactions(t *testing.T) {
|
|||
if fail {
|
||||
err.Status(iris.StatusInternalServerError).
|
||||
// if status given but no reason then the default or the custom http error will be fired (like ctx.EmitError)
|
||||
Reason(firstTransictionFailureMessage)
|
||||
Reason(firstTransactionFailureMessage)
|
||||
}
|
||||
|
||||
// OPTIONAl STEP:
|
||||
|
@ -828,7 +828,7 @@ func TestTransactions(t *testing.T) {
|
|||
|
||||
successTransaction := func(scope *iris.TransactionScope) {
|
||||
scope.Context.HTML(iris.StatusOK,
|
||||
secondTransictionSuccessHTMLMessage)
|
||||
secondTransactionSuccessHTMLMessage)
|
||||
// * if we don't have any 'throw error' logic then no need of scope.Complete()
|
||||
}
|
||||
|
||||
|
@ -861,18 +861,116 @@ func TestTransactions(t *testing.T) {
|
|||
Status(iris.StatusOK).
|
||||
ContentType("text/html", iris.Config.Charset).
|
||||
Body().
|
||||
Equal(firstTransictionFailureMessage + secondTransictionSuccessHTMLMessage + persistMessage)
|
||||
Equal(firstTransactionFailureMessage + secondTransactionSuccessHTMLMessage + persistMessage)
|
||||
|
||||
e.GET("/failFirsTransactionButSuccessSecond").
|
||||
Expect().
|
||||
Status(iris.StatusOK).
|
||||
ContentType("text/html", iris.Config.Charset).
|
||||
Body().
|
||||
Equal(firstTransictionFailureMessage + secondTransictionSuccessHTMLMessage)
|
||||
Equal(firstTransactionFailureMessage + secondTransactionSuccessHTMLMessage)
|
||||
|
||||
e.GET("/failAllBecauseOfRequestScopeAndFailure").
|
||||
Expect().
|
||||
Status(iris.StatusInternalServerError).
|
||||
Body().
|
||||
Equal(firstTransictionFailureMessage)
|
||||
Equal(firstTransactionFailureMessage)
|
||||
}
|
||||
|
||||
func TestTransactionsMiddleware(t *testing.T) {
|
||||
forbiddenMsg := "Error: Not allowed."
|
||||
allowMsg := "Hello!"
|
||||
|
||||
transaction := iris.TransactionFunc(func(scope *iris.TransactionScope) {
|
||||
// must set that to true when we want to bypass the whole handler if this transaction fails.
|
||||
scope.RequestScoped(true)
|
||||
// optional but useful when we want a specific reason message
|
||||
// without register global custom http errors to a status (using iris.OnError)
|
||||
err := iris.NewErrWithStatus()
|
||||
// the difference from ctx.BeginTransaction is that
|
||||
// if that fails it not only skips all transactions but all next handler(s) too
|
||||
// here we use this middleware AFTER a handler, so all handlers are executed before that but
|
||||
// this will fail because this is the difference from normal handler, it resets the whole response if Complete(notEmptyError)
|
||||
if scope.Context.GetString("username") != "iris" {
|
||||
err.Status(iris.StatusForbidden).Reason(forbiddenMsg)
|
||||
}
|
||||
|
||||
scope.Complete(err)
|
||||
})
|
||||
|
||||
failHandlerFunc := func(ctx *iris.Context) {
|
||||
ctx.Set("username", "wrong")
|
||||
ctx.Write("This should not be sent to the client.")
|
||||
|
||||
ctx.Next() // in order to execute the next handler, which is a wrapper of transaction
|
||||
}
|
||||
|
||||
successHandlerFunc := func(ctx *iris.Context) {
|
||||
ctx.Set("username", "iris")
|
||||
ctx.Write("Hello!")
|
||||
|
||||
ctx.Next()
|
||||
}
|
||||
|
||||
// per route after transaction(middleware)
|
||||
api := iris.New()
|
||||
api.Get("/transaction_after_route_middleware_fail_because_of_request_scope_fails", failHandlerFunc, transaction.ToMiddleware()) // after per route
|
||||
|
||||
api.Get("/transaction_after_route_middleware_success_so_response_should_be_sent_to_the_client", successHandlerFunc, transaction.ToMiddleware()) // after per route
|
||||
|
||||
e := httptest.New(api, t)
|
||||
e.GET("/transaction_after_route_middleware_fail_because_of_request_scope_fails").
|
||||
Expect().
|
||||
Status(iris.StatusForbidden).
|
||||
Body().
|
||||
Equal(forbiddenMsg)
|
||||
|
||||
e.GET("/transaction_after_route_middleware_success_so_response_should_be_sent_to_the_client").
|
||||
Expect().
|
||||
Status(iris.StatusOK).
|
||||
Body().
|
||||
Equal(allowMsg)
|
||||
|
||||
// global, after all route's handlers
|
||||
api = iris.New()
|
||||
|
||||
api.DoneTransaction(transaction)
|
||||
api.Get("/failed_because_of_done_transaction", failHandlerFunc)
|
||||
|
||||
api.Get("/succeed_because_of_done_transaction", successHandlerFunc)
|
||||
|
||||
e = httptest.New(api, t)
|
||||
e.GET("/failed_because_of_done_transaction").
|
||||
Expect().
|
||||
Status(iris.StatusForbidden).
|
||||
Body().
|
||||
Equal(forbiddenMsg)
|
||||
|
||||
e.GET("/succeed_because_of_done_transaction").
|
||||
Expect().
|
||||
Status(iris.StatusOK).
|
||||
Body().
|
||||
Equal(allowMsg)
|
||||
|
||||
// global, before all route's handlers transaction, this is not so useful so these transaction will be succesfuly and just adds a message
|
||||
api = iris.New()
|
||||
transactionHTMLResponse := "<b>Transaction here</b>"
|
||||
expectedResponse := transactionHTMLResponse + allowMsg
|
||||
api.UseTransaction(func(scope *iris.TransactionScope) {
|
||||
scope.Context.HTML(iris.StatusOK, transactionHTMLResponse)
|
||||
// scope.Context.Next() is automatically called on UseTransaction
|
||||
})
|
||||
|
||||
api.Get("/route1", func(ctx *iris.Context) {
|
||||
ctx.Write(allowMsg)
|
||||
})
|
||||
|
||||
e = httptest.New(api, t)
|
||||
e.GET("/route1").
|
||||
Expect().
|
||||
Status(iris.StatusOK).
|
||||
ContentType("text/html", api.Config.Charset).
|
||||
Body().
|
||||
Equal(expectedResponse)
|
||||
|
||||
}
|
||||
|
|
82
iris.go
82
iris.go
|
@ -80,7 +80,7 @@ const (
|
|||
// IsLongTermSupport flag is true when the below version number is a long-term-support version
|
||||
IsLongTermSupport = false
|
||||
// Version is the current version number of the Iris web framework
|
||||
Version = "5.1.1"
|
||||
Version = "5.1.2"
|
||||
|
||||
banner = ` _____ _
|
||||
|_ _| (_)
|
||||
|
@ -1152,12 +1152,15 @@ type (
|
|||
MuxAPI interface {
|
||||
Party(string, ...HandlerFunc) MuxAPI
|
||||
// middleware serial, appending
|
||||
Use(...Handler)
|
||||
UseFunc(...HandlerFunc)
|
||||
Use(...Handler) MuxAPI
|
||||
UseFunc(...HandlerFunc) MuxAPI
|
||||
// returns itself, because at the most-cases used like .Layout, at the first-line party's declaration
|
||||
Done(...Handler) MuxAPI
|
||||
DoneFunc(...HandlerFunc) MuxAPI
|
||||
//
|
||||
|
||||
// transactions
|
||||
UseTransaction(...TransactionFunc) MuxAPI
|
||||
DoneTransaction(...TransactionFunc) MuxAPI
|
||||
|
||||
// main handlers
|
||||
Handle(string, string, ...Handler) RouteNameFunc
|
||||
|
@ -1236,13 +1239,13 @@ func (api *muxAPI) Party(relativePath string, handlersFn ...HandlerFunc) MuxAPI
|
|||
}
|
||||
|
||||
// Use registers Handler middleware
|
||||
func Use(handlers ...Handler) {
|
||||
Default.Use(handlers...)
|
||||
func Use(handlers ...Handler) MuxAPI {
|
||||
return Default.Use(handlers...)
|
||||
}
|
||||
|
||||
// UseFunc registers HandlerFunc middleware
|
||||
func UseFunc(handlersFn ...HandlerFunc) {
|
||||
Default.UseFunc(handlersFn...)
|
||||
func UseFunc(handlersFn ...HandlerFunc) MuxAPI {
|
||||
return Default.UseFunc(handlersFn...)
|
||||
}
|
||||
|
||||
// Done registers Handler 'middleware' the only difference from .Use is that it
|
||||
|
@ -1262,13 +1265,16 @@ func DoneFunc(handlersFn ...HandlerFunc) MuxAPI {
|
|||
}
|
||||
|
||||
// Use registers Handler middleware
|
||||
func (api *muxAPI) Use(handlers ...Handler) {
|
||||
// returns itself
|
||||
func (api *muxAPI) Use(handlers ...Handler) MuxAPI {
|
||||
api.middleware = append(api.middleware, handlers...)
|
||||
return api
|
||||
}
|
||||
|
||||
// UseFunc registers HandlerFunc middleware
|
||||
func (api *muxAPI) UseFunc(handlersFn ...HandlerFunc) {
|
||||
api.Use(convertToHandlers(handlersFn)...)
|
||||
// returns itself
|
||||
func (api *muxAPI) UseFunc(handlersFn ...HandlerFunc) MuxAPI {
|
||||
return api.Use(convertToHandlers(handlersFn)...)
|
||||
}
|
||||
|
||||
// Done registers Handler 'middleware' the only difference from .Use is that it
|
||||
|
@ -1296,6 +1302,60 @@ func (api *muxAPI) DoneFunc(handlersFn ...HandlerFunc) MuxAPI {
|
|||
return api.Done(convertToHandlers(handlersFn)...)
|
||||
}
|
||||
|
||||
// UseTransaction adds transaction(s) middleware
|
||||
// the difference from manually adding them to the ctx.BeginTransaction
|
||||
// is that if a transaction is requested scope and is failed then the (next) handler is not executed.
|
||||
//
|
||||
// Returns itself.
|
||||
//
|
||||
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
||||
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
||||
func UseTransaction(pipes ...TransactionFunc) MuxAPI {
|
||||
return Default.UseTransaction(pipes...)
|
||||
}
|
||||
|
||||
// UseTransaction adds transaction(s) middleware
|
||||
// the difference from manually adding them to the ctx.BeginTransaction
|
||||
// is that if a transaction is requested scope and is failed then the (next) handler is not executed.
|
||||
//
|
||||
// Returns itself.
|
||||
//
|
||||
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
||||
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
||||
func (api *muxAPI) UseTransaction(pipes ...TransactionFunc) MuxAPI {
|
||||
return api.UseFunc(func(ctx *Context) {
|
||||
for i := range pipes {
|
||||
ctx.BeginTransaction(pipes[i])
|
||||
if ctx.TransactionsSkipped() {
|
||||
ctx.StopExecution()
|
||||
}
|
||||
}
|
||||
ctx.Next()
|
||||
})
|
||||
}
|
||||
|
||||
// DoneTransaction registers Transaction 'middleware' the only difference from .UseTransaction is that
|
||||
// is executed always last, after all of each route's handlers, returns itself.
|
||||
//
|
||||
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
||||
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
||||
func DoneTransaction(pipes ...TransactionFunc) MuxAPI {
|
||||
return Default.DoneTransaction(pipes...)
|
||||
}
|
||||
|
||||
// DoneTransaction registers Transaction 'middleware' the only difference from .UseTransaction is that
|
||||
// is executed always last, after all of each route's handlers, returns itself.
|
||||
//
|
||||
// See https://github.com/iris-contrib/examples/tree/master/transactions to manually add transactions
|
||||
// and https://github.com/kataras/iris/blob/master/context_test.go for more
|
||||
func (api *muxAPI) DoneTransaction(pipes ...TransactionFunc) MuxAPI {
|
||||
return api.DoneFunc(func(ctx *Context) {
|
||||
for i := range pipes {
|
||||
ctx.BeginTransaction(pipes[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Handle registers a route to the server's router
|
||||
// if empty method is passed then registers handler(s) for all methods, same as .Any, but returns nil as result
|
||||
func Handle(method string, registedPath string, handlers ...Handler) RouteNameFunc {
|
||||
|
|
Loading…
Reference in New Issue
Block a user