Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 288 lines (252 sloc) 8.255 kB
f164930 @robfig rev -> revel. Fixes #54
authored
1 package revel
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
2
3 import (
4 "database/sql"
5 "fmt"
6 "net/http"
7 "os"
8 "path/filepath"
9 "reflect"
10 "runtime"
11 "runtime/debug"
12 "strings"
13 )
14
15 type Controller struct {
f7f405a @robfig Use a Plugin to run Interceptors
authored
16 Name string // The controller name, e.g. "Application"
17 Type *ControllerType // A description of the controller type.
18 MethodType *MethodType // A description of the invoked action type.
19 AppController interface{} // The controller that was instantiated.
b614953 @robfig Add Controller to RenderArgs, and add Action property
authored
20 Action string // The full action name, e.g. "Application.Index"
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
21
22 Request *Request
23 Response *Response
f7f405a @robfig Use a Plugin to run Interceptors
authored
24 Result Result
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
25
f7f405a @robfig Use a Plugin to run Interceptors
authored
26 Flash Flash // User cookie, cleared after 1 request.
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
27 Session Session // Session, stored in cookie, signed.
28 Params *Params // Parameters from URL and form (including multipart).
29 Args map[string]interface{} // Per-request scratch space.
30 RenderArgs map[string]interface{} // Args passed to the template.
31 Validation *Validation // Data validation helpers
ae0235f @tmbrggmn Fixed minor issues found in pull request
tmbrggmn authored
32 Txn *sql.Tx // Nil by default, but may be used by the app / plugins
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
33 }
34
35 func NewController(req *Request, resp *Response, ct *ControllerType) *Controller {
b614953 @robfig Add Controller to RenderArgs, and add Action property
authored
36 c := &Controller{
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
37 Name: ct.Type.Name(),
38 Type: ct,
39 Request: req,
40 Response: resp,
41 Params: ParseParams(req),
9c0fbf3 @robfig Initialize Args with an empty map.
authored
42 Args: map[string]interface{}{},
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
43 RenderArgs: map[string]interface{}{
44 "RunMode": RunMode,
45 },
46 }
b614953 @robfig Add Controller to RenderArgs, and add Action property
authored
47 c.RenderArgs["Controller"] = c
48 return c
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
49 }
50
51 func (c *Controller) FlashParams() {
52 for key, vals := range c.Params.Values {
53 c.Flash.Out[key] = vals[0]
54 }
55 }
56
57 func (c *Controller) SetCookie(cookie *http.Cookie) {
58 http.SetCookie(c.Response.Out, cookie)
59 }
60
61 // Invoke the given method, save headers/cookies to the response, and apply the
62 // result. (e.g. render a template to the response)
63 func (c *Controller) Invoke(appControllerPtr reflect.Value, method reflect.Value, methodArgs []reflect.Value) {
64
65 // Handle panics.
66 defer func() {
67 if err := recover(); err != nil {
68 handleInvocationPanic(c, err)
69 }
d34c8ee @robfig Add a FINALLY plugin & interception time.
authored
70
71 plugins.Finally(c)
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
72 }()
73
74 // Clean up from the request.
75 defer func() {
76 // Delete temp files.
77 if c.Request.MultipartForm != nil {
78 err := c.Request.MultipartForm.RemoveAll()
79 if err != nil {
80 WARN.Println("Error removing temporary files:", err)
81 }
82 }
83
84 for _, tmpFile := range c.Params.tmpFiles {
85 err := os.Remove(tmpFile.Name())
86 if err != nil {
87 WARN.Println("Could not remove upload temp file:", err)
88 }
89 }
90 }()
91
92 // Run the plugins.
93 plugins.BeforeRequest(c)
94
f7f405a @robfig Use a Plugin to run Interceptors
authored
95 if c.Result == nil {
d34c8ee @robfig Add a FINALLY plugin & interception time.
authored
96 // Invoke the action.
015ff44 @robfig Git accident.. revert last commit
authored
97 var resultValue reflect.Value
98 if method.Type().IsVariadic() {
99 resultValue = method.CallSlice(methodArgs)[0]
100 } else {
101 resultValue = method.Call(methodArgs)[0]
102 }
d34c8ee @robfig Add a FINALLY plugin & interception time.
authored
103 if resultValue.Kind() == reflect.Interface && !resultValue.IsNil() {
104 c.Result = resultValue.Interface().(Result)
105 }
106
107 plugins.AfterRequest(c)
108 if c.Result == nil {
109 return
110 }
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
111 }
112
113 // Apply the result, which generally results in the ResponseWriter getting written.
f7f405a @robfig Use a Plugin to run Interceptors
authored
114 c.Result.Apply(c.Request, c.Response)
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
115 }
116
117 // This function handles a panic in an action invocation.
118 // It cleans up the stack trace, logs it, and displays an error page.
119 func handleInvocationPanic(c *Controller, err interface{}) {
120 plugins.OnException(c, err)
121
122 error := NewErrorFromPanic(err)
123 if error == nil {
124 c.Response.Out.WriteHeader(500)
8751a76 @robfig Tweak error formatting
authored
125 c.Response.Out.Write(debug.Stack())
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
126 return
127 }
128
8751a76 @robfig Tweak error formatting
authored
129 ERROR.Print(err, "\n", error.Stack)
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
130 c.RenderError(error).Apply(c.Request, c.Response)
131 }
132
133 func (c *Controller) RenderError(err error) Result {
134 return ErrorResult{c.RenderArgs, err}
135 }
136
137 // Render a template corresponding to the calling Controller method.
138 // Arguments will be added to c.RenderArgs prior to rendering the template.
139 // They are keyed on their local identifier.
140 //
141 // For example:
142 //
f164930 @robfig rev -> revel. Fixes #54
authored
143 // func (c Users) ShowUser(id int) revel.Result {
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
144 // user := loadUser(id)
145 // return c.Render(user)
146 // }
147 //
148 // This action will render views/Users/ShowUser.html, passing in an extra
149 // key-value "user": (User).
150 func (c *Controller) Render(extraRenderArgs ...interface{}) Result {
151 // Get the calling function name.
152 pc, _, line, ok := runtime.Caller(1)
153 if !ok {
154 ERROR.Println("Failed to get Caller information")
155 return nil
156 }
157 // e.g. sample/app/controllers.(*Application).Index
158 var fqViewName string = runtime.FuncForPC(pc).Name()
159 var viewName string = fqViewName[strings.LastIndex(fqViewName, ".")+1 : len(fqViewName)]
160
161 // Determine what method we are in.
162 // (e.g. the invoked controller method might have delegated to another method)
163 methodType := c.MethodType
164 if methodType.Name != viewName {
165 methodType = c.Type.Method(viewName)
166 if methodType == nil {
167 return c.RenderError(fmt.Errorf(
168 "No Method %s in Controller %s when loading the view."+
169 " (delegating Render is only supported within the same controller)",
170 viewName, c.Name))
171 }
172 }
173
174 // Get the extra RenderArgs passed in.
175 if renderArgNames, ok := methodType.RenderArgNames[line]; ok {
176 if len(renderArgNames) == len(extraRenderArgs) {
177 for i, extraRenderArg := range extraRenderArgs {
178 c.RenderArgs[renderArgNames[i]] = extraRenderArg
179 }
180 } else {
181 ERROR.Println(len(renderArgNames), "RenderArg names found for",
182 len(extraRenderArgs), "extra RenderArgs")
183 }
184 } else {
185 ERROR.Println("No RenderArg names found for Render call on line", line,
186 "(Method", methodType, ", ViewName", viewName, ")")
187 }
188
189 return c.RenderTemplate(c.Name + "/" + viewName + ".html")
190 }
191
192 // A less magical way to render a template.
193 // Renders the given template, using the current RenderArgs.
194 func (c *Controller) RenderTemplate(templatePath string) Result {
195
196 // Get the Template.
197 template, err := MainTemplateLoader.Template(templatePath)
198 if err != nil {
199 return c.RenderError(err)
200 }
201
202 return &RenderTemplateResult{
203 Template: template,
204 RenderArgs: c.RenderArgs,
205 }
206 }
207
208 // Uses encoding/json.Marshal to return JSON to the client.
209 func (c *Controller) RenderJson(o interface{}) Result {
210 return RenderJsonResult{o}
211 }
212
213 // Uses encoding/xml.Marshal to return XML to the client.
214 func (c *Controller) RenderXml(o interface{}) Result {
215 return RenderXmlResult{o}
216 }
217
218 // Render plaintext in response, printf style.
219 func (c *Controller) RenderText(text string, objs ...interface{}) Result {
220 finalText := text
221 if len(objs) > 0 {
ae6480d @robfig Make NotFound behave like Printf
authored
222 finalText = fmt.Sprintf(text, objs...)
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
223 }
224 return &RenderTextResult{finalText}
225 }
226
227 // Render a "todo" indicating that the action isn't done yet.
228 func (c *Controller) Todo() Result {
229 c.Response.Status = http.StatusNotImplemented
230 return c.RenderError(&Error{
231 Title: "TODO",
232 Description: "This action is not implemented",
233 })
234 }
235
ae6480d @robfig Make NotFound behave like Printf
authored
236 func (c *Controller) NotFound(msg string, objs ...interface{}) Result {
237 finalText := msg
238 if len(objs) > 0 {
239 finalText = fmt.Sprintf(msg, objs...)
240 }
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
241 c.Response.Status = http.StatusNotFound
242 return c.RenderError(&Error{
243 Title: "Not Found",
ae6480d @robfig Make NotFound behave like Printf
authored
244 Description: finalText,
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
245 })
246 }
247
248 // Return a file, either displayed inline or downloaded as an attachment.
249 // The name and size are taken from the file info.
250 func (c *Controller) RenderFile(file *os.File, delivery ContentDisposition) Result {
251 var length int64 = -1
252 fileInfo, err := file.Stat()
253 if err != nil {
254 WARN.Println("RenderFile error:", err)
255 }
256 if fileInfo != nil {
257 length = fileInfo.Size()
258 }
259 return &BinaryResult{
260 Reader: file,
261 Name: filepath.Base(file.Name()),
262 Length: length,
263 Delivery: delivery,
264 }
265 }
266
267 // Redirect to an action or to a URL.
268 // c.Redirect(Controller.Action)
269 // c.Redirect("/controller/action")
270 // c.Redirect("/controller/%d/action", id)
271 func (c *Controller) Redirect(val interface{}, args ...interface{}) Result {
272 if url, ok := val.(string); ok {
273 if len(args) == 0 {
274 return &RedirectToUrlResult{url}
275 }
276 return &RedirectToUrlResult{fmt.Sprintf(url, args...)}
277 }
278 return &RedirectToActionResult{val}
279 }
2250607 @tmbrggmn Added basic version of i18n sample application.
tmbrggmn authored
280
8751a76 @robfig Tweak error formatting
authored
281 // Perform a message lookup for the given message name using the given arguments
2250607 @tmbrggmn Added basic version of i18n sample application.
tmbrggmn authored
282 // using the current language defined for this controller.
283 //
284 // The current language is set by the i18n plugin.
285 func (c *Controller) Message(message string, args ...interface{}) (value string) {
ae0235f @tmbrggmn Fixed minor issues found in pull request
tmbrggmn authored
286 return Message(c.Request.Locale, message, args...)
2250607 @tmbrggmn Added basic version of i18n sample application.
tmbrggmn authored
287 }
Something went wrong with that request. Please try again.