diff --git a/HISTORY.md b/HISTORY.md index 4ef4ca06..48bf4c81 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,4 @@ -# History/Changelog +# History/Changelog ### Looking for free and real-time support? @@ -17,6 +17,103 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you. +# Mo, 21 May 2018 | v10.6.5 + +First of all, special thanks to [@haritsfahreza](https://github.com/haritsfahreza) for translating the entire Iris' README page & Changelogs to the Bahasa Indonesia language via PR: [#1000](https://github.com/kataras/iris/pull/1000)! + +## New Feature: `Execution Rules` + +From the begin of the Iris' journey we used to use the `ctx.Next()` inside handlers in order to call the next handler in the route's registered handlers chain, otherwise the "next handler" would never be executed. + +We could always "force-break" that handlers chain using the `ctx.StopExecution()` to indicate that any future `ctx.Next()` calls will do nothing. + +These things will never change, they were designed in the lower possible level of the Iris' high-performant and unique router and they're working like a charm:) + +We have introduced `Iris MVC Applications` two years later. Iris is the first and the only one Go web framework with a realistic point-view and feature-rich MVC architectural pattern support without sacrifices, always with speed in mind (handlers vs mvc have almost the same speed here!!!). + +A bit later we introduced another two unique features, `Hero Handlers and Service/Dynamic Bindings` (see the very bottom of this HISTORY page). +You loved it, you're using it a lot, just take a look at the recent github issues the community raised about MVC and etc. + +Two recent discussions/support were about calling `Done` handlers inside MVC applications, you could simply do that by implementing the optional `BaseController` as examples shown, i.e: + +```go +func (c *myController) BeginRequest(ctx iris.Context) {} +func (c *myController) EndRequest(ctx iris.Context) { + ctx.Next() // Call of any `Done` handlers. +} +``` + +But for some reason you found that confused. This is where the new feature comes: **The option to change the default behavior of handlers execution's rules PER PARTY**. + +For example, we want to run all handlers(begin, main and done handlers) with the order you register but without the need of the `ctx.Next()` (in that case the only remained way to stop the lifecycle of an http request when next handlers are registered is to use the `ctx.StopExecution()` which, does not allow the next handler(s) to be executed even if `ctx.Next()` called in some place later on, but you're already know this, I hope :)). + +```go +package main + +import ( + "github.com/kataras/iris" + "github.com/kataras/iris/mvc" +) + +func main() { + app := iris.New() + app.Get("/", func(ctx iris.Context) { ctx.Redirect("/example") }) + + m := mvc.New(app.Party("/example")) + + // IMPORTANT + // the new feature, all options can be filled with Force:true, they are all play nice together. + m.Router.SetExecutionRules(iris.ExecutionRules{ + // Begin: <- from `Use[all]` to `Handle[last]` future route handlers, execute all, execute all even if `ctx.Next()` is missing. + // Main: <- all `Handle` future route handlers, execute all >> >>. + Done: iris.ExecutionOptions{Force: true}, // <- from `Handle[last]` to `Done[all]` future route handlers, execute all >> >>. + }) + m.Router.Done(doneHandler) + // m.Router.Done(...) + // ... + // + + m.Handle(&exampleController{}) + + app.Run(iris.Addr(":8080")) +} + +func doneHandler(ctx iris.Context) { + ctx.WriteString("\nFrom Done Handler") +} + +type exampleController struct{} + +func (c *exampleController) Get() string { + return "From Main Handler" + // Note that here we don't binding the `Context`, and we don't call its `Next()` + // function in order to call the `doneHandler`, + // this is done automatically for us because we changed the execution rules with the `SetExecutionRules`. + // + // Therefore, the final output is: + // From Main Handler + // From Done Handler +} +``` + +Example at: [_examples/mvc/middleware/without-ctx-next](_examples/mvc/middleware/without-ctx-next). + +This feature can be applied to any type of application, the example is an MVC Application because many of you asked for this exactly flow the past days. + +## Thank you + +Thank you for your honest support once again, your posts are the heart of this framework. + +Don't forget to [star](https://github.com/kataras/iris/stargazers) the Iris' github repository whenever you can and spread the world about its potentials! + +Be part of this, + +- complete our User Experience Report: https://goo.gl/forms/lnRbVgA6ICTkPyk02 +- join to our Community live chat: https://kataras.rocket.chat/channel/iris +- connect to our [new facebook group](https://www.facebook.com/iris.framework) to get notifications about new job opportunities relatively to Iris! + +Sincerely, +[Gerasimos Maropoulos](https://twitter.com/MakisMaropoulos). # We, 09 May 2018 | v10.6.4 diff --git a/HISTORY_GR.md b/HISTORY_GR.md index 696a3cf4..847f0658 100644 --- a/HISTORY_GR.md +++ b/HISTORY_GR.md @@ -1,4 +1,4 @@ -# Ιστορικό +# Ιστορικό ### Ψάχνετε για δωρεάν υποστήριξη σε πραγματικό χρόνο; @@ -17,6 +17,10 @@ **Πώς να αναβαθμίσετε**: Ανοίξτε την γραμμή εντολών σας και εκτελέστε αυτήν την εντολή: `go get -u github.com/kataras/iris` ή αφήστε το αυτόματο updater να το κάνει αυτό για σας. +# Mo, 21 May 2018 | v10.6.5 + +This history entry is not translated yet to the Greek language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#mo-21-may-2018--v1065) instead. + # We, 09 May 2018 | v10.6.4 - [διόρθωση του bug #995](https://github.com/kataras/iris/commit/62457279f41a1f157869a19ef35fb5198694fddb) diff --git a/HISTORY_ID.md b/HISTORY_ID.md index 4c44b89a..f4139200 100644 --- a/HISTORY_ID.md +++ b/HISTORY_ID.md @@ -17,6 +17,9 @@ Developers tidak diwajibkan untuk melakukan upgrade apabila mereka tidak membutu **Cara Upgrade**: Bukan command-line anda dan eksekuis perintah ini: `go get -u github.com/kataras/iris` atau biarkan updater otomatis melakukannya untuk anda. +# Mo, 21 May 2018 | v10.6.5 + +This history entry is not translated yet to the Bahasa Indonesia language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#mo-21-may-2018--v1065) instead. # We, 09 May 2018 | v10.6.4 diff --git a/HISTORY_ZH.md b/HISTORY_ZH.md index c69c7dbb..9bb0988f 100644 --- a/HISTORY_ZH.md +++ b/HISTORY_ZH.md @@ -1,4 +1,4 @@ -# 更新记录 +# 更新记录 ### 想得到免费即时的支持? @@ -17,6 +17,9 @@ **如何升级**: 打开命令行执行以下命令: `go get -u github.com/kataras/iris` 或者等待自动更新。 +# Mo, 21 May 2018 | v10.6.5 + +This history entry is not translated yet to the Chinese language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#mo-21-may-2018--v1065) instead. # We, 09 May 2018 | v10.6.4 diff --git a/README.md b/README.md index 8850323e..88c27f78 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Iris Web Framework +# Iris Web Framework @@ -106,7 +106,7 @@ _Updated at: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_ ## Support -- [HISTORY](HISTORY.md#we-09-may-2018--v1064) file is your best friend, it contains information about the latest features and changes +- [HISTORY](HISTORY.md#mo-21-may-2018--v1065) file is your best friend, it contains information about the latest features and changes - Did you happen to find a bug? Post it at [github issues](https://github.com/kataras/iris/issues) - Do you have any questions or need to speak with someone experienced to solve a problem at real-time? Join us to the [community chat](https://chat.iris-go.com) - Complete our form-based user experience report by clicking [here](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_GR.md b/README_GR.md index 5e6841f7..c47727f3 100644 --- a/README_GR.md +++ b/README_GR.md @@ -1,4 +1,4 @@ -# Iris Web Framework +# Iris Web Framework @@ -108,7 +108,7 @@ _Η τελευταία ενημέρωση έγινε την [Τρίτη, 21 Νο ## Υποστήριξη -- To [HISTORY](HISTORY_GR.md#we-09-may-2018--v1064) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές +- To [HISTORY](HISTORY_GR.md#mo-21-may-2018--v1065) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές - Μήπως τυχαίνει να βρήκατε κάποιο bug; Δημοσιεύστε το στα [github issues](https://github.com/kataras/iris/issues) - Έχετε οποιεσδήποτε ερωτήσεις ή πρέπει να μιλήσετε με κάποιον έμπειρο για την επίλυση ενός προβλήματος σε πραγματικό χρόνο; Ελάτε μαζί μας στην [συνομιλία κοινότητας](https://chat.iris-go.com) - Συμπληρώστε την αναφορά εμπειρίας χρήστη κάνοντας κλικ [εδώ](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_ID.md b/README_ID.md index 8512f77e..d53e8779 100644 --- a/README_ID.md +++ b/README_ID.md @@ -1,4 +1,4 @@ -# Iris Web Framework +# Iris Web Framework @@ -106,7 +106,7 @@ _Diperbarui pada: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_ ## Dukungan -- File [HISTORY](HISTORY.md#we-09-may-2018--v1064) adalah sahabat anda, file tersebut memiliki informasi terkait fitur dan perubahan terbaru +- File [HISTORY](HISTORY_ID.md#mo-21-may-2018--v1065) adalah sahabat anda, file tersebut memiliki informasi terkait fitur dan perubahan terbaru - Apakah anda menemukan bug? Laporkan itu melalui [github issues](https://github.com/kataras/iris/issues) - Apakah anda memiliki pertanyaan atau butuh untuk bicara kepada seseorang yang sudah berpengalaman untuk menyelesaikan masalah secara langsung? Gabung bersama kami di [community chat](https://chat.iris-go.com) - Lengkapi laporan user-experience berbasis formulir kami dengan tekan [disini](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_RU.md b/README_RU.md index f50cdeae..305f8d0e 100644 --- a/README_RU.md +++ b/README_RU.md @@ -1,4 +1,4 @@ -# Iris Web Framework +# Iris Web Framework @@ -106,7 +106,7 @@ _Обновлено: [Вторник, 21 ноября 2017 г.](_benchmarks/READ ## Поддержка -- Файл [HISTORY](HISTORY.md#we-09-may-2018--v1064) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях +- Файл [HISTORY](HISTORY_RU.md#mo-21-may-2018--v1065) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях - Вы случайно обнаружили ошибку? Опубликуйте ее на [Github вопросы](https://github.com/kataras/iris/issues) - У Вас есть какие-либо вопросы или Вам нужно поговорить с кем-то, кто бы смог решить Вашу проблему в режиме реального времени? Присоединяйтесь к нам в [чате сообщества](https://chat.iris-go.com) - Заполните наш отчет о пользовательском опыте на основе формы, нажав [здесь](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_ZH.md b/README_ZH.md index 55babb41..e474f20b 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -1,4 +1,4 @@ -# Iris Web Framework +# Iris Web Framework @@ -102,7 +102,7 @@ _更新于: [2017年11月21日星期二](_benchmarks/README_UNIX.md)_ ## 支持 -- [更新记录](HISTORY_ZH.md#we-09-may-2018--v1064) 是您最好的朋友,它包含有关最新功能和更改的信息 +- [更新记录](HISTORY_ZH.md#mo-21-may-2018--v1065) 是您最好的朋友,它包含有关最新功能和更改的信息 - 你碰巧找到了一个错误? 请提交 [github issues](https://github.com/kataras/iris/issues) - 您是否有任何疑问或需要与有经验的人士交谈以实时解决问题? [加入我们的聊天](https://chat.iris-go.com) - [点击这里完成我们基于表单的用户体验报告](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/VERSION b/VERSION index 4829a1aa..b2675429 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.6.4:https://github.com/kataras/iris/blob/master/HISTORY.md#we-09-may-2018--v1064 \ No newline at end of file +10.6.5:https://github.com/kataras/iris/blob/master/HISTORY.md#mo-21-may-2018--v1065 \ No newline at end of file diff --git a/_examples/file-server/favicon/main.go b/_examples/file-server/favicon/main.go index d9935e60..dd857e6e 100644 --- a/_examples/file-server/favicon/main.go +++ b/_examples/file-server/favicon/main.go @@ -8,10 +8,10 @@ func main() { app := iris.New() // This will serve the ./static/favicons/favicon.ico to: localhost:8080/favicon.ico - app.Favicon("./static/favicons/favicon.ico.ico") + app.Favicon("./static/favicons/favicon.ico") - // app.Favicon("./static/favicons/favicon.ico.ico", "/favicon_16_16.ico") - // This will serve the ./static/favicons/favicon.ico.ico to: localhost:8080/favicon_16_16.ico + // app.Favicon("./static/favicons/favicon.\\.ico", "/favicon_16_16.ico") + // This will serve the ./static/favicons/favicon.ico to: localhost:8080/favicon_16_16.ico app.Get("/", func(ctx iris.Context) { ctx.HTML(` press here to see the favicon.ico. diff --git a/_examples/mvc/middleware/without-ctx-next/main.go b/_examples/mvc/middleware/without-ctx-next/main.go new file mode 100644 index 00000000..baa983b0 --- /dev/null +++ b/_examples/mvc/middleware/without-ctx-next/main.go @@ -0,0 +1,72 @@ +/*Package main is a simple example of the behavior change of the execution flow of the handlers, +normally we need the `ctx.Next()` to call the next handler in a route's handler chain, +but with the new `ExecutionRules` we can change this default behavior. +Please read below before continue. + +The `Party#SetExecutionRules` alters the execution flow of the route handlers outside of the handlers themselves. + +For example, if for some reason the desired result is the (done or all) handlers to be executed no matter what +even if no `ctx.Next()` is called in the previous handlers, including the begin(`Use`), +the main(`Handle`) and the done(`Done`) handlers themselves, then: +Party#SetExecutionRules(iris.ExecutionRules { + Begin: iris.ExecutionOptions{Force: true}, + Main: iris.ExecutionOptions{Force: true}, + Done: iris.ExecutionOptions{Force: true}, +}) + +Note that if `true` then the only remained way to "break" the handler chain is by `ctx.StopExecution()` now that `ctx.Next()` does not matter. + +These rules are per-party, so if a `Party` creates a child one then the same rules will be applied to that as well. +Reset of these rules (before `Party#Handle`) can be done with `Party#SetExecutionRules(iris.ExecutionRules{})`. + +The most common scenario for its use can be found inside Iris MVC Applications; +when we want the `Done` handlers of that specific mvc app's `Party` +to be executed but we don't want to add `ctx.Next()` on the `exampleController#EndRequest`*/ +package main + +import ( + "github.com/kataras/iris" + "github.com/kataras/iris/mvc" +) + +func main() { + app := iris.New() + app.Get("/", func(ctx iris.Context) { ctx.Redirect("/example") }) + + // example := app.Party("/example") + // example.SetExecutionRules && mvc.New(example) or... + m := mvc.New(app.Party("/example")) + + // IMPORTANT + // All options can be filled with Force:true, they all play nice together. + m.Router.SetExecutionRules(iris.ExecutionRules{ + // Begin: <- from `Use[all]` to `Handle[last]` future route handlers, execute all, execute all even if `ctx.Next()` is missing. + // Main: <- all `Handle` future route handlers, execute all >> >>. + Done: iris.ExecutionOptions{Force: true}, // <- from `Handle[last]` to `Done[all]` future route handlers, execute all >> >>. + }) + m.Router.Done(doneHandler) + // m.Router.Done(...) + // ... + // + + m.Handle(&exampleController{}) + + app.Run(iris.Addr(":8080")) +} + +func doneHandler(ctx iris.Context) { + ctx.WriteString("\nFrom Done Handler") +} + +type exampleController struct{} + +func (c *exampleController) Get() string { + return "From Main Handler" + // Note that here we don't binding the `Context`, and we don't call its `Next()` + // function in order to call the `doneHandler`, + // this is done automatically for us because we changed the execution rules with the `SetExecutionRules`. + // + // Therefore the final output is: + // From Main Handler + // From Done Handler +} diff --git a/core/maintenance/version.go b/core/maintenance/version.go index 0589e903..04ace282 100644 --- a/core/maintenance/version.go +++ b/core/maintenance/version.go @@ -13,7 +13,7 @@ import ( const ( // Version is the string representation of the current local Iris Web Framework version. - Version = "10.6.4" + Version = "10.6.5" ) // CheckForUpdates checks for any available updates diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 1a21656f..977f5d04 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -94,8 +94,9 @@ type APIBuilder struct { // the per-party done handlers, order matters. doneHandlers context.Handlers - // global done handlers, order doesn't matter + // global done handlers, order doesn't matter. doneGlobalHandlers context.Handlers + // the per-party relativePath string // allowMethods are filled with the `AllowMethods` func. @@ -103,6 +104,9 @@ type APIBuilder struct { // per any party's (and its children) routes registered // if the method "x" wasn't registered already via the `Handle` (and its extensions like `Get`, `Post`...). allowMethods []string + + // the per-party (and its children) execution rules for begin, main and done handlers. + handlerExecutionRules ExecutionRules } var _ Party = (*APIBuilder)(nil) @@ -150,6 +154,34 @@ func (api *APIBuilder) AllowMethods(methods ...string) Party { return api } +// SetExecutionRules alters the execution flow of the route handlers outside of the handlers themselves. +// +// For example, if for some reason the desired result is the (done or all) handlers to be executed no matter what +// even if no `ctx.Next()` is called in the previous handlers, including the begin(`Use`), +// the main(`Handle`) and the done(`Done`) handlers themselves, then: +// Party#SetExecutionRules(iris.ExecutionRules { +// Begin: iris.ExecutionOptions{Force: true}, +// Main: iris.ExecutionOptions{Force: true}, +// Done: iris.ExecutionOptions{Force: true}, +// }) +// +// Note that if : true then the only remained way to "break" the handler chain is by `ctx.StopExecution()` now that `ctx.Next()` does not matter. +// +// These rules are per-party, so if a `Party` creates a child one then the same rules will be applied to that as well. +// Reset of these rules (before `Party#Handle`) can be done with `Party#SetExecutionRules(iris.ExecutionRules{})`. +// +// The most common scenario for its use can be found inside Iris MVC Applications; +// when we want the `Done` handlers of that specific mvc app's `Party` +// to be executed but we don't want to add `ctx.Next()` on the `OurController#EndRequest`. +// +// Returns this Party. +// +// Example: https://github.com/kataras/iris/tree/master/_examples/mvc/middleware/without-ctx-next +func (api *APIBuilder) SetExecutionRules(executionRules ExecutionRules) Party { + api.handlerExecutionRules = executionRules + return api +} + // Handle registers a route to the server's api. // if empty method is passed then handler(s) are being registered to all methods, same as .Any. // @@ -179,14 +211,28 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co return nil } - // before join the middleware + handlers + done handlers. - possibleMainHandlerName := context.HandlerName(handlers[0]) + // note: this can not change the caller's handlers as they're but the entry values(handlers) + // of `middleware`, `doneHandlers` and `handlers` can. + // So if we just put `api.middleware` or `api.doneHandlers` + // then the next `Party` will have those updated handlers + // but dev may change the rules for that child Party, so we have to make clones of them here. + var ( + beginHandlers = joinHandlers(api.middleware, context.Handlers{}) + doneHandlers = joinHandlers(api.doneHandlers, context.Handlers{}) + ) + + mainHandlers := context.Handlers(handlers) + // before join the middleware + handlers + done handlers and apply the execution rules. + possibleMainHandlerName := context.HandlerName(mainHandlers[0]) + + // TODO: for UseGlobal/DoneGlobal that doesn't work. + applyExecutionRules(api.handlerExecutionRules, &beginHandlers, &doneHandlers, &mainHandlers) // global begin handlers -> middleware that are registered before route registration // -> handlers that are passed to this Handle function. - routeHandlers := joinHandlers(api.middleware, handlers) + routeHandlers := joinHandlers(beginHandlers, mainHandlers) // -> done handlers - routeHandlers = joinHandlers(routeHandlers, api.doneHandlers) + routeHandlers = joinHandlers(routeHandlers, doneHandlers) // here we separate the subdomain and relative path subdomain, path := splitSubdomainAndPath(fullpath) @@ -300,10 +346,11 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P doneGlobalHandlers: api.doneGlobalHandlers, reporter: api.reporter, // per-party/children - middleware: middleware, - doneHandlers: api.doneHandlers[0:], - relativePath: fullpath, - allowMethods: allowMethods, + middleware: middleware, + doneHandlers: api.doneHandlers[0:], + relativePath: fullpath, + allowMethods: allowMethods, + handlerExecutionRules: api.handlerExecutionRules, } } @@ -456,12 +503,14 @@ func (api *APIBuilder) DoneGlobal(handlers ...context.Handler) { } // Reset removes all the begin and done handlers that may derived from the parent party via `Use` & `Done`, -// note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`. +// and the execution rules. +// Note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`. // // Returns this Party. func (api *APIBuilder) Reset() Party { api.middleware = api.middleware[0:0] api.doneHandlers = api.doneHandlers[0:0] + api.handlerExecutionRules = ExecutionRules{} return api } diff --git a/core/router/handler_execution_rules.go b/core/router/handler_execution_rules.go new file mode 100644 index 00000000..a28f4c58 --- /dev/null +++ b/core/router/handler_execution_rules.go @@ -0,0 +1,115 @@ +package router + +import ( + "github.com/kataras/iris/context" +) + +// ExecutionRules gives control to the execution of the route handlers outside of the handlers themselves. +// Usage: +// Party#SetExecutionRules(ExecutionRules { +// Done: ExecutionOptions{Force: true}, +// }) +// +// See `Party#SetExecutionRules` for more. +type ExecutionRules struct { + // Begin applies from `Party#Use`/`APIBUilder#UseGlobal` to the first...!last `Party#Handle`'s IF main handlers > 1. + Begin ExecutionOptions + // Done applies to the latest `Party#Handle`'s (even if one) and all done handlers. + Done ExecutionOptions + // Main applies to the `Party#Handle`'s all handlers, plays nice with the `Done` rule + // when more than one handler was registered in `Party#Handle` without `ctx.Next()` (for Force: true). + Main ExecutionOptions +} + +func handlersNames(handlers context.Handlers) (names []string) { + for _, h := range handlers { + if h == nil { + continue + } + + names = append(names, context.HandlerName(h)) + } + + return +} + +func applyExecutionRules(rules ExecutionRules, begin, done, main *context.Handlers) { + if !rules.Begin.Force && !rules.Done.Force && !rules.Main.Force { + return // do not proceed and spend buld-time here if nothing changed. + } + + beginOK := rules.Begin.apply(begin) + mainOK := rules.Main.apply(main) + doneOK := rules.Done.apply(done) + + if !mainOK { + mainCp := (*main)[0:] + + lastIdx := len(mainCp) - 1 + + if beginOK { + if len(mainCp) > 1 { + mainCpFirstButNotLast := make(context.Handlers, lastIdx) + copy(mainCpFirstButNotLast, mainCp[:lastIdx]) + + for i, h := range mainCpFirstButNotLast { + (*main)[i] = rules.Begin.buildHandler(h) + } + } + } + + if doneOK { + latestMainHandler := mainCp[lastIdx] + (*main)[lastIdx] = rules.Done.buildHandler(latestMainHandler) + } + } +} + +// ExecutionOptions is a set of default behaviors that can be changed in order to customize the execution flow of the routes' handlers with ease. +// +// See `ExecutionRules` and `Party#SetExecutionRules` for more. +type ExecutionOptions struct { + // Force if true then the handler9s) will execute even if the previous (or/and current, depends on the type of the rule) + // handler does not calling the `ctx.Next()`, + // note that the only way remained to stop a next handler is with the `ctx.StopExecution()` if this option is true. + // + // If true and `ctx.Next()` exists in the handlers that it shouldn't be, the framework will understand it but use it wisely. + // + // Defaults to false. + Force bool +} + +func (e ExecutionOptions) buildHandler(h context.Handler) context.Handler { + if !e.Force { + return h + } + + return func(ctx context.Context) { + // Proceed will fire the handler and return false here if it doesn't contain a `ctx.Next()`, + // so we add the `ctx.Next()` wherever is necessary in order to eliminate any dev's misuse. + if !ctx.Proceed(h) { + // `ctx.Next()` always checks for `ctx.IsStopped()` and handler(s) positions by-design. + ctx.Next() + } + } +} + +func (e ExecutionOptions) apply(handlers *context.Handlers) bool { + if !e.Force { + return false + } + + tmp := *handlers + + for i, h := range tmp { + if h == nil { + if len(tmp) == 1 { + return false + } + continue + } + (*handlers)[i] = e.buildHandler(h) + } + + return true +} diff --git a/core/router/handler_execution_rules_test.go b/core/router/handler_execution_rules_test.go new file mode 100644 index 00000000..6b6c6f56 --- /dev/null +++ b/core/router/handler_execution_rules_test.go @@ -0,0 +1,91 @@ +package router_test + +import ( + "testing" + + "github.com/kataras/iris" + "github.com/kataras/iris/context" + "github.com/kataras/iris/core/router" + "github.com/kataras/iris/httptest" +) + +var ( + finalExecutionRulesResponse = "1234" + + testExecutionResponse = func(t *testing.T, app *iris.Application, path string) { + e := httptest.New(t, app) + e.GET(path).Expect().Status(httptest.StatusOK).Body().Equal(finalExecutionRulesResponse) + } +) + +func writeStringHandler(text string, withNext bool) context.Handler { + return func(ctx context.Context) { + ctx.WriteString(text) + if withNext { + ctx.Next() + } + } +} + +func TestRouterExecutionRulesForceMain(t *testing.T) { + app := iris.New() + begin := app.Party("/") + begin.SetExecutionRules(router.ExecutionRules{Main: router.ExecutionOptions{Force: true}}) + + // no need of `ctx.Next()` all main handlers should be executed with the Main.Force:True rule. + begin.Get("/", writeStringHandler("12", false), writeStringHandler("3", false), writeStringHandler("4", false)) + + testExecutionResponse(t, app, "/") +} + +func TestRouterExecutionRulesForceBegin(t *testing.T) { + app := iris.New() + begin := app.Party("/begin_force") + begin.SetExecutionRules(router.ExecutionRules{Begin: router.ExecutionOptions{Force: true}}) + + // should execute, begin rule is to force execute them without `ctx.Next()`. + begin.Use(writeStringHandler("1", false)) + begin.Use(writeStringHandler("2", false)) + // begin starts with begin and ends to the main handlers but not last, so this done should not be executed. + begin.Done(writeStringHandler("5", false)) + begin.Get("/", writeStringHandler("3", false), writeStringHandler("4", false)) + + testExecutionResponse(t, app, "/begin_force") +} + +func TestRouterExecutionRulesForceDone(t *testing.T) { + app := iris.New() + done := app.Party("/done_force") + done.SetExecutionRules(router.ExecutionRules{Done: router.ExecutionOptions{Force: true}}) + + // these done should be executed without `ctx.Next()` + done.Done(writeStringHandler("3", false), writeStringHandler("4", false)) + // first with `ctx.Next()`, because Done.Force:True rule will alter the latest of the main handler(s) only. + done.Get("/", writeStringHandler("1", true), writeStringHandler("2", false)) + + // rules should be kept in children. + doneChild := done.Party("/child") + // even if only one, it's the latest, Done.Force:True rule should modify it. + doneChild.Get("/", writeStringHandler("12", false)) + + testExecutionResponse(t, app, "/done_force") + testExecutionResponse(t, app, "/done_force/child") +} + +func TestRouterExecutionRulesShouldNotModifyTheCallersHandlerAndChildrenCanResetExecutionRules(t *testing.T) { + app := iris.New() + app.SetExecutionRules(router.ExecutionRules{Done: router.ExecutionOptions{Force: true}}) + h := writeStringHandler("4", false) + + app.Done(h) + app.Get("/", writeStringHandler("123", false)) + + // remember: the handler stored in var didn't had a `ctx.Next()`, modified its clone above with adding a `ctx.Next()` + // note the "clone" word, the original handler shouldn't be changed. + app.Party("/c").SetExecutionRules(router.ExecutionRules{}).Get("/", h, writeStringHandler("err caller modified!", false)) + + testExecutionResponse(t, app, "/") + + e := httptest.New(t, app) + e.GET("/c").Expect().Status(httptest.StatusOK).Body().Equal("4") // the "should not" should not be written. +} diff --git a/core/router/party.go b/core/router/party.go index f14714aa..8e8b56bd 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -61,7 +61,8 @@ type Party interface { // The difference from .Use is that this/or these Handler(s) are being always running last. Done(handlers ...context.Handler) // Reset removes all the begin and done handlers that may derived from the parent party via `Use` & `Done`, - // note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`. + // and the execution rules. + // Note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`. // // Returns this Party. Reset() Party @@ -73,6 +74,30 @@ type Party interface { // Call of `AllowMethod` will override any previous allow methods. AllowMethods(methods ...string) Party + // SetExecutionRules alters the execution flow of the route handlers outside of the handlers themselves. + // + // For example, if for some reason the desired result is the (done or all) handlers to be executed no matter what + // even if no `ctx.Next()` is called in the previous handlers, including the begin(`Use`), + // the main(`Handle`) and the done(`Done`) handlers themselves, then: + // Party#SetExecutionRules(iris.ExecutionRules { + // Begin: iris.ExecutionOptions{Force: true}, + // Main: iris.ExecutionOptions{Force: true}, + // Done: iris.ExecutionOptions{Force: true}, + // }) + // + // Note that if : true then the only remained way to "break" the handler chain is by `ctx.StopExecution()` now that `ctx.Next()` does not matter. + // + // These rules are per-party, so if a `Party` creates a child one then the same rules will be applied to that as well. + // Reset of these rules (before `Party#Handle`) can be done with `Party#SetExecutionRules(iris.ExecutionRules{})`. + // + // The most common scenario for its use can be found inside Iris MVC Applications; + // when we want the `Done` handlers of that specific mvc app's `Party` + // to be executed but we don't want to add `ctx.Next()` on the `OurController#EndRequest`. + // + // Returns this Party. + // + // Example: https://github.com/kataras/iris/tree/master/_examples/mvc/middleware/without-ctx-next + SetExecutionRules(executionRules ExecutionRules) Party // Handle registers a route to the server's router. // if empty method is passed then handler(s) are being registered to all methods, same as .Any. // diff --git a/doc.go b/doc.go index 4aa52213..b2ff3ed3 100644 --- a/doc.go +++ b/doc.go @@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub: Current Version -10.6.4 +10.6.5 Installation diff --git a/go19.go b/go19.go index adcd8a4f..0290ce2f 100644 --- a/go19.go +++ b/go19.go @@ -57,4 +57,18 @@ type ( // // A shortcut for the `core/router#Party`, useful when `PartyFunc` is being used. Party = router.Party + + // ExecutionRules gives control to the execution of the route handlers outside of the handlers themselves. + // Usage: + // Party#SetExecutionRules(ExecutionRules { + // Done: ExecutionOptions{Force: true}, + // }) + // + // See `core/router/Party#SetExecutionRules` for more. + // Example: https://github.com/kataras/iris/tree/master/_examples/mvc/middleware/without-ctx-next + ExecutionRules = router.ExecutionRules + // ExecutionOptions is a set of default behaviors that can be changed in order to customize the execution flow of the routes' handlers with ease. + // + // See `ExecutionRules` and `core/router/Party#SetExecutionRules` for more. + ExecutionOptions = router.ExecutionOptions )