Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 333 lines (291 sloc) 9.713 kb
f164930 Rob Figueiredo rev -> revel. Fixes #54
authored
1 package revel
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
2
3 import (
4 "database/sql"
abc4648 Rob Figueiredo Filters, booking app works, tests pass
authored
5 "errors"
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
6 "fmt"
7 "net/http"
8 "os"
9 "path/filepath"
10 "reflect"
11 "runtime"
12 "strings"
1dccfef Jeff Graham Switch to use ServeContent instead of io.copy
jgraham909 authored
13 "time"
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
14 )
15
16 type Controller struct {
f7f405a Rob Figueiredo Use a Plugin to run Interceptors
authored
17 Name string // The controller name, e.g. "Application"
abc4648 Rob Figueiredo Filters, booking app works, tests pass
authored
18 Action string // The method name, e.g. "Index"
f7f405a Rob Figueiredo Use a Plugin to run Interceptors
authored
19 Type *ControllerType // A description of the controller type.
20 MethodType *MethodType // A description of the invoked action type.
21 AppController interface{} // The controller that was instantiated.
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
22
23 Request *Request
24 Response *Response
f7f405a Rob Figueiredo Use a Plugin to run Interceptors
authored
25 Result Result
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
26
f7f405a Rob Figueiredo Use a Plugin to run Interceptors
authored
27 Flash Flash // User cookie, cleared after 1 request.
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
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
ae0235f Tom Bruggeman Fixed minor issues found in pull request
tmbrggmn authored
33 Txn *sql.Tx // Nil by default, but may be used by the app / plugins
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
34 }
35
8f02aa0 Rob Figueiredo Filters: Performance fix (7% on JSON benchmark!)
authored
36 func NewController(req *Request, resp *Response) *Controller {
37 return &Controller{
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
38 Request: req,
39 Response: resp,
9c0fbf3 Rob Figueiredo Initialize Args with an empty map.
authored
40 Args: map[string]interface{}{},
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
41 RenderArgs: map[string]interface{}{
42 "RunMode": RunMode,
1381d14 Rob Figueiredo Add .DevMode - a boolean to indicate if RunMode is a development mode.
authored
43 "DevMode": DevMode,
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
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 Rob Figueiredo rev -> revel. Fixes #54
authored
68 // func (c Users) ShowUser(id int) revel.Result {
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
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 Rob Figueiredo Filters, booking app works, tests pass
authored
77 _, _, line, ok := runtime.Caller(1)
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
78 if !ok {
79 ERROR.Println("Failed to get Caller information")
80 }
81
82 // Get the extra RenderArgs passed in.
abc4648 Rob Figueiredo Filters, booking app works, tests pass
authored
83 if renderArgNames, ok := c.MethodType.RenderArgNames[line]; ok {
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
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 Rob Figueiredo Filters, booking app works, tests pass
authored
94 "(Method", c.MethodType.Name, ")")
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
95 }
96
abc4648 Rob Figueiredo Filters, booking app works, tests pass
authored
97 return c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format)
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
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 Rob Figueiredo Make NotFound behave like Printf
authored
130 finalText = fmt.Sprintf(text, objs...)
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
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 Rob Figueiredo Make NotFound behave like Printf
authored
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 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
149 c.Response.Status = http.StatusNotFound
150 return c.RenderError(&Error{
151 Title: "Not Found",
ae6480d Rob Figueiredo Make NotFound behave like Printf
authored
152 Description: finalText,
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
153 })
154 }
155
c09912d Rob Figueiredo Add a Forbidden() Result
authored
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 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
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 Rob Figueiredo Make BinaryResult accept io.Readers instead of io.ReadSeeker
authored
171 var (
172 modtime = time.Now()
173 fileInfo, err = file.Stat()
174 )
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
175 if err != nil {
176 WARN.Println("RenderFile error:", err)
177 }
178 if fileInfo != nil {
1dccfef Jeff Graham Switch to use ServeContent instead of io.copy
jgraham909 authored
179 modtime = fileInfo.ModTime()
22f08f0 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
180 }
181 return &BinaryResult{
1c74448 Rob Figueiredo Make BinaryResult accept io.Readers instead of io.ReadSeeker
authored
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 Rob Figueiredo Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
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 Tom Bruggeman Added basic version of i18n sample application.
tmbrggmn authored
203
8751a76 Rob Figueiredo Tweak error formatting
authored
204 // Perform a message lookup for the given message name using the given arguments
2250607 Tom Bruggeman Added basic version of i18n sample application.
tmbrggmn authored
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 Tom Bruggeman Fixed minor issues found in pull request
tmbrggmn authored
209 return Message(c.Request.Locale, message, args...)
2250607 Tom Bruggeman Added basic version of i18n sample application.
tmbrggmn authored
210 }
abc4648 Rob Figueiredo Filters, booking app works, tests pass
authored
211
212 // SetAction sets the action that is being invoked in the current request.
213 // It sets the following properties: Name, Action, Type, MethodType
214 func (c *Controller) SetAction(controllerName, actionName string) error {
215 c.Name, c.Action = controllerName, actionName
216
217 // Look up the controller and action types.
218 var ok bool
219 if c.Type, ok = controllers[strings.ToLower(controllerName)]; !ok {
220 return errors.New("revel/controller: failed to find controller " + controllerName)
221 }
222 if c.MethodType = c.Type.Method(actionName); c.MethodType == nil {
223 return errors.New("revel/controller: failed to find action " + actionName)
224 }
225
226 // Instantiate the controller.
227 c.AppController = initNewAppController(c.Type.Type, c).Interface()
228
229 return nil
230 }
231
232 // This is a helper that initializes (zeros) a new app controller value.
233 // Generally, everything is set to its zero value, except:
234 // 1. Embedded controller pointers are newed up.
235 // 2. The revel.Controller embedded type is set to the value provided.
236 // Returns a value representing a pointer to the new app controller.
237 func initNewAppController(appControllerType reflect.Type, c *Controller) reflect.Value {
238 // It might be a multi-level embedding, so we have to create new controllers
239 // at every level of the hierarchy. To find the controllers, we follow every
240 // anonymous field, using breadth-first search.
241 appControllerPtr := reflect.New(appControllerType)
242 valueQueue := []reflect.Value{appControllerPtr}
243 for len(valueQueue) > 0 {
244 // Get the next value and de-reference it if necessary.
245 var (
246 value = valueQueue[0]
247 elem = value
248 elemType = value.Type()
249 )
250 if elemType.Kind() == reflect.Ptr {
251 elem = value.Elem()
252 elemType = elem.Type()
253 }
254 valueQueue = valueQueue[1:]
255
256 // Look at all the struct fields.
257 for i := 0; i < elem.NumField(); i++ {
258 // If this is not an anonymous field, skip it.
259 structField := elemType.Field(i)
260 if !structField.Anonymous {
261 continue
262 }
263
264 fieldValue := elem.Field(i)
265 fieldType := structField.Type
266
267 // If it's a Controller, set it to the new instance.
268 if fieldType == controllerPtrType {
269 fieldValue.Set(reflect.ValueOf(c))
270 continue
271 }
272
273 // Else, add it to the valueQueue, after instantiating (if necessary).
274 if fieldValue.Kind() == reflect.Ptr {
275 fieldValue.Set(reflect.New(fieldType.Elem()))
276 }
277 valueQueue = append(valueQueue, fieldValue)
278 }
279 }
280 return appControllerPtr
281 }
282
283 // Controller registry and types.
284
285 type ControllerType struct {
286 Type reflect.Type
287 Methods []*MethodType
288 }
289
290 type MethodType struct {
291 Name string
292 Args []*MethodArg
293 RenderArgNames map[int][]string
294 lowerName string
295 }
296
297 type MethodArg struct {
298 Name string
299 Type reflect.Type
300 }
301
302 // Searches for a given exported method (case insensitive)
303 func (ct *ControllerType) Method(name string) *MethodType {
304 lowerName := strings.ToLower(name)
305 for _, method := range ct.Methods {
306 if method.lowerName == lowerName {
307 return method
308 }
309 }
310 return nil
311 }
312
313 var controllers = make(map[string]*ControllerType)
314
315 // Register a Controller and its Methods with Revel.
316 func RegisterController(c interface{}, methods []*MethodType) {
317 // De-star the controller type
318 // (e.g. given TypeOf((*Application)(nil)), want TypeOf(Application))
319 var t reflect.Type = reflect.TypeOf(c)
320 var elem reflect.Type = t.Elem()
321
322 // De-star all of the method arg types too.
323 for _, m := range methods {
324 m.lowerName = strings.ToLower(m.Name)
325 for _, arg := range m.Args {
326 arg.Type = arg.Type.Elem()
327 }
328 }
329
330 controllers[strings.ToLower(elem.Name())] = &ControllerType{Type: elem, Methods: methods}
331 TRACE.Printf("Registered controller: %s", elem.Name())
332 }
Something went wrong with that request. Please try again.