--- /dev/null
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+ "encoding/csv"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/url"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "os"
+ "sync"
+
+ "github.com/revel/pathtree"
+ "github.com/revel/revel/logger"
+)
+
+const (
+ httpStatusCode = "404"
+)
+
+type Route struct {
+ ModuleSource *Module // Module name of route
+ Method string // e.g. GET
+ Path string // e.g. /app/:id
+ Action string // e.g. "Application.ShowApp", "404"
+ ControllerNamespace string // e.g. "testmodule.",
+ ControllerName string // e.g. "Application", ""
+ MethodName string // e.g. "ShowApp", ""
+ FixedParams []string // e.g. "arg1","arg2","arg3" (CSV formatting)
+ TreePath string // e.g. "/GET/app/:id"
+ TypeOfController *ControllerType // The controller type (if route is not wild carded)
+
+ routesPath string // e.g. /Users/robfig/gocode/src/myapp/conf/routes
+ line int // e.g. 3
+}
+
+type RouteMatch struct {
+ Action string // e.g. 404
+ ControllerName string // e.g. Application
+ MethodName string // e.g. ShowApp
+ FixedParams []string
+ Params map[string][]string // e.g. {id: 123}
+ TypeOfController *ControllerType // The controller type
+ ModuleSource *Module // The module
+}
+
+type ActionPathData struct {
+ Key string // The unique key
+ ControllerNamespace string // The controller namespace
+ ControllerName string // The controller name
+ MethodName string // The method name
+ Action string // The action
+ ModuleSource *Module // The module
+ Route *Route // The route
+ FixedParamsByName map[string]string // The fixed parameters
+ TypeOfController *ControllerType // The controller type
+}
+
+var (
+ // Used to store decoded action path mappings
+ actionPathCacheMap = map[string]*ActionPathData{}
+ // Used to prevent concurrent writes to map
+ actionPathCacheLock = sync.Mutex{}
+ // The path returned if not found
+ notFound = &RouteMatch{Action: "404"}
+)
+
+var routerLog = RevelLog.New("section", "router")
+
+func init() {
+ AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) {
+ // Add in an
+ if typeOf == ROUTE_REFRESH_REQUESTED {
+ // Clear the actionPathCacheMap cache
+ actionPathCacheLock.Lock()
+ defer actionPathCacheLock.Unlock()
+ actionPathCacheMap = map[string]*ActionPathData{}
+ }
+ return
+ })
+}
+
+// NewRoute prepares the route to be used in matching.
+func NewRoute(moduleSource *Module, method, path, action, fixedArgs, routesPath string, line int) (r *Route) {
+ // Handle fixed arguments
+ argsReader := strings.NewReader(string(namespaceReplace([]byte(fixedArgs), moduleSource)))
+ csvReader := csv.NewReader(argsReader)
+ csvReader.TrimLeadingSpace = true
+ fargs, err := csvReader.Read()
+ if err != nil && err != io.EOF {
+ routerLog.Error("NewRoute: Invalid fixed parameters for string ", "error", err, "fixedargs", fixedArgs)
+ }
+
+ r = &Route{
+ ModuleSource: moduleSource,
+ Method: strings.ToUpper(method),
+ Path: path,
+ Action: string(namespaceReplace([]byte(action), moduleSource)),
+ FixedParams: fargs,
+ TreePath: treePath(strings.ToUpper(method), path),
+ routesPath: routesPath,
+ line: line,
+ }
+
+ // URL pattern
+ if !strings.HasPrefix(r.Path, "/") {
+ routerLog.Error("NewRoute: Absolute URL required.")
+ return
+ }
+
+ // Ignore the not found status code
+ if action != httpStatusCode {
+ routerLog.Debugf("NewRoute: New splitActionPath path:%s action:%s", path, action)
+ pathData, found := splitActionPath(&ActionPathData{ModuleSource: moduleSource, Route: r}, r.Action, false)
+ if found {
+ if pathData.TypeOfController != nil {
+ // Assign controller type to avoid looking it up based on name
+ r.TypeOfController = pathData.TypeOfController
+ // Create the fixed parameters
+ if l := len(pathData.Route.FixedParams); l > 0 && len(pathData.FixedParamsByName) == 0 {
+ methodType := pathData.TypeOfController.Method(pathData.MethodName)
+ if methodType != nil {
+ pathData.FixedParamsByName = make(map[string]string, l)
+ for i, argValue := range pathData.Route.FixedParams {
+ Unbind(pathData.FixedParamsByName, methodType.Args[i].Name, argValue)
+ }
+ } else {
+ routerLog.Panicf("NewRoute: Method %s not found for controller %s", pathData.MethodName, pathData.ControllerName)
+ }
+ }
+ }
+ r.ControllerNamespace = pathData.ControllerNamespace
+ r.ControllerName = pathData.ControllerName
+ r.ModuleSource = pathData.ModuleSource
+ r.MethodName = pathData.MethodName
+
+ // The same action path could be used for multiple routes (like the Static.Serve)
+ } else {
+ routerLog.Panicf("NewRoute: Failed to find controller for route path action %s \n%#v\n", path+"?"+r.Action, actionPathCacheMap)
+ }
+ }
+ return
+}
+
+func (route *Route) ActionPath() string {
+ return route.ModuleSource.Namespace() + route.ControllerName
+}
+
+func treePath(method, path string) string {
+ if method == "*" {
+ method = ":METHOD"
+ }
+ return "/" + method + path
+}
+
+type Router struct {
+ Routes []*Route
+ Tree *pathtree.Node
+ Module string // The module the route is associated with
+ path string // path to the routes file
+}
+
+func (router *Router) Route(req *Request) (routeMatch *RouteMatch) {
+ // Override method if set in header
+ if method := req.GetHttpHeader("X-HTTP-Method-Override"); method != "" && req.Method == "POST" {
+ req.Method = method
+ }
+
+ leaf, expansions := router.Tree.Find(treePath(req.Method, req.GetPath()))
+ if leaf == nil {
+ return nil
+ }
+
+ // Create a map of the route parameters.
+ var params url.Values
+ if len(expansions) > 0 {
+ params = make(url.Values)
+ for i, v := range expansions {
+ params[leaf.Wildcards[i]] = []string{v}
+ }
+ }
+ var route *Route
+ var controllerName, methodName string
+
+ // The leaf value is now a list of possible routes to match, only a controller
+ routeList := leaf.Value.([]*Route)
+ var typeOfController *ControllerType
+
+ //INFO.Printf("Found route for path %s %#v", req.URL.Path, len(routeList))
+ for index := range routeList {
+ route = routeList[index]
+ methodName = route.MethodName
+
+ // Special handling for explicit 404's.
+ if route.Action == httpStatusCode {
+ route = nil
+ break
+ }
+
+ // If wildcard match on method name use the method name from the params
+ if methodName[0] == ':' {
+ if methodKey, found := params[methodName[1:]]; found && len(methodKey) > 0 {
+ methodName = strings.ToLower(methodKey[0])
+ } else {
+ routerLog.Fatal("Route: Failure to find method name in parameters", "params", params, "methodName", methodName)
+ }
+ }
+
+ // If the action is variablized, replace into it with the captured args.
+ controllerName = route.ControllerName
+ if controllerName[0] == ':' {
+ controllerName = strings.ToLower(params[controllerName[1:]][0])
+ if typeOfController = route.ModuleSource.ControllerByName(controllerName, methodName); typeOfController != nil {
+ break
+ }
+ } else {
+ typeOfController = route.TypeOfController
+ break
+ }
+ route = nil
+ }
+
+ if route == nil {
+ routeMatch = notFound
+ } else {
+
+ routeMatch = &RouteMatch{
+ ControllerName: route.ControllerNamespace + controllerName,
+ MethodName: methodName,
+ Params: params,
+ FixedParams: route.FixedParams,
+ TypeOfController: typeOfController,
+ ModuleSource: route.ModuleSource,
+ }
+ }
+
+ return
+}
+
+// Refresh re-reads the routes file and re-calculates the routing table.
+// Returns an error if a specified action could not be found.
+func (router *Router) Refresh() (err *Error) {
+ RaiseEvent(ROUTE_REFRESH_REQUESTED, nil)
+ router.Routes, err = parseRoutesFile(appModule, router.path, "", true)
+ RaiseEvent(ROUTE_REFRESH_COMPLETED, nil)
+ if err != nil {
+ return
+ }
+ err = router.updateTree()
+ return
+}
+
+func (router *Router) updateTree() *Error {
+ router.Tree = pathtree.New()
+ pathMap := map[string][]*Route{}
+
+ allPathsOrdered := []string{}
+ // It is possible for some route paths to overlap
+ // based on wildcard matches,
+ // TODO when pathtree is fixed (made to be smart enough to not require a predefined intake order) keeping the routes in order is not necessary
+ for _, route := range router.Routes {
+ if _, found := pathMap[route.TreePath]; !found {
+ pathMap[route.TreePath] = append(pathMap[route.TreePath], route)
+ allPathsOrdered = append(allPathsOrdered, route.TreePath)
+ } else {
+ pathMap[route.TreePath] = append(pathMap[route.TreePath], route)
+ }
+ }
+ for _, path := range allPathsOrdered {
+ routeList := pathMap[path]
+ err := router.Tree.Add(path, routeList)
+
+ // Allow GETs to respond to HEAD requests.
+ if err == nil && routeList[0].Method == "GET" {
+ err = router.Tree.Add(treePath("HEAD", routeList[0].Path), routeList)
+ }
+
+ // Error adding a route to the pathtree.
+ if err != nil {
+ return routeError(err, path, fmt.Sprintf("%#v", routeList), routeList[0].line)
+ }
+ }
+ return nil
+}
+
+// Returns the controller namespace and name, action and module if found from the actionPath specified
+func splitActionPath(actionPathData *ActionPathData, actionPath string, useCache bool) (pathData *ActionPathData, found bool) {
+ actionPath = strings.ToLower(actionPath)
+ if pathData, found = actionPathCacheMap[actionPath]; found && useCache {
+ return
+ }
+ var (
+ controllerNamespace, controllerName, methodName, action string
+ foundModuleSource *Module
+ typeOfController *ControllerType
+ log = routerLog.New("actionPath", actionPath)
+ )
+ actionSplit := strings.Split(actionPath, ".")
+ if actionPathData != nil {
+ foundModuleSource = actionPathData.ModuleSource
+ }
+ if len(actionSplit) == 2 {
+ controllerName, methodName = strings.ToLower(actionSplit[0]), strings.ToLower(actionSplit[1])
+ if i := strings.Index(methodName, "("); i > 0 {
+ methodName = methodName[:i]
+ }
+ log = log.New("controller", controllerName, "method", methodName)
+ log.Debug("splitActionPath: Check for namespace")
+ if i := strings.Index(controllerName, namespaceSeperator); i > -1 {
+ controllerNamespace = controllerName[:i+1]
+ if moduleSource, found := ModuleByName(controllerNamespace[:len(controllerNamespace)-1]); found {
+ log.Debug("Found module namespace")
+ foundModuleSource = moduleSource
+ controllerNamespace = moduleSource.Namespace()
+ } else {
+ log.Warnf("splitActionPath: Unable to find module %s for action: %s", controllerNamespace[:len(controllerNamespace)-1], actionPath)
+ }
+ controllerName = controllerName[i+1:]
+ // Check for the type of controller
+ typeOfController = foundModuleSource.ControllerByName(controllerName, methodName)
+ found = typeOfController != nil
+ } else if controllerName[0] != ':' {
+ // First attempt to find the controller in the module source
+ if foundModuleSource != nil {
+ typeOfController = foundModuleSource.ControllerByName(controllerName, methodName)
+ if typeOfController != nil {
+ controllerNamespace = typeOfController.Namespace
+ }
+ }
+ log.Info("Found controller for path", "controllerType", typeOfController)
+
+ if typeOfController == nil {
+ // Check to see if we can determine the controller from only the controller name
+ // an actionPath without a moduleSource will only come from
+ // Scan through the controllers
+ matchName := controllerName
+ for key, controller := range controllers {
+ // Strip away the namespace from the controller. to be match
+ regularName := key
+ if i := strings.Index(key, namespaceSeperator); i > -1 {
+ regularName = regularName[i+1:]
+ }
+ if regularName == matchName {
+ // Found controller match
+ typeOfController = controller
+ controllerNamespace = typeOfController.Namespace
+ controllerName = typeOfController.ShortName()
+ foundModuleSource = typeOfController.ModuleSource
+ found = true
+ break
+ }
+ }
+ } else {
+ found = true
+ }
+ } else {
+ // If wildcard assign the route the controller namespace found
+ controllerNamespace = actionPathData.ModuleSource.Name + namespaceSeperator
+ foundModuleSource = actionPathData.ModuleSource
+ found = true
+ }
+ action = actionSplit[1]
+ } else {
+ foundPaths := ""
+ for path := range actionPathCacheMap {
+ foundPaths += path + ","
+ }
+ log.Warnf("splitActionPath: Invalid action path %s found paths %s", actionPath, foundPaths)
+ found = false
+ }
+
+ // Make sure no concurrent map writes occur
+ if found {
+ actionPathCacheLock.Lock()
+ defer actionPathCacheLock.Unlock()
+ if actionPathData != nil {
+ actionPathData.ControllerNamespace = controllerNamespace
+ actionPathData.ControllerName = controllerName
+ actionPathData.MethodName = methodName
+ actionPathData.Action = action
+ actionPathData.ModuleSource = foundModuleSource
+ actionPathData.TypeOfController = typeOfController
+ } else {
+ actionPathData = &ActionPathData{
+ ControllerNamespace: controllerNamespace,
+ ControllerName: controllerName,
+ MethodName: methodName,
+ Action: action,
+ ModuleSource: foundModuleSource,
+ TypeOfController: typeOfController,
+ }
+ }
+ actionPathData.TypeOfController = foundModuleSource.ControllerByName(controllerName, "")
+ if actionPathData.TypeOfController == nil && actionPathData.ControllerName[0] != ':' {
+ log.Warnf("splitActionPath: No controller found for %s %#v", foundModuleSource.Namespace()+controllerName, controllers)
+ }
+
+ pathData = actionPathData
+ if pathData.Route != nil && len(pathData.Route.FixedParams) > 0 {
+ // If there are fixed params on the route then add them to the path
+ // This will give it a unique path and it should still be usable for a reverse lookup provided the name is matchable
+ // for example
+ // GET /test/ Application.Index("Test", "Test2")
+ // {{url "Application.Index(test,test)" }}
+ // should be parseable
+ actionPath = actionPath + "(" + strings.ToLower(strings.Join(pathData.Route.FixedParams, ",")) + ")"
+ }
+ if actionPathData.Route != nil {
+ log.Debugf("splitActionPath: Split Storing recognized action path %s for route %#v ", actionPath, actionPathData.Route)
+ }
+ pathData.Key = actionPath
+ actionPathCacheMap[actionPath] = pathData
+ if !strings.Contains(actionPath, namespaceSeperator) && pathData.TypeOfController != nil {
+ actionPathCacheMap[strings.ToLower(pathData.TypeOfController.Namespace)+actionPath] = pathData
+ log.Debugf("splitActionPath: Split Storing recognized action path %s for route %#v ", strings.ToLower(pathData.TypeOfController.Namespace)+actionPath, actionPathData.Route)
+ }
+ }
+ return
+}
+
+// parseRoutesFile reads the given routes file and returns the contained routes.
+func parseRoutesFile(moduleSource *Module, routesPath, joinedPath string, validate bool) ([]*Route, *Error) {
+ contentBytes, err := ioutil.ReadFile(routesPath)
+ if err != nil {
+ return nil, &Error{
+ Title: "Failed to load routes file",
+ Description: err.Error(),
+ }
+ }
+ return parseRoutes(moduleSource, routesPath, joinedPath, string(contentBytes), validate)
+}
+
+// parseRoutes reads the content of a routes file into the routing table.
+func parseRoutes(moduleSource *Module, routesPath, joinedPath, content string, validate bool) ([]*Route, *Error) {
+ var routes []*Route
+
+ // For each line..
+ for n, line := range strings.Split(content, "\n") {
+ line = strings.TrimSpace(line)
+ if len(line) == 0 || line[0] == '#' {
+ continue
+ }
+
+ const modulePrefix = "module:"
+
+ // Handle included routes from modules.
+ // e.g. "module:testrunner" imports all routes from that module.
+ if strings.HasPrefix(line, modulePrefix) {
+ moduleRoutes, err := getModuleRoutes(line[len(modulePrefix):], joinedPath, validate)
+ if err != nil {
+ return nil, routeError(err, routesPath, content, n)
+ }
+ routes = append(routes, moduleRoutes...)
+ continue
+ }
+
+ // A single route
+ method, path, action, fixedArgs, found := parseRouteLine(line)
+ if !found {
+ continue
+ }
+
+ // this will avoid accidental double forward slashes in a route.
+ // this also avoids pathtree freaking out and causing a runtime panic
+ // because of the double slashes
+ if strings.HasSuffix(joinedPath, "/") && strings.HasPrefix(path, "/") {
+ joinedPath = joinedPath[0 : len(joinedPath)-1]
+ }
+ path = strings.Join([]string{AppRoot, joinedPath, path}, "")
+
+ // This will import the module routes under the path described in the
+ // routes file (joinedPath param). e.g. "* /jobs module:jobs" -> all
+ // routes' paths will have the path /jobs prepended to them.
+ // See #282 for more info
+ if method == "*" && strings.HasPrefix(action, modulePrefix) {
+ moduleRoutes, err := getModuleRoutes(action[len(modulePrefix):], path, validate)
+ if err != nil {
+ return nil, routeError(err, routesPath, content, n)
+ }
+ routes = append(routes, moduleRoutes...)
+ continue
+ }
+
+ route := NewRoute(moduleSource, method, path, action, fixedArgs, routesPath, n)
+ routes = append(routes, route)
+
+ if validate {
+ if err := validateRoute(route); err != nil {
+ return nil, routeError(err, routesPath, content, n)
+ }
+ }
+ }
+
+ return routes, nil
+}
+
+// validateRoute checks that every specified action exists.
+func validateRoute(route *Route) error {
+ // Skip 404s
+ if route.Action == httpStatusCode {
+ return nil
+ }
+
+ // Skip variable routes.
+ if route.ControllerName[0] == ':' || route.MethodName[0] == ':' {
+ return nil
+ }
+
+ // Precheck to see if controller exists
+ if _, found := controllers[route.ControllerNamespace+route.ControllerName]; !found {
+ // Scan through controllers to find module
+ for _, c := range controllers {
+ controllerName := strings.ToLower(c.Type.Name())
+ if controllerName == route.ControllerName {
+ route.ControllerNamespace = c.ModuleSource.Name + namespaceSeperator
+ routerLog.Warn("validateRoute: Matched empty namespace route for %s to this namespace %s for the route %s", controllerName, c.ModuleSource.Name, route.Path)
+ }
+ }
+ }
+
+ // TODO need to check later
+ // does it do only validation or validation and instantiate the controller.
+ var c Controller
+ return c.SetTypeAction(route.ControllerNamespace+route.ControllerName, route.MethodName, route.TypeOfController)
+}
+
+// routeError adds context to a simple error message.
+func routeError(err error, routesPath, content string, n int) *Error {
+ if revelError, ok := err.(*Error); ok {
+ return revelError
+ }
+ // Load the route file content if necessary
+ if content == "" {
+ if contentBytes, er := ioutil.ReadFile(routesPath); er != nil {
+ routerLog.Error("routeError: Failed to read route file ", "file", routesPath, "error", er)
+ } else {
+ content = string(contentBytes)
+ }
+ }
+ return &Error{
+ Title: "Route validation error",
+ Description: err.Error(),
+ Path: routesPath,
+ Line: n + 1,
+ SourceLines: strings.Split(content, "\n"),
+ Stack: fmt.Sprintf("%s", logger.NewCallStack()),
+ }
+}
+
+// getModuleRoutes loads the routes file for the given module and returns the
+// list of routes.
+func getModuleRoutes(moduleName, joinedPath string, validate bool) (routes []*Route, err *Error) {
+ // Look up the module. It may be not found due to the common case of e.g. the
+ // testrunner module being active only in dev mode.
+ module, found := ModuleByName(moduleName)
+ if !found {
+ routerLog.Debug("getModuleRoutes: Skipping routes for inactive module", "module", moduleName)
+ return nil, nil
+ }
+ routePath := filepath.Join(module.Path, "conf", "routes")
+ if _, e := os.Stat(routePath); e == nil {
+ routes, err = parseRoutesFile(module, routePath, joinedPath, validate)
+ }
+ if err == nil {
+ for _, route := range routes {
+ route.ModuleSource = module
+ }
+ }
+
+ return routes, err
+}
+
+// Groups:
+// 1: method
+// 4: path
+// 5: action
+// 6: fixedargs
+var routePattern = regexp.MustCompile(
+ "(?i)^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|WS|PROPFIND|MKCOL|COPY|MOVE|PROPPATCH|LOCK|UNLOCK|TRACE|PURGE|\\*)" +
+ "[(]?([^)]*)(\\))?[ \t]+" +
+ "(.*/[^ \t]*)[ \t]+([^ \t(]+)" +
+ `\(?([^)]*)\)?[ \t]*$`)
+
+func parseRouteLine(line string) (method, path, action, fixedArgs string, found bool) {
+ matches := routePattern.FindStringSubmatch(line)
+ if matches == nil {
+ return
+ }
+ method, path, action, fixedArgs = matches[1], matches[4], matches[5], matches[6]
+ found = true
+ return
+}
+
+func NewRouter(routesPath string) *Router {
+ return &Router{
+ Tree: pathtree.New(),
+ path: routesPath,
+ }
+}
+
+type ActionDefinition struct {
+ Host, Method, URL, Action string
+ Star bool
+ Args map[string]string
+}
+
+func (a *ActionDefinition) String() string {
+ return a.URL
+}
+
+func (router *Router) Reverse(action string, argValues map[string]string) (ad *ActionDefinition) {
+ log := routerLog.New("action", action)
+ pathData, found := splitActionPath(nil, action, true)
+ if !found {
+ routerLog.Error("splitActionPath: Failed to find reverse route", "action", action, "arguments", argValues)
+ return nil
+ }
+
+ log.Debug("Checking for route", "pathdataRoute", pathData.Route)
+ if pathData.Route == nil {
+ var possibleRoute *Route
+ // If the route is nil then we need to go through the routes to find the first matching route
+ // from this controllers namespace, this is likely a wildcard route match
+ for _, route := range router.Routes {
+ // Skip routes that are not wild card or empty
+ if route.ControllerName == "" || route.MethodName == "" {
+ continue
+ }
+ if route.ModuleSource == pathData.ModuleSource && route.ControllerName[0] == ':' {
+ // Wildcard match in same module space
+ pathData.Route = route
+ break
+ } else if route.ActionPath() == pathData.ModuleSource.Namespace()+pathData.ControllerName &&
+ (route.Method[0] == ':' || route.Method == pathData.MethodName) {
+ // Action path match
+ pathData.Route = route
+ break
+ } else if route.ControllerName == pathData.ControllerName &&
+ (route.Method[0] == ':' || route.Method == pathData.MethodName) {
+ // Controller name match
+ possibleRoute = route
+ }
+ }
+ if pathData.Route == nil && possibleRoute != nil {
+ pathData.Route = possibleRoute
+ routerLog.Warnf("Reverse: For a url reverse a match was based on %s matched path to route %#v ", action, possibleRoute)
+ }
+ if pathData.Route != nil {
+ routerLog.Debugf("Reverse: Reverse Storing recognized action path %s for route %#v\n", action, pathData.Route)
+ }
+ }
+
+ // Likely unknown route because of a wildcard, perform manual lookup
+ if pathData.Route != nil {
+ route := pathData.Route
+
+ // If the controller or method are wildcards we need to populate the argValues
+ controllerWildcard := route.ControllerName[0] == ':'
+ methodWildcard := route.MethodName[0] == ':'
+
+ // populate route arguments with the names
+ if controllerWildcard {
+ argValues[route.ControllerName[1:]] = pathData.ControllerName
+ }
+ if methodWildcard {
+ argValues[route.MethodName[1:]] = pathData.MethodName
+ }
+ // In theory all routes should be defined and pre-populated, the route controllers may not be though
+ // with wildcard routes
+ if pathData.TypeOfController == nil {
+ if controllerWildcard || methodWildcard {
+ if controller := ControllerTypeByName(pathData.ControllerNamespace+pathData.ControllerName, route.ModuleSource); controller != nil {
+ // Wildcard match boundary
+ pathData.TypeOfController = controller
+ // See if the path exists in the module based
+ } else {
+ routerLog.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName)
+ return
+ }
+ }
+ }
+
+ if pathData.TypeOfController == nil {
+ routerLog.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName)
+ return
+ }
+ var (
+ queryValues = make(url.Values)
+ pathElements = strings.Split(route.Path, "/")
+ )
+ for i, el := range pathElements {
+ if el == "" || (el[0] != ':' && el[0] != '*') {
+ continue
+ }
+ val, ok := pathData.FixedParamsByName[el[1:]]
+ if !ok {
+ val, ok = argValues[el[1:]]
+ }
+ if !ok {
+ val = "<nil>"
+ routerLog.Error("Reverse: reverse route missing route argument ", "argument", el[1:])
+ }
+ pathElements[i] = val
+ delete(argValues, el[1:])
+ continue
+ }
+
+ // Add any args that were not inserted into the path into the query string.
+ for k, v := range argValues {
+ queryValues.Set(k, v)
+ }
+
+ // Calculate the final URL and Method
+ urlPath := strings.Join(pathElements, "/")
+ if len(queryValues) > 0 {
+ urlPath += "?" + queryValues.Encode()
+ }
+
+ method := route.Method
+ star := false
+ if route.Method == "*" {
+ method = "GET"
+ star = true
+ }
+
+ //INFO.Printf("Reversing action %s to %s Using Route %#v",action,url,pathData.Route)
+
+ return &ActionDefinition{
+ URL: urlPath,
+ Method: method,
+ Star: star,
+ Action: action,
+ Args: argValues,
+ Host: "TODO",
+ }
+ }
+
+ routerLog.Error("Reverse: Failed to find controller for reverse route", "action", action, "arguments", argValues)
+ return nil
+}
+
+func RouterFilter(c *Controller, fc []Filter) {
+ // Figure out the Controller/Action
+ route := MainRouter.Route(c.Request)
+ if route == nil {
+ c.Result = c.NotFound("No matching route found: " + c.Request.GetRequestURI())
+ return
+ }
+
+ // The route may want to explicitly return a 404.
+ if route.Action == httpStatusCode {
+ c.Result = c.NotFound("(intentionally)")
+ return
+ }
+
+ // Set the action.
+ if err := c.SetTypeAction(route.ControllerName, route.MethodName, route.TypeOfController); err != nil {
+ c.Result = c.NotFound(err.Error())
+ return
+ }
+
+ // Add the route and fixed params to the Request Params.
+ c.Params.Route = route.Params
+ // Assign logger if from module
+ if c.Type.ModuleSource != nil && c.Type.ModuleSource != appModule {
+ c.Log = c.Type.ModuleSource.Log.New("ip", c.ClientIP,
+ "path", c.Request.URL.Path, "method", c.Request.Method)
+ }
+
+ // Add the fixed parameters mapped by name.
+ // TODO: Pre-calculate this mapping.
+ for i, value := range route.FixedParams {
+ if c.Params.Fixed == nil {
+ c.Params.Fixed = make(url.Values)
+ }
+ if i < len(c.MethodType.Args) {
+ arg := c.MethodType.Args[i]
+ c.Params.Fixed.Set(arg.Name, value)
+ } else {
+ routerLog.Warn("RouterFilter: Too many parameters to action", "action", route.Action, "value", value)
+ break
+ }
+ }
+
+ fc[0](c, fc[1:])
+}
+
+// HTTPMethodOverride overrides allowed http methods via form or browser param
+func HTTPMethodOverride(c *Controller, fc []Filter) {
+ // An array of HTTP verbs allowed.
+ verbs := []string{"POST", "PUT", "PATCH", "DELETE"}
+
+ method := strings.ToUpper(c.Request.Method)
+
+ if method == "POST" {
+ param := ""
+ if f, err := c.Request.GetForm(); err == nil {
+ param = strings.ToUpper(f.Get("_method"))
+ }
+
+ if len(param) > 0 {
+ override := false
+ // Check if param is allowed
+ for _, verb := range verbs {
+ if verb == param {
+ override = true
+ break
+ }
+ }
+
+ if override {
+ c.Request.Method = param
+ } else {
+ c.Response.Status = 405
+ c.Result = c.RenderError(&Error{
+ Title: "Method not allowed",
+ Description: "Method " + param + " is not allowed (valid: " + strings.Join(verbs, ", ") + ")",
+ })
+ return
+ }
+
+ }
+ }
+
+ fc[0](c, fc[1:]) // Execute the next filter stage.
+}
+
+func init() {
+ OnAppStart(func() {
+ MainRouter = NewRouter(filepath.Join(BasePath, "conf", "routes"))
+ err := MainRouter.Refresh()
+ if MainWatcher != nil && Config.BoolDefault("watch.routes", true) {
+ MainWatcher.Listen(MainRouter, MainRouter.path)
+ } else if err != nil {
+ // Not in dev mode and Route loading failed, we should crash.
+ routerLog.Panic("init: router initialize error", "error", err)
+ }
+ })
+}