2017-02-14 04:54:11 +01:00
|
|
|
package iris
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
// RouteInfo is just the (other idea was : RouteInfo but we needed the Change/SetName visible so...)
|
|
|
|
// information of the registered routes.
|
|
|
|
RouteInfo interface {
|
|
|
|
// ChangeName & AllowOPTIONS are the only one route property
|
|
|
|
// which can be change without any bad side-affects
|
|
|
|
// so it's the only setter here.
|
|
|
|
//
|
|
|
|
// It's used on iris.Default.Handle()
|
|
|
|
ChangeName(name string) RouteInfo
|
|
|
|
|
|
|
|
// Name returns the name of the route
|
|
|
|
Name() string
|
|
|
|
// Method returns the http method
|
|
|
|
Method() string
|
|
|
|
// AllowOPTIONS called when this route is targeting OPTIONS methods too
|
|
|
|
// it's an alternative way of registring the same route with '.OPTIONS("/routepath", routeMiddleware)'
|
|
|
|
AllowOPTIONS() RouteInfo
|
|
|
|
// HasCors returns true if the route is targeting OPTIONS methods too
|
|
|
|
// or it has a middleware which conflicts with "httpmethod",
|
|
|
|
// otherwise false
|
|
|
|
HasCors() bool
|
|
|
|
// Subdomain returns the subdomain,if any
|
|
|
|
Subdomain() string
|
|
|
|
// Path returns the path
|
|
|
|
Path() string
|
|
|
|
// Middleware returns the slice of Handler([]Handler) registered to this route
|
|
|
|
Middleware() Middleware
|
|
|
|
// IsOnline returns true if the route is marked as "online" (state)
|
|
|
|
IsOnline() bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// route holds useful information about route
|
|
|
|
route struct {
|
|
|
|
// if no name given then it's the subdomain+path
|
|
|
|
name string
|
|
|
|
subdomain string
|
|
|
|
method string
|
|
|
|
allowOptionsMethod bool
|
|
|
|
path string
|
|
|
|
middleware Middleware
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
var _ RouteInfo = &route{}
|
|
|
|
|
|
|
|
// RouteConflicts checks for route's middleware conflicts
|
|
|
|
func RouteConflicts(r RouteInfo, with string) bool {
|
|
|
|
for _, h := range r.Middleware() {
|
|
|
|
if m, ok := h.(interface {
|
|
|
|
Conflicts() string
|
|
|
|
}); ok {
|
|
|
|
if c := m.Conflicts(); c == with {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Name returns the name of the route
|
|
|
|
func (r route) Name() string {
|
|
|
|
return r.name
|
|
|
|
}
|
|
|
|
|
|
|
|
// Name returns the name of the route
|
|
|
|
func (r *route) ChangeName(name string) RouteInfo {
|
|
|
|
r.name = name
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllowOPTIONS called when this route is targeting OPTIONS methods too
|
|
|
|
// it's an alternative way of registring the same route with '.OPTIONS("/routepath", routeMiddleware)'
|
|
|
|
func (r *route) AllowOPTIONS() RouteInfo {
|
|
|
|
r.allowOptionsMethod = true
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
// Method returns the http method
|
|
|
|
func (r route) Method() string {
|
|
|
|
return r.method
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subdomain returns the subdomain,if any
|
|
|
|
func (r route) Subdomain() string {
|
|
|
|
return r.subdomain
|
|
|
|
}
|
|
|
|
|
|
|
|
// Path returns the path
|
|
|
|
func (r route) Path() string {
|
|
|
|
return r.path
|
|
|
|
}
|
|
|
|
|
|
|
|
// Middleware returns the slice of Handler([]Handler) registered to this route
|
|
|
|
func (r route) Middleware() Middleware {
|
|
|
|
return r.middleware
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsOnline returns true if the route is marked as "online" (state)
|
|
|
|
func (r route) IsOnline() bool {
|
|
|
|
return r.method != MethodNone
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasCors returns true if the route is targeting OPTIONS methods too
|
|
|
|
// or it has a middleware which conflicts with "httpmethod",
|
|
|
|
// otherwise false
|
|
|
|
func (r *route) HasCors() bool {
|
|
|
|
return r.allowOptionsMethod || RouteConflicts(r, "httpmethod")
|
|
|
|
}
|
|
|
|
|
|
|
|
// MethodChangedListener listener signature fired when route method changes
|
|
|
|
type MethodChangedListener func(routeInfo RouteInfo, oldMethod string)
|
|
|
|
|
|
|
|
// RouteRepository contains the interface which is used on custom routers
|
|
|
|
// contains methods and helpers to find a route by its name,
|
|
|
|
// and change its method, path, middleware.
|
|
|
|
//
|
|
|
|
// This is not visible outside except the RouterBuilderPolicy
|
|
|
|
type RouteRepository interface { // RouteEngine kai ContextEngine mesa sto builder adi gia RouteRepository kai ContextEngine
|
|
|
|
RoutesInfo
|
|
|
|
ChangeName(routeInfo RouteInfo, newName string)
|
|
|
|
ChangeMethod(routeInfo RouteInfo, newMethod string)
|
|
|
|
ChangePath(routeInfo RouteInfo, newPath string)
|
|
|
|
ChangeMiddleware(routeInfo RouteInfo, newMiddleware Middleware)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RoutesInfo is the interface which contains the valid actions
|
|
|
|
// permitted at RUNTIME
|
|
|
|
type RoutesInfo interface { // RouteRepository
|
|
|
|
Lookup(routeName string) RouteInfo
|
|
|
|
Visit(visitor func(RouteInfo))
|
|
|
|
OnMethodChanged(methodChangedListener MethodChangedListener)
|
|
|
|
Online(routeInfo RouteInfo, HTTPMethod string) bool
|
|
|
|
Offline(routeInfo RouteInfo) bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// routeRepository contains all the routes.
|
|
|
|
// Implements both RouteRepository and RoutesInfo
|
|
|
|
type routeRepository struct {
|
|
|
|
routes []*route
|
|
|
|
// when builded (TODO: move to its own struct)
|
|
|
|
methodChangedListeners []MethodChangedListener
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ sort.Interface = &routeRepository{}
|
|
|
|
var _ RouteRepository = &routeRepository{}
|
|
|
|
|
|
|
|
// Len is the number of elements in the collection.
|
|
|
|
func (r routeRepository) Len() int {
|
|
|
|
return len(r.routes)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Less reports whether the element with
|
|
|
|
// index i should sort before the element with index j.
|
|
|
|
func (r routeRepository) Less(i, j int) bool {
|
|
|
|
return len(r.routes[i].Subdomain()) > len(r.routes[j].Subdomain())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Swap swaps the elements with indexes i and j.
|
|
|
|
func (r routeRepository) Swap(i, j int) {
|
|
|
|
r.routes[i], r.routes[j] = r.routes[j], r.routes[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *routeRepository) register(method, subdomain, path string,
|
|
|
|
middleware Middleware) *route {
|
|
|
|
|
|
|
|
_route := &route{
|
|
|
|
name: method + subdomain + path,
|
|
|
|
method: method,
|
|
|
|
subdomain: subdomain,
|
|
|
|
path: path,
|
|
|
|
middleware: middleware,
|
|
|
|
}
|
|
|
|
|
|
|
|
r.routes = append(r.routes, _route)
|
|
|
|
return _route
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *routeRepository) getRouteByName(routeName string) *route {
|
|
|
|
for i := range r.routes {
|
|
|
|
_route := r.routes[i]
|
|
|
|
if _route.name == routeName {
|
|
|
|
return _route
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lookup returns a route by its name
|
|
|
|
// used for reverse routing and templates
|
|
|
|
func (r *routeRepository) Lookup(routeName string) RouteInfo {
|
|
|
|
route := r.getRouteByName(routeName)
|
|
|
|
if route == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return route
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChangeName changes the Name of an existing route
|
|
|
|
func (r *routeRepository) ChangeName(routeInfo RouteInfo,
|
|
|
|
newName string) {
|
|
|
|
|
|
|
|
if newName != "" {
|
|
|
|
route := r.getRouteByName(routeInfo.Name())
|
|
|
|
if route != nil {
|
|
|
|
route.name = newName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *routeRepository) OnMethodChanged(methodChangedListener MethodChangedListener) {
|
|
|
|
r.methodChangedListeners = append(r.methodChangedListeners, methodChangedListener)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *routeRepository) fireMethodChangedListeners(routeInfo RouteInfo, oldMethod string) {
|
|
|
|
for i := 0; i < len(r.methodChangedListeners); i++ {
|
|
|
|
r.methodChangedListeners[i](routeInfo, oldMethod)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChangeMethod changes the Method of an existing route
|
|
|
|
func (r *routeRepository) ChangeMethod(routeInfo RouteInfo,
|
|
|
|
newMethod string) {
|
|
|
|
newMethod = strings.ToUpper(newMethod)
|
|
|
|
valid := false
|
|
|
|
for _, m := range AllMethods {
|
|
|
|
if newMethod == m || newMethod == MethodNone {
|
|
|
|
valid = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if valid {
|
2017-02-18 06:03:37 +01:00
|
|
|
|
2017-02-14 04:54:11 +01:00
|
|
|
route := r.getRouteByName(routeInfo.Name())
|
|
|
|
if route != nil && route.method != newMethod {
|
|
|
|
oldMethod := route.method
|
|
|
|
route.method = newMethod
|
|
|
|
r.fireMethodChangedListeners(routeInfo, oldMethod)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Online sets the state of the route to "online" with a specific http method
|
|
|
|
// it re-builds the router
|
|
|
|
//
|
|
|
|
// returns true if state was actually changed
|
|
|
|
//
|
|
|
|
// see context.ExecRoute(routeInfo),
|
|
|
|
// iris.Default.None(...) and iris.Routes.Online/.Routes.Offline
|
|
|
|
// For more details look: https://github.com/kataras/iris/issues/585
|
|
|
|
//
|
|
|
|
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
|
|
|
|
func (r *routeRepository) Online(routeInfo RouteInfo, HTTPMethod string) bool {
|
|
|
|
return r.changeRouteState(routeInfo, HTTPMethod)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Offline sets the state of the route to "offline" and re-builds the router
|
|
|
|
//
|
|
|
|
// returns true if state was actually changed
|
|
|
|
//
|
|
|
|
// see context.ExecRoute(routeInfo),
|
|
|
|
// iris.Default.None(...) and iris.Routes.Online/.Routes.Offline
|
|
|
|
// For more details look: https://github.com/kataras/iris/issues/585
|
|
|
|
//
|
|
|
|
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
|
|
|
|
func (r *routeRepository) Offline(routeInfo RouteInfo) bool {
|
|
|
|
return r.changeRouteState(routeInfo, MethodNone)
|
|
|
|
}
|
|
|
|
|
|
|
|
// changeRouteState changes the state of the route.
|
|
|
|
// iris.MethodNone for offline
|
|
|
|
// and iris.MethodGet/MethodPost/MethodPut/MethodDelete /MethodConnect/MethodOptions/MethodHead/MethodTrace/MethodPatch for online
|
|
|
|
// it re-builds the router
|
|
|
|
//
|
|
|
|
// returns true if state was actually changed
|
|
|
|
func (r *routeRepository) changeRouteState(routeInfo RouteInfo, HTTPMethod string) bool {
|
|
|
|
if routeInfo != nil {
|
|
|
|
nonSpecificMethod := len(HTTPMethod) == 0
|
|
|
|
if routeInfo.Method() != HTTPMethod {
|
|
|
|
if nonSpecificMethod {
|
|
|
|
r.ChangeMethod(routeInfo, MethodGet) // if no method given, then do it for "GET" only
|
|
|
|
} else {
|
|
|
|
r.ChangeMethod(routeInfo, HTTPMethod)
|
|
|
|
}
|
|
|
|
// re-build the router/main handler should be implemented
|
|
|
|
// on the custom router via OnMethodChanged event.
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChangePath changes the Path of an existing route
|
|
|
|
func (r *routeRepository) ChangePath(routeInfo RouteInfo,
|
|
|
|
newPath string) {
|
|
|
|
|
|
|
|
if newPath != "" {
|
|
|
|
route := r.getRouteByName(routeInfo.Name())
|
|
|
|
if route != nil {
|
|
|
|
route.path = newPath
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChangeMiddleware changes the Middleware/Handlers of an existing route
|
|
|
|
func (r *routeRepository) ChangeMiddleware(routeInfo RouteInfo,
|
|
|
|
newMiddleware Middleware) {
|
|
|
|
|
|
|
|
route := r.getRouteByName(routeInfo.Name())
|
|
|
|
if route != nil {
|
|
|
|
route.middleware = newMiddleware
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Visit accepts a visitor func which receives a route(readonly).
|
|
|
|
// That visitor func accepts the next route of each of the route entries.
|
|
|
|
func (r *routeRepository) Visit(visitor func(RouteInfo)) {
|
|
|
|
for i := range r.routes {
|
|
|
|
visitor(r.routes[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort sorts routes by subdomain.
|
|
|
|
func (r *routeRepository) sort() {
|
|
|
|
sort.Sort(r)
|
|
|
|
}
|