tags.
tl := strings.ToLower(text)
if strings.Contains(tl, "") {
insidePre = true
}
// Trim if not inside a statement
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 0000000
Forbidden
{{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}}
These routes have been tried, in this order :
{{range .Router.Routes}}
- {{pad .Method 10}}{{pad .Path 50}}{{.Action}} {{with .ModuleSource}}(Route Module:{{.Name}}){{end}}
{{end}}
revel-1.0.0/templates/errors/404.html 0000664 0000000 0000000 00000000404 13702523120 0017325 0 ustar 00root root 0000000 0000000
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 0000000
Method 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}}
{{.Title}}
{{if .SourceType}}
The {{.SourceType}} {{.Path}} does not compile: {{.Description}}
{{else}}
{{.Description}}
{{end}}
{{if .Path}}
In {{.Path}}
{{if .Line}}
(around {{if .Line}}line {{.Line}}{{end}}{{if .Column}} column {{.Column}}{{end}})
{{end}}
{{range .ContextSource}}
{{.Line}}:
{{.Source}}
{{end}}
{{end}}
{{if .Stack}}
Call Stack
{{.Stack}}
{{end}}
{{if .MetaError}}
Additionally, an error occurred while handling this error.
{{.MetaError}}
{{end}}
{{end}}
revel-1.0.0/templates/errors/500.html 0000664 0000000 0000000 00000000404 13702523120 0017322 0 ustar 00root root 0000000 0000000
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 0000000
{{.Error.Title}}
{{.Error.Description}}
revel-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