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