1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-10 05:25:58 +00:00

Update to version 10.6.5: 1 New Feature And Indonesia Translation | Read HISTORY.md

Former-commit-id: 4788e36e52f6b40c7e15120e0675c097eabf0f0d
This commit is contained in:
Gerasimos Maropoulos
2018-05-21 07:40:43 +03:00
parent 8c41968905
commit 94b93484b5
19 changed files with 503 additions and 30 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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.
}

View File

@@ -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.
//