Permalink
Fetching contributors…
Cannot retrieve contributors at this time
537 lines (464 sloc) 16.7 KB
// 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"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"time"
"github.com/revel/revel/logger"
)
// Controller Revel's controller structure that gets embedded in user defined
// controllers
type Controller struct {
Name string // The controller name, e.g. "Application"
Type *ControllerType // A description of the controller type.
MethodName string // The method name, e.g. "Index"
MethodType *MethodType // A description of the invoked action type.
AppController interface{} // The controller that was instantiated. embeds revel.Controller
Action string // The fully qualified action name, e.g. "App.Index"
ClientIP string // holds IP address of request came from
Request *Request
Response *Response
Result Result
Flash Flash // User cookie, cleared after 1 request.
Session Session // Session, stored in cookie, signed.
Params *Params // Parameters from URL and form (including multipart).
Args map[string]interface{} // Per-request scratch space.
ViewArgs map[string]interface{} // Variables passed to the template.
Validation *Validation // Data validation helpers
Log logger.MultiLogger // Context Logger
}
// The map of controllers, controllers are mapped by using the namespace|controller_name as the key
var controllers = make(map[string]*ControllerType)
var controllerLog = RevelLog.New("section", "controller")
// NewController returns new controller instance for Request and Response
func NewControllerEmpty() *Controller {
return &Controller{Request: NewRequest(nil), Response: NewResponse(nil)}
}
// New controller, creates a new instance wrapping the request and response in it
func NewController(context ServerContext) *Controller {
c := NewControllerEmpty()
c.SetController(context)
return c
}
// Sets the request and the response for the controller
func (c *Controller) SetController(context ServerContext) {
c.Request.SetRequest(context.GetRequest())
c.Response.SetResponse(context.GetResponse())
c.Request.controller = c
c.Params = new(Params)
c.Args = map[string]interface{}{}
c.ViewArgs = map[string]interface{}{
"RunMode": RunMode,
"DevMode": DevMode,
}
}
func (c *Controller) Destroy() {
// When the instantiated controller gets injected
// It inherits this method, so we need to
// check to see if the controller is nil before performing
// any actions
if c == nil {
return
}
if c.AppController != nil {
c.resetAppControllerFields()
// Return this instance to the pool
appController := c.AppController
c.AppController = nil
cachedControllerMap[c.Name].Push(appController)
c.AppController = nil
}
c.Request.Destroy()
c.Response.Destroy()
c.Params = nil
c.Args = nil
c.ViewArgs = nil
c.Name = ""
c.Type = nil
c.MethodName = ""
c.MethodType = nil
c.Action = ""
c.ClientIP = ""
c.Result = nil
c.Flash = Flash{}
c.Session = Session{}
c.Params = nil
c.Validation = nil
c.Log = nil
}
// FlashParams serializes the contents of Controller.Params to the Flash
// cookie.
func (c *Controller) FlashParams() {
for key, vals := range c.Params.Values {
c.Flash.Out[key] = strings.Join(vals, ",")
}
}
func (c *Controller) SetCookie(cookie *http.Cookie) {
c.Response.Out.internalHeader.SetCookie(cookie.String())
}
type ErrorCoder interface {
HTTPCode() int
}
func (c *Controller) RenderError(err error) Result {
if coder, ok := err.(ErrorCoder); ok {
c.setStatusIfNil(coder.HTTPCode())
} else {
c.setStatusIfNil(http.StatusInternalServerError)
}
return ErrorResult{c.ViewArgs, err}
}
func (c *Controller) setStatusIfNil(status int) {
if c.Response.Status == 0 {
c.Response.Status = status
}
}
// Render a template corresponding to the calling Controller method.
// Arguments will be added to c.ViewArgs prior to rendering the template.
// They are keyed on their local identifier.
//
// For example:
//
// func (c Users) ShowUser(id int) revel.Result {
// user := loadUser(id)
// return c.Render(user)
// }
//
// This action will render views/Users/ShowUser.html, passing in an extra
// key-value "user": (User).
//
// This is the slower magical version which uses the runtime
// to determine
// 1) Set c.ViewArgs to the arguments passed into this function
// 2) How to call the RenderTemplate by building the following line
// c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format)
//
// If you want your code to run faster it is recommended you add the template values directly
// to the c.ViewArgs and call c.RenderTemplate directly
func (c *Controller) Render(extraViewArgs ...interface{}) Result {
c.setStatusIfNil(http.StatusOK)
// Get the calling function line number.
_, _, line, ok := runtime.Caller(1)
if !ok {
controllerLog.Error("Render: Failed to get Caller information")
}
// Get the extra ViewArgs passed in.
if renderArgNames, ok := c.MethodType.RenderArgNames[line]; ok {
if len(renderArgNames) == len(extraViewArgs) {
for i, extraRenderArg := range extraViewArgs {
c.ViewArgs[renderArgNames[i]] = extraRenderArg
}
} else {
controllerLog.Error(fmt.Sprint(len(renderArgNames), "RenderArg names found for",
len(extraViewArgs), "extra ViewArgs"))
}
} else {
controllerLog.Error(fmt.Sprint("No RenderArg names found for Render call on line", line,
"(Action", c.Action, ")"))
}
return c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format)
}
// RenderTemplate method does less magical way to render a template.
// Renders the given template, using the current ViewArgs.
func (c *Controller) RenderTemplate(templatePath string) Result {
c.setStatusIfNil(http.StatusOK)
// Get the Template.
lang, _ := c.ViewArgs[CurrentLocaleViewArg].(string)
template, err := MainTemplateLoader.TemplateLang(templatePath, lang)
if err != nil {
return c.RenderError(err)
}
return &RenderTemplateResult{
Template: template,
ViewArgs: c.ViewArgs,
}
}
// TemplateOutput returns the result of the template rendered using the controllers ViewArgs.
func (c *Controller) TemplateOutput(templatePath string) (data []byte,err error) {
return TemplateOutputArgs(templatePath,c.ViewArgs)
}
// RenderJSON uses encoding/json.Marshal to return JSON to the client.
func (c *Controller) RenderJSON(o interface{}) Result {
c.setStatusIfNil(http.StatusOK)
return RenderJSONResult{o, ""}
}
// RenderJSONP renders JSONP result using encoding/json.Marshal
func (c *Controller) RenderJSONP(callback string, o interface{}) Result {
c.setStatusIfNil(http.StatusOK)
return RenderJSONResult{o, callback}
}
// RenderXML uses encoding/xml.Marshal to return XML to the client.
func (c *Controller) RenderXML(o interface{}) Result {
c.setStatusIfNil(http.StatusOK)
return RenderXMLResult{o}
}
// RenderText renders plaintext in response, printf style.
func (c *Controller) RenderText(text string, objs ...interface{}) Result {
c.setStatusIfNil(http.StatusOK)
finalText := text
if len(objs) > 0 {
finalText = fmt.Sprintf(text, objs...)
}
return &RenderTextResult{finalText}
}
// RenderHTML renders html in response
func (c *Controller) RenderHTML(html string) Result {
c.setStatusIfNil(http.StatusOK)
return &RenderHTMLResult{html}
}
// Todo returns an HTTP 501 Not Implemented "todo" indicating that the
// action isn't done yet.
func (c *Controller) Todo() Result {
c.Response.Status = http.StatusNotImplemented
controllerLog.Debug("Todo: Not implemented function", "action", c.Action)
return c.RenderError(&Error{
Title: "TODO",
Description: "This action is not implemented",
})
}
// NotFound returns an HTTP 404 Not Found response whose body is the
// formatted string of msg and objs.
func (c *Controller) NotFound(msg string, objs ...interface{}) Result {
finalText := msg
if len(objs) > 0 {
finalText = fmt.Sprintf(msg, objs...)
}
c.Response.Status = http.StatusNotFound
return c.RenderError(&Error{
Title: "Not Found",
Description: finalText,
})
}
// Forbidden returns an HTTP 403 Forbidden response whose body is the
// formatted string of msg and objs.
func (c *Controller) Forbidden(msg string, objs ...interface{}) Result {
finalText := msg
if len(objs) > 0 {
finalText = fmt.Sprintf(msg, objs...)
}
c.Response.Status = http.StatusForbidden
return c.RenderError(&Error{
Title: "Forbidden",
Description: finalText,
})
}
// RenderFileName returns a file indicated by the path as provided via the filename.
// It can be either displayed inline or downloaded as an attachment.
// The name and size are taken from the file info.
func (c *Controller) RenderFileName(filename string, delivery ContentDisposition) Result {
f, err := os.Open(filename)
if err != nil {
c.Log.Errorf("Cant open file: %v", err)
return c.RenderError(err)
}
return c.RenderFile(f, delivery)
}
// RenderFile returns a file, either displayed inline or downloaded
// as an attachment. The name and size are taken from the file info.
func (c *Controller) RenderFile(file *os.File, delivery ContentDisposition) Result {
c.setStatusIfNil(http.StatusOK)
var (
modtime = time.Now()
fileInfo, err = file.Stat()
)
if err != nil {
controllerLog.Error("RenderFile: error", "error", err)
}
if fileInfo != nil {
modtime = fileInfo.ModTime()
}
return c.RenderBinary(file, filepath.Base(file.Name()), delivery, modtime)
}
// RenderBinary is like RenderFile() except that it instead of a file on disk,
// it renders data from memory (which could be a file that has not been written,
// the output from some function, or bytes streamed from somewhere else, as long
// it implements io.Reader). When called directly on something generated or
// streamed, modtime should mostly likely be time.Now().
func (c *Controller) RenderBinary(memfile io.Reader, filename string, delivery ContentDisposition, modtime time.Time) Result {
c.setStatusIfNil(http.StatusOK)
return &BinaryResult{
Reader: memfile,
Name: filename,
Delivery: delivery,
Length: -1, // http.ServeContent gets the length itself unless memfile is a stream.
ModTime: modtime,
}
}
// Redirect to an action or to a URL.
// c.Redirect(Controller.Action)
// c.Redirect("/controller/action")
// c.Redirect("/controller/%d/action", id)
func (c *Controller) Redirect(val interface{}, args ...interface{}) Result {
c.setStatusIfNil(http.StatusFound)
if url, ok := val.(string); ok {
if len(args) == 0 {
return &RedirectToURLResult{url}
}
return &RedirectToURLResult{fmt.Sprintf(url, args...)}
}
return &RedirectToActionResult{val, args}
}
// This stats returns some interesting stats based on what is cached in memory
// and what is available directly
func (c *Controller) Stats() map[string]interface{} {
result := CurrentEngine.Stats()
result["revel-controllers"] = controllerStack.String()
for key, appStack := range cachedControllerMap {
result["app-"+key] = appStack.String()
}
return result
}
// Message performs a lookup for the given message name using the given
// arguments using the current language defined for this controller.
//
// The current language is set by the i18n plugin.
func (c *Controller) Message(message string, args ...interface{}) string {
return MessageFunc(c.Request.Locale, message, args...)
}
// SetAction sets the action that is being invoked in the current request.
// It sets the following properties: Name, Action, Type, MethodType
func (c *Controller) SetAction(controllerName, methodName string) error {
return c.SetTypeAction(controllerName, methodName, nil)
}
// SetAction sets the assigns the Controller type, sets the action and initializes the controller
func (c *Controller) SetTypeAction(controllerName, methodName string, typeOfController *ControllerType) error {
// Look up the controller and method types.
if typeOfController == nil {
if c.Type = ControllerTypeByName(controllerName, anyModule); c.Type == nil {
return errors.New("revel/controller: failed to find controller " + controllerName)
}
} else {
c.Type = typeOfController
}
// Note method name is case insensitive search
if c.MethodType = c.Type.Method(methodName); c.MethodType == nil {
return errors.New("revel/controller: failed to find action " + controllerName + "." + methodName)
}
c.Name, c.MethodName = c.Type.Type.Name(), c.MethodType.Name
c.Action = c.Name + "." + c.MethodName
// Update Logger with controller and namespace
if c.Log != nil {
c.Log = c.Log.New("action", c.Action, "namespace", c.Type.Namespace)
}
if _, ok := cachedControllerMap[c.Name]; !ok {
// Create a new stack for this controller
localType := c.Type.Type
cachedControllerMap[c.Name] = NewStackLock(
cachedControllerStackSize,
cachedControllerStackMaxSize,
func() interface{} {
return reflect.New(localType).Interface()
})
}
// Instantiate the controller.
c.AppController = cachedControllerMap[c.Name].Pop()
c.setAppControllerFields()
return nil
}
func ControllerTypeByName(controllerName string, moduleSource *Module) (c *ControllerType) {
var found bool
if c, found = controllers[controllerName]; !found {
// Backup, passed in controllerName should be in lower case, but may not be
if c, found = controllers[strings.ToLower(controllerName)]; !found {
controllerLog.Debug("ControllerTypeByName: Cannot find controller in controllers map ", "controller", controllerName)
// Search for the controller by name
for _, cType := range controllers {
testControllerName := strings.ToLower(cType.Type.Name())
if testControllerName == strings.ToLower(controllerName) && (cType.ModuleSource == moduleSource || moduleSource == anyModule) {
controllerLog.Warn("ControllerTypeByName: Matched empty namespace controller ", "controller", controllerName, "namespace", cType.ModuleSource.Name)
c = cType
found = true
break
}
}
}
}
return
}
// Injects this instance (c) into the AppController instance
func (c *Controller) setAppControllerFields() {
appController := reflect.ValueOf(c.AppController).Elem()
cValue := reflect.ValueOf(c)
for _, index := range c.Type.ControllerIndexes {
appController.FieldByIndex(index).Set(cValue)
}
}
// Removes this instance (c) from the AppController instance
func (c *Controller) resetAppControllerFields() {
appController := reflect.ValueOf(c.AppController).Elem()
// Zero out controller
for _, index := range c.Type.ControllerIndexes {
appController.FieldByIndex(index).Set(reflect.Zero(reflect.TypeOf(c.AppController).Elem().FieldByIndex(index).Type))
}
}
func findControllers(appControllerType reflect.Type) (indexes [][]int) {
// It might be a multi-level embedding. To find the controllers, we follow
// every anonymous field, using breadth-first search.
type nodeType struct {
val reflect.Value
index []int
}
appControllerPtr := reflect.New(appControllerType)
queue := []nodeType{{appControllerPtr, []int{}}}
for len(queue) > 0 {
// Get the next value and de-reference it if necessary.
var (
node = queue[0]
elem = node.val
elemType = elem.Type()
)
if elemType.Kind() == reflect.Ptr {
elem = elem.Elem()
elemType = elem.Type()
}
queue = queue[1:]
// #944 if the type's Kind is not `Struct` move on,
// otherwise `elem.NumField()` will panic
if elemType.Kind() != reflect.Struct {
continue
}
// Look at all the struct fields.
for i := 0; i < elem.NumField(); i++ {
// If this is not an anonymous field, skip it.
structField := elemType.Field(i)
if !structField.Anonymous {
continue
}
fieldValue := elem.Field(i)
fieldType := structField.Type
// If it's a Controller, record the field indexes to get here.
if fieldType == controllerPtrType {
indexes = append(indexes, append(node.index, i))
continue
}
queue = append(queue,
nodeType{fieldValue, append(append([]int{}, node.index...), i)})
}
}
return
}
// RegisterController registers a Controller and its Methods with Revel.
func RegisterController(c interface{}, methods []*MethodType) {
// De-star the controller type
// (e.g. given TypeOf((*Application)(nil)), want TypeOf(Application))
elem := reflect.TypeOf(c).Elem()
// De-star all of the method arg types too.
for _, m := range methods {
m.lowerName = strings.ToLower(m.Name)
for _, arg := range m.Args {
arg.Type = arg.Type.Elem()
}
}
// Fetch module for controller, if none found controller must be part of the app
controllerModule := ModuleFromPath(elem.PkgPath(), true)
controllerType := AddControllerType(controllerModule, elem, methods)
controllerLog.Debug("RegisterController:Registered controller", "controller", controllerType.Name())
}