Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 289 lines (253 sloc) 8.28 kB
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
1 package rev
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 stack := string(debug.Stack())
122 ERROR.Println(err, "\n", stack)
123
124 error := NewErrorFromPanic(err)
125 if error == nil {
126 c.Response.Out.WriteHeader(500)
127 c.Response.Out.Write([]byte(stack))
128 return
129 }
130
131 c.RenderError(error).Apply(c.Request, c.Response)
132 }
133
134 func (c *Controller) RenderError(err error) Result {
135 return ErrorResult{c.RenderArgs, err}
136 }
137
138 // Render a template corresponding to the calling Controller method.
139 // Arguments will be added to c.RenderArgs prior to rendering the template.
140 // They are keyed on their local identifier.
141 //
142 // For example:
143 //
144 // func (c Users) ShowUser(id int) rev.Result {
145 // user := loadUser(id)
146 // return c.Render(user)
147 // }
148 //
149 // This action will render views/Users/ShowUser.html, passing in an extra
150 // key-value "user": (User).
151 func (c *Controller) Render(extraRenderArgs ...interface{}) Result {
152 // Get the calling function name.
153 pc, _, line, ok := runtime.Caller(1)
154 if !ok {
155 ERROR.Println("Failed to get Caller information")
156 return nil
157 }
158 // e.g. sample/app/controllers.(*Application).Index
159 var fqViewName string = runtime.FuncForPC(pc).Name()
160 var viewName string = fqViewName[strings.LastIndex(fqViewName, ".")+1 : len(fqViewName)]
161
162 // Determine what method we are in.
163 // (e.g. the invoked controller method might have delegated to another method)
164 methodType := c.MethodType
165 if methodType.Name != viewName {
166 methodType = c.Type.Method(viewName)
167 if methodType == nil {
168 return c.RenderError(fmt.Errorf(
169 "No Method %s in Controller %s when loading the view."+
170 " (delegating Render is only supported within the same controller)",
171 viewName, c.Name))
172 }
173 }
174
175 // Get the extra RenderArgs passed in.
176 if renderArgNames, ok := methodType.RenderArgNames[line]; ok {
177 if len(renderArgNames) == len(extraRenderArgs) {
178 for i, extraRenderArg := range extraRenderArgs {
179 c.RenderArgs[renderArgNames[i]] = extraRenderArg
180 }
181 } else {
182 ERROR.Println(len(renderArgNames), "RenderArg names found for",
183 len(extraRenderArgs), "extra RenderArgs")
184 }
185 } else {
186 ERROR.Println("No RenderArg names found for Render call on line", line,
187 "(Method", methodType, ", ViewName", viewName, ")")
188 }
189
190 return c.RenderTemplate(c.Name + "/" + viewName + ".html")
191 }
192
193 // A less magical way to render a template.
194 // Renders the given template, using the current RenderArgs.
195 func (c *Controller) RenderTemplate(templatePath string) Result {
196
197 // Get the Template.
198 template, err := MainTemplateLoader.Template(templatePath)
199 if err != nil {
200 return c.RenderError(err)
201 }
202
203 return &RenderTemplateResult{
204 Template: template,
205 RenderArgs: c.RenderArgs,
206 }
207 }
208
209 // Uses encoding/json.Marshal to return JSON to the client.
210 func (c *Controller) RenderJson(o interface{}) Result {
211 return RenderJsonResult{o}
212 }
213
214 // Uses encoding/xml.Marshal to return XML to the client.
215 func (c *Controller) RenderXml(o interface{}) Result {
216 return RenderXmlResult{o}
217 }
218
219 // Render plaintext in response, printf style.
220 func (c *Controller) RenderText(text string, objs ...interface{}) Result {
221 finalText := text
222 if len(objs) > 0 {
ae6480d @robfig Make NotFound behave like Printf
authored
223 finalText = fmt.Sprintf(text, objs...)
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
224 }
225 return &RenderTextResult{finalText}
226 }
227
228 // Render a "todo" indicating that the action isn't done yet.
229 func (c *Controller) Todo() Result {
230 c.Response.Status = http.StatusNotImplemented
231 return c.RenderError(&Error{
232 Title: "TODO",
233 Description: "This action is not implemented",
234 })
235 }
236
ae6480d @robfig Make NotFound behave like Printf
authored
237 func (c *Controller) NotFound(msg string, objs ...interface{}) Result {
238 finalText := msg
239 if len(objs) > 0 {
240 finalText = fmt.Sprintf(msg, objs...)
241 }
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
242 c.Response.Status = http.StatusNotFound
243 return c.RenderError(&Error{
244 Title: "Not Found",
ae6480d @robfig Make NotFound behave like Printf
authored
245 Description: finalText,
22f08f0 @robfig Refactor mvc.go into {mvc,controller,session,flash,params}.go
authored
246 })
247 }
248
249 // Return a file, either displayed inline or downloaded as an attachment.
250 // The name and size are taken from the file info.
251 func (c *Controller) RenderFile(file *os.File, delivery ContentDisposition) Result {
252 var length int64 = -1
253 fileInfo, err := file.Stat()
254 if err != nil {
255 WARN.Println("RenderFile error:", err)
256 }
257 if fileInfo != nil {
258 length = fileInfo.Size()
259 }
260 return &BinaryResult{
261 Reader: file,
262 Name: filepath.Base(file.Name()),
263 Length: length,
264 Delivery: delivery,
265 }
266 }
267
268 // Redirect to an action or to a URL.
269 // c.Redirect(Controller.Action)
270 // c.Redirect("/controller/action")
271 // c.Redirect("/controller/%d/action", id)
272 func (c *Controller) Redirect(val interface{}, args ...interface{}) Result {
273 if url, ok := val.(string); ok {
274 if len(args) == 0 {
275 return &RedirectToUrlResult{url}
276 }
277 return &RedirectToUrlResult{fmt.Sprintf(url, args...)}
278 }
279 return &RedirectToActionResult{val}
280 }
2250607 @tmbrggmn Added basic version of i18n sample application.
tmbrggmn authored
281
282 // Perform a message lookup for the given message name using the given arguments
283 // using the current language defined for this controller.
284 //
285 // The current language is set by the i18n plugin.
286 func (c *Controller) Message(message string, args ...interface{}) (value string) {
ae0235f @tmbrggmn Fixed minor issues found in pull request
tmbrggmn authored
287 return Message(c.Request.Locale, message, args...)
2250607 @tmbrggmn Added basic version of i18n sample application.
tmbrggmn authored
288 }
Something went wrong with that request. Please try again.