1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-17 18:07:01 +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:
Gerasimos (Makis) Maropoulos
2020-08-12 07:20:07 +03:00
parent da029d6f37
commit 0761bc35ee
15 changed files with 639 additions and 120 deletions

View File

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