tags. tl := strings.ToLower(text) if strings.Contains(tl, "") { insidePre = true } // Trim if not inside astatement if !insidePre { // Cut trailing/leading whitespace text = strings.Trim(text, " \t\r\n") if len(text) > 0 { if _, err = b2.WriteString(text); err != nil { resultsLog.Error("Apply: ", "error", err) } if _, err = b2.WriteString("\n"); err != nil { resultsLog.Error("Apply: ", "error", err) } } } else { if _, err = b2.WriteString(text); err != nil { resultsLog.Error("Apply: ", "error", err) } } if strings.Contains(tl, "") { insidePre = false } // We are finished if err != nil { break } } return } // Render the error in the response func (r *RenderTemplateResult) renderError(err error, req *Request, resp *Response) { compileError, found := err.(*Error) if !found { var templateContent []string templateName, line, description := ParseTemplateError(err) if templateName == "" { templateLog.Info("Cannot determine template name to render error", "error", err) templateName = r.Template.Name() templateContent = r.Template.Content() } else { lang, _ := r.ViewArgs[CurrentLocaleViewArg].(string) if tmpl, err := MainTemplateLoader.TemplateLang(templateName, lang); err == nil { templateContent = tmpl.Content() } else { templateLog.Info("Unable to retreive template ", "error", err) } } compileError = &Error{ Title: "Template Execution Error", Path: templateName, Description: description, Line: line, SourceLines: templateContent, } } resp.Status = 500 resultsLog.Errorf("render: Template Execution Error (in %s): %s", compileError.Path, compileError.Description) ErrorResult{r.ViewArgs, compileError}.Apply(req, resp) } type RenderHTMLResult struct { html string } func (r RenderHTMLResult) Apply(req *Request, resp *Response) { resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8") if _, err := resp.GetWriter().Write([]byte(r.html)); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } } type RenderJSONResult struct { obj interface{} callback string } func (r RenderJSONResult) Apply(req *Request, resp *Response) { var b []byte var err error if Config.BoolDefault("results.pretty", false) { b, err = json.MarshalIndent(r.obj, "", " ") } else { b, err = json.Marshal(r.obj) } if err != nil { ErrorResult{Error: err}.Apply(req, resp) return } if r.callback == "" { resp.WriteHeader(http.StatusOK, "application/json; charset=utf-8") if _, err = resp.GetWriter().Write(b); err != nil { resultsLog.Error("Apply: Response write failed:", "error", err) } return } resp.WriteHeader(http.StatusOK, "application/javascript; charset=utf-8") if _, err = resp.GetWriter().Write([]byte(r.callback + "(")); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } if _, err = resp.GetWriter().Write(b); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } if _, err = resp.GetWriter().Write([]byte(");")); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } } type RenderXMLResult struct { obj interface{} } func (r RenderXMLResult) Apply(req *Request, resp *Response) { var b []byte var err error if Config.BoolDefault("results.pretty", false) { b, err = xml.MarshalIndent(r.obj, "", " ") } else { b, err = xml.Marshal(r.obj) } if err != nil { ErrorResult{Error: err}.Apply(req, resp) return } resp.WriteHeader(http.StatusOK, "application/xml; charset=utf-8") if _, err = resp.GetWriter().Write(b); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } } type RenderTextResult struct { text string } func (r RenderTextResult) Apply(req *Request, resp *Response) { resp.WriteHeader(http.StatusOK, "text/plain; charset=utf-8") if _, err := resp.GetWriter().Write([]byte(r.text)); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } } type ContentDisposition string var ( NoDisposition ContentDisposition = "" Attachment ContentDisposition = "attachment" Inline ContentDisposition = "inline" ) type BinaryResult struct { Reader io.Reader Name string Length int64 Delivery ContentDisposition ModTime time.Time } func (r *BinaryResult) Apply(req *Request, resp *Response) { if r.Delivery != NoDisposition { disposition := string(r.Delivery) if r.Name != "" { disposition += fmt.Sprintf(`; filename="%s"`, r.Name) } resp.Out.internalHeader.Set("Content-Disposition", disposition) } if resp.ContentType != "" { resp.Out.internalHeader.Set("Content-Type", resp.ContentType) } else { contentType := ContentTypeByFilename(r.Name) resp.Out.internalHeader.Set("Content-Type", contentType) } if content, ok := r.Reader.(io.ReadSeeker); ok && r.Length < 0 { // get the size from the stream // go1.6 compatibility change, go1.6 does not define constants io.SeekStart //if size, err := content.Seek(0, io.SeekEnd); err == nil { // if _, err = content.Seek(0, io.SeekStart); err == nil { if size, err := content.Seek(0, 2); err == nil { if _, err = content.Seek(0, 0); err == nil { r.Length = size } } } // Write stream writes the status code to the header as well if ws := resp.GetStreamWriter(); ws != nil { if err := ws.WriteStream(r.Name, r.Length, r.ModTime, r.Reader); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } } // Close the Reader if we can if v, ok := r.Reader.(io.Closer); ok { _ = v.Close() } } type RedirectToURLResult struct { url string } func (r *RedirectToURLResult) Apply(req *Request, resp *Response) { resp.Out.internalHeader.Set("Location", r.url) resp.WriteHeader(http.StatusFound, "") } type RedirectToActionResult struct { val interface{} args []interface{} } func (r *RedirectToActionResult) Apply(req *Request, resp *Response) { url, err := getRedirectURL(r.val, r.args) if err != nil { req.controller.Log.Error("Apply: Couldn't resolve redirect", "error", err) ErrorResult{Error: err}.Apply(req, resp) return } resp.Out.internalHeader.Set("Location", url) resp.WriteHeader(http.StatusFound, "") } func getRedirectURL(item interface{}, args []interface{}) (string, error) { // Handle strings if url, ok := item.(string); ok { return url, nil } // Handle funcs val := reflect.ValueOf(item) typ := reflect.TypeOf(item) if typ.Kind() == reflect.Func && typ.NumIn() > 0 { // Get the Controller Method recvType := typ.In(0) method := FindMethod(recvType, val) if method == nil { return "", errors.New("couldn't find method") } // Construct the action string (e.g. "Controller.Method") if recvType.Kind() == reflect.Ptr { recvType = recvType.Elem() } module := ModuleFromPath(recvType.PkgPath(), true) action := module.Namespace() + recvType.Name() + "." + method.Name // Fetch the action path to get the defaults pathData, found := splitActionPath(nil, action, true) if !found { return "", fmt.Errorf("Unable to redirect '%s', expected 'Controller.Action'", action) } // Build the map for the router to reverse // Unbind the arguments. argsByName := make(map[string]string) // Bind any static args first fixedParams := len(pathData.FixedParamsByName) methodType := pathData.TypeOfController.Method(pathData.MethodName) for i, argValue := range args { Unbind(argsByName, methodType.Args[i+fixedParams].Name, argValue) } actionDef := MainRouter.Reverse(action, argsByName) if actionDef == nil { return "", errors.New("no route for action " + action) } return actionDef.String(), nil } // Out of guesses return "", errors.New("didn't recognize type: " + typ.String()) } revel-1.0.0/results_test.go 0000664 0000000 0000000 00000004015 13702523120 0015707 0 ustar 00root root 0000000 0000000 // 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 ( "fmt" "net/http/httptest" "strings" "testing" ) // Added test case for redirection testing for strings func TestRedirect(t *testing.T) { startFakeBookingApp() redirect := RedirectToURLResult{fmt.Sprintf("/hotels/index/foo")} resp := httptest.NewRecorder() c := NewTestController(resp, showRequest) redirect.Apply(c.Request, c.Response) if resp.Header().Get("Location") != "/hotels/index/foo" { t.Errorf("Failed to set redirect header correctly. : %s", resp.Header().Get("Location")) } } // Test that the render response is as expected. func TestBenchmarkRender(t *testing.T) { startFakeBookingApp() resp := httptest.NewRecorder() c := NewTestController(resp, showRequest) if err := c.SetAction("Hotels", "Show"); err != nil { t.Errorf("SetAction failed: %s", err) } result := Hotels{c}.Show(3) result.Apply(c.Request, c.Response) if !strings.Contains(resp.Body.String(), "300 Main St.") { t.Errorf("Failed to find hotel address in action response:\n%s", resp.Body) } } func BenchmarkRenderChunked(b *testing.B) { startFakeBookingApp() resp := httptest.NewRecorder() resp.Body = nil c := NewTestController(resp, showRequest) if err := c.SetAction("Hotels", "Show"); err != nil { b.Errorf("SetAction failed: %s", err) } Config.SetOption("results.chunked", "true") b.ResetTimer() hotels := Hotels{c} for i := 0; i < b.N; i++ { hotels.Show(3).Apply(c.Request, c.Response) } } func BenchmarkRenderNotChunked(b *testing.B) { startFakeBookingApp() resp := httptest.NewRecorder() resp.Body = nil c := NewTestController(resp, showRequest) if err := c.SetAction("Hotels", "Show"); err != nil { b.Errorf("SetAction failed: %s", err) } Config.SetOption("results.chunked", "false") b.ResetTimer() hotels := Hotels{c} for i := 0; i < b.N; i++ { hotels.Show(3).Apply(c.Request, c.Response) } } revel-1.0.0/revel.go 0000664 0000000 0000000 00000024436 13702523120 0014275 0 ustar 00root root 0000000 0000000 // 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 ( "go/build" "net/http" "path/filepath" "strings" "encoding/json" "fmt" "github.com/revel/config" "github.com/revel/revel/logger" "github.com/revel/revel/model" ) const ( // RevelImportPath Revel framework import path RevelImportPath = "github.com/revel/revel" ) const ( TEST_MODE_FLAG = "testModeFlag" SPECIAL_USE_FLAG = "specialUseFlag" ) // App details var ( RevelConfig *model.RevelContainer AppName string // e.g. "sample" AppRoot string // e.g. "/app1" BasePath string // e.g. "$GOPATH/src/corp/sample" AppPath string // e.g. "$GOPATH/src/corp/sample/app" ViewsPath string // e.g. "$GOPATH/src/corp/sample/app/views" ImportPath string // e.g. "corp/sample" SourcePath string // e.g. "$GOPATH/src" Config *config.Context RunMode string // Application-defined (by default, "dev" or "prod") DevMode bool // if true, RunMode is a development mode. // Revel installation details RevelPath string // e.g. "$GOPATH/src/github.com/revel/revel" // Where to look for templates // Ordered by priority. (Earlier paths take precedence over later paths.) CodePaths []string // Code base directories, for modules and app TemplatePaths []string // Template path directories manually added // ConfPaths where to look for configurations // Config load order // 1. framework (revel/conf/*) // 2. application (conf/*) // 3. user supplied configs (...) - User configs can override/add any from above ConfPaths []string // Server config. // // Alert: This is how the app is configured, which may be different from // the current process reality. For example, if the app is configured for // port 9000, HTTPPort will always be 9000, even though in dev mode it is // run on a random port and proxied. HTTPPort int // e.g. 9000 HTTPAddr string // e.g. "", "127.0.0.1" HTTPSsl bool // e.g. true if using ssl HTTPSslCert string // e.g. "/path/to/cert.pem" HTTPSslKey string // e.g. "/path/to/key.pem" // All cookies dropped by the framework begin with this prefix. CookiePrefix string // Cookie domain CookieDomain string // Cookie flags CookieSecure bool CookieSameSite http.SameSite // Revel request access log, not exposed from package. // However output settings can be controlled from app.conf // True when revel engine has been initialized (Init has returned) Initialized bool // Private secretKey []byte // Key used to sign cookies. An empty key disables signing. packaged bool // If true, this is running from a pre-built package. initEventList = []EventHandler{} // Event handler list for receiving events packagePathMap = map[string]string{} // The map of the directories needed ) // Init initializes Revel -- it provides paths for getting around the app. // // Params: // mode - the run mode, which determines which app.conf settings are used. // importPath - the Go import path of the application. // srcPath - the path to the source directory, containing Revel and the app. // If not specified (""), then a functioning Go installation is required. func Init(inputmode, importPath, srcPath string) { RevelConfig = &model.RevelContainer{} // Ignore trailing slashes. ImportPath = strings.TrimRight(importPath, "/") SourcePath = srcPath RunMode = updateLog(inputmode) // If the SourcePath is not specified, find it using build.Import. var revelSourcePath string // may be different from the app source path if SourcePath == "" { revelSourcePath, SourcePath = findSrcPaths(importPath) BasePath = SourcePath } else { // If the SourcePath was specified, assume both Revel and the app are within it. SourcePath = filepath.Clean(SourcePath) revelSourcePath = filepath.Join(SourcePath, filepath.FromSlash(RevelImportPath)) BasePath = filepath.Join(SourcePath, filepath.FromSlash(importPath)) packaged = true } RevelPath = revelSourcePath //filepath.Join(revelSourcePath, filepath.FromSlash(RevelImportPath)) AppPath = filepath.Join(BasePath, "app") ViewsPath = filepath.Join(AppPath, "views") RevelLog.Info("Paths","revel", RevelPath,"base", BasePath,"app", AppPath,"views", ViewsPath) CodePaths = []string{AppPath} if ConfPaths == nil { ConfPaths = []string{} } // Config load order // 1. framework (revel/conf/*) // 2. application (conf/*) // 3. user supplied configs (...) - User configs can override/add any from above ConfPaths = append( []string{ filepath.Join(RevelPath, "conf"), filepath.Join(BasePath, "conf"), }, ConfPaths...) TemplatePaths = []string{ ViewsPath, filepath.Join(RevelPath, "templates"), } // Load app.conf var err error Config, err = config.LoadContext("app.conf", ConfPaths) if err != nil || Config == nil { RevelLog.Fatal("Failed to load app.conf:", "error", err) } // After application config is loaded update the logger updateLog(inputmode) // Configure properties from app.conf DevMode = Config.BoolDefault("mode.dev", false) HTTPPort = Config.IntDefault("http.port", 9000) HTTPAddr = Config.StringDefault("http.addr", "") HTTPSsl = Config.BoolDefault("http.ssl", false) HTTPSslCert = Config.StringDefault("http.sslcert", "") HTTPSslKey = Config.StringDefault("http.sslkey", "") if HTTPSsl { if HTTPSslCert == "" { RevelLog.Fatal("No http.sslcert provided.") } if HTTPSslKey == "" { RevelLog.Fatal("No http.sslkey provided.") } } AppName = Config.StringDefault("app.name", "(not set)") AppRoot = Config.StringDefault("app.root", "") CookiePrefix = Config.StringDefault("cookie.prefix", "REVEL") CookieDomain = Config.StringDefault("cookie.domain", "") CookieSecure = Config.BoolDefault("cookie.secure", HTTPSsl) switch Config.StringDefault("cookie.samesite", "") { case "lax": CookieSameSite = http.SameSiteLaxMode case "strict": CookieSameSite = http.SameSiteStrictMode case "none": CookieSameSite = http.SameSiteNoneMode default: CookieSameSite = http.SameSiteDefaultMode } if secretStr := Config.StringDefault("app.secret", ""); secretStr != "" { SetSecretKey([]byte(secretStr)) } RaiseEvent(REVEL_BEFORE_MODULES_LOADED, nil) loadModules() RaiseEvent(REVEL_AFTER_MODULES_LOADED, nil) Initialized = true RevelLog.Info("Initialized Revel", "Version", Version, "BuildDate", BuildDate, "MinimumGoVersion", MinimumGoVersion) } // The input mode can be as simple as "prod" or it can be a JSON string like // {"mode":"%s","testModeFlag":true} // When this function is called it returns the true "inputmode" extracted from the parameter // and it sets the log context appropriately func updateLog(inputmode string) (returnMode string) { if inputmode == "" { returnMode = config.DefaultSection return } else { returnMode = inputmode } // Check to see if the mode is a json object modemap := map[string]interface{}{} var testModeFlag, specialUseFlag bool if err := json.Unmarshal([]byte(inputmode), &modemap); err == nil { returnMode = modemap["mode"].(string) if testmode, found := modemap[TEST_MODE_FLAG]; found { testModeFlag, _ = testmode.(bool) } if specialUse, found := modemap[SPECIAL_USE_FLAG]; found { specialUseFlag, _ = specialUse.(bool) } if packagePathMapI, found := modemap["packagePathMap"]; found { for k,v := range packagePathMapI.(map[string]interface{}) { packagePathMap[k]=v.(string) } } } var newContext *config.Context // If the Config is nil, set the logger to minimal log messages by adding the option if Config == nil { newContext = config.NewContext() newContext.SetOption(TEST_MODE_FLAG, fmt.Sprint(true)) } else { // Ensure that the selected runmode appears in app.conf. // If empty string is passed as the mode, treat it as "DEFAULT" if !Config.HasSection(returnMode) { RevelLog.Fatal("app.conf: Run mode not found:","runmode", returnMode) } Config.SetSection(returnMode) newContext = Config } // Only set the testmode flag if it doesnt exist if _, found := newContext.Bool(TEST_MODE_FLAG); !found { newContext.SetOption(TEST_MODE_FLAG, fmt.Sprint(testModeFlag)) } if _, found := newContext.Bool(SPECIAL_USE_FLAG); !found { newContext.SetOption(SPECIAL_USE_FLAG, fmt.Sprint(specialUseFlag)) } appHandle := logger.InitializeFromConfig(BasePath, newContext) // Set all the log handlers setAppLog(AppLog, appHandle) return } // Set the secret key func SetSecretKey(newKey []byte) error { secretKey = newKey return nil } // ResolveImportPath returns the filesystem path for the given import path. // Returns an error if the import path could not be found. func ResolveImportPath(importPath string) (string, error) { if packaged { return filepath.Join(SourcePath, importPath), nil } if path,found := packagePathMap[importPath];found { return path, nil } modPkg, err := build.Import(importPath, RevelPath, build.FindOnly) if err != nil { return "", err } return modPkg.Dir, nil } // CheckInit method checks `revel.Initialized` if not initialized it panics func CheckInit() { if !Initialized { RevelLog.Panic("CheckInit: Revel has not been initialized!") } } // findSrcPaths uses the "go/build" package to find the source root for Revel // and the app. func findSrcPaths(importPath string) (revelSourcePath, appSourcePath string) { if importFsPath,found := packagePathMap[importPath];found { return packagePathMap[RevelImportPath],importFsPath } var ( gopaths = filepath.SplitList(build.Default.GOPATH) goroot = build.Default.GOROOT ) if len(gopaths) == 0 { RevelLog.Fatal("GOPATH environment variable is not set. " + "Please refer to http://golang.org/doc/code.html to configure your Go environment.") } if ContainsString(gopaths, goroot) { RevelLog.Fatalf("GOPATH (%s) must not include your GOROOT (%s). "+ "Please refer to http://golang.org/doc/code.html to configure your Go environment.", gopaths, goroot) } appPkg, err := build.Import(importPath, "", build.FindOnly) if err != nil { RevelLog.Panic("Failed to import "+importPath+" with error:", "error", err) } revelPkg, err := build.Import(RevelImportPath, appPkg.Dir, build.FindOnly) if err != nil { RevelLog.Fatal("Failed to find Revel with error:", "error", err) } return revelPkg.Dir, appPkg.Dir } revel-1.0.0/revel_hooks.go 0000664 0000000 0000000 00000005016 13702523120 0015471 0 ustar 00root root 0000000 0000000 package revel import ( "sort" ) // The list of startup hooks type ( // The startup hooks structure RevelHook struct { order int // The order f func() // The function to call } RevelHooks []RevelHook ) var ( // The local instance of the list startupHooks RevelHooks // The instance of the list shutdownHooks RevelHooks ) // Called to run the hooks func (r RevelHooks) Run() { serverLogger.Infof("There is %d hooks need to run ...", len(r)) sort.Sort(r) for i, hook := range r { utilLog.Infof("Run the %d hook ...", i+1) hook.f() } } // Adds a new function hook, using the order func (r RevelHooks) Add(fn func(), order ...int) RevelHooks { o := 1 if len(order) > 0 { o = order[0] } return append(r, RevelHook{order: o, f: fn}) } // Sorting function func (slice RevelHooks) Len() int { return len(slice) } // Sorting function func (slice RevelHooks) Less(i, j int) bool { return slice[i].order < slice[j].order } // Sorting function func (slice RevelHooks) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[i] } // OnAppStart registers a function to be run at app startup. // // The order you register the functions will be the order they are run. // You can think of it as a FIFO queue. // This process will happen after the config file is read // and before the server is listening for connections. // // Ideally, your application should have only one call to init() in the file init.go. // The reason being that the call order of multiple init() functions in // the same package is undefined. // Inside of init() call revel.OnAppStart() for each function you wish to register. // // Example: // // // from: yourapp/app/controllers/somefile.go // func InitDB() { // // do DB connection stuff here // } // // func FillCache() { // // fill a cache from DB // // this depends on InitDB having been run // } // // // from: yourapp/app/init.go // func init() { // // set up filters... // // // register startup functions // revel.OnAppStart(InitDB) // revel.OnAppStart(FillCache) // } // // This can be useful when you need to establish connections to databases or third-party services, // setup app components, compile assets, or any thing you need to do between starting Revel and accepting connections. // func OnAppStart(f func(), order ...int) { startupHooks = startupHooks.Add(f, order...) } // Called to add a new stop hook func OnAppStop(f func(), order ...int) { shutdownHooks = shutdownHooks.Add(f, order...) } revel-1.0.0/revel_test.go 0000664 0000000 0000000 00000000373 13702523120 0015326 0 ustar 00root root 0000000 0000000 package revel import ( "net/http" ) func NewTestController(w http.ResponseWriter, r *http.Request) *Controller { context := NewGoContext(nil) context.Request.SetRequest(r) context.Response.SetResponse(w) c := NewController(context) return c } revel-1.0.0/router.go 0000664 0000000 0000000 00000070475 13702523120 0014504 0 ustar 00root root 0000000 0000000 // 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" "errors" ) 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 { foundModuleSource = moduleSource controllerNamespace = moduleSource.Namespace() log = log.New("namespace",controllerNamespace) log.Debug("Found module namespace") } else { log.Warnf("splitActionPath: Unable to find module %s for action: %s", controllerNamespace[:len(controllerNamespace)-1], actionPath) } controllerName = controllerName[i+1:] log = log.New("controllerShortName",controllerName) // Check for the type of controller typeOfController = foundModuleSource.ControllerByName(controllerName, methodName) found = typeOfController != nil log.Debug("Found controller","found",found,"type",typeOfController) } 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) { ad, err := router.ReverseError(action,argValues,nil) if err!=nil { routerLog.Error("splitActionPath: Failed to find reverse route", "action", action, "arguments", argValues) } return ad } func (router *Router) ReverseError(action string, argValues map[string]string, req *Request) (ad *ActionDefinition, err error) { var log logger.MultiLogger if req!=nil { log = req.controller.Log.New("action", action) } else { log = routerLog.New("action", action) } pathData, found := splitActionPath(nil, action, true) if !found { log.Error("splitActionPath: Failed to find reverse route", "action", action, "arguments", argValues) return } 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 { log.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName) err = errors.New("Reverse: Controller not found in reverse lookup") return } } } if pathData.TypeOfController == nil { log.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName) err = errors.New("Reverse: Controller not found in reverse lookup") 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 = "" log.Error("Reverse: reverse route missing route argument ", "argument", el[1:]) panic("Check stack") err = errors.New("Missing route arguement") return } 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 } log.Infof("Reversing action %s to %s Using Route %#v",action,urlPath,pathData.Route) ad = &ActionDefinition{ URL: urlPath, Method: method, Star: star, Action: action, Args: argValues, Host: "TODO", } return } routerLog.Error("Reverse: Failed to find controller for reverse route", "action", action, "arguments", argValues) err = errors.New("Reverse: Failed to find controller for reverse route") return } 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) } }) } revel-1.0.0/router_test.go 0000664 0000000 0000000 00000040047 13702523120 0015533 0 ustar 00root root 0000000 0000000 // 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 ( "fmt" "net/http" "net/url" "strings" "testing" ) // Data-driven tests that check that a given routes-file line translates into // the expected Route object. var routeTestCases = map[string]*Route{ "get / Application.Index": { Method: "GET", Path: "/", Action: "Application.Index", FixedParams: []string{}, }, "post /app/:id Application.SaveApp": { Method: "POST", Path: "/app/:id", Action: "Application.SaveApp", FixedParams: []string{}, }, "get /app/ Application.List": { Method: "GET", Path: "/app/", Action: "Application.List", FixedParams: []string{}, }, `get /app/:appId/ Application.Show`: { Method: "GET", Path: `/app/:appId/`, Action: "Application.Show", FixedParams: []string{}, }, `get /app-wild/*appId/ Application.WildShow`: { Method: "GET", Path: `/app-wild/*appId/`, Action: "Application.WildShow", FixedParams: []string{}, }, `GET /public/:filepath Static.Serve("public")`: { Method: "GET", Path: "/public/:filepath", Action: "Static.Serve", FixedParams: []string{ "public", }, }, `GET /javascript/:filepath Static.Serve("public/js")`: { Method: "GET", Path: "/javascript/:filepath", Action: "Static.Serve", FixedParams: []string{ "public", }, }, "* /apps/:id/:action Application.:action": { Method: "*", Path: "/apps/:id/:action", Action: "Application.:action", FixedParams: []string{}, }, "* /:controller/:action :controller.:action": { Method: "*", Path: "/:controller/:action", Action: ":controller.:action", FixedParams: []string{}, }, `GET / Application.Index("Test", "Test2")`: { Method: "GET", Path: "/", Action: "Application.Index", FixedParams: []string{ "Test", "Test2", }, }, } // Run the test cases above. func TestComputeRoute(t *testing.T) { for routeLine, expected := range routeTestCases { method, path, action, fixedArgs, found := parseRouteLine(routeLine) if !found { t.Error("Failed to parse route line:", routeLine) continue } actual := NewRoute(appModule, method, path, action, fixedArgs, "", 0) eq(t, "Method", actual.Method, expected.Method) eq(t, "Path", actual.Path, expected.Path) eq(t, "Action", actual.Action, expected.Action) if t.Failed() { t.Fatal("Failed on route:", routeLine) } } } // Router Tests const TestRoutes = ` # This is a comment GET / Application.Index GET /test/ Application.Index("Test", "Test2") GET /app/:id/ Application.Show GET /app-wild/*id/ Application.WildShow POST /app/:id Application.Save PATCH /app/:id/ Application.Update PROPFIND /app/:id Application.WebDevMethodPropFind MKCOL /app/:id Application.WebDevMethodMkCol COPY /app/:id Application.WebDevMethodCopy MOVE /app/:id Application.WebDevMethodMove PROPPATCH /app/:id Application.WebDevMethodPropPatch LOCK /app/:id Application.WebDevMethodLock UNLOCK /app/:id Application.WebDevMethodUnLock TRACE /app/:id Application.WebDevMethodTrace PURGE /app/:id Application.CacheMethodPurge GET /javascript/:filepath App\Static.Serve("public/js") GET /public/*filepath Static.Serve("public") * /:controller/:action :controller.:action GET /favicon.ico 404 ` var routeMatchTestCases = map[*http.Request]*RouteMatch{ { Method: "GET", URL: &url.URL{Path: "/"}, }: { ControllerName: "application", MethodName: "Index", FixedParams: []string{}, Params: map[string][]string{}, }, { Method: "GET", URL: &url.URL{Path: "/test/"}, }: { ControllerName: "application", MethodName: "Index", FixedParams: []string{"Test", "Test2"}, Params: map[string][]string{}, }, { Method: "GET", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "Show", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "PATCH", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "Update", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "POST", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "Save", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "GET", URL: &url.URL{Path: "/app/123/"}, }: { ControllerName: "application", MethodName: "Show", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "GET", URL: &url.URL{Path: "/public/css/style.css"}, }: { ControllerName: "static", MethodName: "Serve", FixedParams: []string{"public"}, Params: map[string][]string{"filepath": {"css/style.css"}}, }, { Method: "GET", URL: &url.URL{Path: "/javascript/sessvars.js"}, }: { ControllerName: "static", MethodName: "Serve", FixedParams: []string{"public/js"}, Params: map[string][]string{"filepath": {"sessvars.js"}}, }, { Method: "GET", URL: &url.URL{Path: "/Implicit/Route"}, }: { ControllerName: "implicit", MethodName: "Route", FixedParams: []string{}, Params: map[string][]string{ "METHOD": {"GET"}, "controller": {"Implicit"}, "action": {"Route"}, }, }, { Method: "GET", URL: &url.URL{Path: "/favicon.ico"}, }: { ControllerName: "", MethodName: "", Action: "404", FixedParams: []string{}, Params: map[string][]string{}, }, { Method: "POST", URL: &url.URL{Path: "/app/123"}, Header: http.Header{"X-Http-Method-Override": []string{"PATCH"}}, }: { ControllerName: "application", MethodName: "Update", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "GET", URL: &url.URL{Path: "/app/123"}, Header: http.Header{"X-Http-Method-Override": []string{"PATCH"}}, }: { ControllerName: "application", MethodName: "Show", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "PATCH", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "Update", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "PROPFIND", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodPropFind", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "MKCOL", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodMkCol", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "COPY", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodCopy", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "MOVE", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodMove", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "PROPPATCH", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodPropPatch", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "LOCK", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodLock", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "UNLOCK", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodUnLock", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "TRACE", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodTrace", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "PURGE", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "CacheMethodPurge", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, } func TestRouteMatches(t *testing.T) { initControllers() BasePath = "/BasePath" router := NewRouter("") router.Routes, _ = parseRoutes(appModule, "", "", TestRoutes, false) if err := router.updateTree(); err != nil { t.Errorf("updateTree failed: %s", err) } for req, expected := range routeMatchTestCases { t.Log("Routing:", req.Method, req.URL) context := NewGoContext(nil) context.Request.SetRequest(req) c := NewTestController(nil, req) actual := router.Route(c.Request) if !eq(t, "Found route", actual != nil, expected != nil) { continue } if expected.ControllerName != "" { eq(t, "ControllerName", actual.ControllerName, appModule.Namespace()+expected.ControllerName) } else { eq(t, "ControllerName", actual.ControllerName, expected.ControllerName) } eq(t, "MethodName", actual.MethodName, strings.ToLower(expected.MethodName)) eq(t, "len(Params)", len(actual.Params), len(expected.Params)) for key, actualValue := range actual.Params { eq(t, "Params "+key, actualValue[0], expected.Params[key][0]) } eq(t, "len(FixedParams)", len(actual.FixedParams), len(expected.FixedParams)) for i, actualValue := range actual.FixedParams { eq(t, "FixedParams", actualValue, expected.FixedParams[i]) } } } // Reverse Routing type ReverseRouteArgs struct { action string args map[string]string } var reverseRoutingTestCases = map[*ReverseRouteArgs]*ActionDefinition{ { action: "Application.Index", args: map[string]string{}, }: { URL: "/", Method: "GET", Star: false, Action: "Application.Index", }, { action: "Application.Show", args: map[string]string{"id": "123"}, }: { URL: "/app/123/", Method: "GET", Star: false, Action: "Application.Show", }, { action: "Implicit.Route", args: map[string]string{}, }: { URL: "/implicit/route", Method: "GET", Star: true, Action: "Implicit.Route", }, { action: "Application.Save", args: map[string]string{"id": "123", "c": "http://continue"}, }: { URL: "/app/123?c=http%3A%2F%2Fcontinue", Method: "POST", Star: false, Action: "Application.Save", }, { action: "Application.WildShow", args: map[string]string{"id": "123"}, }: { URL: "/app-wild/123/", Method: "GET", Star: false, Action: "Application.WildShow", }, { action: "Application.WebDevMethodPropFind", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "PROPFIND", Star: false, Action: "Application.WebDevMethodPropFind", }, { action: "Application.WebDevMethodMkCol", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "MKCOL", Star: false, Action: "Application.WebDevMethodMkCol", }, { action: "Application.WebDevMethodCopy", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "COPY", Star: false, Action: "Application.WebDevMethodCopy", }, { action: "Application.WebDevMethodMove", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "MOVE", Star: false, Action: "Application.WebDevMethodMove", }, { action: "Application.WebDevMethodPropPatch", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "PROPPATCH", Star: false, Action: "Application.WebDevMethodPropPatch", }, { action: "Application.WebDevMethodLock", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "LOCK", Star: false, Action: "Application.WebDevMethodLock", }, { action: "Application.WebDevMethodUnLock", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "UNLOCK", Star: false, Action: "Application.WebDevMethodUnLock", }, { action: "Application.WebDevMethodTrace", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "TRACE", Star: false, Action: "Application.WebDevMethodTrace", }, { action: "Application.CacheMethodPurge", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "PURGE", Star: false, Action: "Application.CacheMethodPurge", }, } type testController struct { *Controller } func initControllers() { registerControllers() } func TestReverseRouting(t *testing.T) { initControllers() router := NewRouter("") router.Routes, _ = parseRoutes(appModule, "", "", TestRoutes, false) for routeArgs, expected := range reverseRoutingTestCases { actual := router.Reverse(routeArgs.action, routeArgs.args) if !eq(t, fmt.Sprintf("Found route %s %s", routeArgs.action, actual), actual != nil, expected != nil) { continue } eq(t, "Url", actual.URL, expected.URL) eq(t, "Method", actual.Method, expected.Method) eq(t, "Star", actual.Star, expected.Star) eq(t, "Action", actual.Action, expected.Action) } } func BenchmarkRouter(b *testing.B) { router := NewRouter("") router.Routes, _ = parseRoutes(nil, "", "", TestRoutes, false) if err := router.updateTree(); err != nil { b.Errorf("updateTree failed: %s", err) } b.ResetTimer() for i := 0; i < b.N/len(routeMatchTestCases); i++ { for req := range routeMatchTestCases { c := NewTestController(nil, req) r := router.Route(c.Request) if r == nil { b.Errorf("Request not found: %s", req.URL.Path) } } } } // The benchmark from github.com/ant0ine/go-urlrouter func BenchmarkLargeRouter(b *testing.B) { router := NewRouter("") routePaths := []string{ "/", "/signin", "/signout", "/profile", "/settings", "/upload/*file", } for i := 0; i < 10; i++ { for j := 0; j < 5; j++ { routePaths = append(routePaths, fmt.Sprintf("/resource%d/:id/property%d", i, j)) } routePaths = append(routePaths, fmt.Sprintf("/resource%d/:id", i)) routePaths = append(routePaths, fmt.Sprintf("/resource%d", i)) } routePaths = append(routePaths, "/:any") for _, p := range routePaths { router.Routes = append(router.Routes, NewRoute(appModule, "GET", p, "Controller.Action", "", "", 0)) } if err := router.updateTree(); err != nil { b.Errorf("updateTree failed: %s", err) } requestUrls := []string{ "http://example.org/", "http://example.org/resource9/123", "http://example.org/resource9/123/property1", "http://example.org/doesnotexist", } var reqs []*http.Request for _, url := range requestUrls { req, _ := http.NewRequest("GET", url, nil) reqs = append(reqs, req) } b.ResetTimer() for i := 0; i < b.N/len(reqs); i++ { for _, req := range reqs { c := NewTestController(nil, req) route := router.Route(c.Request) if route == nil { b.Errorf("Failed to route: %s", req.URL.Path) } } } } func BenchmarkRouterFilter(b *testing.B) { startFakeBookingApp() controllers := []*Controller{ NewTestController(nil, showRequest), NewTestController(nil, staticRequest), } for _, c := range controllers { c.Params = &Params{} ParseParams(c.Params, c.Request) } b.ResetTimer() for i := 0; i < b.N/len(controllers); i++ { for _, c := range controllers { RouterFilter(c, NilChain) } } } func TestOverrideMethodFilter(t *testing.T) { req, _ := http.NewRequest("POST", "/hotels/3", strings.NewReader("_method=put")) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") c := NewTestController(nil, req) if HTTPMethodOverride(c, NilChain); c.Request.Method != "PUT" { t.Errorf("Expected to override current method '%s' in route, found '%s' instead", "", c.Request.Method) } } // Helpers func eq(t *testing.T, name string, a, b interface{}) bool { if a != b { t.Error(name, ": (actual)", a, " != ", b, "(expected)") return false } return true } revel-1.0.0/server-engine.go 0000664 0000000 0000000 00000014542 13702523120 0015726 0 ustar 00root root 0000000 0000000 // 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 ( "errors" "io" "mime/multipart" "net/url" "strings" "time" ) const ( /* Minimum Engine Type Values */ _ = iota ENGINE_RESPONSE_STATUS ENGINE_WRITER ENGINE_PARAMETERS ENGINE_PATH ENGINE_REQUEST ENGINE_RESPONSE ) const ( /* HTTP Engine Type Values Starts at 1000 */ HTTP_QUERY = ENGINE_PARAMETERS HTTP_PATH = ENGINE_PATH HTTP_BODY = iota + 1000 HTTP_FORM = iota + 1000 HTTP_MULTIPART_FORM = iota + 1000 HTTP_METHOD = iota + 1000 HTTP_REQUEST_URI = iota + 1000 HTTP_REQUEST_CONTEXT = iota + 1000 HTTP_REMOTE_ADDR = iota + 1000 HTTP_HOST = iota + 1000 HTTP_URL = iota + 1000 HTTP_SERVER_HEADER = iota + 1000 HTTP_STREAM_WRITER = iota + 1000 HTTP_WRITER = ENGINE_WRITER ) type ( ServerContext interface { GetRequest() ServerRequest GetResponse() ServerResponse } // Callback ServerRequest type ServerRequest interface { GetRaw() interface{} Get(theType int) (interface{}, error) Set(theType int, theValue interface{}) bool } // Callback ServerResponse type ServerResponse interface { ServerRequest } // Callback WebSocket type ServerWebSocket interface { ServerResponse MessageSendJSON(v interface{}) error MessageReceiveJSON(v interface{}) error MessageSend(v interface{}) error MessageReceive(v interface{}) error } // Expected response for HTTP_SERVER_HEADER type (if implemented) ServerHeader interface { SetCookie(cookie string) // Sets the cookie GetCookie(key string) (value ServerCookie, err error) // Gets the cookie Set(key string, value string) Add(key string, value string) Del(key string) Get(key string) (value []string) GetKeys() (headerKeys []string) SetStatus(statusCode int) } // Expected response for FROM_HTTP_COOKIE type (if implemented) ServerCookie interface { GetValue() string } // Expected response for HTTP_MULTIPART_FORM ServerMultipartForm interface { GetFiles() map[string][]*multipart.FileHeader GetValues() url.Values RemoveAll() error } StreamWriter interface { WriteStream(name string, contentlen int64, modtime time.Time, reader io.Reader) error } ServerEngine interface { // Initialize the server (non blocking) Init(init *EngineInit) // Starts the server. This will block until server is stopped Start() // Fires a new event to the server Event(event Event, args interface{}) EventResponse // Returns the engine instance for specific calls Engine() interface{} // Returns the engine Name Name() string // Returns any stats Stats() map[string]interface{} } // The initialization structure passed into the engine EngineInit struct { Address, // The address Network string // The network Port int // The port HTTPMuxList ServerMuxList // The HTTPMux Callback func(ServerContext) // The ServerContext callback endpoint } // An empty server engine ServerEngineEmpty struct { } // The route handler structure ServerMux struct { PathPrefix string // The path prefix Callback interface{} // The callback interface as appropriate to the server } // A list of handlers used for adding special route functions ServerMuxList []ServerMux ) // Sorting function func (r ServerMuxList) Len() int { return len(r) } // Sorting function func (r ServerMuxList) Less(i, j int) bool { return len(r[i].PathPrefix) > len(r[j].PathPrefix) } // Sorting function func (r ServerMuxList) Swap(i, j int) { r[i], r[j] = r[j], r[i] } // Search function, returns the largest path matching this func (r ServerMuxList) Find(path string) (interface{}, bool) { for _, p := range r { if p.PathPrefix == path || strings.HasPrefix(path, p.PathPrefix) { return p.Callback, true } } return nil, false } // Adds this routehandler to the route table. It will be called (if the path prefix matches) // before the Revel mux, this can only be called after the ENGINE_BEFORE_INITIALIZED event func AddHTTPMux(path string, callback interface{}) { ServerEngineInit.HTTPMuxList = append(ServerEngineInit.HTTPMuxList, ServerMux{PathPrefix: path, Callback: callback}) } // Callback point for the server to handle the func handleInternal(ctx ServerContext) { start := time.Now() var c *Controller if RevelConfig.Controller.Reuse { c = RevelConfig.Controller.Stack.Pop().(*Controller) defer func() { RevelConfig.Controller.Stack.Push(c) }() } else { c = NewControllerEmpty() } var ( req, resp = c.Request, c.Response ) c.SetController(ctx) req.WebSocket, _ = ctx.GetResponse().(ServerWebSocket) clientIP := ClientIP(req) // Once finished in the internal, we can return these to the stack c.ClientIP = clientIP c.Log = AppLog.New("ip", clientIP, "path", req.GetPath(), "method", req.Method) // Call the first filter, this will process the request Filters[0](c, Filters[1:]) if c.Result != nil { c.Result.Apply(req, resp) } else if c.Response.Status != 0 { c.Response.SetStatus(c.Response.Status) } // Close the Writer if we can if w, ok := resp.GetWriter().(io.Closer); ok { _ = w.Close() } // Revel request access log format // RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath // Sample format: terminal format // INFO 2017/08/02 22:31:41 server-engine.go:168: Request Stats ip=::1 path=/public/img/favicon.png method=GET action=Static.Serve namespace=static\\ start=2017/08/02 22:31:41 status=200 duration_seconds=0.0007656 // Recommended storing format to json code which looks like // {"action":"Static.Serve","caller":"server-engine.go:168","duration_seconds":0.00058336,"ip":"::1","lvl":3, // "method":"GET","msg":"Request Stats","namespace":"static\\","path":"/public/img/favicon.png", // "start":"2017-08-02T22:34:08-0700","status":200,"t":"2017-08-02T22:34:08.303112145-07:00"} c.Log.Info("Request Stats", "start", start, "status", c.Response.Status, "duration_seconds", time.Since(start).Seconds(), "section", "requestlog", ) } var ( ENGINE_UNKNOWN_GET = errors.New("Server Engine Invalid Get") ) func (e *ServerEngineEmpty) Get(_ string) interface{} { return nil } func (e *ServerEngineEmpty) Set(_ string, _ interface{}) bool { return false } revel-1.0.0/server.go 0000664 0000000 0000000 00000011700 13702523120 0014454 0 ustar 00root root 0000000 0000000 // 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 ( "fmt" "github.com/revel/revel/session" "os" "strconv" "strings" "github.com/revel/revel/utils" ) // Revel's variables server, router, etc var ( MainRouter *Router MainTemplateLoader *TemplateLoader MainWatcher *Watcher serverEngineMap = map[string]func() ServerEngine{} CurrentEngine ServerEngine ServerEngineInit *EngineInit serverLogger = RevelLog.New("section", "server") ) func RegisterServerEngine(name string, loader func() ServerEngine) { serverLogger.Debug("RegisterServerEngine: Registered engine ", "name", name) serverEngineMap[name] = loader } // InitServer initializes the server and returns the handler // It can be used as an alternative entry-point if one needs the http handler // to be exposed. E.g. to run on multiple addresses and ports or to set custom // TLS options. func InitServer() { CurrentEngine.Init(ServerEngineInit) initControllerStack() startupHooks.Run() // Load templates MainTemplateLoader = NewTemplateLoader(TemplatePaths) if err := MainTemplateLoader.Refresh(); err != nil { serverLogger.Debug("InitServer: Main template loader failed to refresh", "error", err) } // The "watch" config variable can turn on and off all watching. // (As a convenient way to control it all together.) if Config.BoolDefault("watch", true) { MainWatcher = NewWatcher() Filters = append([]Filter{WatchFilter}, Filters...) } // If desired (or by default), create a watcher for templates and routes. // The watcher calls Refresh() on things on the first request. if MainWatcher != nil && Config.BoolDefault("watch.templates", true) { MainWatcher.Listen(MainTemplateLoader, MainTemplateLoader.paths...) } } // Run the server. // This is called from the generated main file. // If port is non-zero, use that. Else, read the port from app.conf. func Run(port int) { defer func() { if r := recover(); r != nil { RevelLog.Crit("Recovered error in startup", "error", r) RaiseEvent(REVEL_FAILURE, r) panic("Fatal error in startup") } }() // Initialize the session logger, must be initiated from this app to avoid // circular references session.InitSession(RevelLog) // Create the CurrentEngine instance from the application config InitServerEngine(port, Config.StringDefault("server.engine", GO_NATIVE_SERVER_ENGINE)) RaiseEvent(ENGINE_BEFORE_INITIALIZED, nil) InitServer() RaiseEvent(ENGINE_STARTED, nil) // This is needed for the harness to recognize that the server is started, it looks for the word // "Listening" in the stdout stream fmt.Fprintf(os.Stdout, "Revel engine is listening on.. %s\n", ServerEngineInit.Address) // Start never returns, CurrentEngine.Start() fmt.Fprintf(os.Stdout, "Revel engine is NOT listening on.. %s\n", ServerEngineInit.Address) RaiseEvent(ENGINE_SHUTDOWN, nil) shutdownHooks.Run() println("\nRevel exited normally\n") } // Build an engine initialization object and start the engine func InitServerEngine(port int, serverEngine string) { address := HTTPAddr if address == "" { address = "localhost" } if port == 0 { port = HTTPPort } var network = "tcp" var localAddress string // If the port is zero, treat the address as a fully qualified local address. // This address must be prefixed with the network type followed by a colon, // e.g. unix:/tmp/app.socket or tcp6:::1 (equivalent to tcp6:0:0:0:0:0:0:0:1) if port == 0 { parts := strings.SplitN(address, ":", 2) network = parts[0] localAddress = parts[1] } else { localAddress = address + ":" + strconv.Itoa(port) } if engineLoader, ok := serverEngineMap[serverEngine]; !ok { panic("Server Engine " + serverEngine + " Not found") } else { CurrentEngine = engineLoader() serverLogger.Debug("InitServerEngine: Found server engine and invoking", "name", CurrentEngine.Name()) ServerEngineInit = &EngineInit{ Address: localAddress, Network: network, Port: port, Callback: handleInternal, } } AddInitEventHandler(CurrentEngine.Event) } // Initialize the controller stack for the application func initControllerStack() { RevelConfig.Controller.Reuse = Config.BoolDefault("revel.controller.reuse",true) if RevelConfig.Controller.Reuse { RevelConfig.Controller.Stack = utils.NewStackLock( Config.IntDefault("revel.controller.stack", 10), Config.IntDefault("revel.controller.maxstack", 200), func() interface{} { return NewControllerEmpty() }) RevelConfig.Controller.CachedStackSize = Config.IntDefault("revel.cache.controller.stack", 10) RevelConfig.Controller.CachedStackMaxSize = Config.IntDefault("revel.cache.controller.maxstack", 100) RevelConfig.Controller.CachedMap = map[string]*utils.SimpleLockStack{} } } // Called to stop the server func StopServer(value interface{}) EventResponse { return RaiseEvent(ENGINE_SHUTDOWN_REQUEST,value) } revel-1.0.0/server_adapter_go.go 0000664 0000000 0000000 00000040776 13702523120 0016660 0 ustar 00root root 0000000 0000000 package revel import ( "net" "net/http" "time" "context" "golang.org/x/net/websocket" "io" "mime/multipart" "net/url" "os" "os/signal" "path" "sort" "strconv" "strings" "github.com/revel/revel/utils" ) // Register the GoHttpServer engine func init() { AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) { if typeOf == REVEL_BEFORE_MODULES_LOADED { RegisterServerEngine(GO_NATIVE_SERVER_ENGINE, func() ServerEngine { return &GoHttpServer{} }) } return }) } // The Go HTTP server type GoHttpServer struct { Server *http.Server // The server instance ServerInit *EngineInit // The server engine initialization MaxMultipartSize int64 // The largest size of file to accept goContextStack *utils.SimpleLockStack // The context stack Set via server.context.stack, server.context.maxstack goMultipartFormStack *utils.SimpleLockStack // The multipart form stack set via server.form.stack, server.form.maxstack HttpMuxList ServerMuxList HasAppMux bool signalChan chan os.Signal } // Called to initialize the server with this EngineInit func (g *GoHttpServer) Init(init *EngineInit) { g.MaxMultipartSize = int64(Config.IntDefault("server.request.max.multipart.filesize", 32)) << 20 /* 32 MB */ g.goContextStack = utils.NewStackLock(Config.IntDefault("server.context.stack", 100), Config.IntDefault("server.context.maxstack", 200), func() interface{} { return NewGoContext(g) }) g.goMultipartFormStack = utils.NewStackLock(Config.IntDefault("server.form.stack", 100), Config.IntDefault("server.form.maxstack", 200), func() interface{} { return &GoMultipartForm{} }) g.ServerInit = init revelHandler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { g.Handle(writer, request) }) // Adds the mux list g.HttpMuxList = init.HTTPMuxList sort.Sort(g.HttpMuxList) g.HasAppMux = len(g.HttpMuxList) > 0 g.signalChan = make(chan os.Signal) g.Server = &http.Server{ Addr: init.Address, Handler: revelHandler, ReadTimeout: time.Duration(Config.IntDefault("http.timeout.read", 0)) * time.Second, WriteTimeout: time.Duration(Config.IntDefault("http.timeout.write", 0)) * time.Second, } } // Handler is assigned in the Init func (g *GoHttpServer) Start() { go func() { time.Sleep(100 * time.Millisecond) serverLogger.Debugf("Start: Listening on %s...", g.Server.Addr) }() if HTTPSsl { if g.ServerInit.Network != "tcp" { // This limitation is just to reduce complexity, since it is standard // to terminate SSL upstream when using unix domain sockets. serverLogger.Fatal("SSL is only supported for TCP sockets. Specify a port to listen on.") } serverLogger.Fatal("Failed to listen:", "error", g.Server.ListenAndServeTLS(HTTPSslCert, HTTPSslKey)) } else { listener, err := net.Listen(g.ServerInit.Network, g.Server.Addr) if err != nil { serverLogger.Fatal("Failed to listen:", "error", err) } serverLogger.Warn("Server exiting:", "error", g.Server.Serve(listener)) } } // Handle the request and response for the server func (g *GoHttpServer) Handle(w http.ResponseWriter, r *http.Request) { // This section is called if the developer has added custom mux to the app if g.HasAppMux && g.handleAppMux(w, r) { return } g.handleMux(w, r) } // Handle the request and response for the servers mux func (g *GoHttpServer) handleAppMux(w http.ResponseWriter, r *http.Request) bool { // Check the prefix and split them requestPath := path.Clean(r.URL.Path) if handler, hasHandler := g.HttpMuxList.Find(requestPath); hasHandler { clientIP := HttpClientIP(r) localLog := AppLog.New("ip", clientIP, "path", r.URL.Path, "method", r.Method) defer func() { if err := recover(); err != nil { localLog.Error("An error was caught using the handler", "path", requestPath, "error", err) w.WriteHeader(http.StatusInternalServerError) } }() start := time.Now() handler.(http.HandlerFunc)(w, r) localLog.Info("Request Stats", "start", start, "duration_seconds", time.Since(start).Seconds(), "section", "requestlog", ) return true } return false } // Passes the server request to Revel func (g *GoHttpServer) handleMux(w http.ResponseWriter, r *http.Request) { if maxRequestSize := int64(Config.IntDefault("http.maxrequestsize", 0)); maxRequestSize > 0 { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) } upgrade := r.Header.Get("Upgrade") context := g.goContextStack.Pop().(*GoContext) defer func() { g.goContextStack.Push(context) }() context.Request.SetRequest(r) context.Response.SetResponse(w) if upgrade == "websocket" || upgrade == "Websocket" { websocket.Handler(func(ws *websocket.Conn) { //Override default Read/Write timeout with sane value for a web socket request if err := ws.SetDeadline(time.Now().Add(time.Hour * 24)); err != nil { serverLogger.Error("SetDeadLine failed:", err) } r.Method = "WS" context.Request.WebSocket = ws context.WebSocket = &GoWebSocket{Conn: ws, GoResponse: *context.Response} g.ServerInit.Callback(context) }).ServeHTTP(w, r) } else { g.ServerInit.Callback(context) } } // ClientIP method returns client IP address from HTTP request. // // Note: Set property "app.behind.proxy" to true only if Revel is running // behind proxy like nginx, haproxy, apache, etc. Otherwise // you may get inaccurate Client IP address. Revel parses the // IP address in the order of X-Forwarded-For, X-Real-IP. // // By default revel will get http.Request's RemoteAddr func HttpClientIP(r *http.Request) string { if Config.BoolDefault("app.behind.proxy", false) { // Header X-Forwarded-For if fwdFor := strings.TrimSpace(r.Header.Get(HdrForwardedFor)); fwdFor != "" { index := strings.Index(fwdFor, ",") if index == -1 { return fwdFor } return fwdFor[:index] } // Header X-Real-Ip if realIP := strings.TrimSpace(r.Header.Get(HdrRealIP)); realIP != "" { return realIP } } if remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { return remoteAddr } return "" } // The server key name const GO_NATIVE_SERVER_ENGINE = "go" // Returns the name of this engine func (g *GoHttpServer) Name() string { return GO_NATIVE_SERVER_ENGINE } // Returns stats for this engine func (g *GoHttpServer) Stats() map[string]interface{} { return map[string]interface{}{ "Go Engine Context": g.goContextStack.String(), "Go Engine Forms": g.goMultipartFormStack.String(), } } // Return the engine instance func (g *GoHttpServer) Engine() interface{} { return g.Server } // Handles an event from Revel func (g *GoHttpServer) Event(event Event, args interface{}) (r EventResponse) { switch event { case ENGINE_STARTED: signal.Notify(g.signalChan, os.Interrupt, os.Kill) go func() { _ = <-g.signalChan serverLogger.Info("Received quit singal Please wait ... ") RaiseEvent(ENGINE_SHUTDOWN_REQUEST, nil) }() case ENGINE_SHUTDOWN_REQUEST: ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(Config.IntDefault("app.cancel.timeout", 60))) defer cancel() g.Server.Shutdown(ctx) default: } return } type ( // The go context GoContext struct { Request *GoRequest // The request Response *GoResponse // The response WebSocket *GoWebSocket // The websocket } // The go request GoRequest struct { Original *http.Request // The original FormParsed bool // True if form parsed MultiFormParsed bool // True if multipart form parsed WebSocket *websocket.Conn // The websocket ParsedForm *GoMultipartForm // The parsed form data Goheader *GoHeader // The header Engine *GoHttpServer // THe server } // The response GoResponse struct { Original http.ResponseWriter // The original writer Goheader *GoHeader // The header Writer io.Writer // The writer Request *GoRequest // The request Engine *GoHttpServer // The engine } // The multipart form GoMultipartForm struct { Form *multipart.Form // The form } // The go header GoHeader struct { Source interface{} // The source isResponse bool // True if response header } // The websocket GoWebSocket struct { Conn *websocket.Conn // The connection GoResponse // The response } // The cookie GoCookie http.Cookie ) // Create a new go context func NewGoContext(instance *GoHttpServer) *GoContext { // This bit in here is for the test cases, which pass in a nil value if instance == nil { instance = &GoHttpServer{MaxMultipartSize: 32 << 20} instance.goContextStack = utils.NewStackLock(100, 200, func() interface{} { return NewGoContext(instance) }) instance.goMultipartFormStack = utils.NewStackLock(100, 200, func() interface{} { return &GoMultipartForm{} }) } c := &GoContext{Request: &GoRequest{Goheader: &GoHeader{}, Engine: instance}} c.Response = &GoResponse{Goheader: &GoHeader{}, Request: c.Request, Engine: instance} return c } // get the request func (c *GoContext) GetRequest() ServerRequest { return c.Request } // Get the response func (c *GoContext) GetResponse() ServerResponse { if c.WebSocket != nil { return c.WebSocket } return c.Response } // Destroy the context func (c *GoContext) Destroy() { c.Response.Destroy() c.Request.Destroy() if c.WebSocket != nil { c.WebSocket.Destroy() c.WebSocket = nil } } // Communicate with the server engine to exchange data func (r *GoRequest) Get(key int) (value interface{}, err error) { switch key { case HTTP_SERVER_HEADER: value = r.GetHeader() case HTTP_MULTIPART_FORM: value, err = r.GetMultipartForm() case HTTP_QUERY: value = r.Original.URL.Query() case HTTP_FORM: value, err = r.GetForm() case HTTP_REQUEST_URI: value = r.Original.URL.String() case HTTP_REQUEST_CONTEXT: value = r.Original.Context() case HTTP_REMOTE_ADDR: value = r.Original.RemoteAddr case HTTP_METHOD: value = r.Original.Method case HTTP_PATH: value = r.Original.URL.Path case HTTP_HOST: value = r.Original.Host case HTTP_URL: value = r.Original.URL case HTTP_BODY: value = r.Original.Body default: err = ENGINE_UNKNOWN_GET } return } // Sets the request key with value func (r *GoRequest) Set(key int, value interface{}) bool { return false } // Returns the form func (r *GoRequest) GetForm() (url.Values, error) { if !r.FormParsed { if e := r.Original.ParseForm(); e != nil { return nil, e } r.FormParsed = true } return r.Original.Form, nil } // Returns the form func (r *GoRequest) GetMultipartForm() (ServerMultipartForm, error) { if !r.MultiFormParsed { if e := r.Original.ParseMultipartForm(r.Engine.MaxMultipartSize); e != nil { return nil, e } r.ParsedForm = r.Engine.goMultipartFormStack.Pop().(*GoMultipartForm) r.ParsedForm.Form = r.Original.MultipartForm } return r.ParsedForm, nil } // Returns the header func (r *GoRequest) GetHeader() ServerHeader { return r.Goheader } // Returns the raw value func (r *GoRequest) GetRaw() interface{} { return r.Original } // Sets the request func (r *GoRequest) SetRequest(req *http.Request) { r.Original = req r.Goheader.Source = r r.Goheader.isResponse = false } // Destroy the request func (r *GoRequest) Destroy() { r.Goheader.Source = nil r.Original = nil r.FormParsed = false r.MultiFormParsed = false r.ParsedForm = nil } // Gets the key from the response func (r *GoResponse) Get(key int) (value interface{}, err error) { switch key { case HTTP_SERVER_HEADER: value = r.Header() case HTTP_STREAM_WRITER: value = r case HTTP_WRITER: value = r.Writer default: err = ENGINE_UNKNOWN_GET } return } // Sets the key with the value func (r *GoResponse) Set(key int, value interface{}) (set bool) { switch key { case ENGINE_RESPONSE_STATUS: r.Header().SetStatus(value.(int)) set = true case HTTP_WRITER: r.SetWriter(value.(io.Writer)) set = true } return } // Sets the header func (r *GoResponse) Header() ServerHeader { return r.Goheader } // Gets the original response func (r *GoResponse) GetRaw() interface{} { return r.Original } // Sets the writer func (r *GoResponse) SetWriter(writer io.Writer) { r.Writer = writer } // Write output to stream func (r *GoResponse) WriteStream(name string, contentlen int64, modtime time.Time, reader io.Reader) error { // Check to see if the output stream is modified, if not send it using the // Native writer written := false if _, ok := r.Writer.(http.ResponseWriter); ok { if rs, ok := reader.(io.ReadSeeker); ok { http.ServeContent(r.Original, r.Request.Original, name, modtime, rs) written = true } } if !written { // Else, do a simple io.Copy. ius := r.Request.Original.Header.Get("If-Unmodified-Since") if t, err := http.ParseTime(ius); err == nil && !modtime.IsZero() { // The Date-Modified header truncates sub-second precision, so // use mtime < t+1s instead of mtime <= t to check for unmodified. if modtime.Before(t.Add(1 * time.Second)) { h := r.Original.Header() delete(h, "Content-Type") delete(h, "Content-Length") if h.Get("Etag") != "" { delete(h, "Last-Modified") } r.Original.WriteHeader(http.StatusNotModified) return nil } } if contentlen != -1 { header := ServerHeader(r.Goheader) if writer, found := r.Writer.(*CompressResponseWriter); found { header = ServerHeader(writer.Header) } header.Set("Content-Length", strconv.FormatInt(contentlen, 10)) } if _, err := io.Copy(r.Writer, reader); err != nil { r.Original.WriteHeader(http.StatusInternalServerError) return err } } return nil } // Frees response func (r *GoResponse) Destroy() { if c, ok := r.Writer.(io.Closer); ok { c.Close() } r.Goheader.Source = nil r.Original = nil r.Writer = nil } // Sets the response func (r *GoResponse) SetResponse(w http.ResponseWriter) { r.Original = w r.Writer = w r.Goheader.Source = r r.Goheader.isResponse = true } // Sets the cookie func (r *GoHeader) SetCookie(cookie string) { if r.isResponse { r.Source.(*GoResponse).Original.Header().Add("Set-Cookie", cookie) } } // Gets the cookie func (r *GoHeader) GetCookie(key string) (value ServerCookie, err error) { if !r.isResponse { var cookie *http.Cookie if cookie, err = r.Source.(*GoRequest).Original.Cookie(key); err == nil { value = GoCookie(*cookie) } } return } // Sets (replaces) header key func (r *GoHeader) Set(key string, value string) { if r.isResponse { r.Source.(*GoResponse).Original.Header().Set(key, value) } } // Adds the header key func (r *GoHeader) Add(key string, value string) { if r.isResponse { r.Source.(*GoResponse).Original.Header().Add(key, value) } } // Deletes the header key func (r *GoHeader) Del(key string) { if r.isResponse { r.Source.(*GoResponse).Original.Header().Del(key) } } // Gets the header key func (r *GoHeader) Get(key string) (value []string) { if !r.isResponse { value = r.Source.(*GoRequest).Original.Header[key] if len(value) == 0 { if ihead := r.Source.(*GoRequest).Original.Header.Get(key); ihead != "" { value = append(value, ihead) } } } else { value = r.Source.(*GoResponse).Original.Header()[key] } return } // Returns list of header keys func (r *GoHeader) GetKeys() (value []string) { if !r.isResponse { for key := range r.Source.(*GoRequest).Original.Header { value = append(value, key) } } else { for key := range r.Source.(*GoResponse).Original.Header() { value = append(value, key) } } return } // Sets the status of the header func (r *GoHeader) SetStatus(statusCode int) { if r.isResponse { r.Source.(*GoResponse).Original.WriteHeader(statusCode) } } // Return cookies value func (r GoCookie) GetValue() string { return r.Value } // Return files from the form func (f *GoMultipartForm) GetFiles() map[string][]*multipart.FileHeader { return f.Form.File } // Return values from the form func (f *GoMultipartForm) GetValues() url.Values { return url.Values(f.Form.Value) } // Remove all values from the form freeing memory func (f *GoMultipartForm) RemoveAll() error { return f.Form.RemoveAll() } /** * Message send JSON */ func (g *GoWebSocket) MessageSendJSON(v interface{}) error { return websocket.JSON.Send(g.Conn, v) } /** * Message receive JSON */ func (g *GoWebSocket) MessageReceiveJSON(v interface{}) error { return websocket.JSON.Receive(g.Conn, v) } /** * Message Send */ func (g *GoWebSocket) MessageSend(v interface{}) error { return websocket.Message.Send(g.Conn, v) } /** * Message receive */ func (g *GoWebSocket) MessageReceive(v interface{}) error { return websocket.Message.Receive(g.Conn, v) } revel-1.0.0/server_test.go 0000664 0000000 0000000 00000007304 13702523120 0015520 0 ustar 00root root 0000000 0000000 // 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 ( "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" ) // This tries to benchmark the usual request-serving pipeline to get an overall // performance metric. // // Each iteration runs one mock request to display a hotel's detail page by id. // // Contributing parts: // - Routing // - Controller lookup / invocation // - Parameter binding // - Session, flash, i18n cookies // - Render() call magic // - Template rendering func BenchmarkServeAction(b *testing.B) { benchmarkRequest(b, showRequest) } func BenchmarkServeJson(b *testing.B) { benchmarkRequest(b, jsonRequest) } func BenchmarkServePlaintext(b *testing.B) { benchmarkRequest(b, plaintextRequest) } // This tries to benchmark the static serving overhead when serving an "average // size" 7k file. func BenchmarkServeStatic(b *testing.B) { benchmarkRequest(b, staticRequest) } func benchmarkRequest(b *testing.B, req *http.Request) { startFakeBookingApp() b.ResetTimer() resp := httptest.NewRecorder() for i := 0; i < b.N; i++ { CurrentEngine.(*GoHttpServer).Handle(resp, req) } } // Test that the booking app can be successfully run for a test. func TestFakeServer(t *testing.T) { startFakeBookingApp() resp := httptest.NewRecorder() // First, test that the expected responses are actually generated CurrentEngine.(*GoHttpServer).Handle(resp, showRequest) if !strings.Contains(resp.Body.String(), "300 Main St.") { t.Errorf("Failed to find hotel address in action response:\n%s", resp.Body) t.FailNow() } resp.Body.Reset() CurrentEngine.(*GoHttpServer).Handle(resp, staticRequest) sessvarsSize := getFileSize(t, filepath.Join(BasePath, "public", "js", "sessvars.js")) if int64(resp.Body.Len()) != sessvarsSize { t.Errorf("Expected sessvars.js to have %d bytes, got %d:\n%s", sessvarsSize, resp.Body.Len(), resp.Body) t.FailNow() } resp.Body.Reset() CurrentEngine.(*GoHttpServer).Handle(resp, jsonRequest) if !strings.Contains(resp.Body.String(), `"Address":"300 Main St."`) { t.Errorf("Failed to find hotel address in JSON response:\n%s", resp.Body) t.FailNow() } resp.Body.Reset() CurrentEngine.(*GoHttpServer).Handle(resp, plaintextRequest) if resp.Body.String() != "Hello, World!" { t.Errorf("Failed to find greeting in plaintext response:\n%s", resp.Body) t.FailNow() } resp.Body = nil } func getFileSize(t *testing.T, name string) int64 { fi, err := os.Stat(name) if err != nil { t.Errorf("Unable to stat file:\n%s", name) t.FailNow() } return fi.Size() } // Ensure on app start runs in order func TestOnAppStart(t *testing.T) { str := "" a := assert.New(t) OnAppStart(func() { str += " World" }, 2) OnAppStart(func() { str += "Hello" }, 1) startFakeBookingApp() a.Equal("Hello World", str, "Failed to order OnAppStart") } // Ensure on app stop runs in order func TestOnAppStop(t *testing.T) { a := assert.New(t) startFakeBookingApp() i := "" OnAppStop(func() { i += "cruel world" t.Logf("i: %v \n", i) }, 2) OnAppStop(func() { i += "goodbye " t.Logf("i: %v \n", i) }, 1) go func() { time.Sleep(2 * time.Second) RaiseEvent(ENGINE_SHUTDOWN_REQUEST, nil) }() Run(0) a.Equal("goodbye cruel world", i, "Did not get shutdown events") } var ( showRequest, _ = http.NewRequest("GET", "/hotels/3", nil) staticRequest, _ = http.NewRequest("GET", "/public/js/sessvars.js", nil) jsonRequest, _ = http.NewRequest("GET", "/hotels/3/booking", nil) plaintextRequest, _ = http.NewRequest("GET", "/hotels", nil) ) revel-1.0.0/session/ 0000775 0000000 0000000 00000000000 13702523120 0014303 5 ustar 00root root 0000000 0000000 revel-1.0.0/session/init.go 0000664 0000000 0000000 00000000341 13702523120 0015573 0 ustar 00root root 0000000 0000000 package session // The logger for the session import "github.com/revel/revel/logger" var sessionLog logger.MultiLogger func InitSession(coreLogger logger.MultiLogger) { sessionLog = coreLogger.New("section", "session") } revel-1.0.0/session/session.go 0000664 0000000 0000000 00000024371 13702523120 0016324 0 ustar 00root root 0000000 0000000 // 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 session import ( "encoding/hex" "encoding/json" "errors" "github.com/twinj/uuid" "reflect" "strconv" "strings" "time" ) const ( // The key for the identity of the session SessionIDKey = "_ID" // The expiration date of the session TimestampKey = "_TS" // The value name indicating how long the session should persist - ie should it persist after the browser closes // this is set under the TimestampKey if the session data should expire immediately SessionValueName = "session" // The key container for the json objects of the data, any non strings found in the map will be placed in here // serialized by key using JSON SessionObjectKeyName = "_object_" // The mapped session object SessionMapKeyName = "_map_" // The suffix of the session cookie SessionCookieSuffix = "_SESSION" ) // Session data, can be any data, there are reserved keywords used by the storage data // SessionIDKey Is the key name for the session // TimestampKey Is the time that the session should expire // type Session map[string]interface{} func NewSession() Session { return Session{} } // ID retrieves from the cookie or creates a time-based UUID identifying this // session. func (s Session) ID() string { if sessionIDStr, ok := s[SessionIDKey]; ok { return sessionIDStr.(string) } buffer := uuid.NewV4() s[SessionIDKey] = hex.EncodeToString(buffer.Bytes()) return s[SessionIDKey].(string) } // getExpiration return a time.Time with the session's expiration date. // It uses the passed in expireAfterDuration to add with the current time if the timeout is not // browser dependent (ie session). If previous session has set to "session", the time returned is time.IsZero() func (s Session) GetExpiration(expireAfterDuration time.Duration) time.Time { if expireAfterDuration == 0 || s[TimestampKey] == SessionValueName { // Expire after closing browser return time.Time{} } return time.Now().Add(expireAfterDuration) } // SetNoExpiration sets session to expire when browser session ends func (s Session) SetNoExpiration() { s[TimestampKey] = SessionValueName } // SetDefaultExpiration sets session to expire after default duration func (s Session) SetDefaultExpiration() { delete(s, TimestampKey) } // sessionTimeoutExpiredOrMissing returns a boolean of whether the session // cookie is either not present or present but beyond its time to live; i.e., // whether there is not a valid session. func (s Session) SessionTimeoutExpiredOrMissing() bool { if exp, present := s[TimestampKey]; !present { return true } else if exp == SessionValueName { return false } else if expInt, _ := strconv.Atoi(exp.(string)); int64(expInt) < time.Now().Unix() { return true } return false } // Constant error if session value is not found var SESSION_VALUE_NOT_FOUND = errors.New("Session value not found") // Get an object or property from the session // it may be embedded inside the session. func (s Session) Get(key string) (newValue interface{}, err error) { // First check to see if it is in the session if v, found := s[key]; found { return v, nil } return s.GetInto(key, nil, false) } // Get into the specified value. // If value exists in the session it will just return the value func (s Session) GetInto(key string, target interface{}, force bool) (result interface{}, err error) { if v, found := s[key]; found && !force { return v, nil } splitKey := strings.Split(key, ".") rootKey := splitKey[0] // Force always recreates the object from the session data map if force { if target == nil { if result, err = s.sessionDataFromMap(key); err != nil { return } } else if result, err = s.sessionDataFromObject(rootKey, target); err != nil { return } return s.getNestedProperty(splitKey, result) } // Attempt to find the key in the session, this is the most generalized form v, found := s[rootKey] if !found { if target == nil { // Try to fetch it from the session if v, err = s.sessionDataFromMap(rootKey); err != nil { return } } else if v, err = s.sessionDataFromObject(rootKey, target); err != nil { return } } return s.getNestedProperty(splitKey, v) } // Returns the default value if the key is not found func (s Session) GetDefault(key string, value interface{}, defaultValue interface{}) interface{} { v, e := s.GetInto(key, value, false) if e != nil { v = defaultValue } return v } // Extract the values from the session func (s Session) GetProperty(key string, value interface{}) (interface{}, error) { // Capitalize the first letter key = strings.Title(key) sessionLog.Info("getProperty", "key", key, "value", value) // For a map it is easy if reflect.TypeOf(value).Kind() == reflect.Map { val := reflect.ValueOf(value) valueOf := val.MapIndex(reflect.ValueOf(key)) if valueOf == reflect.Zero(reflect.ValueOf(value).Type()) { return nil, nil } //idx := val.MapIndex(reflect.ValueOf(key)) if !valueOf.IsValid() { return nil, nil } return valueOf.Interface(), nil } objValue := s.reflectValue(value) field := objValue.FieldByName(key) if !field.IsValid() { return nil, SESSION_VALUE_NOT_FOUND } return field.Interface(), nil } // Places the object into the session, a nil value will cause remove the key from the session // (or you can use the Session.Del(key) function func (s Session) Set(key string, value interface{}) error { if value == nil { s.Del(key) return nil } s[key] = value return nil } // Delete the key from the sessionObjects and Session func (s Session) Del(key string) { sessionJsonMap := s.getSessionJsonMap() delete(sessionJsonMap, key) delete(s, key) } // Extracts the session as a map of [string keys] and json values func (s Session) getSessionJsonMap() map[string]string { if sessionJson, found := s[SessionObjectKeyName]; found { if _, valid := sessionJson.(map[string]string); !valid { sessionLog.Error("Session object key corrupted, reset", "was", sessionJson) s[SessionObjectKeyName] = map[string]string{} } // serialized data inside the session _objects } else { s[SessionObjectKeyName] = map[string]string{} } return s[SessionObjectKeyName].(map[string]string) } // Convert the map to a simple map[string]string map // this will marshal any non string objects encountered and store them the the jsonMap // The expiration time will also be assigned func (s Session) Serialize() map[string]string { sessionJsonMap := s.getSessionJsonMap() newMap := map[string]string{} newObjectMap := map[string]string{} for key, value := range sessionJsonMap { newObjectMap[key] = value } for key, value := range s { if key == SessionObjectKeyName || key == SessionMapKeyName { continue } if reflect.ValueOf(value).Kind() == reflect.String { newMap[key] = value.(string) continue } if data, err := json.Marshal(value); err != nil { sessionLog.Error("Unable to marshal session ", "key", key, "error", err) continue } else { newObjectMap[key] = string(data) } } if len(newObjectMap) > 0 { if data, err := json.Marshal(newObjectMap); err != nil { sessionLog.Error("Unable to marshal session ", "key", SessionObjectKeyName, "error", err) } else { newMap[SessionObjectKeyName] = string(data) } } return newMap } // Set the session object from the loaded data func (s Session) Load(data map[string]string) { for key, value := range data { if key == SessionObjectKeyName { target := map[string]string{} if err := json.Unmarshal([]byte(value), &target); err != nil { sessionLog.Error("Unable to unmarshal session ", "key", SessionObjectKeyName, "error", err) } else { s[key] = target } } else { s[key] = value } } } // Checks to see if the session is empty func (s Session) Empty() bool { i := 0 for k := range s { i++ if k == SessionObjectKeyName || k == SessionMapKeyName { continue } } return i == 0 } func (s *Session) reflectValue(obj interface{}) reflect.Value { var val reflect.Value if reflect.TypeOf(obj).Kind() == reflect.Ptr { val = reflect.ValueOf(obj).Elem() } else { val = reflect.ValueOf(obj) } return val } // Starting at position 1 drill into the object func (s Session) getNestedProperty(keys []string, newValue interface{}) (result interface{}, err error) { for x := 1; x < len(keys); x++ { newValue, err = s.GetProperty(keys[x], newValue) if err != nil || newValue == nil { return newValue, err } } return newValue, nil } // Always converts the data from the session mapped objects into the target, // it will store the results under the session key name SessionMapKeyName func (s Session) sessionDataFromMap(key string) (result interface{}, err error) { var mapValue map[string]interface{} uncastMapValue, found := s[SessionMapKeyName] if !found { mapValue = map[string]interface{}{} s[SessionMapKeyName] = mapValue } else if mapValue, found = uncastMapValue.(map[string]interface{}); !found { // Unusual means that the value in the session was not expected sessionLog.Errorf("Unusual means that the value in the session was not expected", "session", uncastMapValue) mapValue = map[string]interface{}{} s[SessionMapKeyName] = mapValue } // Try to extract the key from the map result, found = mapValue[key] if !found { result, err = s.convertSessionData(key, nil) if err == nil { mapValue[key] = result } } return } // Unpack the object from the session map and store it in the session when done, if no error occurs func (s Session) sessionDataFromObject(key string, newValue interface{}) (result interface{}, err error) { result, err = s.convertSessionData(key, newValue) if err != nil { return } s[key] = result return } // Converts from the session json map into the target, func (s Session) convertSessionData(key string, target interface{}) (result interface{}, err error) { sessionJsonMap := s.getSessionJsonMap() v, found := sessionJsonMap[key] if !found { return target, SESSION_VALUE_NOT_FOUND } // Create a target if needed if target == nil { target = map[string]interface{}{} if err := json.Unmarshal([]byte(v), &target); err != nil { return target, err } } else if err := json.Unmarshal([]byte(v), target); err != nil { return target, err } result = target return } revel-1.0.0/session/session_cookie_test.go 0000664 0000000 0000000 00000004461 13702523120 0020712 0 ustar 00root root 0000000 0000000 // 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 session_test import ( "testing" "github.com/revel/revel" "github.com/revel/revel/session" "github.com/stretchr/testify/assert" "net/http" "time" ) func TestCookieRestore(t *testing.T) { a := assert.New(t) session.InitSession(revel.RevelLog) cse := revel.NewSessionCookieEngine() originSession := session.NewSession() setSharedDataTest(originSession) originSession["foo"] = "foo" originSession["bar"] = "bar" cookie := cse.GetCookie(originSession) if !cookie.Expires.IsZero() { t.Error("incorrect cookie expire", cookie.Expires) } restoredSession := session.NewSession() cse.DecodeCookie(revel.GoCookie(*cookie), restoredSession) a.Equal("foo",restoredSession["foo"]) a.Equal("bar",restoredSession["bar"]) testSharedData(originSession, restoredSession, t, a) } func TestCookieSessionExpire(t *testing.T) { session.InitSession(revel.RevelLog) cse := revel.NewSessionCookieEngine() cse.ExpireAfterDuration = time.Hour session := session.NewSession() session["user"] = "Tom" var cookie *http.Cookie for i := 0; i < 3; i++ { cookie = cse.GetCookie(session) time.Sleep(time.Second) cse.DecodeCookie(revel.GoCookie(*cookie), session) } expectExpire := time.Now().Add(cse.ExpireAfterDuration) if cookie.Expires.Unix() < expectExpire.Add(-time.Second).Unix() { t.Error("expect expires", cookie.Expires, "after", expectExpire.Add(-time.Second)) } if cookie.Expires.Unix() > expectExpire.Unix() { t.Error("expect expires", cookie.Expires, "before", expectExpire) } // Test that the expiration time is zero for a "browser" session session.SetNoExpiration() cookie = cse.GetCookie(session) if !cookie.Expires.IsZero() { t.Error("expect cookie expires is zero") } // Check the default session is set session.SetDefaultExpiration() cookie = cse.GetCookie(session) expectExpire = time.Now().Add(cse.ExpireAfterDuration) if cookie.Expires.Unix() < expectExpire.Add(-time.Second).Unix() { t.Error("expect expires", cookie.Expires, "after", expectExpire.Add(-time.Second)) } if cookie.Expires.Unix() > expectExpire.Unix() { t.Error("expect expires", cookie.Expires, "before", expectExpire) } } revel-1.0.0/session/session_test.go 0000664 0000000 0000000 00000002776 13702523120 0017370 0 ustar 00root root 0000000 0000000 // Copyright (c) 2012-2018 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 session_test import ( "fmt" "github.com/revel/revel" "github.com/revel/revel/session" "github.com/stretchr/testify/assert" "testing" ) // test the commands func TestSessionString(t *testing.T) { session.InitSession(revel.RevelLog) a := assert.New(t) s := session.NewSession() s.Set("happy", "day") a.Equal("day", s.GetDefault("happy", nil, ""), fmt.Sprintf("Session Data %#v\n", s)) } func TestSessionStruct(t *testing.T) { session.InitSession(revel.RevelLog) a := assert.New(t) s := session.NewSession() setSharedDataTest(s) a.Equal("test", s.GetDefault("happy.a.aa", nil, ""), fmt.Sprintf("Session Data %#v\n", s)) stringMap := s.Serialize() s1 := session.NewSession() s1.Load(stringMap) testSharedData(s, s1, t, a) } func setSharedDataTest(s session.Session) { data := struct { A struct { Aa string } B int C string D float32 }{A: struct { Aa string }{Aa: "test"}, B: 5, C: "test", D: -325.25} s.Set("happy", data) } func testSharedData(s, s1 session.Session, t *testing.T, a *assert.Assertions) { // Compress the session to a string t.Logf("Original session %#v\n", s) t.Logf("New built session %#v\n", s1) data,err := s1.Get("happy.a.aa") a.Nil(err,"Expected nil") a.Equal("test", data, fmt.Sprintf("Session Data %#v\n", s)) t.Logf("After test session %#v\n", s1) } revel-1.0.0/session_adapter_cookie.go 0000664 0000000 0000000 00000007661 13702523120 0017675 0 ustar 00root root 0000000 0000000 package revel import ( "fmt" "github.com/revel/revel/session" "net/http" "net/url" "strconv" "strings" "time" ) type ( // The session cookie engine SessionCookieEngine struct { ExpireAfterDuration time.Duration } ) // A logger for the session engine var sessionEngineLog = RevelLog.New("section", "session-engine") // Create a new instance to test func init() { RegisterSessionEngine(initCookieEngine, "revel-cookie") } // For testing purposes this engine is used func NewSessionCookieEngine() *SessionCookieEngine { ce := &SessionCookieEngine{} return ce } // Called when the the application starts, retrieves data from the app config so cannot be run until then func initCookieEngine() SessionEngine { ce := &SessionCookieEngine{} var err error if expiresString, ok := Config.String("session.expires"); !ok { ce.ExpireAfterDuration = 30 * 24 * time.Hour } else if expiresString == session.SessionValueName { ce.ExpireAfterDuration = 0 } else if ce.ExpireAfterDuration, err = time.ParseDuration(expiresString); err != nil { panic(fmt.Errorf("session.expires invalid: %s", err)) } return ce } // Decode the session information from the cookie retrieved from the controller request func (cse *SessionCookieEngine) Decode(c *Controller) { // Decode the session from a cookie. c.Session = map[string]interface{}{} sessionMap := c.Session if cookie, err := c.Request.Cookie(CookiePrefix + session.SessionCookieSuffix); err != nil { return } else { cse.DecodeCookie(cookie, sessionMap) c.Session = sessionMap } } // Encode the session information to the cookie, set the cookie on the controller func (cse *SessionCookieEngine) Encode(c *Controller) { c.SetCookie(cse.GetCookie(c.Session)) } // Exposed only for testing purposes func (cse *SessionCookieEngine) DecodeCookie(cookie ServerCookie, s session.Session) { // Decode the session from a cookie. // Separate the data from the signature. cookieValue := cookie.GetValue() hyphen := strings.Index(cookieValue, "-") if hyphen == -1 || hyphen >= len(cookieValue)-1 { return } sig, data := cookieValue[:hyphen], cookieValue[hyphen+1:] // Verify the signature. if !Verify(data, sig) { sessionEngineLog.Warn("Session cookie signature failed") return } // Parse the cookie into a temp map, and then load it into the session object tempMap := map[string]string{} ParseKeyValueCookie(data, func(key, val string) { tempMap[key] = val }) s.Load(tempMap) // Check timeout after unpacking values - if timeout missing (or removed) destroy all session // objects if s.SessionTimeoutExpiredOrMissing() { // If this fails we need to delete all the keys from the session for key := range s { delete(s, key) } } } // Convert session to cookie func (cse *SessionCookieEngine) GetCookie(s session.Session) *http.Cookie { var sessionValue string ts := s.GetExpiration(cse.ExpireAfterDuration) if ts.IsZero() { s[session.TimestampKey] = session.SessionValueName } else { s[session.TimestampKey] = strconv.FormatInt(ts.Unix(), 10) } // Convert the key to a string map stringMap := s.Serialize() for key, value := range stringMap { if strings.ContainsAny(key, ":\x00") { panic("Session keys may not have colons or null bytes") } if strings.Contains(value, "\x00") { panic("Session values may not have null bytes") } sessionValue += "\x00" + key + ":" + value + "\x00" } if len(sessionValue) > 1024*4 { sessionEngineLog.Error("SessionCookieEngine.Cookie, session data has exceeded 4k limit (%d) cookie data will not be reliable", "length", len(sessionValue)) } sessionData := url.QueryEscape(sessionValue) sessionCookie := &http.Cookie{ Name: CookiePrefix + session.SessionCookieSuffix, Value: Sign(sessionData) + "-" + sessionData, Domain: CookieDomain, Path: "/", HttpOnly: true, Secure: CookieSecure, SameSite: CookieSameSite, Expires: ts.UTC(), MaxAge: int(cse.ExpireAfterDuration.Seconds()), } return sessionCookie } revel-1.0.0/session_engine.go 0000664 0000000 0000000 00000002022 13702523120 0016153 0 ustar 00root root 0000000 0000000 package revel // The session engine provides an interface to allow for storage of session data type ( SessionEngine interface { Decode(c *Controller) // Called to decode the session information on the controller Encode(c *Controller) // Called to encode the session information on the controller } ) var ( sessionEngineMap = map[string]func() SessionEngine{} CurrentSessionEngine SessionEngine ) // Initialize session engine on startup func init() { OnAppStart(initSessionEngine, 5) } func RegisterSessionEngine(f func() SessionEngine, name string) { sessionEngineMap[name] = f } // Called when application is starting up func initSessionEngine() { // Check for session engine to use and assign it sename := Config.StringDefault("session.engine", "revel-cookie") if se, found := sessionEngineMap[sename]; found { CurrentSessionEngine = se() } else { sessionLog.Warn("Session engine '%s' not found, using default session engine revel-cookie", sename) CurrentSessionEngine = sessionEngineMap["revel-cookie"]() } } revel-1.0.0/session_filter.go 0000664 0000000 0000000 00000001412 13702523120 0016175 0 ustar 00root root 0000000 0000000 package revel // SessionFilter is a Revel Filter that retrieves and sets the session cookie. // Within Revel, it is available as a Session attribute on Controller instances. // The name of the Session cookie is set as CookiePrefix + "_SESSION". import () var sessionLog = RevelLog.New("section", "session") func SessionFilter(c *Controller, fc []Filter) { CurrentSessionEngine.Decode(c) sessionWasEmpty := c.Session.Empty() // Make session vars available in templates as {{.session.xyz}} c.ViewArgs["session"] = c.Session c.ViewArgs["_controller"] = c fc[0](c, fc[1:]) // If session is not empty or if session was not empty then // pass it back to the session engine to be encoded if !c.Session.Empty() || !sessionWasEmpty { CurrentSessionEngine.Encode(c) } } revel-1.0.0/template.go 0000664 0000000 0000000 00000036413 13702523120 0014771 0 ustar 00root root 0000000 0000000 // 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 ( "bufio" "bytes" "fmt" "io" "io/ioutil" "os" "path/filepath" "regexp" "strconv" "strings" "sync" "sync/atomic" ) // ErrorCSSClass httml CSS error class name var ErrorCSSClass = "hasError" // TemplateLoader object handles loading and parsing of templates. // Everything below the application's views directory is treated as a template. type TemplateLoader struct { // Paths to search for templates, in priority order. paths []string // load version seed for templates loadVersionSeed int // A templateRuntime of looked up template results runtimeLoader atomic.Value // Lock to prevent concurrent map writes templateMutex sync.Mutex } type Template interface { // The name of the template. Name() string // Name of template // The content of the template as a string (Used in error handling). Content() []string // Content // Called by the server to render the template out the io.Writer, context contains the view args to be passed to the template. Render(wr io.Writer, context interface{}) error // The full path to the file on the disk. Location() string // Disk location } var invalidSlugPattern = regexp.MustCompile(`[^a-z0-9 _-]`) var whiteSpacePattern = regexp.MustCompile(`\s+`) var templateLog = RevelLog.New("section", "template") // TemplateOutputArgs returns the result of the template rendered using the passed in arguments. func TemplateOutputArgs(templatePath string, args map[string]interface{}) (data []byte, err error) { // Get the Template. lang, _ := args[CurrentLocaleViewArg].(string) template, err := MainTemplateLoader.TemplateLang(templatePath, lang) if err != nil { return nil, err } tr := &RenderTemplateResult{ Template: template, ViewArgs: args, } b, err := tr.ToBytes() if err != nil { return nil, err } return b.Bytes(), nil } func NewTemplateLoader(paths []string) *TemplateLoader { loader := &TemplateLoader{ paths: paths, } return loader } // WatchDir returns true of directory doesn't start with . (dot) // otherwise false func (loader *TemplateLoader) WatchDir(info os.FileInfo) bool { // Watch all directories, except the ones starting with a dot. return !strings.HasPrefix(info.Name(), ".") } // WatchFile returns true of file doesn't start with . (dot) // otherwise false func (loader *TemplateLoader) WatchFile(basename string) bool { // Watch all files, except the ones starting with a dot. return !strings.HasPrefix(basename, ".") } // DEPRECATED Use TemplateLang, will be removed in future release func (loader *TemplateLoader) Template(name string) (tmpl Template, err error) { runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime) return runtimeLoader.TemplateLang(name, "") } func (loader *TemplateLoader) TemplateLang(name, lang string) (tmpl Template, err error) { runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime) return runtimeLoader.TemplateLang(name, lang) } // Refresh method scans the views directory and parses all templates as Go Templates. // If a template fails to parse, the error is set on the loader. // (It's awkward to refresh a single Go Template) func (loader *TemplateLoader) Refresh() (err *Error) { loader.templateMutex.Lock() defer loader.templateMutex.Unlock() loader.loadVersionSeed++ runtimeLoader := &templateRuntime{loader: loader, version: loader.loadVersionSeed, templateMap: map[string]Template{}} templateLog.Debug("Refresh: Refreshing templates from ", "path", loader.paths) if err = loader.initializeEngines(runtimeLoader, Config.StringDefault("template.engines", GO_TEMPLATE)); err != nil { return } for _, engine := range runtimeLoader.templatesAndEngineList { engine.Event(TEMPLATE_REFRESH_REQUESTED, nil) } RaiseEvent(TEMPLATE_REFRESH_REQUESTED, nil) defer func() { for _, engine := range runtimeLoader.templatesAndEngineList { engine.Event(TEMPLATE_REFRESH_COMPLETED, nil) } RaiseEvent(TEMPLATE_REFRESH_COMPLETED, nil) // Reset the runtimeLoader loader.runtimeLoader.Store(runtimeLoader) }() // Resort the paths, make sure the revel path is the last path, // so anything can override it revelTemplatePath := filepath.Join(RevelPath, "templates") // Go through the paths for i, o := range loader.paths { if o == revelTemplatePath && i != len(loader.paths)-1 { loader.paths[i] = loader.paths[len(loader.paths)-1] loader.paths[len(loader.paths)-1] = revelTemplatePath } } templateLog.Debug("Refresh: Refreshing templates from", "path", loader.paths) runtimeLoader.compileError = nil runtimeLoader.TemplatePaths = map[string]string{} for _, basePath := range loader.paths { // Walk only returns an error if the template loader is completely unusable // (namely, if one of the TemplateFuncs does not have an acceptable signature). // Handling symlinked directories var fullSrcDir string f, err := os.Lstat(basePath) if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink { fullSrcDir, err = filepath.EvalSymlinks(basePath) if err != nil { templateLog.Panic("Refresh: Eval symlinks error ", "error", err) } } else { fullSrcDir = basePath } var templateWalker filepath.WalkFunc templateWalker = func(path string, info os.FileInfo, err error) error { if err != nil { templateLog.Error("Refresh: error walking templates:", "error", err) return nil } // Walk into watchable directories if info.IsDir() { if !loader.WatchDir(info) { return filepath.SkipDir } return nil } // Only add watchable if !loader.WatchFile(info.Name()) { return nil } fileBytes, err := runtimeLoader.findAndAddTemplate(path, fullSrcDir, basePath) if err != nil { // Add in this template name to the list of templates unable to be compiled runtimeLoader.compileErrorNameList = append(runtimeLoader.compileErrorNameList, filepath.ToSlash(path[len(fullSrcDir)+1:])) } // Store / report the first error encountered. if err != nil && runtimeLoader.compileError == nil { runtimeLoader.compileError, _ = err.(*Error) if nil == runtimeLoader.compileError { _, line, description := ParseTemplateError(err) runtimeLoader.compileError = &Error{ Title: "Template Compilation Error", Path: path, Description: description, Line: line, SourceLines: strings.Split(string(fileBytes), "\n"), } } templateLog.Errorf("Refresh: Template compilation error (In %s around line %d):\n\t%s", path, runtimeLoader.compileError.Line, err.Error()) } else if nil != err { //&& strings.HasPrefix(templateName, "errors/") { if compileError, ok := err.(*Error); ok { templateLog.Errorf("Template compilation error (In %s around line %d):\n\t%s", path, compileError.Line, err.Error()) } else { templateLog.Errorf("Template compilation error (In %s ):\n\t%s", path, err.Error()) } } return nil } if _, err = os.Lstat(fullSrcDir); os.IsNotExist(err) { // #1058 Given views/template path is not exists // so no need to walk, move on to next path continue } funcErr := Walk(fullSrcDir, templateWalker) // If there was an error with the Funcs, set it and return immediately. if funcErr != nil { runtimeLoader.compileError = NewErrorFromPanic(funcErr) return runtimeLoader.compileError } } // Note: compileError may or may not be set. return runtimeLoader.compileError } type templateRuntime struct { loader *TemplateLoader // load version for templates version int // Template data and implementation templatesAndEngineList []TemplateEngine // If an error was encountered parsing the templates, it is stored here. compileError *Error // A list of the names of the templates with errors compileErrorNameList []string // Map from template name to the path from whence it was loaded. TemplatePaths map[string]string // A map of looked up template results templateMap map[string]Template } // Checks to see if template exists in templatePaths, if so it is skipped (templates are imported in order // reads the template file into memory, replaces namespace keys with module (if found func (runtimeLoader *templateRuntime) findAndAddTemplate(path, fullSrcDir, basePath string) (fileBytes []byte, err error) { templateName := filepath.ToSlash(path[len(fullSrcDir)+1:]) // Convert template names to use forward slashes, even on Windows. if os.PathSeparator == '\\' { templateName = strings.Replace(templateName, `\`, `/`, -1) // ` } // Check to see if template was found if place, found := runtimeLoader.TemplatePaths[templateName]; found { templateLog.Debug("findAndAddTemplate: Not Loading, template is already exists: ", "name", templateName, "old", place, "new", path) return } fileBytes, err = ioutil.ReadFile(path) if err != nil { templateLog.Error("findAndAddTemplate: Failed reading file:", "path", path, "error", err) return } // Parse template file and replace the "_LOCAL_|" in the template with the module name // allow for namespaces to be renamed "_LOCAL_(.*?)|" if module := ModuleFromPath(path, false); module != nil { fileBytes = namespaceReplace(fileBytes, module) } // if we have an engine picked for this template process it now baseTemplate := NewBaseTemplate(templateName, path, basePath, fileBytes) // Try to find a default engine for the file for _, engine := range runtimeLoader.templatesAndEngineList { if engine.Handles(baseTemplate) { _, err = runtimeLoader.loadIntoEngine(engine, baseTemplate) return } } // Try all engines available var defaultError error for _, engine := range runtimeLoader.templatesAndEngineList { if loaded, loaderr := runtimeLoader.loadIntoEngine(engine, baseTemplate); loaded { return } else { templateLog.Debugf("findAndAddTemplate: Engine '%s' unable to compile %s %s", engine.Name(), path, loaderr.Error()) if defaultError == nil { defaultError = loaderr } } } // Assign the error from the first parser err = defaultError // No engines could be found return the err if err == nil { err = fmt.Errorf("Failed to parse template file using engines %s", path) } return } func (runtimeLoader *templateRuntime) loadIntoEngine(engine TemplateEngine, baseTemplate *TemplateView) (loaded bool, err error) { if loadedTemplate, found := runtimeLoader.templateMap[baseTemplate.TemplateName]; found { // Duplicate template found in map templateLog.Debug("template already exists in map: ", baseTemplate.TemplateName, " in engine ", engine.Name(), "\r\n\told file:", loadedTemplate.Location(), "\r\n\tnew file:", baseTemplate.FilePath) return } if loadedTemplate := engine.Lookup(baseTemplate.TemplateName); loadedTemplate != nil { // Duplicate template found for engine templateLog.Debug("loadIntoEngine: template already exists: ", "template", baseTemplate.TemplateName, "inengine ", engine.Name(), "old", loadedTemplate.Location(), "new", baseTemplate.FilePath) loaded = true return } if err = engine.ParseAndAdd(baseTemplate); err == nil { if tmpl := engine.Lookup(baseTemplate.TemplateName); tmpl != nil { runtimeLoader.templateMap[baseTemplate.TemplateName] = tmpl } runtimeLoader.TemplatePaths[baseTemplate.TemplateName] = baseTemplate.FilePath templateLog.Debugf("loadIntoEngine:Engine '%s' compiled %s", engine.Name(), baseTemplate.FilePath) loaded = true } else { templateLog.Debug("loadIntoEngine: Engine failed to compile", "engine", engine.Name(), "file", baseTemplate.FilePath, "error", err) } return } // Parse the line, and description from an error message like: // html/template:Application/Register.html:36: no such template "footer.html" func ParseTemplateError(err error) (templateName string, line int, description string) { if e, ok := err.(*Error); ok { return "", e.Line, e.Description } description = err.Error() i := regexp.MustCompile(`:\d+:`).FindStringIndex(description) if i != nil { line, err = strconv.Atoi(description[i[0]+1 : i[1]-1]) if err != nil { templateLog.Error("ParseTemplateError: Failed to parse line number from error message:", "error", err) } templateName = description[:i[0]] if colon := strings.Index(templateName, ":"); colon != -1 { templateName = templateName[colon+1:] } templateName = strings.TrimSpace(templateName) description = description[i[1]+1:] } return templateName, line, description } // Template returns the Template with the given name. The name is the template's path // relative to a template loader root. // // An Error is returned if there was any problem with any of the templates. (In // this case, if a template is returned, it may still be usable.) func (runtimeLoader *templateRuntime) TemplateLang(name, lang string) (tmpl Template, err error) { if runtimeLoader.compileError != nil { for _, errName := range runtimeLoader.compileErrorNameList { if name == errName { return nil, runtimeLoader.compileError } } } // Fetch the template from the map tmpl = runtimeLoader.templateLoad(name, lang) if tmpl == nil { err = fmt.Errorf("Template %s not found.", name) } return } // Load and also updates map if name is not found (to speed up next lookup) func (runtimeLoader *templateRuntime) templateLoad(name, lang string) (tmpl Template) { langName := name found := false if lang != "" { // Look up and return the template. langName = name + "." + lang tmpl, found = runtimeLoader.templateMap[langName] if found { return } tmpl, found = runtimeLoader.templateMap[name] } else { tmpl, found = runtimeLoader.templateMap[name] if found { return } } if !found { // Neither name is found // Look up and return the template. for _, engine := range runtimeLoader.templatesAndEngineList { if tmpl = engine.Lookup(langName); tmpl != nil { found = true break } if tmpl = engine.Lookup(name); tmpl != nil { found = true break } } if !found { return } } // If we found anything store it in the map, we need to copy so we do not // run into concurrency issues runtimeLoader.loader.templateMutex.Lock() defer runtimeLoader.loader.templateMutex.Unlock() // In case another thread has loaded the map, reload the atomic value and check newRuntimeLoader := runtimeLoader.loader.runtimeLoader.Load().(*templateRuntime) if newRuntimeLoader.version != runtimeLoader.version { return } newTemplateMap := map[string]Template{} for k, v := range newRuntimeLoader.templateMap { newTemplateMap[k] = v } newTemplateMap[langName] = tmpl if _, found := newTemplateMap[name]; !found { newTemplateMap[name] = tmpl } runtimeCopy := &templateRuntime{} *runtimeCopy = *newRuntimeLoader runtimeCopy.templateMap = newTemplateMap // Set the atomic value runtimeLoader.loader.runtimeLoader.Store(runtimeCopy) return } func (i *TemplateView) Location() string { return i.FilePath } func (i *TemplateView) Content() (content []string) { if i.FileBytes != nil { // Parse the bytes buffer := bytes.NewBuffer(i.FileBytes) reader := bufio.NewScanner(buffer) for reader.Scan() { content = append(content, string(reader.Bytes())) } } return content } func NewBaseTemplate(templateName, filePath, basePath string, fileBytes []byte) *TemplateView { return &TemplateView{TemplateName: templateName, FilePath: filePath, FileBytes: fileBytes, BasePath: basePath} } revel-1.0.0/template_adapter_go.go 0000664 0000000 0000000 00000007671 13702523120 0017162 0 ustar 00root root 0000000 0000000 package revel import ( "html/template" "io" "log" "strings" ) const GO_TEMPLATE = "go" // Called on startup, initialized when the REVEL_BEFORE_MODULES_LOADED is called func init() { AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) { if typeOf == REVEL_BEFORE_MODULES_LOADED { RegisterTemplateLoader(GO_TEMPLATE, func(loader *TemplateLoader) (TemplateEngine, error) { // Set the template delimiters for the project if present, then split into left // and right delimiters around a space character TemplateDelims := Config.StringDefault("template.go.delimiters", "") var splitDelims []string if TemplateDelims != "" { splitDelims = strings.Split(TemplateDelims, " ") if len(splitDelims) != 2 { log.Fatalln("app.conf: Incorrect format for template.delimiters") } } return &GoEngine{ loader: loader, templateSet: template.New("__root__").Funcs(TemplateFuncs), templatesByName: map[string]*GoTemplate{}, splitDelims: splitDelims, }, nil }) } return }) } // Adapter for Go Templates. type GoTemplate struct { *template.Template engine *GoEngine *TemplateView } // return a 'revel.Template' from Go's template. func (gotmpl GoTemplate) Render(wr io.Writer, arg interface{}) error { return gotmpl.Execute(wr, arg) } // The main template engine for Go type GoEngine struct { // The template loader loader *TemplateLoader // THe current template set templateSet *template.Template // A map of templates by name templatesByName map[string]*GoTemplate // The delimiter that is used to indicate template code, defaults to {{ splitDelims []string // True if map is case insensitive CaseInsensitive bool } // Convert the path to lower case if needed func (i *GoEngine) ConvertPath(path string) string { if i.CaseInsensitive { return strings.ToLower(path) } return path } // Returns true if this engine can handle the response func (i *GoEngine) Handles(templateView *TemplateView) bool { return EngineHandles(i, templateView) } // Parses the template vide and adds it to the template set func (engine *GoEngine) ParseAndAdd(baseTemplate *TemplateView) error { // If alternate delimiters set for the project, change them for this set if engine.splitDelims != nil && strings.Index(baseTemplate.Location(), ViewsPath) > -1 { engine.templateSet.Delims(engine.splitDelims[0], engine.splitDelims[1]) } else { // Reset to default otherwise engine.templateSet.Delims("", "") } templateSource := string(baseTemplate.FileBytes) templateName := engine.ConvertPath(baseTemplate.TemplateName) tpl, err := engine.templateSet.New(baseTemplate.TemplateName).Parse(templateSource) if nil != err { _, line, description := ParseTemplateError(err) return &Error{ Title: "Template Compilation Error", Path: baseTemplate.TemplateName, Description: description, Line: line, SourceLines: strings.Split(templateSource, "\n"), } } engine.templatesByName[templateName] = &GoTemplate{Template: tpl, engine: engine, TemplateView: baseTemplate} return nil } // Lookups the template name, to see if it is contained in this engine func (engine *GoEngine) Lookup(templateName string) Template { // Case-insensitive matching of template file name if tpl, found := engine.templatesByName[engine.ConvertPath(templateName)]; found { return tpl } return nil } // Return the engine name func (engine *GoEngine) Name() string { return GO_TEMPLATE } // An event listener to listen for Revel INIT events func (engine *GoEngine) Event(action Event, i interface{}) { if action == TEMPLATE_REFRESH_REQUESTED { // At this point all the templates have been passed into the engine.templatesByName = map[string]*GoTemplate{} engine.templateSet = template.New("__root__").Funcs(TemplateFuncs) // Check to see what should be used for case sensitivity engine.CaseInsensitive = Config.BoolDefault("go.template.caseinsensitive", true) } } revel-1.0.0/template_engine.go 0000664 0000000 0000000 00000010107 13702523120 0016306 0 ustar 00root root 0000000 0000000 package revel import ( "bufio" "bytes" "errors" "fmt" "path/filepath" "strings" ) type TemplateEngine interface { // prase template string and add template to the set. ParseAndAdd(basePath *TemplateView) error // returns Template corresponding to the given templateName, or nil Lookup(templateName string) Template // Fired by the template loader when events occur Event(event Event, arg interface{}) // returns true if this engine should be used to parse the file specified in baseTemplate Handles(templateView *TemplateView) bool // returns the name of the engine Name() string } // The template view information type TemplateView struct { TemplateName string // The name of the view FilePath string // The file path (view relative) BasePath string // The file system base path FileBytes []byte // The file loaded EngineType string // The name of the engine used to render the view } var templateLoaderMap = map[string]func(loader *TemplateLoader) (TemplateEngine, error){} // Allow for templates to be registered during init but not initialized until application has been started func RegisterTemplateLoader(key string, loader func(loader *TemplateLoader) (TemplateEngine, error)) (err error) { if _, found := templateLoaderMap[key]; found { err = fmt.Errorf("Template loader %s already exists", key) } templateLog.Debug("Registered template engine loaded", "name", key) templateLoaderMap[key] = loader return } // Sets the template name from Config // Sets the template API methods for parsing and storing templates before rendering func (loader *TemplateLoader) CreateTemplateEngine(templateEngineName string) (TemplateEngine, error) { if "" == templateEngineName { templateEngineName = GO_TEMPLATE } factory := templateLoaderMap[templateEngineName] if nil == factory { fmt.Printf("registered factories %#v\n %s \n", templateLoaderMap, templateEngineName) return nil, errors.New("Unknown template engine name - " + templateEngineName + ".") } templateEngine, err := factory(loader) if nil != err { return nil, errors.New("Failed to init template engine (" + templateEngineName + "), " + err.Error()) } templateLog.Debug("CreateTemplateEngine: init templates", "name", templateEngineName) return templateEngine, nil } // Passing in a comma delimited list of engine names to be used with this loader to parse the template files func (loader *TemplateLoader) initializeEngines(runtimeLoader *templateRuntime, templateEngineNameList string) (err *Error) { // Walk through the template loader's paths and build up a template set. if templateEngineNameList == "" { templateEngineNameList = GO_TEMPLATE } runtimeLoader.templatesAndEngineList = []TemplateEngine{} for _, engine := range strings.Split(templateEngineNameList, ",") { engine := strings.TrimSpace(strings.ToLower(engine)) if templateLoader, err := loader.CreateTemplateEngine(engine); err != nil { runtimeLoader.compileError = &Error{ Title: "Panic (Template Loader)", Description: err.Error(), } return runtimeLoader.compileError } else { // Always assign a default engine, switch it if it is specified in the config runtimeLoader.templatesAndEngineList = append(runtimeLoader.templatesAndEngineList, templateLoader) } } return } func EngineHandles(engine TemplateEngine, templateView *TemplateView) bool { if line, _, e := bufio.NewReader(bytes.NewBuffer(templateView.FileBytes)).ReadLine(); e == nil && string(line[:3]) == "#! " { // Extract the shebang and look at the rest of the line // #! pong2 // #! go templateType := strings.TrimSpace(string(line[2:])) if engine.Name() == templateType { // Advance the read file bytes so it does not include the shebang templateView.FileBytes = templateView.FileBytes[len(line)+1:] templateView.EngineType = templateType return true } } filename := filepath.Base(templateView.FilePath) bits := strings.Split(filename, ".") if len(bits) > 2 { templateType := strings.TrimSpace(bits[len(bits)-2]) if engine.Name() == templateType { templateView.EngineType = templateType return true } } return false } revel-1.0.0/template_functions.go 0000664 0000000 0000000 00000024343 13702523120 0017060 0 ustar 00root root 0000000 0000000 package revel import ( "bytes" "errors" "fmt" "github.com/xeonx/timeago" "html" "html/template" "reflect" "strings" "time" ) var ( // The functions available for use in the templates. TemplateFuncs = map[string]interface{}{ "url": ReverseURL, "set": func(viewArgs map[string]interface{}, key string, value interface{}) template.JS { viewArgs[key] = value return template.JS("") }, "append": func(viewArgs map[string]interface{}, key string, value interface{}) template.JS { if viewArgs[key] == nil { viewArgs[key] = []interface{}{value} } else { viewArgs[key] = append(viewArgs[key].([]interface{}), value) } return template.JS("") }, "field": NewField, "firstof": func(args ...interface{}) interface{} { for _, val := range args { switch val.(type) { case nil: continue case string: if val == "" { continue } return val default: return val } } return nil }, "option": func(f *Field, val interface{}, label string) template.HTML { selected := "" if f.Flash() == val || (f.Flash() == "" && f.Value() == val) { selected = " selected" } return template.HTML(fmt.Sprintf(``, html.EscapeString(fmt.Sprintf("%v", val)), selected, html.EscapeString(label))) }, "radio": func(f *Field, val string) template.HTML { checked := "" if f.Flash() == val { checked = " checked" } return template.HTML(fmt.Sprintf(``, html.EscapeString(f.Name), html.EscapeString(val), checked)) }, "checkbox": func(f *Field, val string) template.HTML { checked := "" if f.Flash() == val { checked = " checked" } return template.HTML(fmt.Sprintf(``, html.EscapeString(f.Name), html.EscapeString(val), checked)) }, // Pads the given string with 's up to the given width. "pad": func(str string, width int) template.HTML { if len(str) >= width { return template.HTML(html.EscapeString(str)) } return template.HTML(html.EscapeString(str) + strings.Repeat(" ", width-len(str))) }, "errorClass": func(name string, viewArgs map[string]interface{}) template.HTML { errorMap, ok := viewArgs["errors"].(map[string]*ValidationError) if !ok || errorMap == nil { templateLog.Warn("errorClass: Called 'errorClass' without 'errors' in the view args.") return template.HTML("") } valError, ok := errorMap[name] if !ok || valError == nil { return template.HTML("") } return template.HTML(ErrorCSSClass) }, "msg": func(viewArgs map[string]interface{}, message string, args ...interface{}) template.HTML { str, ok := viewArgs[CurrentLocaleViewArg].(string) if !ok { return "" } return template.HTML(MessageFunc(str, message, args...)) }, // Replaces newlines with
"nl2br": func(text string) template.HTML { return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "
", -1)) }, // Skips sanitation on the parameter. Do not use with dynamic data. "raw": func(text string) template.HTML { return template.HTML(text) }, // Pluralize, a helper for pluralizing words to correspond to data of dynamic length. // items - a slice of items, or an integer indicating how many items there are. // pluralOverrides - optional arguments specifying the output in the // singular and plural cases. by default "" and "s" "pluralize": func(items interface{}, pluralOverrides ...string) string { singular, plural := "", "s" if len(pluralOverrides) >= 1 { singular = pluralOverrides[0] if len(pluralOverrides) == 2 { plural = pluralOverrides[1] } } switch v := reflect.ValueOf(items); v.Kind() { case reflect.Int: if items.(int) != 1 { return plural } case reflect.Slice: if v.Len() != 1 { return plural } default: templateLog.Error("pluralize: unexpected type: ", "value", v) } return singular }, // Format a date according to the application's default date(time) format. "date": func(date time.Time) string { return date.Format(DateFormat) }, "datetime": func(date time.Time) string { return date.Format(DateTimeFormat) }, // Fetch an object from the session. "session": func(key string, viewArgs map[string]interface{}) interface{} { if viewArgs != nil { if c, found := viewArgs["_controller"]; found { if v, err := c.(*Controller).Session.Get(key); err == nil { return v } else { templateLog.Errorf("template.session, key %s error %v", key, err) } } else { templateLog.Warnf("template.session, key %s requested without controller", key) } } else { templateLog.Warnf("template.session, key %s requested passing in view args", key) } return "" }, "slug": Slug, "even": func(a int) bool { return (a % 2) == 0 }, // Using https://github.com/xeonx/timeago "timeago": TimeAgo, "i18ntemplate": func(args ...interface{}) (template.HTML, error) { templateName, lang := "", "" var viewArgs interface{} switch len(args) { case 0: templateLog.Error("i18ntemplate: No arguments passed to template call") case 1: // Assume only the template name is passed in templateName = args[0].(string) case 2: // Assume template name and viewArgs is passed in templateName = args[0].(string) viewArgs = args[1] // Try to extract language from the view args if viewargsmap, ok := viewArgs.(map[string]interface{}); ok { lang, _ = viewargsmap[CurrentLocaleViewArg].(string) } default: // Assume third argument is the region templateName = args[0].(string) viewArgs = args[1] lang, _ = args[2].(string) if len(args) > 3 { templateLog.Error("i18ntemplate: Received more parameters then needed for", "template", templateName) } } var buf bytes.Buffer // Get template tmpl, err := MainTemplateLoader.TemplateLang(templateName, lang) if err == nil { err = tmpl.Render(&buf, viewArgs) } else { templateLog.Error("i18ntemplate: Failed to render i18ntemplate ", "name", templateName, "error", err) } return template.HTML(buf.String()), err }, } ) ///////////////////// // Template functions ///////////////////// // ReverseURL returns a url capable of invoking a given controller method: // "Application.ShowApp 123" => "/app/123" func ReverseURL(args ...interface{}) (template.URL, error) { if len(args) == 0 { return "", errors.New("no arguments provided to reverse route") } action := args[0].(string) if action == "Root" { return template.URL(AppRoot), nil } pathData, found := splitActionPath(nil, action, true) if !found { return "", fmt.Errorf("reversing '%s', expected 'Controller.Action'", action) } // Look up the types. if pathData.TypeOfController == nil { return "", fmt.Errorf("Failed reversing %s: controller not found %#v", action, pathData) } // Note method name is case insensitive search methodType := pathData.TypeOfController.Method(pathData.MethodName) if methodType == nil { return "", errors.New("revel/controller: In " + action + " failed to find function " + pathData.MethodName) } if len(methodType.Args) < len(args)-1 { return "", fmt.Errorf("reversing %s: route defines %d args, but received %d", action, len(methodType.Args), len(args)-1) } // Unbind the arguments. argsByName := make(map[string]string) // Bind any static args first fixedParams := len(pathData.FixedParamsByName) for i, argValue := range args[1:] { Unbind(argsByName, methodType.Args[i+fixedParams].Name, argValue) } return template.URL(MainRouter.Reverse(args[0].(string), argsByName).URL), nil } func Slug(text string) string { separator := "-" text = strings.ToLower(text) text = invalidSlugPattern.ReplaceAllString(text, "") text = whiteSpacePattern.ReplaceAllString(text, separator) text = strings.Trim(text, separator) return text } var timeAgoLangs = map[string]timeago.Config{} func TimeAgo(args ...interface{}) string { datetime := time.Now() lang := "" var viewArgs interface{} switch len(args) { case 0: templateLog.Error("TimeAgo: No arguments passed to timeago") case 1: // only the time is passed in datetime = args[0].(time.Time) case 2: // time and region is passed in datetime = args[0].(time.Time) switch v := reflect.ValueOf(args[1]); v.Kind() { case reflect.String: // second params type string equals region lang, _ = args[1].(string) case reflect.Map: // second params type map equals viewArgs viewArgs = args[1] if viewargsmap, ok := viewArgs.(map[string]interface{}); ok { lang, _ = viewargsmap[CurrentLocaleViewArg].(string) } default: templateLog.Error("TimeAgo: unexpected type: ", "value", v) } default: // Assume third argument is the region datetime = args[0].(time.Time) if reflect.ValueOf(args[1]).Kind() != reflect.Map { templateLog.Error("TimeAgo: unexpected type", "value", args[1]) } if reflect.ValueOf(args[2]).Kind() != reflect.String { templateLog.Error("TimeAgo: unexpected type: ", "value", args[2]) } viewArgs = args[1] lang, _ = args[2].(string) if len(args) > 3 { templateLog.Error("TimeAgo: Received more parameters then needed for timeago") } } if lang == "" { lang, _ = Config.String(defaultLanguageOption) if lang == "en" { timeAgoLangs[lang] = timeago.English } } _, ok := timeAgoLangs[lang] if !ok { timeAgoLangs[lang] = timeago.Config{ PastPrefix: "", PastSuffix: " " + MessageFunc(lang, "ago"), FuturePrefix: MessageFunc(lang, "in") + " ", FutureSuffix: "", Periods: []timeago.FormatPeriod{ {time.Second, MessageFunc(lang, "about a second"), MessageFunc(lang, "%d seconds")}, {time.Minute, MessageFunc(lang, "about a minute"), MessageFunc(lang, "%d minutes")}, {time.Hour, MessageFunc(lang, "about an hour"), MessageFunc(lang, "%d hours")}, {timeago.Day, MessageFunc(lang, "one day"), MessageFunc(lang, "%d days")}, {timeago.Month, MessageFunc(lang, "one month"), MessageFunc(lang, "%d months")}, {timeago.Year, MessageFunc(lang, "one year"), MessageFunc(lang, "%d years")}, }, Zero: MessageFunc(lang, "about a second"), Max: 73 * time.Hour, DefaultLayout: "2006-01-02", } } return timeAgoLangs[lang].Format(datetime) } revel-1.0.0/templates/ 0000775 0000000 0000000 00000000000 13702523120 0014616 5 ustar 00root root 0000000 0000000 revel-1.0.0/templates/errors/ 0000775 0000000 0000000 00000000000 13702523120 0016132 5 ustar 00root root 0000000 0000000 revel-1.0.0/templates/errors/403.html 0000664 0000000 0000000 00000000270 13702523120 0017325 0 ustar 00root root 0000000 0000000Forbidden {{with .Error}}{{.Title}}
{{.Description}}
{{end}} revel-1.0.0/templates/errors/403.json 0000664 0000000 0000000 00000000127 13702523120 0017333 0 ustar 00root root 0000000 0000000 { "title": "{{js .Error.Title}}", "description": "{{js .Error.Description}}" } revel-1.0.0/templates/errors/403.txt 0000664 0000000 0000000 00000000051 13702523120 0017175 0 ustar 00root root 0000000 0000000 {{.Error.Title}} {{.Error.Description}} revel-1.0.0/templates/errors/403.xml 0000664 0000000 0000000 00000000056 13702523120 0017163 0 ustar 00root root 0000000 0000000{{.Error.Description}} revel-1.0.0/templates/errors/404-dev.html 0000664 0000000 0000000 00000001743 13702523120 0020110 0 ustar 00root root 0000000 0000000{{with .Error}}{{.Title}}
{{.Description}}
{{end}}revel-1.0.0/templates/errors/404.html 0000664 0000000 0000000 00000000404 13702523120 0017325 0 ustar 00root root 0000000 0000000These routes have been tried, in this order :
{{range .Router.Routes}}
- {{pad .Method 10}}{{pad .Path 50}}{{.Action}} {{with .ModuleSource}}(Route Module:{{.Name}}){{end}}
{{end}}Not found {{if .DevMode}} {{template "errors/404-dev.html" .}} {{else}} {{with .Error}}{{.Title}}
{{.Description}}
{{end}} {{end}} revel-1.0.0/templates/errors/404.json 0000664 0000000 0000000 00000000127 13702523120 0017334 0 ustar 00root root 0000000 0000000 { "title": "{{js .Error.Title}}", "description": "{{js .Error.Description}}" } revel-1.0.0/templates/errors/404.txt 0000664 0000000 0000000 00000000051 13702523120 0017176 0 ustar 00root root 0000000 0000000 {{.Error.Title}} {{.Error.Description}} revel-1.0.0/templates/errors/404.xml 0000664 0000000 0000000 00000000054 13702523120 0017162 0 ustar 00root root 0000000 0000000{{.Error.Description}} revel-1.0.0/templates/errors/405.html 0000664 0000000 0000000 00000000301 13702523120 0017322 0 ustar 00root root 0000000 0000000Method not allowed {{with .Error}}{{.Title}}
{{.Description}}
{{end}} revel-1.0.0/templates/errors/405.json 0000664 0000000 0000000 00000000127 13702523120 0017335 0 ustar 00root root 0000000 0000000 { "title": "{{js .Error.Title}}", "description": "{{js .Error.Description}}" } revel-1.0.0/templates/errors/405.txt 0000664 0000000 0000000 00000000051 13702523120 0017177 0 ustar 00root root 0000000 0000000 {{.Error.Title}} {{.Error.Description}} revel-1.0.0/templates/errors/405.xml 0000664 0000000 0000000 00000000100 13702523120 0017153 0 ustar 00root root 0000000 0000000{{.Error.Description}} revel-1.0.0/templates/errors/500-dev.html 0000664 0000000 0000000 00000004712 13702523120 0020104 0 ustar 00root root 0000000 0000000 {{with .Error}}{{if .Path}}{{.Title}}
{{if .SourceType}} The {{.SourceType}} {{.Path}} does not compile: {{.Description}} {{else}} {{.Description}} {{end}}
{{end}} {{if .Stack}}In {{.Path}} {{if .Line}} (around {{if .Line}}line {{.Line}}{{end}}{{if .Column}} column {{.Column}}{{end}}) {{end}}
{{range .ContextSource}}{{.Line}}:{{end}}{{.Source}}{{end}} {{if .MetaError}}Call Stack
{{.Stack}}
{{end}} {{end}} revel-1.0.0/templates/errors/500.html 0000664 0000000 0000000 00000000404 13702523120 0017322 0 ustar 00root root 0000000 0000000Additionally, an error occurred while handling this error.
{{.MetaError}}Application error {{if .DevMode}} {{template "errors/500-dev.html" .}} {{else}}Oops, an error occured
This exception has been logged.
{{end}} revel-1.0.0/templates/errors/500.json 0000664 0000000 0000000 00000000127 13702523120 0017331 0 ustar 00root root 0000000 0000000 { "title": "{{js .Error.Title}}", "description": "{{js .Error.Description}}" } revel-1.0.0/templates/errors/500.txt 0000664 0000000 0000000 00000000424 13702523120 0017177 0 ustar 00root root 0000000 0000000 {{.Error.Title}} {{.Error.Description}} {{if eq .RunMode "dev"}} {{with .Error}} {{if .Path}} ---------- In {{.Path}} {{if .Line}}(around line {{.Line}}){{end}} {{range .ContextSource}} {{if .IsError}}>{{else}} {{end}} {{.Line}}: {{.Source}}{{end}} {{end}} {{end}} {{end}} revel-1.0.0/templates/errors/500.xml 0000664 0000000 0000000 00000000145 13702523120 0017160 0 ustar 00root root 0000000 0000000revel-1.0.0/testdata/ 0000775 0000000 0000000 00000000000 13702523120 0014431 5 ustar 00root root 0000000 0000000 revel-1.0.0/testdata/app/ 0000775 0000000 0000000 00000000000 13702523120 0015211 5 ustar 00root root 0000000 0000000 revel-1.0.0/testdata/app/views/ 0000775 0000000 0000000 00000000000 13702523120 0016346 5 ustar 00root root 0000000 0000000 revel-1.0.0/testdata/app/views/footer.html 0000664 0000000 0000000 00000000630 13702523120 0020531 0 ustar 00root root 0000000 0000000 {{.Error.Title}} {{.Error.Description}}