1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-24 05:17:03 +00:00
Former-commit-id: 5576c44b64014fb00dd79e618b815b5f52b705e4
This commit is contained in:
Gerasimos Maropoulos
2018-03-10 14:22:56 +02:00
parent 1165b4527a
commit 4993918a12
11 changed files with 131 additions and 364 deletions

View File

@@ -1,7 +1,6 @@
package main
// $ go get github.com/rs/cors
// $ go run main.go
// go get -u github.com/iris-contrib/middleware/...
import (
"github.com/kataras/iris"
@@ -12,14 +11,12 @@ import (
func main() {
app := iris.New()
// `crs := cors.NewAllowAllPartyMiddleware()`, or:
crs := cors.NewPartyMiddleware(cors.Options{
crs := cors.New(cors.Options{
AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts.
AllowCredentials: true,
})
v1 := app.Party("/api/v1")
v1.ConfigureParty(crs)
v1 := app.Party("/api/v1", crs).AllowMethods(iris.MethodOptions) // <- important for the preflight.
{
v1.Get("/home", func(ctx iris.Context) {
ctx.WriteString("Hello from /home")
@@ -38,13 +35,5 @@ func main() {
})
}
// or use that to wrap the entire router
// even before the path and method matching
// this should work better and with all cors' features.
// Use that instead, if suits you.
// app.WrapRouter(cors.WrapNext(cors.Options{
// AllowedOrigins: []string{"*"},
// AllowCredentials: true,
// }))
app.Run(iris.Addr("localhost:8080"))
}

View File

@@ -1,29 +0,0 @@
package main
import "github.com/kataras/iris"
func main() {
app := iris.New()
// add a fallback handler to process requests that would not be declared in the router.
app.Fallback(fallbackHandler)
// 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() == iris.MethodDelete {
ctx.NextOrNotFound()
return
}
ctx.Writef("[%s] %s : From fallback handler", ctx.Method(), ctx.Path())
}

View File

@@ -49,6 +49,7 @@ type Application interface {
// then it creates & registers a new trivial handler on the-fly.
FireErrorCode(ctx Context)
// RouteExists checks if a route exists
RouteExists(method string, path string, ctx Context) bool
// RouteExists reports whether a particular route exists
// It will search from the current subdomain of context's host, if not inside the root domain.
RouteExists(ctx Context, method, path string) bool
}

View File

@@ -910,7 +910,7 @@ type Context interface {
// TransactionsSkipped returns true if the transactions skipped or canceled at all.
TransactionsSkipped() bool
// Exec calls the framewrok's ServeCtx
// Exec calls the `context/Application#ServeCtx`
// based on this context but with a changed method and path
// like it was requested by the user, but it is not.
//
@@ -933,10 +933,11 @@ type Context interface {
// Context's Values and the Session are kept in order to be able to communicate via the result route.
//
// It's for extreme use cases, 99% of the times will never be useful for you.
Exec(method string, path string)
Exec(method, path string)
// RouteExists checks if a route exists
RouteExists(method string, path string) bool
// RouteExists reports whether a particular route exists
// It will search from the current subdomain of context's host, if not inside the root domain.
RouteExists(method, path string) bool
// Application returns the iris app instance which belongs to this context.
// Worth to notice that this function returns an interface
@@ -3164,7 +3165,10 @@ func (ctx *context) TransactionsSkipped() bool {
//
// It's for extreme use cases, 99% of the times will never be useful for you.
func (ctx *context) Exec(method string, path string) {
if path != "" {
if path == "" {
return
}
if method == "" {
method = "GET"
}
@@ -3178,7 +3182,6 @@ func (ctx *context) Exec(method string, path string) {
backupMethod := ctx.Method()
// don't backupValues := ctx.Values().ReadOnly()
// [sessions stays]
// [values stays]
// reset handlers
ctx.SetHandlers(nil)
@@ -3188,6 +3191,8 @@ func (ctx *context) Exec(method string, path string) {
req.RequestURI = path
req.URL.Path = path
req.Method = method
req.Host = req.Host
// execute the route from the (internal) context router
// this way we keep the sessions and the values
ctx.Application().ServeHTTPC(ctx)
@@ -3206,11 +3211,11 @@ func (ctx *context) Exec(method string, path string) {
// ctx.Values().Set(key, value)
// })
}
}
// RouteExists checks if a route exists
func (ctx *context) RouteExists(method string, path string) bool {
return ctx.Application().RouteExists(method, path, ctx)
// RouteExists reports whether a particular route exists
// It will search from the current subdomain of context's host, if not inside the root domain.
func (ctx *context) RouteExists(method, path string) bool {
return ctx.Application().RouteExists(ctx, method, path)
}
// Application returns the iris app instance which belongs to this context.

View File

@@ -42,6 +42,12 @@ type repository struct {
}
func (r *repository) register(route *Route) {
for _, r := range r.routes {
if r.String() == route.String() {
return // do not register any duplicates, the sooner the better.
}
}
r.routes = append(r.routes, route)
}
@@ -90,14 +96,17 @@ type APIBuilder struct {
doneHandlers context.Handlers
// global done handlers, order doesn't matter
doneGlobalHandlers context.Handlers
// fallback stack, LIFO order, initialized on first `Fallback`.
fallbackStack *FallbackStack
// the per-party
relativePath string
// allowMethods are filled with the `AllowMethods` func.
// 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`...).
allowMethods []string
}
var _ Party = &APIBuilder{}
var _ RoutesProvider = &APIBuilder{} // passed to the default request handler (routerHandler)
var _ Party = (*APIBuilder)(nil)
var _ 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.
@@ -108,20 +117,11 @@ func NewAPIBuilder() *APIBuilder {
reporter: errors.NewReporter(),
relativePath: "/",
routes: new(repository),
fallbackStack: NewFallbackStack(),
}
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.
// i.e:
// if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users".
@@ -140,6 +140,16 @@ func (api *APIBuilder) GetReporter() *errors.Reporter {
return api.reporter
}
// AllowMethods will re-register the future routes that will be registered
// via `Handle`, `Get`, `Post`, ... to the given "methods" on that Party and its children "Parties",
// duplicates are not registered.
//
// Call of `AllowMethod` will override any previous allow methods.
func (api *APIBuilder) AllowMethods(methods ...string) Party {
api.allowMethods = methods
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.
//
@@ -181,23 +191,30 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
// here we separate the subdomain and relative path
subdomain, path := splitSubdomainAndPath(fullpath)
r, err := NewRoute(method, subdomain, path, possibleMainHandlerName, routeHandlers, api.macros)
// if allowMethods are empty, then simply register with the passed, main, method.
methods := append(api.allowMethods, method)
var (
route *Route // the latest one is this route registered, see methods append.
err error // not used outside of loop scope.
)
for _, m := range methods {
route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, api.macros)
if err != nil { // template path parser errors:
api.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path)
return nil
return nil // fail on first error.
}
// Add UseGlobal & DoneGlobal Handlers
r.use(api.beginGlobalHandlers)
r.done(api.doneGlobalHandlers)
route.use(api.beginGlobalHandlers)
route.done(api.doneGlobalHandlers)
// global
api.routes.register(r)
api.routes.register(route)
}
// per -party, used for done handlers
// api.apiRoutes = append(api.apiRoutes, r)
return r
return route
}
// HandleMany works like `Handle` but can receive more than one
@@ -270,6 +287,10 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P
// append the parent's + child's handlers
middleware := joinHandlers(api.middleware, handlers)
// the allow methods per party and its children.
allowMethods := make([]string, len(api.allowMethods))
copy(allowMethods, api.allowMethods)
return &APIBuilder{
// global/api builder
macros: api.macros,
@@ -280,9 +301,9 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P
reporter: api.reporter,
// per-party/children
middleware: middleware,
doneHandlers: api.doneHandlers,
fallbackStack: api.fallbackStack.Fork(),
doneHandlers: api.doneHandlers[0:],
relativePath: fullpath,
allowMethods: allowMethods,
}
}
@@ -434,21 +455,6 @@ func (api *APIBuilder) DoneGlobal(handlers ...context.Handler) {
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.NextOrNotFound()` method is not called.
// Done & DoneGlobal Handlers are not called.
func (api *APIBuilder) Fallback(middleware ...context.Handler) {
api.fallbackStack.Add(middleware)
}
// GetFallBackStack 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`,
// note that the `Reset` will not reset the handlers that are registered via `UseGlobal` & `DoneGlobal`.
//

View File

@@ -1,65 +0,0 @@
package router
import "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#NextOrNotFound()` 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 empty Fallback stack.
func NewFallbackStack() *FallbackStack { return &FallbackStack{} }

View File

@@ -1,111 +0,0 @@
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.NextOrNotFound() // it checks if we have next, otherwise fire 404 not found.
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")
}

View File

@@ -22,8 +22,8 @@ type RequestHandler interface {
HandleRequest(context.Context)
// Build should builds the handler, it's being called on router's BuildRouter.
Build(provider RoutesProvider) error
// RouteExists checks if a route exists
RouteExists(method, path string, ctx context.Context) bool
// RouteExists reports whether a particular route exists.
RouteExists(ctx context.Context, method, path string) bool
}
type tree struct {
@@ -37,12 +37,6 @@ type tree struct {
type routerHandler struct {
trees []*tree
hosts bool // true if at least one route contains a Subdomain.
fallbackStack *FallbackStack
// on build: true if fallbackStack.Size() > 0,
// reduces the checks because fallbackStack is NEVER nil (api_builder.go always initializes it).
// If re-checked needed (serve-time fallback handler added)
// then a re-build/refresh of the application's router is necessary, as with every handler.
hasFallbackHandlers bool
}
var _ RequestHandler = &routerHandler{}
@@ -90,15 +84,11 @@ func NewDefaultHandler() RequestHandler {
type RoutesProvider interface { // api builder
GetRoutes() []*Route
GetRoute(routeName string) *Route
GetFallBackStack() *FallbackStack
}
func (h *routerHandler) Build(provider RoutesProvider) error {
registeredRoutes := provider.GetRoutes()
h.trees = h.trees[0:0] // reset, inneed when rebuilding.
h.fallbackStack = provider.GetFallBackStack()
h.hasFallbackHandlers = h.fallbackStack.Size() > 0
// sort, subdomains goes first.
sort.Slice(registeredRoutes, func(i, j int) bool {
@@ -262,16 +252,12 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
}
}
if h.hasFallbackHandlers {
ctx.Do(h.fallbackStack.List())
return
}
ctx.StatusCode(http.StatusNotFound)
}
// RouteExists checks if a route exists
func (h *routerHandler) RouteExists(method, path string, ctx context.Context) bool {
// RouteExists reports whether a particular route exists
// It will search from the current subdomain of context's host, if not inside the root domain.
func (h *routerHandler) RouteExists(ctx context.Context, method, path string) bool {
for i := range h.trees {
t := h.trees[i]
if method != t.Method {

View File

@@ -6,21 +6,11 @@ import (
"github.com/kataras/iris/core/router/macro"
)
// Party is here to separate the concept of
// 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 could also be named as 'Join' or 'Node' or 'Group' , Party chosen because it is fun.
//
// Look the "APIBuilder" for its implementation.
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.
// i.e:
// if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users".
@@ -67,13 +57,6 @@ type Party interface {
// If the current Party is the root, then it registers the middleware to all child Parties' routes too.
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.
// The difference from .Use is that this/or these Handler(s) are being always running last.
Done(handlers ...context.Handler)
@@ -82,6 +65,14 @@ type Party interface {
//
// Returns this Party.
Reset() Party
// AllowMethods will re-register the future routes that will be registered
// via `Handle`, `Get`, `Post`, ... to the given "methods" on that Party and its children "Parties",
// duplicates are not registered.
//
// Call of `AllowMethod` will override any previous allow methods.
AllowMethods(methods ...string) 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.
//

View File

@@ -147,9 +147,10 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
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)
// RouteExists reports whether a particular route exists
// It will search from the current subdomain of context's host, if not inside the root domain.
func (router *Router) RouteExists(ctx context.Context, method, path string) bool {
return router.requestHandler.RouteExists(ctx, method, path)
}
type wrapper struct {

View File

@@ -176,13 +176,6 @@ func (app *Application) Handle(controller interface{}) *Application {
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
// of the current mvc Mpplication's dependencies.
//