Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / results.go
1 // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
2 // Revel Framework source code and usage is governed by a MIT style
3 // license that can be found in the LICENSE file.
4
5 package revel
6
7 import (
8         "bytes"
9         "encoding/json"
10         "encoding/xml"
11         "errors"
12         "fmt"
13         "io"
14         "io/ioutil"
15         "net/http"
16         "reflect"
17         "strconv"
18         "strings"
19         "time"
20 )
21
22 type Result interface {
23         Apply(req *Request, resp *Response)
24 }
25
26 // ErrorResult structure used to handles all kinds of error codes (500, 404, ..).
27 // It renders the relevant error page (errors/CODE.format, e.g. errors/500.json).
28 // If RunMode is "dev", this results in a friendly error page.
29 type ErrorResult struct {
30         ViewArgs map[string]interface{}
31         Error    error
32 }
33
34 var resultsLog = RevelLog.New("section", "results")
35
36 func (r ErrorResult) Apply(req *Request, resp *Response) {
37         format := req.Format
38         status := resp.Status
39         if status == 0 {
40                 status = http.StatusInternalServerError
41         }
42
43         contentType := ContentTypeByFilename("xxx." + format)
44         if contentType == DefaultFileContentType {
45                 contentType = "text/plain"
46         }
47         lang, _ := r.ViewArgs[CurrentLocaleViewArg].(string)
48         // Get the error template.
49         var err error
50         templatePath := fmt.Sprintf("errors/%d.%s", status, format)
51         tmpl, err := MainTemplateLoader.TemplateLang(templatePath, lang)
52
53         // This func shows a plaintext error message, in case the template rendering
54         // doesn't work.
55         showPlaintext := func(err error) {
56                 PlaintextErrorResult{fmt.Errorf("Server Error:\n%s\n\n"+
57                         "Additionally, an error occurred when rendering the error page:\n%s",
58                         r.Error, err)}.Apply(req, resp)
59         }
60
61         if tmpl == nil {
62                 if err == nil {
63                         err = fmt.Errorf("Couldn't find template %s", templatePath)
64                 }
65                 templateLog.Warn("Got an error rendering template", "error", err, "template", templatePath, "lang", lang)
66                 showPlaintext(err)
67                 return
68         }
69
70         // If it's not a revel error, wrap it in one.
71         var revelError *Error
72         switch e := r.Error.(type) {
73         case *Error:
74                 revelError = e
75         case error:
76                 revelError = &Error{
77                         Title:       "Server Error",
78                         Description: e.Error(),
79                 }
80         }
81
82         if revelError == nil {
83                 panic("no error provided")
84         }
85
86         if r.ViewArgs == nil {
87                 r.ViewArgs = make(map[string]interface{})
88         }
89         r.ViewArgs["RunMode"] = RunMode
90         r.ViewArgs["DevMode"] = DevMode
91         r.ViewArgs["Error"] = revelError
92         r.ViewArgs["Router"] = MainRouter
93
94         resultsLog.Info("Rendering error template", "template", templatePath, "error", revelError)
95
96         // Render it.
97         var b bytes.Buffer
98         err = tmpl.Render(&b, r.ViewArgs)
99
100         // If there was an error, print it in plain text.
101         if err != nil {
102                 templateLog.Warn("Got an error rendering template", "error", err, "template", templatePath, "lang", lang)
103                 showPlaintext(err)
104                 return
105         }
106
107         // need to check if we are on a websocket here
108         // net/http panics if we write to a hijacked connection
109         if req.Method == "WS" {
110                 if err := req.WebSocket.MessageSendJSON(fmt.Sprint(revelError)); err != nil {
111                         resultsLog.Error("Apply: Send failed", "error", err)
112                 }
113         } else {
114                 resp.WriteHeader(status, contentType)
115                 if _, err := b.WriteTo(resp.GetWriter()); err != nil {
116                         resultsLog.Error("Apply: Response WriteTo failed:", "error", err)
117                 }
118         }
119
120 }
121
122 type PlaintextErrorResult struct {
123         Error error
124 }
125
126 // Apply method is used when the template loader or error template is not available.
127 func (r PlaintextErrorResult) Apply(req *Request, resp *Response) {
128         resp.WriteHeader(http.StatusInternalServerError, "text/plain; charset=utf-8")
129         if _, err := resp.GetWriter().Write([]byte(r.Error.Error())); err != nil {
130                 resultsLog.Error("Apply: Write error:", "error", err)
131         }
132 }
133
134 // RenderTemplateResult action methods returns this result to request
135 // a template be rendered.
136 type RenderTemplateResult struct {
137         Template Template
138         ViewArgs map[string]interface{}
139 }
140
141 func (r *RenderTemplateResult) Apply(req *Request, resp *Response) {
142         // Handle panics when rendering templates.
143         defer func() {
144                 if err := recover(); err != nil {
145                         resultsLog.Error("Apply: panic recovery", "error", err)
146                         PlaintextErrorResult{fmt.Errorf("Template Execution Panic in %s:\n%s",
147                                 r.Template.Name(), err)}.Apply(req, resp)
148                 }
149         }()
150
151         chunked := Config.BoolDefault("results.chunked", false)
152
153         // If it's a HEAD request, throw away the bytes.
154         out := io.Writer(resp.GetWriter())
155         if req.Method == "HEAD" {
156                 out = ioutil.Discard
157         }
158
159         // In a prod mode, write the status, render, and hope for the best.
160         // (In a dev mode, always render to a temporary buffer first to avoid having
161         // error pages distorted by HTML already written)
162         if chunked && !DevMode {
163                 resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
164                 if err := r.renderOutput(out); err != nil {
165                         r.renderError(err, req, resp)
166                 }
167                 return
168         }
169
170         // Render the template into a temporary buffer, to see if there was an error
171         // rendering the template.  If not, then copy it into the response buffer.
172         // Otherwise, template render errors may result in unpredictable HTML (and
173         // would carry a 200 status code)
174         b, err := r.ToBytes()
175         if err != nil {
176                 r.renderError(err, req, resp)
177                 return
178         }
179
180         if !chunked {
181                 resp.Out.Header().Set("Content-Length", strconv.Itoa(b.Len()))
182         }
183         resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
184         if _, err := b.WriteTo(out); err != nil {
185                 resultsLog.Error("Apply: Response write failed", "error", err)
186         }
187 }
188
189 // Return a byte array and or an error object if the template failed to render
190 func (r *RenderTemplateResult) ToBytes() (b *bytes.Buffer, err error) {
191         defer func() {
192                 if rerr := recover(); rerr != nil {
193                         resultsLog.Error("ApplyBytes: panic recovery", "recover-error", rerr)
194                         err = fmt.Errorf("Template Execution Panic in %s:\n%s", r.Template.Name(), rerr)
195                 }
196         }()
197         b = &bytes.Buffer{}
198         if err = r.renderOutput(b); err == nil {
199                 if Config.BoolDefault("results.trim.html", false) {
200                         b = r.compressHtml(b)
201                 }
202         }
203         return
204 }
205
206 // Output the template to the writer, catch any panics and return as an error
207 func (r *RenderTemplateResult) renderOutput(wr io.Writer) (err error) {
208         defer func() {
209                 if rerr := recover(); rerr != nil {
210                         resultsLog.Error("ApplyBytes: panic recovery", "recover-error", rerr)
211                         err = fmt.Errorf("Template Execution Panic in %s:\n%s", r.Template.Name(), rerr)
212                 }
213         }()
214         err = r.Template.Render(wr, r.ViewArgs)
215         return
216 }
217
218 // Trimming the HTML will do the following:
219 // * Remove all leading & trailing whitespace on every line
220 // * Remove all empty lines
221 // * Attempt to keep formatting inside <pre></pre> tags
222 //
223 // This is safe unless white-space: pre; is used in css for formatting.
224 // Since there is no way to detect that, you will have to keep trimming off in these cases.
225 func (r *RenderTemplateResult) compressHtml(b *bytes.Buffer) (b2 *bytes.Buffer) {
226
227         // Allocate length of original buffer, so we can write everything without allocating again
228         b2.Grow(b.Len())
229         insidePre := false
230         for {
231                 text, err := b.ReadString('\n')
232                 // Convert to lower case for finding <pre> tags.
233                 tl := strings.ToLower(text)
234                 if strings.Contains(tl, "<pre>") {
235                         insidePre = true
236                 }
237                 // Trim if not inside a <pre> statement
238                 if !insidePre {
239                         // Cut trailing/leading whitespace
240                         text = strings.Trim(text, " \t\r\n")
241                         if len(text) > 0 {
242                                 if _, err = b2.WriteString(text); err != nil {
243                                         resultsLog.Error("Apply: ", "error", err)
244                                 }
245                                 if _, err = b2.WriteString("\n"); err != nil {
246                                         resultsLog.Error("Apply: ", "error", err)
247                                 }
248                         }
249                 } else {
250                         if _, err = b2.WriteString(text); err != nil {
251                                 resultsLog.Error("Apply: ", "error", err)
252                         }
253                 }
254                 if strings.Contains(tl, "</pre>") {
255                         insidePre = false
256                 }
257                 // We are finished
258                 if err != nil {
259                         break
260                 }
261         }
262
263         return
264 }
265
266 // Render the error in the response
267 func (r *RenderTemplateResult) renderError(err error, req *Request, resp *Response) {
268         compileError, found := err.(*Error)
269         if !found {
270                 var templateContent []string
271                 templateName, line, description := ParseTemplateError(err)
272                 if templateName == "" {
273                         templateLog.Info("Cannot determine template name to render error", "error", err)
274                         templateName = r.Template.Name()
275                         templateContent = r.Template.Content()
276
277                 } else {
278                         lang, _ := r.ViewArgs[CurrentLocaleViewArg].(string)
279                         if tmpl, err := MainTemplateLoader.TemplateLang(templateName, lang); err == nil {
280                                 templateContent = tmpl.Content()
281                         } else {
282                                 templateLog.Info("Unable to retreive template ", "error", err)
283                         }
284                 }
285                 compileError = &Error{
286                         Title:       "Template Execution Error",
287                         Path:        templateName,
288                         Description: description,
289                         Line:        line,
290                         SourceLines: templateContent,
291                 }
292         }
293         resp.Status = 500
294         resultsLog.Errorf("render: Template Execution Error (in %s): %s", compileError.Path, compileError.Description)
295         ErrorResult{r.ViewArgs, compileError}.Apply(req, resp)
296 }
297
298 type RenderHTMLResult struct {
299         html string
300 }
301
302 func (r RenderHTMLResult) Apply(req *Request, resp *Response) {
303         resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
304         if _, err := resp.GetWriter().Write([]byte(r.html)); err != nil {
305                 resultsLog.Error("Apply: Response write failed", "error", err)
306         }
307 }
308
309 type RenderJSONResult struct {
310         obj      interface{}
311         callback string
312 }
313
314 func (r RenderJSONResult) Apply(req *Request, resp *Response) {
315         var b []byte
316         var err error
317         if Config.BoolDefault("results.pretty", false) {
318                 b, err = json.MarshalIndent(r.obj, "", "  ")
319         } else {
320                 b, err = json.Marshal(r.obj)
321         }
322
323         if err != nil {
324                 ErrorResult{Error: err}.Apply(req, resp)
325                 return
326         }
327
328         if r.callback == "" {
329                 resp.WriteHeader(http.StatusOK, "application/json; charset=utf-8")
330                 if _, err = resp.GetWriter().Write(b); err != nil {
331                         resultsLog.Error("Apply: Response write failed:", "error", err)
332                 }
333                 return
334         }
335
336         resp.WriteHeader(http.StatusOK, "application/javascript; charset=utf-8")
337         if _, err = resp.GetWriter().Write([]byte(r.callback + "(")); err != nil {
338                 resultsLog.Error("Apply: Response write failed", "error", err)
339         }
340         if _, err = resp.GetWriter().Write(b); err != nil {
341                 resultsLog.Error("Apply: Response write failed", "error", err)
342         }
343         if _, err = resp.GetWriter().Write([]byte(");")); err != nil {
344                 resultsLog.Error("Apply: Response write failed", "error", err)
345         }
346 }
347
348 type RenderXMLResult struct {
349         obj interface{}
350 }
351
352 func (r RenderXMLResult) Apply(req *Request, resp *Response) {
353         var b []byte
354         var err error
355         if Config.BoolDefault("results.pretty", false) {
356                 b, err = xml.MarshalIndent(r.obj, "", "  ")
357         } else {
358                 b, err = xml.Marshal(r.obj)
359         }
360
361         if err != nil {
362                 ErrorResult{Error: err}.Apply(req, resp)
363                 return
364         }
365
366         resp.WriteHeader(http.StatusOK, "application/xml; charset=utf-8")
367         if _, err = resp.GetWriter().Write(b); err != nil {
368                 resultsLog.Error("Apply: Response write failed", "error", err)
369         }
370 }
371
372 type RenderTextResult struct {
373         text string
374 }
375
376 func (r RenderTextResult) Apply(req *Request, resp *Response) {
377         resp.WriteHeader(http.StatusOK, "text/plain; charset=utf-8")
378         if _, err := resp.GetWriter().Write([]byte(r.text)); err != nil {
379                 resultsLog.Error("Apply: Response write failed", "error", err)
380         }
381 }
382
383 type ContentDisposition string
384
385 var (
386         NoDisposition ContentDisposition = ""
387         Attachment    ContentDisposition = "attachment"
388         Inline        ContentDisposition = "inline"
389 )
390
391 type BinaryResult struct {
392         Reader   io.Reader
393         Name     string
394         Length   int64
395         Delivery ContentDisposition
396         ModTime  time.Time
397 }
398
399 func (r *BinaryResult) Apply(req *Request, resp *Response) {
400         if r.Delivery != NoDisposition {
401                 disposition := string(r.Delivery)
402                 if r.Name != "" {
403                         disposition += fmt.Sprintf(`; filename="%s"`, r.Name)
404                 }
405                 resp.Out.internalHeader.Set("Content-Disposition", disposition)
406         }
407         if resp.ContentType != "" {
408                 resp.Out.internalHeader.Set("Content-Type", resp.ContentType)
409         } else {
410                 contentType := ContentTypeByFilename(r.Name)
411                 resp.Out.internalHeader.Set("Content-Type", contentType)
412         }
413         if content, ok := r.Reader.(io.ReadSeeker); ok && r.Length < 0 {
414                 // get the size from the stream
415                 // go1.6 compatibility change, go1.6 does not define constants io.SeekStart
416                 //if size, err := content.Seek(0, io.SeekEnd); err == nil {
417                 //      if _, err = content.Seek(0, io.SeekStart); err == nil {
418                 if size, err := content.Seek(0, 2); err == nil {
419                         if _, err = content.Seek(0, 0); err == nil {
420                                 r.Length = size
421                         }
422                 }
423         }
424
425         // Write stream writes the status code to the header as well
426         if ws := resp.GetStreamWriter(); ws != nil {
427                 if err := ws.WriteStream(r.Name, r.Length, r.ModTime, r.Reader); err != nil {
428                         resultsLog.Error("Apply: Response write failed", "error", err)
429                 }
430         }
431
432         // Close the Reader if we can
433         if v, ok := r.Reader.(io.Closer); ok {
434                 _ = v.Close()
435         }
436 }
437
438 type RedirectToURLResult struct {
439         url string
440 }
441
442 func (r *RedirectToURLResult) Apply(req *Request, resp *Response) {
443         resp.Out.internalHeader.Set("Location", r.url)
444         resp.WriteHeader(http.StatusFound, "")
445 }
446
447 type RedirectToActionResult struct {
448         val  interface{}
449         args []interface{}
450 }
451
452 func (r *RedirectToActionResult) Apply(req *Request, resp *Response) {
453         url, err := getRedirectURL(r.val, r.args)
454         if err != nil {
455                 resultsLog.Error("Apply: Couldn't resolve redirect", "error", err)
456                 ErrorResult{Error: err}.Apply(req, resp)
457                 return
458         }
459         resp.Out.internalHeader.Set("Location", url)
460         resp.WriteHeader(http.StatusFound, "")
461 }
462
463 func getRedirectURL(item interface{}, args []interface{}) (string, error) {
464         // Handle strings
465         if url, ok := item.(string); ok {
466                 return url, nil
467         }
468
469         // Handle funcs
470         val := reflect.ValueOf(item)
471         typ := reflect.TypeOf(item)
472         if typ.Kind() == reflect.Func && typ.NumIn() > 0 {
473                 // Get the Controller Method
474                 recvType := typ.In(0)
475                 method := FindMethod(recvType, val)
476                 if method == nil {
477                         return "", errors.New("couldn't find method")
478                 }
479
480                 // Construct the action string (e.g. "Controller.Method")
481                 if recvType.Kind() == reflect.Ptr {
482                         recvType = recvType.Elem()
483                 }
484                 module := ModuleFromPath(recvType.PkgPath(), true)
485                 action := module.Namespace() + recvType.Name() + "." + method.Name
486                 // Fetch the action path to get the defaults
487                 pathData, found := splitActionPath(nil, action, true)
488                 if !found {
489                         return "", fmt.Errorf("Unable to redirect '%s', expected 'Controller.Action'", action)
490                 }
491
492                 // Build the map for the router to reverse
493                 // Unbind the arguments.
494                 argsByName := make(map[string]string)
495                 // Bind any static args first
496                 fixedParams := len(pathData.FixedParamsByName)
497                 methodType := pathData.TypeOfController.Method(pathData.MethodName)
498
499                 for i, argValue := range args {
500                         Unbind(argsByName, methodType.Args[i+fixedParams].Name, argValue)
501                 }
502
503                 actionDef := MainRouter.Reverse(action, argsByName)
504                 if actionDef == nil {
505                         return "", errors.New("no route for action " + action)
506                 }
507
508                 return actionDef.String(), nil
509         }
510
511         // Out of guesses
512         return "", errors.New("didn't recognize type: " + typ.String())
513 }