diff --git a/context/application.go b/context/application.go index 5313c721..90738ce7 100644 --- a/context/application.go +++ b/context/application.go @@ -34,6 +34,14 @@ type Application interface { // It is ready to use after Build state. ServeHTTP(w http.ResponseWriter, r *http.Request) + // GetRouteReadOnly returns the registered "read-only" route based on its name, otherwise nil. + // One note: "routeName" should be case-sensitive. Used by the context to get the current route. + // It returns an interface instead to reduce wrong usage and to keep the decoupled design between + // the context and the routes. + // + // Look core/router/APIBuilder#GetRoute for more. + GetRouteReadOnly(routeName string) RouteReadOnly + // FireErrorCode executes an error http status code handler // based on the context's status code. // diff --git a/context/context.go b/context/context.go index fd576c94..cd0afc0f 100644 --- a/context/context.go +++ b/context/context.go @@ -148,7 +148,6 @@ func (r RequestParams) Len() int { // context.Context is very extensible and developers can override // its methods if that is actually needed. type Context interface { - // BeginRequest is executing once for each request // it should prepare the (new or acquired from pool) context's fields for the new request. // @@ -176,6 +175,20 @@ type Context interface { // Request returns the original *http.Request, as expected. Request() *http.Request + // SetCurrentRouteName sets the route's name internally, + // in order to be able to find the correct current "read-only" Route when + // end-developer calls the `GetCurrentRoute()` function. + // It's being initialized by the Router, if you change that name + // manually nothing really happens except that you'll get other + // route via `GetCurrentRoute()`. + // Instead, to execute a different path + // from this context you should use the `Exec` function + // or change the handlers via `SetHandlers/AddHandler` functions. + SetCurrentRouteName(currentRouteName string) + // GetCurrentRoute returns the current registered "read-only" route that + // was being registered to this request's path. + GetCurrentRoute() RouteReadOnly + // Do calls the SetHandlers(handlers) // and executes the first handler, // handlers should not be empty. @@ -799,6 +812,9 @@ type context struct { writer ResponseWriter // the original http.Request request *http.Request + // the current route's name registered to this request path. + currentRouteName string + // the local key-value storage params RequestParams // url named parameters values memstore.Store // generic storage, middleware communication @@ -880,6 +896,25 @@ func (ctx *context) Request() *http.Request { return ctx.request } +// SetCurrentRouteName sets the route's name internally, +// in order to be able to find the correct current "read-only" Route when +// end-developer calls the `GetCurrentRoute()` function. +// It's being initialized by the Router, if you change that name +// manually nothing really happens except that you'll get other +// route via `GetCurrentRoute()`. +// Instead, to execute a different path +// from this context you should use the `Exec` function +// or change the handlers via `SetHandlers/AddHandler` functions. +func (ctx *context) SetCurrentRouteName(currentRouteName string) { + ctx.currentRouteName = currentRouteName +} + +// GetCurrentRoute returns the current registered "read-only" route that +// was being registered to this request's path. +func (ctx *context) GetCurrentRoute() RouteReadOnly { + return ctx.app.GetRouteReadOnly(ctx.currentRouteName) +} + // Do calls the SetHandlers(handlers) // and executes the first handler, // handlers should not be empty. diff --git a/context/route.go b/context/route.go new file mode 100644 index 00000000..c56c4ffe --- /dev/null +++ b/context/route.go @@ -0,0 +1,18 @@ +package context + +// RouteReadOnly allows decoupled access to the current route +// inside the context. +type RouteReadOnly interface { + // Name returns the route's name. + Name() string + // String returns the form of METHOD, SUBDOMAIN, TMPL PATH. + String() string + // Path returns the route's original registered path. + Path() string + + // IsOnline returns true if the route is marked as "online" (state). + IsOnline() bool + + // ResolvePath returns the formatted path's %v replaced with the args. + ResolvePath(args ...string) string +} diff --git a/core/router/api_builder.go b/core/router/api_builder.go index c5526ff9..376e8429 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -319,6 +319,20 @@ func (api *APIBuilder) GetRoute(routeName string) *Route { return api.routes.get(routeName) } +// GetRouteReadOnly returns the registered "read-only" route based on its name, otherwise nil. +// One note: "routeName" should be case-sensitive. Used by the context to get the current route. +// It returns an interface instead to reduce wrong usage and to keep the decoupled design between +// the context and the routes. +// +// Look `GetRoute` for more. +func (api *APIBuilder) GetRouteReadOnly(routeName string) context.RouteReadOnly { + r := api.GetRoute(routeName) + if r == nil { + return nil + } + return routeReadOnlyWrapper{r} +} + // 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. // diff --git a/core/router/handler.go b/core/router/handler.go index 16fa8e04..89060681 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -49,7 +49,15 @@ func (h *routerHandler) getTree(method, subdomain string) *tree { return nil } -func (h *routerHandler) addRoute(method, subdomain, path string, handlers context.Handlers) error { +func (h *routerHandler) addRoute(r *Route) error { + var ( + routeName = r.Name + method = r.Method + subdomain = r.Subdomain + path = r.Path + handlers = r.Handlers + ) + t := h.getTree(method, subdomain) if t == nil { @@ -58,7 +66,7 @@ func (h *routerHandler) addRoute(method, subdomain, path string, handlers contex t = &tree{Method: method, Subdomain: subdomain, Nodes: &n} h.trees = append(h.trees, t) } - return t.Nodes.Add(path, handlers) + return t.Nodes.Add(routeName, path, handlers) } // NewDefaultHandler returns the handler which is responsible @@ -125,7 +133,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error { // on route, it will be stacked shown in this build state // and no in the lines of the user's action, they should read // the docs better. Or TODO: add a link here in order to help new users. - if err := h.addRoute(r.Method, r.Subdomain, r.Path, r.Handlers); err != nil { + if err := h.addRoute(r); err != nil { // node errors: rp.Add("%v -> %s", err, r.String()) } @@ -202,8 +210,9 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { continue } } - handlers := t.Nodes.Find(path, ctx.Params()) + routeName, handlers := t.Nodes.Find(path, ctx.Params()) if len(handlers) > 0 { + ctx.SetCurrentRouteName(routeName) ctx.Do(handlers) // found return diff --git a/core/router/node/node.go b/core/router/node/node.go index 3cce094f..9dd354e4 100644 --- a/core/router/node/node.go +++ b/core/router/node/node.go @@ -13,6 +13,7 @@ type Nodes []*node type node struct { s string + routeName string wildcardParamName string // name of the wildcard parameter, only one per whole Node is allowed paramNames []string // only-names childrenNodes Nodes @@ -28,7 +29,7 @@ var ErrDublicate = errors.New("two or more routes have the same registered path" /// TODO: clean up needed until v8.5 // Add adds a node to the tree, returns an ErrDublicate error on failure. -func (nodes *Nodes) Add(path string, handlers context.Handlers) error { +func (nodes *Nodes) Add(routeName string, path string, handlers context.Handlers) error { // println("[Add] adding path: " + path) // resolve params and if that node should be added as root var params []string @@ -66,13 +67,13 @@ func (nodes *Nodes) Add(path string, handlers context.Handlers) error { for _, idx := range p { // print("-2 nodes.Add: path: " + path + " params len: ") // println(len(params)) - if err := nodes.add(path[:idx], nil, nil, true); err != nil { + if err := nodes.add(routeName, path[:idx], nil, nil, true); err != nil { return err } // print("-1 nodes.Add: path: " + path + " params len: ") // println(len(params)) if nidx := idx + 1; len(path) > nidx { - if err := nodes.add(path[:nidx], nil, nil, true); err != nil { + if err := nodes.add(routeName, path[:nidx], nil, nil, true); err != nil { return err } } @@ -80,7 +81,7 @@ func (nodes *Nodes) Add(path string, handlers context.Handlers) error { // print("nodes.Add: path: " + path + " params len: ") // println(len(params)) - if err := nodes.add(path, params, handlers, true); err != nil { + if err := nodes.add(routeName, path, params, handlers, true); err != nil { return err } @@ -89,7 +90,7 @@ func (nodes *Nodes) Add(path string, handlers context.Handlers) error { return nil } -func (nodes *Nodes) add(path string, paramNames []string, handlers context.Handlers, root bool) (err error) { +func (nodes *Nodes) add(routeName, path string, paramNames []string, handlers context.Handlers, root bool) (err error) { // println("[add] adding path: " + path) @@ -115,6 +116,7 @@ func (nodes *Nodes) add(path string, paramNames []string, handlers context.Handl n := &node{ rootWildcard: rootWildcard, s: path, + routeName: routeName, wildcardParamName: wildcardParamName, paramNames: paramNames, handlers: handlers, @@ -154,6 +156,7 @@ loop: childrenNodes: Nodes{ { s: n.s[i:], + routeName: n.routeName, wildcardParamName: n.wildcardParamName, // wildcardParamName paramNames: n.paramNames, childrenNodes: n.childrenNodes, @@ -161,6 +164,7 @@ loop: }, { s: path[i:], + routeName: routeName, wildcardParamName: wildcardParamName, paramNames: paramNames, handlers: handlers, @@ -179,11 +183,13 @@ loop: *n = node{ s: n.s[:len(path)], + routeName: routeName, wildcardParamName: wildcardParamName, paramNames: paramNames, childrenNodes: Nodes{ { s: n.s[len(path):], + routeName: n.routeName, wildcardParamName: n.wildcardParamName, // wildcardParamName paramNames: n.paramNames, childrenNodes: n.childrenNodes, @@ -201,6 +207,7 @@ loop: if n.wildcardParamName != "" { n := &node{ s: path, + routeName: routeName, wildcardParamName: wildcardParamName, paramNames: paramNames, handlers: handlers, @@ -211,7 +218,7 @@ loop: return } // println("4. nodes.Add path: " + path[len(n.s):]) - err = n.childrenNodes.add(path[len(n.s):], paramNames, handlers, false) + err = n.childrenNodes.add(routeName, path[len(n.s):], paramNames, handlers, false) return err } @@ -230,6 +237,7 @@ loop: n := &node{ s: path, + routeName: routeName, wildcardParamName: wildcardParamName, paramNames: paramNames, handlers: handlers, @@ -243,7 +251,7 @@ loop: // Find resolves the path, fills its params // and returns the registered to the resolved node's handlers. -func (nodes Nodes) Find(path string, params *context.RequestParams) context.Handlers { +func (nodes Nodes) Find(path string, params *context.RequestParams) (string, context.Handlers) { n, paramValues := nodes.findChild(path, nil) if n != nil { // map the params, @@ -270,10 +278,10 @@ func (nodes Nodes) Find(path string, params *context.RequestParams) context.Hand params.Set(n.wildcardParamName, lastWildcardVal) } } - return n.handlers + return n.routeName, n.handlers } - return nil + return "", nil } // Exists returns true if a node with that "path" exists, diff --git a/core/router/route.go b/core/router/route.go index ccd02855..8809f033 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -50,7 +50,7 @@ func NewRoute(method, subdomain, unparsedPath string, } path = cleanPath(path) // maybe unnecessary here but who cares in this moment - defaultName := method + subdomain + path + defaultName := method + subdomain + tmpl.Src formattedPath := formatPath(path) route := &Route{ @@ -106,7 +106,7 @@ func (r *Route) BuildHandlers() { } // note: no mutex needed, this should be called in-sync when server is not running of course. } -// String returns the form of METHOD, SUBDOMAIN, TMPL PATH +// String returns the form of METHOD, SUBDOMAIN, TMPL PATH. func (r Route) String() string { return fmt.Sprintf("%s %s%s", r.Method, r.Subdomain, r.Tmpl().Src) @@ -184,3 +184,19 @@ func (r Route) ResolvePath(args ...string) string { } return formattedPath } + +type routeReadOnlyWrapper struct { + *Route +} + +func (rd routeReadOnlyWrapper) Name() string { + return rd.Route.Name +} + +func (rd routeReadOnlyWrapper) Subdomain() string { + return rd.Route.Subdomain +} + +func (rd routeReadOnlyWrapper) Path() string { + return rd.Route.tmpl.Src +} diff --git a/mvc/controller.go b/mvc/controller.go index 1c18165a..be5537e8 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -64,6 +64,10 @@ import ( type Controller struct { // Name contains the current controller's full name. Name string + + // Route is the current request context's route. + Route context.RouteReadOnly + // contains the `Name` as different words, all lowercase, // without the "Controller" suffix if exists. // we need this as field because the activator @@ -178,6 +182,7 @@ func (c *Controller) RelTmpl() string { // It's called internally. // End-Developer can ovverride it but it still MUST be called. func (c *Controller) BeginRequest(ctx context.Context) { + c.Route = ctx.GetCurrentRoute() // path and path params c.Path = ctx.Path() c.Params = ctx.Params()