Permalink
Browse files

Refactor mvc.go into {mvc,controller,session,flash,params}.go

Also, use a Plugin for session, flash, and validation.
Change session.cookie to cookie.prefix (since it is also used for
validation and flash)
  • Loading branch information...
1 parent 0ce48eb commit 22f08f03aa1ca3ab2eb10a87c2155582e835fd6d @robfig committed Nov 11, 2012
Showing with 536 additions and 481 deletions.
  1. +290 −0 controller.go
  2. +69 −0 flash.go
  3. +0 −480 mvc.go
  4. +61 −0 params.go
  5. +4 −0 revel.go
  6. +62 −0 session.go
  7. +1 −1 skeleton/conf/app.conf.template
  8. +49 −0 validation.go
View
@@ -0,0 +1,290 @@
+package rev
+
+import (
+ "database/sql"
+ "fmt"
+ "net/http"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "runtime/debug"
+ "strings"
+)
+
+type Controller struct {
+ Name string
+ Type *ControllerType
+ MethodType *MethodType
+
+ Request *Request
+ Response *Response
+
+ Flash Flash // User cookie, cleared after each 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.
+ RenderArgs map[string]interface{} // Args passed to the template.
+ Validation *Validation // Data validation helpers
+ Txn *sql.Tx // Nil by default, but may be used by the app / plugins
+}
+
+func NewController(req *Request, resp *Response, ct *ControllerType) *Controller {
+ return &Controller{
+ Name: ct.Type.Name(),
+ Type: ct,
+ Request: req,
+ Response: resp,
+ Params: ParseParams(req),
+ RenderArgs: map[string]interface{}{
+ "RunMode": RunMode,
+ },
+ }
+}
+
+func (c *Controller) FlashParams() {
+ for key, vals := range c.Params.Values {
+ c.Flash.Out[key] = vals[0]
+ }
+}
+
+func (c *Controller) SetCookie(cookie *http.Cookie) {
+ http.SetCookie(c.Response.Out, cookie)
+}
+
+// Invoke the given method, save headers/cookies to the response, and apply the
+// result. (e.g. render a template to the response)
+func (c *Controller) Invoke(appControllerPtr reflect.Value, method reflect.Value, methodArgs []reflect.Value) {
+
+ // Handle panics.
+ defer func() {
+ if err := recover(); err != nil {
+ handleInvocationPanic(c, err)
+ }
+ }()
+
+ // Clean up from the request.
+ defer func() {
+ // Delete temp files.
+ if c.Request.MultipartForm != nil {
+ err := c.Request.MultipartForm.RemoveAll()
+ if err != nil {
+ WARN.Println("Error removing temporary files:", err)
+ }
+ }
+
+ for _, tmpFile := range c.Params.tmpFiles {
+ err := os.Remove(tmpFile.Name())
+ if err != nil {
+ WARN.Println("Could not remove upload temp file:", err)
+ }
+ }
+ }()
+
+ // Run the plugins.
+ plugins.BeforeRequest(c)
+
+ // Calculate the Result by running the interceptors and the action.
+ resultValue := func() reflect.Value {
+ // Call the BEFORE interceptors
+ result := c.invokeInterceptors(BEFORE, appControllerPtr)
+ if result != nil {
+ return reflect.ValueOf(result)
+ }
+
+ // Invoke the action.
+ resultValue := method.Call(methodArgs)[0]
+
+ // Call the AFTER interceptors
+ result = c.invokeInterceptors(AFTER, appControllerPtr)
+ if result != nil {
+ return reflect.ValueOf(result)
+ }
+ return resultValue
+ }()
+
+ plugins.AfterRequest(c)
+
+ if resultValue.IsNil() {
+ return
+ }
+ result := resultValue.Interface().(Result)
+
+ // Apply the result, which generally results in the ResponseWriter getting written.
+ result.Apply(c.Request, c.Response)
+}
+
+// This function handles a panic in an action invocation.
+// It cleans up the stack trace, logs it, and displays an error page.
+func handleInvocationPanic(c *Controller, err interface{}) {
+ plugins.OnException(c, err)
+ stack := string(debug.Stack())
+ ERROR.Println(err, "\n", stack)
+
+ error := NewErrorFromPanic(err)
+ if error == nil {
+ c.Response.Out.WriteHeader(500)
+ c.Response.Out.Write([]byte(stack))
+ return
+ }
+
+ c.RenderError(error).Apply(c.Request, c.Response)
+}
+
+func (c *Controller) invokeInterceptors(when InterceptTime, appControllerPtr reflect.Value) Result {
+ var result Result
+ for _, intc := range getInterceptors(when, appControllerPtr) {
+ resultValue := intc.Invoke(appControllerPtr)
+ if !resultValue.IsNil() {
+ result = resultValue.Interface().(Result)
+ }
+ if when == BEFORE && result != nil {
+ return result
+ }
+ }
+ return result
+}
+
+func (c *Controller) RenderError(err error) Result {
+ return ErrorResult{c.RenderArgs, err}
+}
+
+// Render a template corresponding to the calling Controller method.
+// Arguments will be added to c.RenderArgs prior to rendering the template.
+// They are keyed on their local identifier.
+//
+// For example:
+//
+// func (c Users) ShowUser(id int) rev.Result {
+// user := loadUser(id)
+// return c.Render(user)
+// }
+//
+// This action will render views/Users/ShowUser.html, passing in an extra
+// key-value "user": (User).
+func (c *Controller) Render(extraRenderArgs ...interface{}) Result {
+ // Get the calling function name.
+ pc, _, line, ok := runtime.Caller(1)
+ if !ok {
+ ERROR.Println("Failed to get Caller information")
+ return nil
+ }
+ // e.g. sample/app/controllers.(*Application).Index
+ var fqViewName string = runtime.FuncForPC(pc).Name()
+ var viewName string = fqViewName[strings.LastIndex(fqViewName, ".")+1 : len(fqViewName)]
+
+ // Determine what method we are in.
+ // (e.g. the invoked controller method might have delegated to another method)
+ methodType := c.MethodType
+ if methodType.Name != viewName {
+ methodType = c.Type.Method(viewName)
+ if methodType == nil {
+ return c.RenderError(fmt.Errorf(
+ "No Method %s in Controller %s when loading the view."+
+ " (delegating Render is only supported within the same controller)",
+ viewName, c.Name))
+ }
+ }
+
+ // Get the extra RenderArgs passed in.
+ if renderArgNames, ok := methodType.RenderArgNames[line]; ok {
+ if len(renderArgNames) == len(extraRenderArgs) {
+ for i, extraRenderArg := range extraRenderArgs {
+ c.RenderArgs[renderArgNames[i]] = extraRenderArg
+ }
+ } else {
+ ERROR.Println(len(renderArgNames), "RenderArg names found for",
+ len(extraRenderArgs), "extra RenderArgs")
+ }
+ } else {
+ ERROR.Println("No RenderArg names found for Render call on line", line,
+ "(Method", methodType, ", ViewName", viewName, ")")
+ }
+
+ return c.RenderTemplate(c.Name + "/" + viewName + ".html")
+}
+
+// A less magical way to render a template.
+// Renders the given template, using the current RenderArgs.
+func (c *Controller) RenderTemplate(templatePath string) Result {
+
+ // Get the Template.
+ template, err := MainTemplateLoader.Template(templatePath)
+ if err != nil {
+ return c.RenderError(err)
+ }
+
+ return &RenderTemplateResult{
+ Template: template,
+ RenderArgs: c.RenderArgs,
+ }
+}
+
+// Uses encoding/json.Marshal to return JSON to the client.
+func (c *Controller) RenderJson(o interface{}) Result {
+ return RenderJsonResult{o}
+}
+
+// Uses encoding/xml.Marshal to return XML to the client.
+func (c *Controller) RenderXml(o interface{}) Result {
+ return RenderXmlResult{o}
+}
+
+// Render plaintext in response, printf style.
+func (c *Controller) RenderText(text string, objs ...interface{}) Result {
+ finalText := text
+ if len(objs) > 0 {
+ finalText = fmt.Sprintf(text, objs)
+ }
+ return &RenderTextResult{finalText}
+}
+
+// Render a "todo" indicating that the action isn't done yet.
+func (c *Controller) Todo() Result {
+ c.Response.Status = http.StatusNotImplemented
+ return c.RenderError(&Error{
+ Title: "TODO",
+ Description: "This action is not implemented",
+ })
+}
+
+func (c *Controller) NotFound(msg string) Result {
+ c.Response.Status = http.StatusNotFound
+ return c.RenderError(&Error{
+ Title: "Not Found",
+ Description: msg,
+ })
+}
+
+// Return 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 {
+ var length int64 = -1
+ fileInfo, err := file.Stat()
+ if err != nil {
+ WARN.Println("RenderFile error:", err)
+ }
+ if fileInfo != nil {
+ length = fileInfo.Size()
+ }
+ return &BinaryResult{
+ Reader: file,
+ Name: filepath.Base(file.Name()),
+ Length: length,
+ Delivery: delivery,
+ }
+}
+
+// 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 {
+ if url, ok := val.(string); ok {
+ if len(args) == 0 {
+ return &RedirectToUrlResult{url}
+ }
+ return &RedirectToUrlResult{fmt.Sprintf(url, args...)}
+ }
+ return &RedirectToActionResult{val}
+}
View
@@ -0,0 +1,69 @@
+package rev
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+)
+
+// Flash represents a cookie that gets overwritten on each request.
+// It allows data to be stored across one page at a time.
+// This is commonly used to implement success or error messages.
+// e.g. the Post/Redirect/Get pattern: http://en.wikipedia.org/wiki/Post/Redirect/Get
+type Flash struct {
+ Data, Out map[string]string
+}
+
+func (f Flash) Error(msg string, args ...interface{}) {
+ if len(args) == 0 {
+ f.Out["error"] = msg
+ } else {
+ f.Out["error"] = fmt.Sprintf(msg, args...)
+ }
+}
+
+func (f Flash) Success(msg string, args ...interface{}) {
+ if len(args) == 0 {
+ f.Out["success"] = msg
+ } else {
+ f.Out["success"] = fmt.Sprintf(msg, args...)
+ }
+}
+
+type FlashPlugin struct{ EmptyPlugin }
+
+func (p FlashPlugin) BeforeRequest(c *Controller) {
+ c.Flash = restoreFlash(c.Request.Request)
+ c.RenderArgs["flash"] = c.Flash.Data
+}
+
+func (p FlashPlugin) AfterRequest(c *Controller) {
+ // Store the flash.
+ var flashValue string
+ for key, value := range c.Flash.Out {
+ flashValue += "\x00" + key + ":" + value + "\x00"
+ }
+ c.SetCookie(&http.Cookie{
+ Name: CookiePrefix + "_FLASH",
+ Value: url.QueryEscape(flashValue),
+ Path: "/",
+ })
+}
+
+// Restore flash from a request.
+func restoreFlash(req *http.Request) Flash {
+ flash := Flash{
+ Data: make(map[string]string),
+ Out: make(map[string]string),
+ }
+ if cookie, err := req.Cookie(CookiePrefix + "_FLASH"); err == nil {
+ ParseKeyValueCookie(cookie.Value, func(key, val string) {
+ flash.Data[key] = val
+ })
+ }
+ return flash
+}
+
+func init() {
+ RegisterPlugin(FlashPlugin{})
+}
Oops, something went wrong.

0 comments on commit 22f08f0

Please sign in to comment.