Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / router.go
1 // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
2 // Revel Framework source code and usage is governed by a MIT style
3 // license that can be found in the LICENSE file.
4
5 package revel
6
7 import (
8         "encoding/csv"
9         "fmt"
10         "io"
11         "io/ioutil"
12         "net/url"
13         "path/filepath"
14         "regexp"
15         "strings"
16
17         "os"
18         "sync"
19
20         "github.com/revel/pathtree"
21         "github.com/revel/revel/logger"
22 )
23
24 const (
25         httpStatusCode = "404"
26 )
27
28 type Route struct {
29         ModuleSource        *Module         // Module name of route
30         Method              string          // e.g. GET
31         Path                string          // e.g. /app/:id
32         Action              string          // e.g. "Application.ShowApp", "404"
33         ControllerNamespace string          // e.g. "testmodule.",
34         ControllerName      string          // e.g. "Application", ""
35         MethodName          string          // e.g. "ShowApp", ""
36         FixedParams         []string        // e.g. "arg1","arg2","arg3" (CSV formatting)
37         TreePath            string          // e.g. "/GET/app/:id"
38         TypeOfController    *ControllerType // The controller type (if route is not wild carded)
39
40         routesPath string // e.g. /Users/robfig/gocode/src/myapp/conf/routes
41         line       int    // e.g. 3
42 }
43
44 type RouteMatch struct {
45         Action           string // e.g. 404
46         ControllerName   string // e.g. Application
47         MethodName       string // e.g. ShowApp
48         FixedParams      []string
49         Params           map[string][]string // e.g. {id: 123}
50         TypeOfController *ControllerType     // The controller type
51         ModuleSource     *Module             // The module
52 }
53
54 type ActionPathData struct {
55         Key                 string            // The unique key
56         ControllerNamespace string            // The controller namespace
57         ControllerName      string            // The controller name
58         MethodName          string            // The method name
59         Action              string            // The action
60         ModuleSource        *Module           // The module
61         Route               *Route            // The route
62         FixedParamsByName   map[string]string // The fixed parameters
63         TypeOfController    *ControllerType   // The controller type
64 }
65
66 var (
67         // Used to store decoded action path mappings
68         actionPathCacheMap = map[string]*ActionPathData{}
69         // Used to prevent concurrent writes to map
70         actionPathCacheLock = sync.Mutex{}
71         // The path returned if not found
72         notFound = &RouteMatch{Action: "404"}
73 )
74
75 var routerLog = RevelLog.New("section", "router")
76
77 func init() {
78         AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) {
79                 // Add in an
80                 if typeOf == ROUTE_REFRESH_REQUESTED {
81                         // Clear the actionPathCacheMap cache
82                         actionPathCacheLock.Lock()
83                         defer actionPathCacheLock.Unlock()
84                         actionPathCacheMap = map[string]*ActionPathData{}
85                 }
86                 return
87         })
88 }
89
90 // NewRoute prepares the route to be used in matching.
91 func NewRoute(moduleSource *Module, method, path, action, fixedArgs, routesPath string, line int) (r *Route) {
92         // Handle fixed arguments
93         argsReader := strings.NewReader(string(namespaceReplace([]byte(fixedArgs), moduleSource)))
94         csvReader := csv.NewReader(argsReader)
95         csvReader.TrimLeadingSpace = true
96         fargs, err := csvReader.Read()
97         if err != nil && err != io.EOF {
98                 routerLog.Error("NewRoute: Invalid fixed parameters for string ", "error", err, "fixedargs", fixedArgs)
99         }
100
101         r = &Route{
102                 ModuleSource: moduleSource,
103                 Method:       strings.ToUpper(method),
104                 Path:         path,
105                 Action:       string(namespaceReplace([]byte(action), moduleSource)),
106                 FixedParams:  fargs,
107                 TreePath:     treePath(strings.ToUpper(method), path),
108                 routesPath:   routesPath,
109                 line:         line,
110         }
111
112         // URL pattern
113         if !strings.HasPrefix(r.Path, "/") {
114                 routerLog.Error("NewRoute: Absolute URL required.")
115                 return
116         }
117
118         // Ignore the not found status code
119         if action != httpStatusCode {
120                 routerLog.Debugf("NewRoute: New splitActionPath path:%s action:%s", path, action)
121                 pathData, found := splitActionPath(&ActionPathData{ModuleSource: moduleSource, Route: r}, r.Action, false)
122                 if found {
123                         if pathData.TypeOfController != nil {
124                                 // Assign controller type to avoid looking it up based on name
125                                 r.TypeOfController = pathData.TypeOfController
126                                 // Create the fixed parameters
127                                 if l := len(pathData.Route.FixedParams); l > 0 && len(pathData.FixedParamsByName) == 0 {
128                                         methodType := pathData.TypeOfController.Method(pathData.MethodName)
129                                         if methodType != nil {
130                                                 pathData.FixedParamsByName = make(map[string]string, l)
131                                                 for i, argValue := range pathData.Route.FixedParams {
132                                                         Unbind(pathData.FixedParamsByName, methodType.Args[i].Name, argValue)
133                                                 }
134                                         } else {
135                                                 routerLog.Panicf("NewRoute: Method %s not found for controller %s", pathData.MethodName, pathData.ControllerName)
136                                         }
137                                 }
138                         }
139                         r.ControllerNamespace = pathData.ControllerNamespace
140                         r.ControllerName = pathData.ControllerName
141                         r.ModuleSource = pathData.ModuleSource
142                         r.MethodName = pathData.MethodName
143
144                         // The same action path could be used for multiple routes (like the Static.Serve)
145                 } else {
146                         routerLog.Panicf("NewRoute: Failed to find controller for route path action %s \n%#v\n", path+"?"+r.Action, actionPathCacheMap)
147                 }
148         }
149         return
150 }
151
152 func (route *Route) ActionPath() string {
153         return route.ModuleSource.Namespace() + route.ControllerName
154 }
155
156 func treePath(method, path string) string {
157         if method == "*" {
158                 method = ":METHOD"
159         }
160         return "/" + method + path
161 }
162
163 type Router struct {
164         Routes []*Route
165         Tree   *pathtree.Node
166         Module string // The module the route is associated with
167         path   string // path to the routes file
168 }
169
170 func (router *Router) Route(req *Request) (routeMatch *RouteMatch) {
171         // Override method if set in header
172         if method := req.GetHttpHeader("X-HTTP-Method-Override"); method != "" && req.Method == "POST" {
173                 req.Method = method
174         }
175
176         leaf, expansions := router.Tree.Find(treePath(req.Method, req.GetPath()))
177         if leaf == nil {
178                 return nil
179         }
180
181         // Create a map of the route parameters.
182         var params url.Values
183         if len(expansions) > 0 {
184                 params = make(url.Values)
185                 for i, v := range expansions {
186                         params[leaf.Wildcards[i]] = []string{v}
187                 }
188         }
189         var route *Route
190         var controllerName, methodName string
191
192         // The leaf value is now a list of possible routes to match, only a controller
193         routeList := leaf.Value.([]*Route)
194         var typeOfController *ControllerType
195
196         //INFO.Printf("Found route for path %s %#v", req.URL.Path, len(routeList))
197         for index := range routeList {
198                 route = routeList[index]
199                 methodName = route.MethodName
200
201                 // Special handling for explicit 404's.
202                 if route.Action == httpStatusCode {
203                         route = nil
204                         break
205                 }
206
207                 // If wildcard match on method name use the method name from the params
208                 if methodName[0] == ':' {
209                         if methodKey, found := params[methodName[1:]]; found && len(methodKey) > 0 {
210                                 methodName = strings.ToLower(methodKey[0])
211                         } else {
212                                 routerLog.Fatal("Route: Failure to find method name in parameters", "params", params, "methodName", methodName)
213                         }
214                 }
215
216                 // If the action is variablized, replace into it with the captured args.
217                 controllerName = route.ControllerName
218                 if controllerName[0] == ':' {
219                         controllerName = strings.ToLower(params[controllerName[1:]][0])
220                         if typeOfController = route.ModuleSource.ControllerByName(controllerName, methodName); typeOfController != nil {
221                                 break
222                         }
223                 } else {
224                         typeOfController = route.TypeOfController
225                         break
226                 }
227                 route = nil
228         }
229
230         if route == nil {
231                 routeMatch = notFound
232         } else {
233
234                 routeMatch = &RouteMatch{
235                         ControllerName:   route.ControllerNamespace + controllerName,
236                         MethodName:       methodName,
237                         Params:           params,
238                         FixedParams:      route.FixedParams,
239                         TypeOfController: typeOfController,
240                         ModuleSource:     route.ModuleSource,
241                 }
242         }
243
244         return
245 }
246
247 // Refresh re-reads the routes file and re-calculates the routing table.
248 // Returns an error if a specified action could not be found.
249 func (router *Router) Refresh() (err *Error) {
250         RaiseEvent(ROUTE_REFRESH_REQUESTED, nil)
251         router.Routes, err = parseRoutesFile(appModule, router.path, "", true)
252         RaiseEvent(ROUTE_REFRESH_COMPLETED, nil)
253         if err != nil {
254                 return
255         }
256         err = router.updateTree()
257         return
258 }
259
260 func (router *Router) updateTree() *Error {
261         router.Tree = pathtree.New()
262         pathMap := map[string][]*Route{}
263
264         allPathsOrdered := []string{}
265         // It is possible for some route paths to overlap
266         // based on wildcard matches,
267         // 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
268         for _, route := range router.Routes {
269                 if _, found := pathMap[route.TreePath]; !found {
270                         pathMap[route.TreePath] = append(pathMap[route.TreePath], route)
271                         allPathsOrdered = append(allPathsOrdered, route.TreePath)
272                 } else {
273                         pathMap[route.TreePath] = append(pathMap[route.TreePath], route)
274                 }
275         }
276         for _, path := range allPathsOrdered {
277                 routeList := pathMap[path]
278                 err := router.Tree.Add(path, routeList)
279
280                 // Allow GETs to respond to HEAD requests.
281                 if err == nil && routeList[0].Method == "GET" {
282                         err = router.Tree.Add(treePath("HEAD", routeList[0].Path), routeList)
283                 }
284
285                 // Error adding a route to the pathtree.
286                 if err != nil {
287                         return routeError(err, path, fmt.Sprintf("%#v", routeList), routeList[0].line)
288                 }
289         }
290         return nil
291 }
292
293 // Returns the controller namespace and name, action and module if found from the actionPath specified
294 func splitActionPath(actionPathData *ActionPathData, actionPath string, useCache bool) (pathData *ActionPathData, found bool) {
295         actionPath = strings.ToLower(actionPath)
296         if pathData, found = actionPathCacheMap[actionPath]; found && useCache {
297                 return
298         }
299         var (
300                 controllerNamespace, controllerName, methodName, action string
301                 foundModuleSource                                       *Module
302                 typeOfController                                        *ControllerType
303                 log                                                     = routerLog.New("actionPath", actionPath)
304         )
305         actionSplit := strings.Split(actionPath, ".")
306         if actionPathData != nil {
307                 foundModuleSource = actionPathData.ModuleSource
308         }
309         if len(actionSplit) == 2 {
310                 controllerName, methodName = strings.ToLower(actionSplit[0]), strings.ToLower(actionSplit[1])
311                 if i := strings.Index(methodName, "("); i > 0 {
312                         methodName = methodName[:i]
313                 }
314                 log = log.New("controller", controllerName, "method", methodName)
315                 log.Debug("splitActionPath: Check for namespace")
316                 if i := strings.Index(controllerName, namespaceSeperator); i > -1 {
317                         controllerNamespace = controllerName[:i+1]
318                         if moduleSource, found := ModuleByName(controllerNamespace[:len(controllerNamespace)-1]); found {
319                                 log.Debug("Found module namespace")
320                                 foundModuleSource = moduleSource
321                                 controllerNamespace = moduleSource.Namespace()
322                         } else {
323                                 log.Warnf("splitActionPath: Unable to find module %s for action: %s", controllerNamespace[:len(controllerNamespace)-1], actionPath)
324                         }
325                         controllerName = controllerName[i+1:]
326                         // Check for the type of controller
327                         typeOfController = foundModuleSource.ControllerByName(controllerName, methodName)
328                         found = typeOfController != nil
329                 } else if controllerName[0] != ':' {
330                         // First attempt to find the controller in the module source
331                         if foundModuleSource != nil {
332                                 typeOfController = foundModuleSource.ControllerByName(controllerName, methodName)
333                                 if typeOfController != nil {
334                                         controllerNamespace = typeOfController.Namespace
335                                 }
336                         }
337                         log.Info("Found controller for path", "controllerType", typeOfController)
338
339                         if typeOfController == nil {
340                                 // Check to see if we can determine the controller from only the controller name
341                                 // an actionPath without a moduleSource will only come from
342                                 // Scan through the controllers
343                                 matchName := controllerName
344                                 for key, controller := range controllers {
345                                         // Strip away the namespace from the controller. to be match
346                                         regularName := key
347                                         if i := strings.Index(key, namespaceSeperator); i > -1 {
348                                                 regularName = regularName[i+1:]
349                                         }
350                                         if regularName == matchName {
351                                                 // Found controller match
352                                                 typeOfController = controller
353                                                 controllerNamespace = typeOfController.Namespace
354                                                 controllerName = typeOfController.ShortName()
355                                                 foundModuleSource = typeOfController.ModuleSource
356                                                 found = true
357                                                 break
358                                         }
359                                 }
360                         } else {
361                                 found = true
362                         }
363                 } else {
364                         // If wildcard assign the route the controller namespace found
365                         controllerNamespace = actionPathData.ModuleSource.Name + namespaceSeperator
366                         foundModuleSource = actionPathData.ModuleSource
367                         found = true
368                 }
369                 action = actionSplit[1]
370         } else {
371                 foundPaths := ""
372                 for path := range actionPathCacheMap {
373                         foundPaths += path + ","
374                 }
375                 log.Warnf("splitActionPath: Invalid action path %s found paths %s", actionPath, foundPaths)
376                 found = false
377         }
378
379         // Make sure no concurrent map writes occur
380         if found {
381                 actionPathCacheLock.Lock()
382                 defer actionPathCacheLock.Unlock()
383                 if actionPathData != nil {
384                         actionPathData.ControllerNamespace = controllerNamespace
385                         actionPathData.ControllerName = controllerName
386                         actionPathData.MethodName = methodName
387                         actionPathData.Action = action
388                         actionPathData.ModuleSource = foundModuleSource
389                         actionPathData.TypeOfController = typeOfController
390                 } else {
391                         actionPathData = &ActionPathData{
392                                 ControllerNamespace: controllerNamespace,
393                                 ControllerName:      controllerName,
394                                 MethodName:          methodName,
395                                 Action:              action,
396                                 ModuleSource:        foundModuleSource,
397                                 TypeOfController:    typeOfController,
398                         }
399                 }
400                 actionPathData.TypeOfController = foundModuleSource.ControllerByName(controllerName, "")
401                 if actionPathData.TypeOfController == nil && actionPathData.ControllerName[0] != ':' {
402                         log.Warnf("splitActionPath: No controller found for %s %#v", foundModuleSource.Namespace()+controllerName, controllers)
403                 }
404
405                 pathData = actionPathData
406                 if pathData.Route != nil && len(pathData.Route.FixedParams) > 0 {
407                         // If there are fixed params on the route then add them to the path
408                         // This will give it a unique path and it should still be usable for a reverse lookup provided the name is matchable
409                         // for example
410                         // GET   /test/                     Application.Index("Test", "Test2")
411                         // {{url "Application.Index(test,test)" }}
412                         // should be parseable
413                         actionPath = actionPath + "(" + strings.ToLower(strings.Join(pathData.Route.FixedParams, ",")) + ")"
414                 }
415                 if actionPathData.Route != nil {
416                         log.Debugf("splitActionPath: Split Storing recognized action path %s for route  %#v ", actionPath, actionPathData.Route)
417                 }
418                 pathData.Key = actionPath
419                 actionPathCacheMap[actionPath] = pathData
420                 if !strings.Contains(actionPath, namespaceSeperator) && pathData.TypeOfController != nil {
421                         actionPathCacheMap[strings.ToLower(pathData.TypeOfController.Namespace)+actionPath] = pathData
422                         log.Debugf("splitActionPath: Split Storing recognized action path %s for route  %#v ", strings.ToLower(pathData.TypeOfController.Namespace)+actionPath, actionPathData.Route)
423                 }
424         }
425         return
426 }
427
428 // parseRoutesFile reads the given routes file and returns the contained routes.
429 func parseRoutesFile(moduleSource *Module, routesPath, joinedPath string, validate bool) ([]*Route, *Error) {
430         contentBytes, err := ioutil.ReadFile(routesPath)
431         if err != nil {
432                 return nil, &Error{
433                         Title:       "Failed to load routes file",
434                         Description: err.Error(),
435                 }
436         }
437         return parseRoutes(moduleSource, routesPath, joinedPath, string(contentBytes), validate)
438 }
439
440 // parseRoutes reads the content of a routes file into the routing table.
441 func parseRoutes(moduleSource *Module, routesPath, joinedPath, content string, validate bool) ([]*Route, *Error) {
442         var routes []*Route
443
444         // For each line..
445         for n, line := range strings.Split(content, "\n") {
446                 line = strings.TrimSpace(line)
447                 if len(line) == 0 || line[0] == '#' {
448                         continue
449                 }
450
451                 const modulePrefix = "module:"
452
453                 // Handle included routes from modules.
454                 // e.g. "module:testrunner" imports all routes from that module.
455                 if strings.HasPrefix(line, modulePrefix) {
456                         moduleRoutes, err := getModuleRoutes(line[len(modulePrefix):], joinedPath, validate)
457                         if err != nil {
458                                 return nil, routeError(err, routesPath, content, n)
459                         }
460                         routes = append(routes, moduleRoutes...)
461                         continue
462                 }
463
464                 // A single route
465                 method, path, action, fixedArgs, found := parseRouteLine(line)
466                 if !found {
467                         continue
468                 }
469
470                 // this will avoid accidental double forward slashes in a route.
471                 // this also avoids pathtree freaking out and causing a runtime panic
472                 // because of the double slashes
473                 if strings.HasSuffix(joinedPath, "/") && strings.HasPrefix(path, "/") {
474                         joinedPath = joinedPath[0 : len(joinedPath)-1]
475                 }
476                 path = strings.Join([]string{AppRoot, joinedPath, path}, "")
477
478                 // This will import the module routes under the path described in the
479                 // routes file (joinedPath param). e.g. "* /jobs module:jobs" -> all
480                 // routes' paths will have the path /jobs prepended to them.
481                 // See #282 for more info
482                 if method == "*" && strings.HasPrefix(action, modulePrefix) {
483                         moduleRoutes, err := getModuleRoutes(action[len(modulePrefix):], path, validate)
484                         if err != nil {
485                                 return nil, routeError(err, routesPath, content, n)
486                         }
487                         routes = append(routes, moduleRoutes...)
488                         continue
489                 }
490
491                 route := NewRoute(moduleSource, method, path, action, fixedArgs, routesPath, n)
492                 routes = append(routes, route)
493
494                 if validate {
495                         if err := validateRoute(route); err != nil {
496                                 return nil, routeError(err, routesPath, content, n)
497                         }
498                 }
499         }
500
501         return routes, nil
502 }
503
504 // validateRoute checks that every specified action exists.
505 func validateRoute(route *Route) error {
506         // Skip 404s
507         if route.Action == httpStatusCode {
508                 return nil
509         }
510
511         // Skip variable routes.
512         if route.ControllerName[0] == ':' || route.MethodName[0] == ':' {
513                 return nil
514         }
515
516         // Precheck to see if controller exists
517         if _, found := controllers[route.ControllerNamespace+route.ControllerName]; !found {
518                 // Scan through controllers to find module
519                 for _, c := range controllers {
520                         controllerName := strings.ToLower(c.Type.Name())
521                         if controllerName == route.ControllerName {
522                                 route.ControllerNamespace = c.ModuleSource.Name + namespaceSeperator
523                                 routerLog.Warn("validateRoute: Matched empty namespace route for %s to this namespace %s for the route %s", controllerName, c.ModuleSource.Name, route.Path)
524                         }
525                 }
526         }
527
528         // TODO need to check later
529         // does it do only validation or validation and instantiate the controller.
530         var c Controller
531         return c.SetTypeAction(route.ControllerNamespace+route.ControllerName, route.MethodName, route.TypeOfController)
532 }
533
534 // routeError adds context to a simple error message.
535 func routeError(err error, routesPath, content string, n int) *Error {
536         if revelError, ok := err.(*Error); ok {
537                 return revelError
538         }
539         // Load the route file content if necessary
540         if content == "" {
541                 if contentBytes, er := ioutil.ReadFile(routesPath); er != nil {
542                         routerLog.Error("routeError: Failed to read route file ", "file", routesPath, "error", er)
543                 } else {
544                         content = string(contentBytes)
545                 }
546         }
547         return &Error{
548                 Title:       "Route validation error",
549                 Description: err.Error(),
550                 Path:        routesPath,
551                 Line:        n + 1,
552                 SourceLines: strings.Split(content, "\n"),
553                 Stack:       fmt.Sprintf("%s", logger.NewCallStack()),
554         }
555 }
556
557 // getModuleRoutes loads the routes file for the given module and returns the
558 // list of routes.
559 func getModuleRoutes(moduleName, joinedPath string, validate bool) (routes []*Route, err *Error) {
560         // Look up the module.  It may be not found due to the common case of e.g. the
561         // testrunner module being active only in dev mode.
562         module, found := ModuleByName(moduleName)
563         if !found {
564                 routerLog.Debug("getModuleRoutes: Skipping routes for inactive module", "module", moduleName)
565                 return nil, nil
566         }
567         routePath := filepath.Join(module.Path, "conf", "routes")
568         if _, e := os.Stat(routePath); e == nil {
569                 routes, err = parseRoutesFile(module, routePath, joinedPath, validate)
570         }
571         if err == nil {
572                 for _, route := range routes {
573                         route.ModuleSource = module
574                 }
575         }
576
577         return routes, err
578 }
579
580 // Groups:
581 // 1: method
582 // 4: path
583 // 5: action
584 // 6: fixedargs
585 var routePattern = regexp.MustCompile(
586         "(?i)^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|WS|PROPFIND|MKCOL|COPY|MOVE|PROPPATCH|LOCK|UNLOCK|TRACE|PURGE|\\*)" +
587                 "[(]?([^)]*)(\\))?[ \t]+" +
588                 "(.*/[^ \t]*)[ \t]+([^ \t(]+)" +
589                 `\(?([^)]*)\)?[ \t]*$`)
590
591 func parseRouteLine(line string) (method, path, action, fixedArgs string, found bool) {
592         matches := routePattern.FindStringSubmatch(line)
593         if matches == nil {
594                 return
595         }
596         method, path, action, fixedArgs = matches[1], matches[4], matches[5], matches[6]
597         found = true
598         return
599 }
600
601 func NewRouter(routesPath string) *Router {
602         return &Router{
603                 Tree: pathtree.New(),
604                 path: routesPath,
605         }
606 }
607
608 type ActionDefinition struct {
609         Host, Method, URL, Action string
610         Star                      bool
611         Args                      map[string]string
612 }
613
614 func (a *ActionDefinition) String() string {
615         return a.URL
616 }
617
618 func (router *Router) Reverse(action string, argValues map[string]string) (ad *ActionDefinition) {
619         log := routerLog.New("action", action)
620         pathData, found := splitActionPath(nil, action, true)
621         if !found {
622                 routerLog.Error("splitActionPath: Failed to find reverse route", "action", action, "arguments", argValues)
623                 return nil
624         }
625
626         log.Debug("Checking for route", "pathdataRoute", pathData.Route)
627         if pathData.Route == nil {
628                 var possibleRoute *Route
629                 // If the route is nil then we need to go through the routes to find the first matching route
630                 // from this controllers namespace, this is likely a wildcard route match
631                 for _, route := range router.Routes {
632                         // Skip routes that are not wild card or empty
633                         if route.ControllerName == "" || route.MethodName == "" {
634                                 continue
635                         }
636                         if route.ModuleSource == pathData.ModuleSource && route.ControllerName[0] == ':' {
637                                 // Wildcard match in same module space
638                                 pathData.Route = route
639                                 break
640                         } else if route.ActionPath() == pathData.ModuleSource.Namespace()+pathData.ControllerName &&
641                                 (route.Method[0] == ':' || route.Method == pathData.MethodName) {
642                                 // Action path match
643                                 pathData.Route = route
644                                 break
645                         } else if route.ControllerName == pathData.ControllerName &&
646                                 (route.Method[0] == ':' || route.Method == pathData.MethodName) {
647                                 // Controller name match
648                                 possibleRoute = route
649                         }
650                 }
651                 if pathData.Route == nil && possibleRoute != nil {
652                         pathData.Route = possibleRoute
653                         routerLog.Warnf("Reverse: For a url reverse a match was based on  %s matched path to route %#v ", action, possibleRoute)
654                 }
655                 if pathData.Route != nil {
656                         routerLog.Debugf("Reverse: Reverse Storing recognized action path %s for route %#v\n", action, pathData.Route)
657                 }
658         }
659
660         // Likely unknown route because of a wildcard, perform manual lookup
661         if pathData.Route != nil {
662                 route := pathData.Route
663
664                 // If the controller or method are wildcards we need to populate the argValues
665                 controllerWildcard := route.ControllerName[0] == ':'
666                 methodWildcard := route.MethodName[0] == ':'
667
668                 // populate route arguments with the names
669                 if controllerWildcard {
670                         argValues[route.ControllerName[1:]] = pathData.ControllerName
671                 }
672                 if methodWildcard {
673                         argValues[route.MethodName[1:]] = pathData.MethodName
674                 }
675                 // In theory all routes should be defined and pre-populated, the route controllers may not be though
676                 // with wildcard routes
677                 if pathData.TypeOfController == nil {
678                         if controllerWildcard || methodWildcard {
679                                 if controller := ControllerTypeByName(pathData.ControllerNamespace+pathData.ControllerName, route.ModuleSource); controller != nil {
680                                         // Wildcard match boundary
681                                         pathData.TypeOfController = controller
682                                         // See if the path exists in the module based
683                                 } else {
684                                         routerLog.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName)
685                                         return
686                                 }
687                         }
688                 }
689
690                 if pathData.TypeOfController == nil {
691                         routerLog.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName)
692                         return
693                 }
694                 var (
695                         queryValues  = make(url.Values)
696                         pathElements = strings.Split(route.Path, "/")
697                 )
698                 for i, el := range pathElements {
699                         if el == "" || (el[0] != ':' && el[0] != '*') {
700                                 continue
701                         }
702                         val, ok := pathData.FixedParamsByName[el[1:]]
703                         if !ok {
704                                 val, ok = argValues[el[1:]]
705                         }
706                         if !ok {
707                                 val = "<nil>"
708                                 routerLog.Error("Reverse: reverse route missing route argument ", "argument", el[1:])
709                         }
710                         pathElements[i] = val
711                         delete(argValues, el[1:])
712                         continue
713                 }
714
715                 // Add any args that were not inserted into the path into the query string.
716                 for k, v := range argValues {
717                         queryValues.Set(k, v)
718                 }
719
720                 // Calculate the final URL and Method
721                 urlPath := strings.Join(pathElements, "/")
722                 if len(queryValues) > 0 {
723                         urlPath += "?" + queryValues.Encode()
724                 }
725
726                 method := route.Method
727                 star := false
728                 if route.Method == "*" {
729                         method = "GET"
730                         star = true
731                 }
732
733                 //INFO.Printf("Reversing action %s to %s Using Route %#v",action,url,pathData.Route)
734
735                 return &ActionDefinition{
736                         URL:    urlPath,
737                         Method: method,
738                         Star:   star,
739                         Action: action,
740                         Args:   argValues,
741                         Host:   "TODO",
742                 }
743         }
744
745         routerLog.Error("Reverse: Failed to find controller for reverse route", "action", action, "arguments", argValues)
746         return nil
747 }
748
749 func RouterFilter(c *Controller, fc []Filter) {
750         // Figure out the Controller/Action
751         route := MainRouter.Route(c.Request)
752         if route == nil {
753                 c.Result = c.NotFound("No matching route found: " + c.Request.GetRequestURI())
754                 return
755         }
756
757         // The route may want to explicitly return a 404.
758         if route.Action == httpStatusCode {
759                 c.Result = c.NotFound("(intentionally)")
760                 return
761         }
762
763         // Set the action.
764         if err := c.SetTypeAction(route.ControllerName, route.MethodName, route.TypeOfController); err != nil {
765                 c.Result = c.NotFound(err.Error())
766                 return
767         }
768
769         // Add the route and fixed params to the Request Params.
770         c.Params.Route = route.Params
771         // Assign logger if from module
772         if c.Type.ModuleSource != nil && c.Type.ModuleSource != appModule {
773                 c.Log = c.Type.ModuleSource.Log.New("ip", c.ClientIP,
774                         "path", c.Request.URL.Path, "method", c.Request.Method)
775         }
776
777         // Add the fixed parameters mapped by name.
778         // TODO: Pre-calculate this mapping.
779         for i, value := range route.FixedParams {
780                 if c.Params.Fixed == nil {
781                         c.Params.Fixed = make(url.Values)
782                 }
783                 if i < len(c.MethodType.Args) {
784                         arg := c.MethodType.Args[i]
785                         c.Params.Fixed.Set(arg.Name, value)
786                 } else {
787                         routerLog.Warn("RouterFilter: Too many parameters to action", "action", route.Action, "value", value)
788                         break
789                 }
790         }
791
792         fc[0](c, fc[1:])
793 }
794
795 // HTTPMethodOverride overrides allowed http methods via form or browser param
796 func HTTPMethodOverride(c *Controller, fc []Filter) {
797         // An array of HTTP verbs allowed.
798         verbs := []string{"POST", "PUT", "PATCH", "DELETE"}
799
800         method := strings.ToUpper(c.Request.Method)
801
802         if method == "POST" {
803                 param := ""
804                 if f, err := c.Request.GetForm(); err == nil {
805                         param = strings.ToUpper(f.Get("_method"))
806                 }
807
808                 if len(param) > 0 {
809                         override := false
810                         // Check if param is allowed
811                         for _, verb := range verbs {
812                                 if verb == param {
813                                         override = true
814                                         break
815                                 }
816                         }
817
818                         if override {
819                                 c.Request.Method = param
820                         } else {
821                                 c.Response.Status = 405
822                                 c.Result = c.RenderError(&Error{
823                                         Title:       "Method not allowed",
824                                         Description: "Method " + param + " is not allowed (valid: " + strings.Join(verbs, ", ") + ")",
825                                 })
826                                 return
827                         }
828
829                 }
830         }
831
832         fc[0](c, fc[1:]) // Execute the next filter stage.
833 }
834
835 func init() {
836         OnAppStart(func() {
837                 MainRouter = NewRouter(filepath.Join(BasePath, "conf", "routes"))
838                 err := MainRouter.Refresh()
839                 if MainWatcher != nil && Config.BoolDefault("watch.routes", true) {
840                         MainWatcher.Listen(MainRouter, MainRouter.path)
841                 } else if err != nil {
842                         // Not in dev mode and Route loading failed, we should crash.
843                         routerLog.Panic("init: router initialize error", "error", err)
844                 }
845         })
846 }