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
)