mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
NEW Feature: Fallback handlers by @ZaniaDeveloper via PR: #909
Relative: https://github.com/kataras/iris/issues/884 and https://github.com/iris-contrib/middleware/issues/32 Former-commit-id: 5d79d9fbb5792521ee707ba513cb40681438ea6f
This commit is contained in:
commit
eeac8ccdbd
|
@ -5,6 +5,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
|
"github.com/kataras/iris/core/router"
|
||||||
|
|
||||||
"github.com/iris-contrib/middleware/cors"
|
"github.com/iris-contrib/middleware/cors"
|
||||||
)
|
)
|
||||||
|
@ -12,13 +13,16 @@ import (
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
crs := cors.New(cors.Options{
|
|
||||||
|
// `crs := cors.NewAllowAllPartyMiddleware()`, or:
|
||||||
|
crs := cors.NewPartyMiddleware(cors.Options{
|
||||||
AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts.
|
AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts.
|
||||||
|
AllowedMethods: router.AllMethods[:],
|
||||||
AllowCredentials: true,
|
AllowCredentials: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
v1 := app.Party("/api/v1")
|
v1 := app.Party("/api/v1")
|
||||||
v1.Use(crs)
|
v1.ConfigureParty(crs)
|
||||||
{
|
{
|
||||||
v1.Get("/home", func(ctx iris.Context) {
|
v1.Get("/home", func(ctx iris.Context) {
|
||||||
ctx.WriteString("Hello from /home")
|
ctx.WriteString("Hello from /home")
|
||||||
|
@ -29,6 +33,12 @@ func main() {
|
||||||
v1.Post("/send", func(ctx iris.Context) {
|
v1.Post("/send", func(ctx iris.Context) {
|
||||||
ctx.WriteString("sent")
|
ctx.WriteString("sent")
|
||||||
})
|
})
|
||||||
|
v1.Put("/send", func(ctx iris.Context) {
|
||||||
|
ctx.WriteString("updated")
|
||||||
|
})
|
||||||
|
v1.Delete("/send", func(ctx iris.Context) {
|
||||||
|
ctx.WriteString("deleted")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// or use that to wrap the entire router
|
// or use that to wrap the entire router
|
||||||
|
|
29
_examples/routing/fallback-handlers/main.go
Normal file
29
_examples/routing/fallback-handlers/main.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kataras/iris"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := iris.New()
|
||||||
|
|
||||||
|
// this works as expected now,
|
||||||
|
// will handle *all* expect DELETE requests, even if there is no routes
|
||||||
|
app.Get("/action/{p}", h)
|
||||||
|
|
||||||
|
app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
|
||||||
|
}
|
||||||
|
|
||||||
|
func h(ctx iris.Context) {
|
||||||
|
ctx.Writef("[%s] %s : Parameter = `%s`", ctx.Method(), ctx.Path(), ctx.Params().Get("p"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fallbackHandler(ctx iris.Context) {
|
||||||
|
if ctx.Method() == "DELETE" {
|
||||||
|
ctx.Next()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Writef("[%s] %s : From fallback handler", ctx.Method(), ctx.Path())
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
none := app.None("/invisible/{username}", func(ctx iris.Context) {
|
none := app.None("/invisible/{username}", func(ctx iris.Context) {
|
||||||
ctx.Writef("Hello %s with method: %s", ctx.Values().GetString("username"), ctx.Method())
|
ctx.Writef("Hello %s with method: %s", ctx.Params().Get("username"), ctx.Method())
|
||||||
|
|
||||||
if from := ctx.Values().GetString("from"); from != "" {
|
if from := ctx.Values().GetString("from"); from != "" {
|
||||||
ctx.Writef("\nI see that you're coming from %s", from)
|
ctx.Writef("\nI see that you're coming from %s", from)
|
||||||
|
|
|
@ -48,4 +48,7 @@ type Application interface {
|
||||||
// If a handler is not already registered,
|
// If a handler is not already registered,
|
||||||
// then it creates & registers a new trivial handler on the-fly.
|
// then it creates & registers a new trivial handler on the-fly.
|
||||||
FireErrorCode(ctx Context)
|
FireErrorCode(ctx Context)
|
||||||
|
|
||||||
|
// RouteExists checks if a route exists
|
||||||
|
RouteExists(method string, path string, ctx Context) bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -904,6 +904,9 @@ type Context interface {
|
||||||
// It's for extreme use cases, 99% of the times will never be useful for you.
|
// It's for extreme use cases, 99% of the times will never be useful for you.
|
||||||
Exec(method string, path string)
|
Exec(method string, path string)
|
||||||
|
|
||||||
|
// RouteExists checks if a route exists
|
||||||
|
RouteExists(method string, path string) bool
|
||||||
|
|
||||||
// Application returns the iris app instance which belongs to this context.
|
// Application returns the iris app instance which belongs to this context.
|
||||||
// Worth to notice that this function returns an interface
|
// Worth to notice that this function returns an interface
|
||||||
// of the Application, which contains methods that are safe
|
// of the Application, which contains methods that are safe
|
||||||
|
@ -3130,6 +3133,11 @@ func (ctx *context) Exec(method string, path string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RouteExists checks if a route exists
|
||||||
|
func (ctx *context) RouteExists(method string, path string) bool {
|
||||||
|
return ctx.Application().RouteExists(method, path, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// Application returns the iris app instance which belongs to this context.
|
// Application returns the iris app instance which belongs to this context.
|
||||||
// Worth to notice that this function returns an interface
|
// Worth to notice that this function returns an interface
|
||||||
// of the Application, which contains methods that are safe
|
// of the Application, which contains methods that are safe
|
||||||
|
|
|
@ -90,6 +90,8 @@ type APIBuilder struct {
|
||||||
doneHandlers context.Handlers
|
doneHandlers context.Handlers
|
||||||
// global done handlers, order doesn't matter
|
// global done handlers, order doesn't matter
|
||||||
doneGlobalHandlers context.Handlers
|
doneGlobalHandlers context.Handlers
|
||||||
|
// fallback stack, LIFO order
|
||||||
|
fallbackStack *FallbackStack
|
||||||
// the per-party
|
// the per-party
|
||||||
relativePath string
|
relativePath string
|
||||||
}
|
}
|
||||||
|
@ -106,11 +108,20 @@ func NewAPIBuilder() *APIBuilder {
|
||||||
reporter: errors.NewReporter(),
|
reporter: errors.NewReporter(),
|
||||||
relativePath: "/",
|
relativePath: "/",
|
||||||
routes: new(repository),
|
routes: new(repository),
|
||||||
|
fallbackStack: NewFallbackStack(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigureParty configures this party like `iris.Application#Configure`
|
||||||
|
// That allows middlewares focused on the Party like CORS middleware
|
||||||
|
func (api *APIBuilder) ConfigureParty(conf ...PartyConfigurator) {
|
||||||
|
for _, h := range conf {
|
||||||
|
h(api)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetRelPath returns the current party's relative path.
|
// GetRelPath returns the current party's relative path.
|
||||||
// i.e:
|
// i.e:
|
||||||
// if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users".
|
// if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users".
|
||||||
|
@ -270,6 +281,7 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P
|
||||||
// per-party/children
|
// per-party/children
|
||||||
middleware: middleware,
|
middleware: middleware,
|
||||||
doneHandlers: api.doneHandlers,
|
doneHandlers: api.doneHandlers,
|
||||||
|
fallbackStack: api.fallbackStack.Fork(),
|
||||||
relativePath: fullpath,
|
relativePath: fullpath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -422,6 +434,21 @@ func (api *APIBuilder) DoneGlobal(handlers ...context.Handler) {
|
||||||
api.doneGlobalHandlers = append(api.doneGlobalHandlers, handlers...)
|
api.doneGlobalHandlers = append(api.doneGlobalHandlers, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback appends Handler(s) to the current fallback stack.
|
||||||
|
// Handler(s) is(are) called from Fallback stack when no route found and before sending NotFound status.
|
||||||
|
// Therefore Handler(s) in Fallback stack could send another thing than NotFound status,
|
||||||
|
// if `Context.Next()` method is not called.
|
||||||
|
// Done & DoneGlobal Handlers are not called.
|
||||||
|
func (api *APIBuilder) Fallback(middleware ...context.Handler) {
|
||||||
|
api.fallbackStack.Add(middleware)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FallBackStack returns Fallback stack, this is implementation of interface RoutesProvider
|
||||||
|
// that is used in Router building by the RequestHandler.
|
||||||
|
func (api *APIBuilder) GetFallBackStack() *FallbackStack {
|
||||||
|
return api.fallbackStack
|
||||||
|
}
|
||||||
|
|
||||||
// Reset removes all the begin and done handlers that may derived from the parent party via `Use` & `Done`,
|
// 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`.
|
// note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`.
|
||||||
//
|
//
|
||||||
|
|
78
core/router/fallback_stack.go
Normal file
78
core/router/fallback_stack.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FallbackStack is a stack (with LIFO calling order) for fallback handlers
|
||||||
|
// A fallback handler(s) is(are) called from Fallback stack
|
||||||
|
// when no route found and before sending NotFound status.
|
||||||
|
// Therefore Handler(s) in Fallback stack could send another thing than NotFound status,
|
||||||
|
// if `Context.Next()` method is not called.
|
||||||
|
// Done & DoneGlobal Handlers are not called.
|
||||||
|
type FallbackStack struct {
|
||||||
|
parent *FallbackStack
|
||||||
|
handlers context.Handlers
|
||||||
|
}
|
||||||
|
|
||||||
|
// _size is a terminal recursive method for computing size the stack
|
||||||
|
func (stk *FallbackStack) _size(i int) int {
|
||||||
|
res := i + len(stk.handlers)
|
||||||
|
|
||||||
|
if stk.parent == nil {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
return stk.parent._size(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate is a recursive method for concatenating handlers to `list` parameter
|
||||||
|
func (stk *FallbackStack) populate(list context.Handlers) {
|
||||||
|
n := copy(list, stk.handlers)
|
||||||
|
|
||||||
|
if stk.parent != nil {
|
||||||
|
stk.parent.populate(list[n:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size gives the size of the full stack hierarchy
|
||||||
|
func (stk *FallbackStack) Size() int {
|
||||||
|
return stk._size(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add appends handlers to the beginning of the stack to have a LIFO calling order
|
||||||
|
func (stk *FallbackStack) Add(h context.Handlers) {
|
||||||
|
stk.handlers = append(stk.handlers, h...)
|
||||||
|
|
||||||
|
copy(stk.handlers[len(h):], stk.handlers)
|
||||||
|
copy(stk.handlers, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fork make a new stack from this stack, and so create a stack child (leaf from a tree of stacks)
|
||||||
|
func (stk *FallbackStack) Fork() *FallbackStack {
|
||||||
|
return &FallbackStack{
|
||||||
|
parent: stk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List concatenate all handlers in stack hierarchy
|
||||||
|
func (stk *FallbackStack) List() context.Handlers {
|
||||||
|
res := make(context.Handlers, stk.Size())
|
||||||
|
stk.populate(res)
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFallbackStack create a new Fallback stack with as first entry
|
||||||
|
// a handler which send NotFound status (the default)
|
||||||
|
func NewFallbackStack() *FallbackStack {
|
||||||
|
return &FallbackStack{
|
||||||
|
handlers: context.Handlers{
|
||||||
|
func(ctx context.Context) {
|
||||||
|
ctx.StatusCode(http.StatusNotFound)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
112
core/router/fallback_stack_test.go
Normal file
112
core/router/fallback_stack_test.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFallbackStackAdd(t *testing.T) {
|
||||||
|
l := make([]string, 0)
|
||||||
|
|
||||||
|
stk := &router.FallbackStack{}
|
||||||
|
stk.Add(context.Handlers{
|
||||||
|
func(context.Context) {
|
||||||
|
l = append(l, "POS1")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
stk.Add(context.Handlers{
|
||||||
|
func(context.Context) {
|
||||||
|
l = append(l, "POS2")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if stk.Size() != 2 {
|
||||||
|
t.Fatalf("Bad size (%d != 2)", stk.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range stk.List() {
|
||||||
|
h(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l[0] != "POS2") || (l[1] != "POS1") {
|
||||||
|
t.Fatal("Bad positions: ", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFallbackStackFork(t *testing.T) {
|
||||||
|
l := make([]string, 0)
|
||||||
|
|
||||||
|
stk := &router.FallbackStack{}
|
||||||
|
|
||||||
|
stk.Add(context.Handlers{
|
||||||
|
func(context.Context) {
|
||||||
|
l = append(l, "POS1")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
stk.Add(context.Handlers{
|
||||||
|
func(context.Context) {
|
||||||
|
l = append(l, "POS2")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
stk = stk.Fork()
|
||||||
|
|
||||||
|
stk.Add(context.Handlers{
|
||||||
|
func(context.Context) {
|
||||||
|
l = append(l, "POS3")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
stk.Add(context.Handlers{
|
||||||
|
func(context.Context) {
|
||||||
|
l = append(l, "POS4")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if stk.Size() != 4 {
|
||||||
|
t.Fatalf("Bad size (%d != 4)", stk.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range stk.List() {
|
||||||
|
h(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l[0] != "POS4") || (l[1] != "POS3") || (l[2] != "POS2") || (l[3] != "POS1") {
|
||||||
|
t.Fatal("Bad positions: ", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFallbackStackCall(t *testing.T) {
|
||||||
|
// build the api
|
||||||
|
app := iris.New()
|
||||||
|
|
||||||
|
// setup an existing routes
|
||||||
|
app.Handle("GET", "/route", func(ctx context.Context) {
|
||||||
|
ctx.WriteString("ROUTED")
|
||||||
|
})
|
||||||
|
|
||||||
|
// setup fallback handler
|
||||||
|
app.Fallback(func(ctx context.Context) {
|
||||||
|
if ctx.Method() != "GET" {
|
||||||
|
ctx.Next()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.WriteString("FALLBACK")
|
||||||
|
})
|
||||||
|
|
||||||
|
// run the tests
|
||||||
|
e := httptest.New(t, app, httptest.Debug(false))
|
||||||
|
|
||||||
|
e.Request("GET", "/route").Expect().Status(iris.StatusOK).Body().Equal("ROUTED")
|
||||||
|
e.Request("POST", "/route").Expect().Status(iris.StatusNotFound)
|
||||||
|
e.Request("POST", "/noroute").Expect().Status(iris.StatusNotFound)
|
||||||
|
e.Request("GET", "/noroute").Expect().Status(iris.StatusOK).Body().Equal("FALLBACK")
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ type RequestHandler interface {
|
||||||
HandleRequest(context.Context)
|
HandleRequest(context.Context)
|
||||||
// Build should builds the handler, it's being called on router's BuildRouter.
|
// Build should builds the handler, it's being called on router's BuildRouter.
|
||||||
Build(provider RoutesProvider) error
|
Build(provider RoutesProvider) error
|
||||||
|
// RouteExists checks if a route exists
|
||||||
|
RouteExists(method, path string, ctx context.Context) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type tree struct {
|
type tree struct {
|
||||||
|
@ -35,6 +37,7 @@ type tree struct {
|
||||||
type routerHandler struct {
|
type routerHandler struct {
|
||||||
trees []*tree
|
trees []*tree
|
||||||
hosts bool // true if at least one route contains a Subdomain.
|
hosts bool // true if at least one route contains a Subdomain.
|
||||||
|
fallbackStack *FallbackStack
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ RequestHandler = &routerHandler{}
|
var _ RequestHandler = &routerHandler{}
|
||||||
|
@ -82,11 +85,14 @@ func NewDefaultHandler() RequestHandler {
|
||||||
type RoutesProvider interface { // api builder
|
type RoutesProvider interface { // api builder
|
||||||
GetRoutes() []*Route
|
GetRoutes() []*Route
|
||||||
GetRoute(routeName string) *Route
|
GetRoute(routeName string) *Route
|
||||||
|
|
||||||
|
GetFallBackStack() *FallbackStack
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *routerHandler) Build(provider RoutesProvider) error {
|
func (h *routerHandler) Build(provider RoutesProvider) error {
|
||||||
registeredRoutes := provider.GetRoutes()
|
registeredRoutes := provider.GetRoutes()
|
||||||
h.trees = h.trees[0:0] // reset, inneed when rebuilding.
|
h.trees = h.trees[0:0] // reset, inneed when rebuilding.
|
||||||
|
h.fallbackStack = provider.GetFallBackStack()
|
||||||
|
|
||||||
// sort, subdomains goes first.
|
// sort, subdomains goes first.
|
||||||
sort.Slice(registeredRoutes, func(i, j int) bool {
|
sort.Slice(registeredRoutes, func(i, j int) bool {
|
||||||
|
@ -242,5 +248,62 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h.fallbackStack == nil {
|
||||||
ctx.StatusCode(http.StatusNotFound)
|
ctx.StatusCode(http.StatusNotFound)
|
||||||
|
} else {
|
||||||
|
ctx.Do(h.fallbackStack.List())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouteExists checks if a route exists
|
||||||
|
func (h *routerHandler) RouteExists(method, path string, ctx context.Context) bool {
|
||||||
|
for i := range h.trees {
|
||||||
|
t := h.trees[i]
|
||||||
|
if method != t.Method {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.hosts && t.Subdomain != "" {
|
||||||
|
requestHost := ctx.Host()
|
||||||
|
if netutil.IsLoopbackSubdomain(requestHost) {
|
||||||
|
// this fixes a bug when listening on
|
||||||
|
// 127.0.0.1:8080 for example
|
||||||
|
// and have a wildcard subdomain and a route registered to root domain.
|
||||||
|
continue // it's not a subdomain, it's something like 127.0.0.1 probably
|
||||||
|
}
|
||||||
|
// it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
|
||||||
|
if t.Subdomain == SubdomainWildcardIndicator {
|
||||||
|
// mydomain.com -> invalid
|
||||||
|
// localhost -> invalid
|
||||||
|
// sub.mydomain.com -> valid
|
||||||
|
// sub.localhost -> valid
|
||||||
|
serverHost := ctx.Application().ConfigurationReadOnly().GetVHost()
|
||||||
|
if serverHost == requestHost {
|
||||||
|
continue // it's not a subdomain, it's a full domain (with .com...)
|
||||||
|
}
|
||||||
|
|
||||||
|
dotIdx := strings.IndexByte(requestHost, '.')
|
||||||
|
slashIdx := strings.IndexByte(requestHost, '/')
|
||||||
|
if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) {
|
||||||
|
// if "." was found anywhere but not at the first path segment (host).
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// continue to that, any subdomain is valid.
|
||||||
|
} else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, handlers := t.Nodes.Find(path, ctx.Params())
|
||||||
|
if len(handlers) > 0 {
|
||||||
|
// found
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// not found or method not allowed.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,18 @@ import (
|
||||||
// Party is here to separate the concept of
|
// Party is here to separate the concept of
|
||||||
// api builder and the sub api builder.
|
// api builder and the sub api builder.
|
||||||
|
|
||||||
|
// PartyConfigurator is handler for configuring a party (it works with iris.Application)
|
||||||
|
type PartyConfigurator func(party Party)
|
||||||
|
|
||||||
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
|
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
|
||||||
// Party could also be named as 'Join' or 'Node' or 'Group' , Party chosen because it is fun.
|
// Party could also be named as 'Join' or 'Node' or 'Group' , Party chosen because it is fun.
|
||||||
//
|
//
|
||||||
// Look the "APIBuilder" for its implementation.
|
// Look the "APIBuilder" for its implementation.
|
||||||
type Party interface {
|
type Party interface {
|
||||||
|
// ConfigureParty configures this party like `iris.Application#Configure`
|
||||||
|
// That allows middlewares focused on the Party like CORS middleware
|
||||||
|
ConfigureParty(...PartyConfigurator)
|
||||||
|
|
||||||
// GetRelPath returns the current party's relative path.
|
// GetRelPath returns the current party's relative path.
|
||||||
// i.e:
|
// i.e:
|
||||||
// if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users".
|
// if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users".
|
||||||
|
@ -60,6 +67,13 @@ type Party interface {
|
||||||
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
|
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
|
||||||
Use(middleware ...context.Handler)
|
Use(middleware ...context.Handler)
|
||||||
|
|
||||||
|
// Fallback appends Handler(s) to the current Party's fallback stack.
|
||||||
|
// Handler(s) is(are) called from Fallback stack when no route found and before sending NotFound status.
|
||||||
|
// Therefore Handler(s) in Fallback stack could send another thing than NotFound status,
|
||||||
|
// if `Context.Next()` method is not called.
|
||||||
|
// Done Handler(s) is(are) not called.
|
||||||
|
Fallback(middleware ...context.Handler)
|
||||||
|
|
||||||
// Done appends to the very end, Handler(s) to the current Party's routes and child routes.
|
// Done appends to the very end, Handler(s) to the current Party's routes and child routes.
|
||||||
// The difference from .Use is that this/or these Handler(s) are being always running last.
|
// The difference from .Use is that this/or these Handler(s) are being always running last.
|
||||||
Done(handlers ...context.Handler)
|
Done(handlers ...context.Handler)
|
||||||
|
|
|
@ -147,6 +147,11 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
router.mainHandler(w, r)
|
router.mainHandler(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RouteExists checks if a route exists
|
||||||
|
func (router *Router) RouteExists(method, path string, ctx context.Context) bool {
|
||||||
|
return router.requestHandler.RouteExists(method, path, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
type wrapper struct {
|
type wrapper struct {
|
||||||
router http.HandlerFunc // http.HandlerFunc to catch the CURRENT state of its .ServeHTTP on case of future change.
|
router http.HandlerFunc // http.HandlerFunc to catch the CURRENT state of its .ServeHTTP on case of future change.
|
||||||
wrapperFunc func(http.ResponseWriter, *http.Request, http.HandlerFunc)
|
wrapperFunc func(http.ResponseWriter, *http.Request, http.HandlerFunc)
|
||||||
|
|
41
core/router/router_test.go
Normal file
41
core/router/router_test.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package router_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kataras/iris"
|
||||||
|
"github.com/kataras/iris/context"
|
||||||
|
"github.com/kataras/iris/httptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRouteExists(t *testing.T) {
|
||||||
|
// build the api
|
||||||
|
app := iris.New()
|
||||||
|
emptyHandler := func(context.Context) {}
|
||||||
|
|
||||||
|
// setup the tested routes
|
||||||
|
app.Handle("GET", "/route-exists", emptyHandler)
|
||||||
|
app.Handle("POST", "/route-with-param/{param}", emptyHandler)
|
||||||
|
|
||||||
|
// check RouteExists
|
||||||
|
app.Handle("GET", "/route-test", func(ctx context.Context) {
|
||||||
|
if ctx.RouteExists("GET", "/route-not-exists") {
|
||||||
|
t.Error("Route with path should not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.RouteExists("POST", "/route-exists") {
|
||||||
|
t.Error("Route with method should not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.RouteExists("GET", "/route-exists") {
|
||||||
|
t.Error("Route 1 should exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.RouteExists("POST", "/route-with-param/a-param") {
|
||||||
|
t.Error("Route 2 should exists")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// run the tests
|
||||||
|
httptest.New(t, app, httptest.Debug(false)).Request("GET", "/route-test").Expect().Status(iris.StatusOK)
|
||||||
|
}
|
|
@ -176,6 +176,13 @@ func (app *Application) Handle(controller interface{}) *Application {
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback is an alias to `app.Router.Fallback(handlers...)`
|
||||||
|
//
|
||||||
|
// See `core/router#Party.Fallback`
|
||||||
|
func (app *Application) Fallback(handlers ...context.Handler) {
|
||||||
|
app.Router.Fallback(handlers...)
|
||||||
|
}
|
||||||
|
|
||||||
// Clone returns a new mvc Application which has the dependencies
|
// Clone returns a new mvc Application which has the dependencies
|
||||||
// of the current mvc Mpplication's dependencies.
|
// of the current mvc Mpplication's dependencies.
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in New Issue
Block a user