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