Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 377 lines (330 sloc) 11.306 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 (
abc4648 @robfig Filters, booking app works, tests pass
authored
4 "errors"
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
5 "fmt"
278b71a @jcvernaleo Server a file from memory.
jcvernaleo authored
6 "io"
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
7 "net/http"
8 "os"
9 "path/filepath"
10 "reflect"
11 "runtime"
12 "strings"
1dccfef @jgraham909 Switch to use ServeContent instead of io.copy
jgraham909 authored
13 "time"
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
14 )
15
16 type Controller struct {
f7f405a @robfig Use a Plugin to run Interceptors
authored
17 Name string // The controller name, e.g. "Application"
18 Type *ControllerType // A description of the controller type.
2f34d9c @robfig Revert the semantic change to Controller.Action and add MethodName in…
authored
19 MethodName string // The method name, e.g. "Index"
f7f405a @robfig Use a Plugin to run Interceptors
authored
20 MethodType *MethodType // A description of the invoked action type.
21 AppController interface{} // The controller that was instantiated.
2f34d9c @robfig Revert the semantic change to Controller.Action and add MethodName in…
authored
22 Action string // The fully qualified action name, e.g. "App.Index"
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
23
24 Request *Request
25 Response *Response
f7f405a @robfig Use a Plugin to run Interceptors
authored
26 Result Result
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
27
f7f405a @robfig Use a Plugin to run Interceptors
authored
28 Flash Flash // User cookie, cleared after 1 request.
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
29 Session Session // Session, stored in cookie, signed.
30 Params *Params // Parameters from URL and form (including multipart).
31 Args map[string]interface{} // Per-request scratch space.
32 RenderArgs map[string]interface{} // Args passed to the template.
33 Validation *Validation // Data validation helpers
34 }
35
8f02aa0 @robfig Filters: Performance fix (7% on JSON benchmark!)
authored
36 func NewController(req *Request, resp *Response) *Controller {
37 return &Controller{
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
38 Request: req,
39 Response: resp,
da8b6b4 @robfig Params surgery: Split out the types of params and provided a unified …
authored
40 Params: new(Params),
9c0fbf3 @robfig Initialize Args with an empty map.
authored
41 Args: map[string]interface{}{},
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
42 RenderArgs: map[string]interface{}{
43 "RunMode": RunMode,
1381d14 @robfig Add .DevMode - a boolean to indicate if RunMode is a development mode.
authored
44 "DevMode": DevMode,
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
45 },
46 }
47 }
48
724e506 @ijl Documentation additions to session, flash, field, controller, and http
ijl authored
49 // FlashParams serializes the contents of Controller.Params to the Flash
50 // cookie.
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
51 func (c *Controller) FlashParams() {
52 for key, vals := range c.Params.Values {
5dd6176 @immon flash all param values
immon authored
53 c.Flash.Out[key] = strings.Join(vals, ",")
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
54 }
55 }
56
57 func (c *Controller) SetCookie(cookie *http.Cookie) {
58 http.SetCookie(c.Response.Out, cookie)
59 }
60
61 func (c *Controller) RenderError(err error) Result {
62 return ErrorResult{c.RenderArgs, err}
63 }
64
65 // Render a template corresponding to the calling Controller method.
66 // Arguments will be added to c.RenderArgs prior to rendering the template.
67 // They are keyed on their local identifier.
68 //
69 // For example:
70 //
f164930 @robfig rev -> revel. Fixes #54
authored
71 // func (c Users) ShowUser(id int) revel.Result {
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
72 // user := loadUser(id)
73 // return c.Render(user)
74 // }
75 //
76 // This action will render views/Users/ShowUser.html, passing in an extra
77 // key-value "user": (User).
78 func (c *Controller) Render(extraRenderArgs ...interface{}) Result {
79 // Get the calling function name.
abc4648 @robfig Filters, booking app works, tests pass
authored
80 _, _, line, ok := runtime.Caller(1)
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
81 if !ok {
82 ERROR.Println("Failed to get Caller information")
83 }
84
85 // Get the extra RenderArgs passed in.
abc4648 @robfig Filters, booking app works, tests pass
authored
86 if renderArgNames, ok := c.MethodType.RenderArgNames[line]; ok {
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
87 if len(renderArgNames) == len(extraRenderArgs) {
88 for i, extraRenderArg := range extraRenderArgs {
89 c.RenderArgs[renderArgNames[i]] = extraRenderArg
90 }
91 } else {
92 ERROR.Println(len(renderArgNames), "RenderArg names found for",
93 len(extraRenderArgs), "extra RenderArgs")
94 }
95 } else {
96 ERROR.Println("No RenderArg names found for Render call on line", line,
2d44bf8 @lidaobing #428: improve error information when render failed
lidaobing authored
97 "(Action", c.Action, ")")
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
98 }
99
abc4648 @robfig Filters, booking app works, tests pass
authored
100 return c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format)
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
101 }
102
103 // A less magical way to render a template.
104 // Renders the given template, using the current RenderArgs.
105 func (c *Controller) RenderTemplate(templatePath string) Result {
106
107 // Get the Template.
108 template, err := MainTemplateLoader.Template(templatePath)
109 if err != nil {
110 return c.RenderError(err)
111 }
112
113 return &RenderTemplateResult{
114 Template: template,
115 RenderArgs: c.RenderArgs,
116 }
117 }
118
119 // Uses encoding/json.Marshal to return JSON to the client.
120 func (c *Controller) RenderJson(o interface{}) Result {
f933936 @andrewmunsell Use existing RenderJsonResult
andrewmunsell authored
121 return RenderJsonResult{o, ""}
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
122 }
123
912ee0b @andrewmunsell Add JSONP result type and method to controller
andrewmunsell authored
124 // Renders a JSONP result using encoding/json.Marshal
125 func (c *Controller) RenderJsonP(callback string, o interface{}) Result {
f933936 @andrewmunsell Use existing RenderJsonResult
andrewmunsell authored
126 return RenderJsonResult{o, callback}
912ee0b @andrewmunsell Add JSONP result type and method to controller
andrewmunsell authored
127 }
128
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
129 // Uses encoding/xml.Marshal to return XML to the client.
130 func (c *Controller) RenderXml(o interface{}) Result {
131 return RenderXmlResult{o}
132 }
133
134 // Render plaintext in response, printf style.
135 func (c *Controller) RenderText(text string, objs ...interface{}) Result {
136 finalText := text
137 if len(objs) > 0 {
ae6480d @robfig Make NotFound behave like Printf
authored
138 finalText = fmt.Sprintf(text, objs...)
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
139 }
140 return &RenderTextResult{finalText}
141 }
142
a516de8 @romanoff Added method to render html
romanoff authored
143 // Render html in response
144 func (c *Controller) RenderHtml(html string) Result {
145 return &RenderHtmlResult{html}
146 }
147
724e506 @ijl Documentation additions to session, flash, field, controller, and http
ijl authored
148 // Todo returns an HTTP 501 Not Implemented "todo" indicating that the
149 // action isn't done yet.
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
150 func (c *Controller) Todo() Result {
151 c.Response.Status = http.StatusNotImplemented
152 return c.RenderError(&Error{
153 Title: "TODO",
154 Description: "This action is not implemented",
155 })
156 }
157
724e506 @ijl Documentation additions to session, flash, field, controller, and http
ijl authored
158 // NotFound returns an HTTP 404 Not Found response whose body is the
159 // formatted string of msg and objs.
ae6480d @robfig Make NotFound behave like Printf
authored
160 func (c *Controller) NotFound(msg string, objs ...interface{}) Result {
161 finalText := msg
162 if len(objs) > 0 {
163 finalText = fmt.Sprintf(msg, objs...)
164 }
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
165 c.Response.Status = http.StatusNotFound
166 return c.RenderError(&Error{
167 Title: "Not Found",
ae6480d @robfig Make NotFound behave like Printf
authored
168 Description: finalText,
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
169 })
170 }
171
724e506 @ijl Documentation additions to session, flash, field, controller, and http
ijl authored
172 // Forbidden returns an HTTP 403 Forbidden response whose body is the
173 // formatted string of msg and objs.
c09912d @robfig Add a Forbidden() Result
authored
174 func (c *Controller) Forbidden(msg string, objs ...interface{}) Result {
175 finalText := msg
176 if len(objs) > 0 {
177 finalText = fmt.Sprintf(msg, objs...)
178 }
179 c.Response.Status = http.StatusForbidden
180 return c.RenderError(&Error{
181 Title: "Forbidden",
182 Description: finalText,
183 })
184 }
185
724e506 @ijl Documentation additions to session, flash, field, controller, and http
ijl authored
186 // RenderFile returns a file, either displayed inline or downloaded
187 // as an attachment. The name and size are taken from the file info.
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
188 func (c *Controller) RenderFile(file *os.File, delivery ContentDisposition) Result {
1c74448 @robfig Make BinaryResult accept io.Readers instead of io.ReadSeeker
authored
189 var (
190 modtime = time.Now()
191 fileInfo, err = file.Stat()
192 )
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
193 if err != nil {
194 WARN.Println("RenderFile error:", err)
195 }
196 if fileInfo != nil {
1dccfef @jgraham909 Switch to use ServeContent instead of io.copy
jgraham909 authored
197 modtime = fileInfo.ModTime()
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
198 }
34d8c95 @jcvernaleo Modify RenderFile to call RenderBinary.
jcvernaleo authored
199 return c.RenderBinary(file, filepath.Base(file.Name()), delivery, modtime)
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
200 }
201
e693f0c @jcvernaleo Adjust function name and comments to better reflect what it does.
jcvernaleo authored
202 // RenderBinary is like RenderFile() except that it instead of a file on disk,
203 // it renders data from memory (which could be a file that has not been written,
204 // the output from some function, or bytes streamed from somewhere else, as long
f5f5b26 @jcvernaleo Update some comments.
jcvernaleo authored
205 // it implements io.Reader). When called directly on something generated or
206 // streamed, modtime should mostly likely be time.Now().
34d8c95 @jcvernaleo Modify RenderFile to call RenderBinary.
jcvernaleo authored
207 func (c *Controller) RenderBinary(memfile io.Reader, filename string, delivery ContentDisposition, modtime time.Time) Result {
278b71a @jcvernaleo Server a file from memory.
jcvernaleo authored
208 return &BinaryResult{
209 Reader: memfile,
210 Name: filename,
211 Delivery: delivery,
f5f5b26 @jcvernaleo Update some comments.
jcvernaleo authored
212 Length: -1, // http.ServeContent gets the length itself unless memfile is a stream.
278b71a @jcvernaleo Server a file from memory.
jcvernaleo authored
213 ModTime: modtime,
214 }
215 }
216
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
217 // Redirect to an action or to a URL.
218 // c.Redirect(Controller.Action)
219 // c.Redirect("/controller/action")
220 // c.Redirect("/controller/%d/action", id)
221 func (c *Controller) Redirect(val interface{}, args ...interface{}) Result {
222 if url, ok := val.(string); ok {
223 if len(args) == 0 {
224 return &RedirectToUrlResult{url}
225 }
226 return &RedirectToUrlResult{fmt.Sprintf(url, args...)}
227 }
228 return &RedirectToActionResult{val}
229 }
2250607 @tmbrggmn Added basic version of i18n sample application.
tmbrggmn authored
230
8751a76 @robfig Tweak error formatting
authored
231 // Perform a message lookup for the given message name using the given arguments
2250607 @tmbrggmn Added basic version of i18n sample application.
tmbrggmn authored
232 // using the current language defined for this controller.
233 //
234 // The current language is set by the i18n plugin.
235 func (c *Controller) Message(message string, args ...interface{}) (value string) {
ae0235f @tmbrggmn Fixed minor issues found in pull request
tmbrggmn authored
236 return Message(c.Request.Locale, message, args...)
2250607 @tmbrggmn Added basic version of i18n sample application.
tmbrggmn authored
237 }
abc4648 @robfig Filters, booking app works, tests pass
authored
238
239 // SetAction sets the action that is being invoked in the current request.
240 // It sets the following properties: Name, Action, Type, MethodType
2f34d9c @robfig Revert the semantic change to Controller.Action and add MethodName in…
authored
241 func (c *Controller) SetAction(controllerName, methodName string) error {
abc4648 @robfig Filters, booking app works, tests pass
authored
242
2f34d9c @robfig Revert the semantic change to Controller.Action and add MethodName in…
authored
243 // Look up the controller and method types.
abc4648 @robfig Filters, booking app works, tests pass
authored
244 var ok bool
245 if c.Type, ok = controllers[strings.ToLower(controllerName)]; !ok {
246 return errors.New("revel/controller: failed to find controller " + controllerName)
247 }
2f34d9c @robfig Revert the semantic change to Controller.Action and add MethodName in…
authored
248 if c.MethodType = c.Type.Method(methodName); c.MethodType == nil {
249 return errors.New("revel/controller: failed to find action " + methodName)
abc4648 @robfig Filters, booking app works, tests pass
authored
250 }
251
1342d8e @pix64 Fix build failure
pix64 authored
252 c.Name, c.MethodName = c.Type.Type.Name(), methodName
94317fe @pix64 Use c.Name instead of c.Type.Type.Name() a second time
pix64 authored
253 c.Action = c.Name + "." + c.MethodName
2e3f6c5 @pix64 Make Controller Name use Controller Type instead of router output
pix64 authored
254
abc4648 @robfig Filters, booking app works, tests pass
authored
255 // Instantiate the controller.
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
256 c.AppController = initNewAppController(c.Type, c).Interface()
abc4648 @robfig Filters, booking app works, tests pass
authored
257
258 return nil
259 }
260
261 // This is a helper that initializes (zeros) a new app controller value.
5f8324e @robfig Improved testing
authored
262 // Specifically, it sets all *revel.Controller embedded types to the provided controller.
abc4648 @robfig Filters, booking app works, tests pass
authored
263 // Returns a value representing a pointer to the new app controller.
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
264 func initNewAppController(appControllerType *ControllerType, c *Controller) reflect.Value {
265 var (
266 appControllerPtr = reflect.New(appControllerType.Type)
267 appController = appControllerPtr.Elem()
268 cValue = reflect.ValueOf(c)
269 )
270 for _, index := range appControllerType.ControllerIndexes {
271 appController.FieldByIndex(index).Set(cValue)
272 }
273 return appControllerPtr
274 }
275
276 func findControllers(appControllerType reflect.Type) (indexes [][]int) {
5f8324e @robfig Improved testing
authored
277 // It might be a multi-level embedding. To find the controllers, we follow
278 // every anonymous field, using breadth-first search.
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
279 type nodeType struct {
280 val reflect.Value
281 index []int
282 }
abc4648 @robfig Filters, booking app works, tests pass
authored
283 appControllerPtr := reflect.New(appControllerType)
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
284 queue := []nodeType{{appControllerPtr, []int{}}}
285 for len(queue) > 0 {
abc4648 @robfig Filters, booking app works, tests pass
authored
286 // Get the next value and de-reference it if necessary.
287 var (
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
288 node = queue[0]
289 elem = node.val
290 elemType = elem.Type()
abc4648 @robfig Filters, booking app works, tests pass
authored
291 )
292 if elemType.Kind() == reflect.Ptr {
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
293 elem = elem.Elem()
abc4648 @robfig Filters, booking app works, tests pass
authored
294 elemType = elem.Type()
295 }
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
296 queue = queue[1:]
abc4648 @robfig Filters, booking app works, tests pass
authored
297
298 // Look at all the struct fields.
299 for i := 0; i < elem.NumField(); i++ {
300 // If this is not an anonymous field, skip it.
301 structField := elemType.Field(i)
302 if !structField.Anonymous {
303 continue
304 }
305
306 fieldValue := elem.Field(i)
307 fieldType := structField.Type
308
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
309 // If it's a Controller, record the field indexes to get here.
abc4648 @robfig Filters, booking app works, tests pass
authored
310 if fieldType == controllerPtrType {
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
311 indexes = append(indexes, append(node.index, i))
abc4648 @robfig Filters, booking app works, tests pass
authored
312 continue
313 }
314
5f8324e @robfig Improved testing
authored
315 queue = append(queue,
316 nodeType{fieldValue, append(append([]int{}, node.index...), i)})
abc4648 @robfig Filters, booking app works, tests pass
authored
317 }
318 }
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
319 return
abc4648 @robfig Filters, booking app works, tests pass
authored
320 }
321
322 // Controller registry and types.
323
324 type ControllerType struct {
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
325 Type reflect.Type
326 Methods []*MethodType
327 ControllerIndexes [][]int // FieldByIndex to all embedded *Controllers
abc4648 @robfig Filters, booking app works, tests pass
authored
328 }
329
330 type MethodType struct {
331 Name string
332 Args []*MethodArg
333 RenderArgNames map[int][]string
334 lowerName string
335 }
336
337 type MethodArg struct {
338 Name string
339 Type reflect.Type
340 }
341
724e506 @ijl Documentation additions to session, flash, field, controller, and http
ijl authored
342 // Method searches for a given exported method (case insensitive)
abc4648 @robfig Filters, booking app works, tests pass
authored
343 func (ct *ControllerType) Method(name string) *MethodType {
344 lowerName := strings.ToLower(name)
345 for _, method := range ct.Methods {
346 if method.lowerName == lowerName {
347 return method
348 }
349 }
350 return nil
351 }
352
353 var controllers = make(map[string]*ControllerType)
354
355 // Register a Controller and its Methods with Revel.
356 func RegisterController(c interface{}, methods []*MethodType) {
357 // De-star the controller type
358 // (e.g. given TypeOf((*Application)(nil)), want TypeOf(Application))
359 var t reflect.Type = reflect.TypeOf(c)
360 var elem reflect.Type = t.Elem()
361
362 // De-star all of the method arg types too.
363 for _, m := range methods {
364 m.lowerName = strings.ToLower(m.Name)
365 for _, arg := range m.Args {
366 arg.Type = arg.Type.Elem()
367 }
368 }
369
2a7e77b @robfig Pre-calculate the indexes to embedded controllers.
authored
370 controllers[strings.ToLower(elem.Name())] = &ControllerType{
371 Type: elem,
372 Methods: methods,
373 ControllerIndexes: findControllers(elem),
374 }
abc4648 @robfig Filters, booking app works, tests pass
authored
375 TRACE.Printf("Registered controller: %s", elem.Name())
376 }
Something went wrong with that request. Please try again.