mirror of
https://github.com/kataras/iris.git
synced 2025-12-24 05:17:03 +00:00
General Improvements (UseRouter per Party, fix AutoTLS). Read HISTORY.md
relative to: https://github.com/kataras/iris/issues/1577 and https://github.com/kataras/iris/issues/1578
This commit is contained in:
@@ -153,6 +153,10 @@ func overlapRoute(r *Route, next *Route) {
|
||||
// APIBuilder the visible API for constructing the router
|
||||
// and child routers.
|
||||
type APIBuilder struct {
|
||||
// parent is the creator of this Party.
|
||||
// It is nil on Root.
|
||||
parent *APIBuilder // currently it's used only on UseRouter feature.
|
||||
|
||||
// the per-party APIBuilder with DI.
|
||||
apiBuilderDI *APIContainer
|
||||
|
||||
@@ -184,7 +188,7 @@ type APIBuilder struct {
|
||||
|
||||
// the per-party relative path.
|
||||
relativePath string
|
||||
// allowMethods are filled with the `AllowMethods` func.
|
||||
// allowMethods are filled with the `AllowMethods` method.
|
||||
// They are used to create new routes
|
||||
// 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`...).
|
||||
@@ -194,23 +198,64 @@ type APIBuilder struct {
|
||||
handlerExecutionRules ExecutionRules
|
||||
// the per-party (and its children) route registration rule, see `SetRegisterRule`.
|
||||
routeRegisterRule RouteRegisterRule
|
||||
|
||||
// routerFilters field is shared across Parties. Each Party registers
|
||||
// one or more middlewares to run before the router itself using the `UseRouter` method.
|
||||
// Each Party calls the shared filter (`partyMatcher`) that decides if its `UseRouter` handlers
|
||||
// can be executed. By default it's based on party's static path and/or subdomain,
|
||||
// it can be modified through an `Application.SetPartyMatcher` call
|
||||
// once before or after routerFilters filled.
|
||||
//
|
||||
// The Key is the Party (instance of APIBuilder),
|
||||
// value wraps the partyFilter + the handlers registered through `UseRouter`.
|
||||
// See `GetRouterFilters` too.
|
||||
routerFilters map[Party]*Filter
|
||||
// partyMatcher field is shared across all Parties,
|
||||
// can be modified through the Application level only.
|
||||
//
|
||||
// It defaults to the internal, simple, "defaultPartyMatcher".
|
||||
// It applies when "routerFilters" are used.
|
||||
partyMatcher PartyMatcherFunc
|
||||
}
|
||||
|
||||
var _ Party = (*APIBuilder)(nil)
|
||||
var _ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handler (routerHandler)
|
||||
var (
|
||||
_ Party = (*APIBuilder)(nil)
|
||||
_ PartyMatcher = (*APIBuilder)(nil)
|
||||
_ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handler (routerHandler)
|
||||
)
|
||||
|
||||
// NewAPIBuilder creates & returns a new builder
|
||||
// which is responsible to build the API and the router handler.
|
||||
func NewAPIBuilder() *APIBuilder {
|
||||
return &APIBuilder{
|
||||
macros: macro.Defaults,
|
||||
errors: errgroup.New("API Builder"),
|
||||
relativePath: "/",
|
||||
routes: new(repository),
|
||||
apiBuilderDI: &APIContainer{Container: hero.New()},
|
||||
parent: nil,
|
||||
macros: macro.Defaults,
|
||||
errors: errgroup.New("API Builder"),
|
||||
relativePath: "/",
|
||||
routes: new(repository),
|
||||
apiBuilderDI: &APIContainer{Container: hero.New()},
|
||||
routerFilters: make(map[Party]*Filter),
|
||||
partyMatcher: defaultPartyMatcher,
|
||||
}
|
||||
}
|
||||
|
||||
// IsRoot reports whether this Party is the root Application's one.
|
||||
// It will return false on all children Parties, no exception.
|
||||
func (api *APIBuilder) IsRoot() bool {
|
||||
return api.parent == nil
|
||||
}
|
||||
|
||||
/* If requested:
|
||||
// GetRoot returns the very first Party (the Application).
|
||||
func (api *APIBuilder) GetRoot() *APIBuilder {
|
||||
root := api.parent
|
||||
for root != nil {
|
||||
root = api.parent
|
||||
}
|
||||
|
||||
return root
|
||||
}*/
|
||||
|
||||
// ConfigureContainer accepts one or more functions that can be used
|
||||
// to configure dependency injection features of this Party
|
||||
// such as register dependency and register handlers that will automatically inject any valid dependency.
|
||||
@@ -565,14 +610,18 @@ func removeDuplicates(elements []string) (result []string) {
|
||||
return result
|
||||
}
|
||||
|
||||
// Party groups routes which may have the same prefix and share same handlers,
|
||||
// returns that new rich subrouter.
|
||||
// Party returns a new child Party which inherites its
|
||||
// parent's options and middlewares.
|
||||
// If "relativePath" matches the parent's one then it returns the current Party.
|
||||
// A Party groups routes which may have the same prefix or subdomain and share same middlewares.
|
||||
//
|
||||
// You can even declare a subdomain with relativePath as "mysub." or see `Subdomain`.
|
||||
// To create a group of routes for subdomains
|
||||
// use the `Subdomain` or `WildcardSubdomain` methods
|
||||
// or pass a "relativePath" as "admin." or "*." respectfully.
|
||||
func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party {
|
||||
// if app.Party("/"), root party, then just add the middlewares
|
||||
// and return itself.
|
||||
if api.relativePath == "/" && (relativePath == "" || relativePath == "/") {
|
||||
// if app.Party("/"), root party or app.Party("/user") == app.Party("/user")
|
||||
// then just add the middlewares and return itself.
|
||||
if relativePath == "" || api.relativePath == relativePath {
|
||||
api.Use(handlers...)
|
||||
return api
|
||||
}
|
||||
@@ -614,7 +663,10 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P
|
||||
beginGlobalHandlers: api.beginGlobalHandlers,
|
||||
doneGlobalHandlers: api.doneGlobalHandlers,
|
||||
errors: api.errors,
|
||||
routerFilters: api.routerFilters, // shared.
|
||||
partyMatcher: api.partyMatcher, // shared.
|
||||
// per-party/children
|
||||
parent: api,
|
||||
middleware: middleware,
|
||||
doneHandlers: api.doneHandlers[0:],
|
||||
relativePath: fullpath,
|
||||
@@ -758,6 +810,134 @@ func (api *APIBuilder) GetRouteReadOnlyByPath(tmplPath string) context.RouteRead
|
||||
return r.ReadOnly
|
||||
}
|
||||
|
||||
type (
|
||||
// PartyMatcherFunc used to build a filter which decides
|
||||
// if the given Party is responsible to fire its `UseRouter` handlers or not.
|
||||
// Can be customized through `SetPartyMatcher` method. See `Match` method too.
|
||||
PartyMatcherFunc func(Party, *context.Context) bool
|
||||
// PartyMatcher decides if `UseRouter` handlers should be executed or not.
|
||||
// A different interface becauwe we want to separate
|
||||
// the Party's public API from `UseRouter` internals.
|
||||
PartyMatcher interface {
|
||||
Match(ctx *context.Context) bool
|
||||
}
|
||||
// Filter is a wraper for a Router Filter contains information
|
||||
// for its Party's fullpath, subdomain the Party's
|
||||
// matcher and the associated handlers to be executed before main router's request handler.
|
||||
Filter struct {
|
||||
Party Party // the Party itself
|
||||
Matcher PartyMatcher // it's a Party, for freedom that can be changed through a custom matcher which accepts the same filter.
|
||||
Subdomain string
|
||||
Path string
|
||||
Handlers context.Handlers
|
||||
}
|
||||
)
|
||||
|
||||
// SetPartyMatcher accepts a function which runs against
|
||||
// a Party and should report whether its `UseRouter` handlers should be executed.
|
||||
// PartyMatchers are run through parent to children.
|
||||
// It modifies the default Party filter that decides
|
||||
// which `UseRouter` middlewares to run before the Router,
|
||||
// each one of those middlewares can skip `Context.Next` or call `Context.StopXXX`
|
||||
// to stop the main router from searching for a route match.
|
||||
// Can be called before or after `UseRouter`, it doesn't matter.
|
||||
func (api *APIBuilder) SetPartyMatcher(matcherFunc PartyMatcherFunc) {
|
||||
if matcherFunc == nil {
|
||||
matcherFunc = defaultPartyMatcher
|
||||
}
|
||||
api.partyMatcher = matcherFunc
|
||||
}
|
||||
|
||||
// Match reports whether the `UseRouter` handlers should be executed.
|
||||
// Calls its parent's Match if possible.
|
||||
// Implements the `PartyMatcher` interface.
|
||||
func (api *APIBuilder) Match(ctx *context.Context) bool {
|
||||
return api.partyMatcher(api, ctx)
|
||||
}
|
||||
|
||||
func defaultPartyMatcher(p Party, ctx *context.Context) bool {
|
||||
subdomain, path := splitSubdomainAndPath(p.GetRelPath())
|
||||
staticPath := staticPath(path)
|
||||
hosts := subdomain != ""
|
||||
|
||||
if p.IsRoot() {
|
||||
// ALWAYS executed first when registered
|
||||
// through an `Application.UseRouter` call.
|
||||
return true
|
||||
}
|
||||
|
||||
if hosts {
|
||||
// Note(@kataras): do NOT try to implement something like party matcher for each party
|
||||
// separately. We will introduce a new problem with subdomain inside a subdomain:
|
||||
// they are not by prefix, so parenting calls will not help
|
||||
// e.g. admin. and control.admin, control.admin is a sub of the admin.
|
||||
if !canHandleSubdomain(ctx, subdomain) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// this is the longest static path.
|
||||
return strings.HasPrefix(ctx.Path(), staticPath)
|
||||
}
|
||||
|
||||
// GetRouterFilters returns the global router filters.
|
||||
// Read `UseRouter` for more.
|
||||
// The map can be altered before router built.
|
||||
// The router internally prioritized them by the subdomains and
|
||||
// longest static path.
|
||||
// Implements the `RoutesProvider` interface.
|
||||
func (api *APIBuilder) GetRouterFilters() map[Party]*Filter {
|
||||
return api.routerFilters
|
||||
}
|
||||
|
||||
// UseRouter upserts one or more handlers that will be fired
|
||||
// right before the main router's request handler.
|
||||
//
|
||||
// Use this method to register handlers, that can ran
|
||||
// independently of the incoming request's values,
|
||||
// that they will be executed ALWAYS against ALL children incoming requests.
|
||||
// Example of use-case: CORS.
|
||||
//
|
||||
// Note that because these are executed before the router itself
|
||||
// the Context should not have access to the `GetCurrentRoute`
|
||||
// as it is not decided yet which route is responsible to handle the incoming request.
|
||||
// It's one level higher than the `WrapRouter`.
|
||||
// The context SHOULD call its `Next` method in order to proceed to
|
||||
// the next handler in the chain or the main request handler one.
|
||||
func (api *APIBuilder) UseRouter(handlers ...context.Handler) {
|
||||
if len(handlers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
beginHandlers := context.Handlers(handlers)
|
||||
// respect any execution rules (begin).
|
||||
api.handlerExecutionRules.Begin.apply(&beginHandlers)
|
||||
|
||||
if f := api.routerFilters[api]; f != nil && len(f.Handlers) > 0 { // exists.
|
||||
beginHandlers = context.UpsertHandlers(f.Handlers, beginHandlers) // remove dupls.
|
||||
} else {
|
||||
// Note(@kataras): we don't add the parent's filter handlers
|
||||
// on `Party` method because we need to know if a `UseRouter` call exist
|
||||
// before prepending the parent's ones and fill a new Filter on `routerFilters`,
|
||||
// that key should NOT exist on a Party without `UseRouter` handlers (see router.go).
|
||||
// That's the only reason we need the `parent` field.
|
||||
if api.parent != nil {
|
||||
// If it's not root, add the parent's handlers here.
|
||||
if root, ok := api.routerFilters[api.parent]; ok {
|
||||
beginHandlers = context.UpsertHandlers(root.Handlers, beginHandlers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subdomain, path := splitSubdomainAndPath(api.relativePath)
|
||||
api.routerFilters[api] = &Filter{
|
||||
Matcher: api,
|
||||
Subdomain: subdomain,
|
||||
Path: path,
|
||||
Handlers: beginHandlers,
|
||||
}
|
||||
}
|
||||
|
||||
// Use appends Handler(s) to the current Party's routes and child routes.
|
||||
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
|
||||
//
|
||||
@@ -774,19 +954,7 @@ func (api *APIBuilder) Use(handlers ...context.Handler) {
|
||||
// or on the basis of the middleware already existing,
|
||||
// replace that existing middleware instead.
|
||||
func (api *APIBuilder) UseOnce(handlers ...context.Handler) {
|
||||
reg:
|
||||
for _, handler := range handlers {
|
||||
name := context.HandlerName(handler)
|
||||
for i, registeredHandler := range api.middleware {
|
||||
registeredName := context.HandlerName(registeredHandler)
|
||||
if name == registeredName {
|
||||
api.middleware[i] = handler // replace this handler with the new one.
|
||||
continue reg // break and continue to the next handler.
|
||||
}
|
||||
}
|
||||
|
||||
api.middleware = append(api.middleware, handler) // or just insert it.
|
||||
}
|
||||
api.middleware = context.UpsertHandlers(api.middleware, handlers)
|
||||
}
|
||||
|
||||
// UseGlobal registers handlers that should run at the very beginning.
|
||||
|
||||
@@ -118,6 +118,10 @@ func (h *routerHandler) AddRoute(r *Route) error {
|
||||
type RoutesProvider interface { // api builder
|
||||
GetRoutes() []*Route
|
||||
GetRoute(routeName string) *Route
|
||||
// GetRouterFilters returns the app's router filters.
|
||||
// Read `UseRouter` for more.
|
||||
// The map can be altered before router built.
|
||||
GetRouterFilters() map[Party]*Filter
|
||||
}
|
||||
|
||||
func (h *routerHandler) Build(provider RoutesProvider) error {
|
||||
@@ -318,12 +322,12 @@ func bindMultiParamTypesHandler(r *Route) {
|
||||
r.topLink.beginHandlers = append(context.Handlers{decisionHandler}, r.topLink.beginHandlers...)
|
||||
}
|
||||
|
||||
func (h *routerHandler) canHandleSubdomain(ctx *context.Context, subdomain string) bool {
|
||||
func canHandleSubdomain(ctx *context.Context, subdomain string) bool {
|
||||
if subdomain == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
requestHost := ctx.Host()
|
||||
requestHost := ctx.Request().URL.Host
|
||||
if netutil.IsLoopbackSubdomain(requestHost) {
|
||||
// this fixes a bug when listening on
|
||||
// 127.0.0.1:8080 for example
|
||||
@@ -349,7 +353,7 @@ func (h *routerHandler) canHandleSubdomain(ctx *context.Context, subdomain strin
|
||||
return false
|
||||
}
|
||||
// continue to that, any subdomain is valid.
|
||||
} else if !strings.HasPrefix(requestHost, subdomain) { // subdomain contains the dot.
|
||||
} else if !strings.HasPrefix(requestHost, subdomain) { // subdomain contains the dot, e.g. "admin."
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -396,7 +400,7 @@ func (h *routerHandler) HandleRequest(ctx *context.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
if h.hosts && !h.canHandleSubdomain(ctx, t.subdomain) {
|
||||
if h.hosts && !canHandleSubdomain(ctx, t.subdomain) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -499,7 +503,7 @@ func (h *routerHandler) FireErrorCode(ctx *context.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
if h.errorHosts && !h.canHandleSubdomain(ctx, t.subdomain) {
|
||||
if h.errorHosts && !canHandleSubdomain(ctx, t.subdomain) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ import (
|
||||
//
|
||||
// Look the `APIBuilder` structure for its implementation.
|
||||
type Party interface {
|
||||
// IsRoot reports whether this Party is the root Application's one.
|
||||
// It will return false on all children Parties, no exception.
|
||||
IsRoot() bool
|
||||
|
||||
// ConfigureContainer accepts one or more functions that can be used
|
||||
// to configure dependency injection features of this Party
|
||||
// such as register dependency and register handlers that will automatically inject any valid dependency.
|
||||
@@ -44,10 +48,14 @@ type Party interface {
|
||||
// Look `OnErrorCode` too.
|
||||
OnAnyErrorCode(handlers ...context.Handler) []*Route
|
||||
|
||||
// Party groups routes which may have the same prefix and share same handlers,
|
||||
// returns that new rich subrouter.
|
||||
// Party returns a new child Party which inherites its
|
||||
// parent's options and middlewares.
|
||||
// If "relativePath" matches the parent's one then it returns the current Party.
|
||||
// A Party groups routes which may have the same prefix or subdomain and share same middlewares.
|
||||
//
|
||||
// You can even declare a subdomain with relativePath as "mysub." or see `Subdomain`.
|
||||
// To create a group of routes for subdomains
|
||||
// use the `Subdomain` or `WildcardSubdomain` methods
|
||||
// or pass a "relativePath" as "admin." or "*." respectfully.
|
||||
Party(relativePath string, middleware ...context.Handler) Party
|
||||
// PartyFunc same as `Party`, groups routes that share a base path or/and same handlers.
|
||||
// However this function accepts a function that receives this created Party instead.
|
||||
@@ -73,6 +81,21 @@ type Party interface {
|
||||
// So if app.Subdomain("admin").Subdomain("panel") then the result is: "panel.admin.".
|
||||
Subdomain(subdomain string, middleware ...context.Handler) Party
|
||||
|
||||
// UseRouter upserts one or more handlers that will be fired
|
||||
// right before the main router's request handler.
|
||||
//
|
||||
// Use this method to register handlers, that can ran
|
||||
// independently of the incoming request's values,
|
||||
// that they will be executed ALWAYS against ALL children incoming requests.
|
||||
// Example of use-case: CORS.
|
||||
//
|
||||
// Note that because these are executed before the router itself
|
||||
// the Context should not have access to the `GetCurrentRoute`
|
||||
// as it is not decided yet which route is responsible to handle the incoming request.
|
||||
// It's one level higher than the `WrapRouter`.
|
||||
// The context SHOULD call its `Next` method in order to proceed to
|
||||
// the next handler in the chain or the main request handler one.
|
||||
UseRouter(handlers ...context.Handler)
|
||||
// Use appends Handler(s) to the current Party's routes and child routes.
|
||||
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
|
||||
Use(middleware ...context.Handler)
|
||||
|
||||
@@ -235,6 +235,14 @@ func splitSubdomainAndPath(fullUnparsedPath string) (subdomain string, path stri
|
||||
subdomain = s[0:slashIdx]
|
||||
}
|
||||
|
||||
if slashIdx == -1 {
|
||||
// this will only happen when this function
|
||||
// is called to Party's relative path (e.g. control.admin.),
|
||||
// and not a route's one (the route's one always contains a slash).
|
||||
// return all as subdomain and "/" as path.
|
||||
return s, "/"
|
||||
}
|
||||
|
||||
path = s[slashIdx:]
|
||||
if !strings.Contains(path, "{") {
|
||||
path = strings.ReplaceAll(path, "//", "/")
|
||||
|
||||
@@ -3,6 +3,8 @@ package router
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
@@ -19,7 +21,6 @@ import (
|
||||
type Router struct {
|
||||
mu sync.Mutex // for Downgrade, WrapRouter & BuildRouter,
|
||||
|
||||
preHandlers context.Handlers // run before requestHandler, as middleware, same way context's handlers run, see `UseRouter`.
|
||||
requestHandler RequestHandler // build-accessible, can be changed to define a custom router or proxy, used on RefreshRouter too.
|
||||
mainHandler http.HandlerFunc // init-accessible
|
||||
wrapperFunc WrapperFunc
|
||||
@@ -87,23 +88,78 @@ func (router *Router) FindClosestPaths(subdomain, searchPath string, n int) []st
|
||||
return list
|
||||
}
|
||||
|
||||
// UseRouter registers one or more handlers that are fired
|
||||
// before the main router's request handler.
|
||||
//
|
||||
// Use this method to register handlers, that can ran
|
||||
// independently of the incoming request's method and path values,
|
||||
// that they will be executed ALWAYS against ALL incoming requests.
|
||||
// Example of use-case: CORS.
|
||||
//
|
||||
// Note that because these are executed before the router itself
|
||||
// the Context should not have access to the `GetCurrentRoute`
|
||||
// as it is not decided yet which route is responsible to handle the incoming request.
|
||||
// It's one level higher than the `WrapRouter`.
|
||||
// The context SHOULD call its `Next` method in order to proceed to
|
||||
// the next handler in the chain or the main request handler one.
|
||||
// ExecutionRules are NOT applied here.
|
||||
func (router *Router) UseRouter(handlers ...context.Handler) {
|
||||
router.preHandlers = append(router.preHandlers, handlers...)
|
||||
func (router *Router) buildMainHandlerWithFilters(routerFilters map[Party]*Filter, cPool *context.Pool, requestHandler RequestHandler) {
|
||||
sortedFilters := make([]*Filter, 0, len(routerFilters))
|
||||
// key was just there to enforce uniqueness on API level.
|
||||
for _, f := range routerFilters {
|
||||
sortedFilters = append(sortedFilters, f)
|
||||
// append it as one handlers so execution rules are being respected in that step too.
|
||||
f.Handlers = append(f.Handlers, func(ctx *context.Context) {
|
||||
// set the handler index back to 0 so the route's handlers can be executed as expected.
|
||||
ctx.HandlerIndex(0)
|
||||
// execute the main request handler, this will fire the found route's handlers
|
||||
// or if error the error code's associated handler.
|
||||
router.requestHandler.HandleRequest(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
sort.SliceStable(sortedFilters, func(i, j int) bool {
|
||||
left, right := sortedFilters[i], sortedFilters[j]
|
||||
var (
|
||||
leftSubLen = len(left.Subdomain)
|
||||
rightSubLen = len(right.Subdomain)
|
||||
|
||||
leftSlashLen = strings.Count(left.Path, "/")
|
||||
rightSlashLen = strings.Count(right.Path, "/")
|
||||
)
|
||||
|
||||
if leftSubLen == rightSubLen {
|
||||
if leftSlashLen > rightSlashLen {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if leftSubLen > rightSubLen {
|
||||
return true
|
||||
}
|
||||
|
||||
if leftSlashLen > rightSlashLen {
|
||||
return true
|
||||
}
|
||||
|
||||
if leftSlashLen == rightSlashLen {
|
||||
if len(left.Path) > len(right.Path) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return len(left.Path) > len(right.Path)
|
||||
})
|
||||
|
||||
router.mainHandler = func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := cPool.Acquire(w, r)
|
||||
|
||||
filterExecuted := false
|
||||
for _, f := range sortedFilters {
|
||||
// fmt.Printf("Sorted filter execution: [%s] [%s]\n", f.Subdomain, f.Path)
|
||||
if f.Matcher.Match(ctx) {
|
||||
// fmt.Printf("Matched [%s] and execute [%d] handlers [%s]\n\n", ctx.Path(), len(f.Handlers), context.HandlersNames(f.Handlers))
|
||||
filterExecuted = true
|
||||
// execute the final handlers chain.
|
||||
ctx.Do(f.Handlers)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !filterExecuted {
|
||||
// If not at least one match filter found and executed,
|
||||
// then just run the router.
|
||||
router.requestHandler.HandleRequest(ctx)
|
||||
}
|
||||
|
||||
cPool.Release(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// BuildRouter builds the router based on
|
||||
@@ -149,22 +205,9 @@ func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHan
|
||||
}
|
||||
}
|
||||
|
||||
// the important
|
||||
if len(router.preHandlers) > 0 {
|
||||
handlers := append(router.preHandlers, func(ctx *context.Context) {
|
||||
// set the handler index back to 0 so the route's handlers can be executed as exepcted.
|
||||
ctx.HandlerIndex(0)
|
||||
// execute the main request handler, this will fire the found route's handlers
|
||||
// or if error the error code's associated handler.
|
||||
router.requestHandler.HandleRequest(ctx)
|
||||
})
|
||||
|
||||
router.mainHandler = func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := cPool.Acquire(w, r)
|
||||
// execute the final handlers chain.
|
||||
ctx.Do(handlers)
|
||||
cPool.Release(ctx)
|
||||
}
|
||||
// the important stuff.
|
||||
if routerFilters := routesProvider.GetRouterFilters(); len(routerFilters) > 0 {
|
||||
router.buildMainHandlerWithFilters(routerFilters, cPool, requestHandler)
|
||||
} else {
|
||||
router.mainHandler = func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := cPool.Acquire(w, r)
|
||||
|
||||
@@ -35,7 +35,13 @@ var (
|
||||
secondUseHandler = writeHandler(secondUseResponse)
|
||||
|
||||
firstUseRouterResponse = "userouter1"
|
||||
firstUseRouterHandler = writeHandler(firstUseRouterResponse)
|
||||
// Use inline handler, no the `writeHandler`,
|
||||
// because it will be overriden by `secondUseRouterHandler` otherwise,
|
||||
// look `UseRouter:context.UpsertHandlers` for more.
|
||||
firstUseRouterHandler = func(ctx iris.Context) {
|
||||
ctx.WriteString(firstUseRouterResponse)
|
||||
ctx.Next()
|
||||
}
|
||||
|
||||
secondUseRouterResponse = "userouter2"
|
||||
secondUseRouterHandler = writeHandler(secondUseRouterResponse)
|
||||
@@ -178,3 +184,104 @@ func TestUseRouterStopExecution(t *testing.T) {
|
||||
e = httptest.New(t, app)
|
||||
e.GET("/").Expect().Status(iris.StatusForbidden).Body().Equal("err: custom error")
|
||||
}
|
||||
|
||||
func TestUseRouterParentDisallow(t *testing.T) {
|
||||
const expectedResponse = "no_userouter_allowed"
|
||||
|
||||
app := iris.New()
|
||||
app.UseRouter(func(ctx iris.Context) {
|
||||
ctx.WriteString("always")
|
||||
ctx.Next()
|
||||
})
|
||||
app.Get("/index", func(ctx iris.Context) {
|
||||
ctx.WriteString(expectedResponse)
|
||||
})
|
||||
|
||||
app.SetPartyMatcher(func(p iris.Party, ctx iris.Context) bool {
|
||||
// modifies the PartyMatcher to not match any UseRouter,
|
||||
// tests should receive the handlers response alone.
|
||||
return false
|
||||
})
|
||||
|
||||
app.PartyFunc("/", func(p iris.Party) { // it's the same instance of app.
|
||||
p.UseRouter(func(ctx iris.Context) {
|
||||
ctx.WriteString("_2")
|
||||
ctx.Next()
|
||||
})
|
||||
p.Get("/", func(ctx iris.Context) {
|
||||
ctx.WriteString(expectedResponse)
|
||||
})
|
||||
})
|
||||
|
||||
app.PartyFunc("/user", func(p iris.Party) {
|
||||
p.UseRouter(func(ctx iris.Context) {
|
||||
ctx.WriteString("_3")
|
||||
ctx.Next()
|
||||
})
|
||||
|
||||
p.Get("/", func(ctx iris.Context) {
|
||||
ctx.WriteString(expectedResponse)
|
||||
})
|
||||
})
|
||||
|
||||
e := httptest.New(t, app)
|
||||
e.GET("/index").Expect().Status(iris.StatusOK).Body().Equal(expectedResponse)
|
||||
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedResponse)
|
||||
e.GET("/user").Expect().Status(iris.StatusOK).Body().Equal(expectedResponse)
|
||||
}
|
||||
|
||||
func TestUseRouterSubdomains(t *testing.T) {
|
||||
app := iris.New()
|
||||
app.UseRouter(func(ctx iris.Context) {
|
||||
if ctx.Subdomain() == "old" {
|
||||
ctx.Next() // call the router, do not write.
|
||||
return
|
||||
}
|
||||
|
||||
// if we write here, it will always give 200 OK,
|
||||
// even on not registered routes, that's the point at the end,
|
||||
// full control here when we need it.
|
||||
ctx.WriteString("always_")
|
||||
ctx.Next()
|
||||
})
|
||||
|
||||
adminAPI := app.Subdomain("admin")
|
||||
adminAPI.UseRouter(func(ctx iris.Context) {
|
||||
ctx.WriteString("admin always_")
|
||||
ctx.Next()
|
||||
})
|
||||
adminAPI.Get("/", func(ctx iris.Context) {
|
||||
ctx.WriteString("admin")
|
||||
})
|
||||
|
||||
adminControlAPI := adminAPI.Subdomain("control")
|
||||
adminControlAPI.UseRouter(func(ctx iris.Context) {
|
||||
ctx.WriteString("control admin always_")
|
||||
ctx.Next()
|
||||
})
|
||||
adminControlAPI.Get("/", func(ctx iris.Context) {
|
||||
ctx.WriteString("control admin")
|
||||
})
|
||||
|
||||
oldAPI := app.Subdomain("old")
|
||||
oldAPI.Get("/", func(ctx iris.Context) {
|
||||
ctx.WriteString("chat")
|
||||
})
|
||||
|
||||
e := httptest.New(t, app, httptest.URL("http://example.com"))
|
||||
e.GET("/notfound").Expect().Status(iris.StatusOK).Body().Equal("always_")
|
||||
|
||||
e.GET("/").WithURL("http://admin.example.com").Expect().Status(iris.StatusOK).Body().
|
||||
Equal("always_admin always_admin")
|
||||
|
||||
e.GET("/").WithURL("http://control.admin.example.com").Expect().Status(iris.StatusOK).Body().
|
||||
Equal("always_admin always_control admin always_control admin")
|
||||
|
||||
// It has a route, and use router just proceeds to the router.
|
||||
e.GET("/").WithURL("http://old.example.com").Expect().Status(iris.StatusOK).Body().
|
||||
Equal("chat")
|
||||
// this is not a registered path, should fire 404, the UseRouter does not write
|
||||
// anything to the response writer, so the router has control over it.
|
||||
e.GET("/notfound").WithURL("http://old.example.com").Expect().Status(iris.StatusNotFound).Body().
|
||||
Equal("Not Found")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user