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.
22 type Result interface {
23 Apply(req *Request, resp *Response)
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{}
34 var resultsLog = RevelLog.New("section", "results")
36 func (r ErrorResult) Apply(req *Request, resp *Response) {
40 status = http.StatusInternalServerError
43 contentType := ContentTypeByFilename("xxx." + format)
44 if contentType == DefaultFileContentType {
45 contentType = "text/plain"
47 lang, _ := r.ViewArgs[CurrentLocaleViewArg].(string)
48 // Get the error template.
50 templatePath := fmt.Sprintf("errors/%d.%s", status, format)
51 tmpl, err := MainTemplateLoader.TemplateLang(templatePath, lang)
53 // This func shows a plaintext error message, in case the template rendering
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)
63 err = fmt.Errorf("Couldn't find template %s", templatePath)
65 templateLog.Warn("Got an error rendering template", "error", err, "template", templatePath, "lang", lang)
70 // If it's not a revel error, wrap it in one.
72 switch e := r.Error.(type) {
77 Title: "Server Error",
78 Description: e.Error(),
82 if revelError == nil {
83 panic("no error provided")
86 if r.ViewArgs == nil {
87 r.ViewArgs = make(map[string]interface{})
89 r.ViewArgs["RunMode"] = RunMode
90 r.ViewArgs["DevMode"] = DevMode
91 r.ViewArgs["Error"] = revelError
92 r.ViewArgs["Router"] = MainRouter
94 resultsLog.Info("Rendering error template", "template", templatePath, "error", revelError)
98 err = tmpl.Render(&b, r.ViewArgs)
100 // If there was an error, print it in plain text.
102 templateLog.Warn("Got an error rendering template", "error", err, "template", templatePath, "lang", lang)
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)
114 resp.WriteHeader(status, contentType)
115 if _, err := b.WriteTo(resp.GetWriter()); err != nil {
116 resultsLog.Error("Apply: Response WriteTo failed:", "error", err)
122 type PlaintextErrorResult struct {
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)
134 // RenderTemplateResult action methods returns this result to request
135 // a template be rendered.
136 type RenderTemplateResult struct {
138 ViewArgs map[string]interface{}
141 func (r *RenderTemplateResult) Apply(req *Request, resp *Response) {
142 // Handle panics when rendering templates.
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)
151 chunked := Config.BoolDefault("results.chunked", false)
153 // If it's a HEAD request, throw away the bytes.
154 out := io.Writer(resp.GetWriter())
155 if req.Method == "HEAD" {
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)
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()
176 r.renderError(err, req, resp)
181 resp.Out.Header().Set("Content-Length", strconv.Itoa(b.Len()))
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)
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) {
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)
198 if err = r.renderOutput(b); err == nil {
199 if Config.BoolDefault("results.trim.html", false) {
200 b = r.compressHtml(b)
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) {
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)
214 err = r.Template.Render(wr, r.ViewArgs)
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
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) {
227 // Allocate length of original buffer, so we can write everything without allocating again
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>") {
237 // Trim if not inside a <pre> statement
239 // Cut trailing/leading whitespace
240 text = strings.Trim(text, " \t\r\n")
242 if _, err = b2.WriteString(text); err != nil {
243 resultsLog.Error("Apply: ", "error", err)
245 if _, err = b2.WriteString("\n"); err != nil {
246 resultsLog.Error("Apply: ", "error", err)
250 if _, err = b2.WriteString(text); err != nil {
251 resultsLog.Error("Apply: ", "error", err)
254 if strings.Contains(tl, "</pre>") {
266 // Render the error in the response
267 func (r *RenderTemplateResult) renderError(err error, req *Request, resp *Response) {
268 compileError, found := err.(*Error)
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()
278 lang, _ := r.ViewArgs[CurrentLocaleViewArg].(string)
279 if tmpl, err := MainTemplateLoader.TemplateLang(templateName, lang); err == nil {
280 templateContent = tmpl.Content()
282 templateLog.Info("Unable to retreive template ", "error", err)
285 compileError = &Error{
286 Title: "Template Execution Error",
288 Description: description,
290 SourceLines: templateContent,
294 resultsLog.Errorf("render: Template Execution Error (in %s): %s", compileError.Path, compileError.Description)
295 ErrorResult{r.ViewArgs, compileError}.Apply(req, resp)
298 type RenderHTMLResult struct {
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)
309 type RenderJSONResult struct {
314 func (r RenderJSONResult) Apply(req *Request, resp *Response) {
317 if Config.BoolDefault("results.pretty", false) {
318 b, err = json.MarshalIndent(r.obj, "", " ")
320 b, err = json.Marshal(r.obj)
324 ErrorResult{Error: err}.Apply(req, resp)
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)
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)
340 if _, err = resp.GetWriter().Write(b); err != nil {
341 resultsLog.Error("Apply: Response write failed", "error", err)
343 if _, err = resp.GetWriter().Write([]byte(");")); err != nil {
344 resultsLog.Error("Apply: Response write failed", "error", err)
348 type RenderXMLResult struct {
352 func (r RenderXMLResult) Apply(req *Request, resp *Response) {
355 if Config.BoolDefault("results.pretty", false) {
356 b, err = xml.MarshalIndent(r.obj, "", " ")
358 b, err = xml.Marshal(r.obj)
362 ErrorResult{Error: err}.Apply(req, resp)
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)
372 type RenderTextResult struct {
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)
383 type ContentDisposition string
386 NoDisposition ContentDisposition = ""
387 Attachment ContentDisposition = "attachment"
388 Inline ContentDisposition = "inline"
391 type BinaryResult struct {
395 Delivery ContentDisposition
399 func (r *BinaryResult) Apply(req *Request, resp *Response) {
400 if r.Delivery != NoDisposition {
401 disposition := string(r.Delivery)
403 disposition += fmt.Sprintf(`; filename="%s"`, r.Name)
405 resp.Out.internalHeader.Set("Content-Disposition", disposition)
407 if resp.ContentType != "" {
408 resp.Out.internalHeader.Set("Content-Type", resp.ContentType)
410 contentType := ContentTypeByFilename(r.Name)
411 resp.Out.internalHeader.Set("Content-Type", contentType)
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 {
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)
432 // Close the Reader if we can
433 if v, ok := r.Reader.(io.Closer); ok {
438 type RedirectToURLResult struct {
442 func (r *RedirectToURLResult) Apply(req *Request, resp *Response) {
443 resp.Out.internalHeader.Set("Location", r.url)
444 resp.WriteHeader(http.StatusFound, "")
447 type RedirectToActionResult struct {
452 func (r *RedirectToActionResult) Apply(req *Request, resp *Response) {
453 url, err := getRedirectURL(r.val, r.args)
455 resultsLog.Error("Apply: Couldn't resolve redirect", "error", err)
456 ErrorResult{Error: err}.Apply(req, resp)
459 resp.Out.internalHeader.Set("Location", url)
460 resp.WriteHeader(http.StatusFound, "")
463 func getRedirectURL(item interface{}, args []interface{}) (string, error) {
465 if url, ok := item.(string); ok {
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)
477 return "", errors.New("couldn't find method")
480 // Construct the action string (e.g. "Controller.Method")
481 if recvType.Kind() == reflect.Ptr {
482 recvType = recvType.Elem()
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)
489 return "", fmt.Errorf("Unable to redirect '%s', expected 'Controller.Action'", action)
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)
499 for i, argValue := range args {
500 Unbind(argsByName, methodType.Args[i+fixedParams].Name, argValue)
503 actionDef := MainRouter.Reverse(action, argsByName)
504 if actionDef == nil {
505 return "", errors.New("no route for action " + action)
508 return actionDef.String(), nil
512 return "", errors.New("didn't recognize type: " + typ.String())