Results

package revel

type Result interface {
    Apply(req *Request, resp *Response)
}

// This result handles all kinds of error codes (500, 404, ..).
// It renders the relevant error page (errors/CODE.format, e.g. errors/500.json).
// If RunMode is "dev", this results in a friendly error page.
type ErrorResult struct {
    RenderArgs map[string]interface{}
    Error      error
}

func (r ErrorResult) Apply(req *Request, resp *Response) {
    format := req.Format
    status := resp.Status
    if status == 0 {
        status = http.StatusInternalServerError
    }

    contentType := ContentTypeByFilename("xxx." + format)
    if contentType == DefaultFileContentType {
        contentType = "text/plain"
    }

    // Get the error template.
    var err error
    templatePath := fmt.Sprintf("errors/%d.%s", status, format)
    tmpl, err := MainTemplateLoader.Template(templatePath)

    // This func shows a plaintext error message, in case the template rendering
    // doesn't work.
    showPlaintext := func(err error) {
        PlaintextErrorResult{fmt.Errorf("Server Error:\n%s\n\n"+
            "Additionally, an error occurred when rendering the error page:\n%s",
            r.Error, err)}.Apply(req, resp)
    }

    if tmpl == nil {
        if err == nil {
            err = fmt.Errorf("Couldn't find template %s", templatePath)
        }
        showPlaintext(err)
        return
    }

    // If it's not a revel error, wrap it in one.
    var revelError *Error
    switch e := r.Error.(type) {
    case *Error:
        revelError = e
    case error:
        revelError = &Error{
            Title:       "Server Error",
            Description: e.Error(),
        }
    }

    if revelError == nil {
        panic("no error provided")
    }

    if r.RenderArgs == nil {
        r.RenderArgs = make(map[string]interface{})
    }
    r.RenderArgs["RunMode"] = RunMode
    r.RenderArgs["Error"] = revelError
    r.RenderArgs["Router"] = MainRouter

    // Render it.
    var b bytes.Buffer
    err = tmpl.Render(&b, r.RenderArgs)

    // If there was an error, print it in plain text.
    if err != nil {
        showPlaintext(err)
        return
    }

    resp.WriteHeader(status, contentType)
    b.WriteTo(resp.Out)
}

type PlaintextErrorResult struct {
    Error error
}

// This method is used when the template loader or error template is not available.
func (r PlaintextErrorResult) Apply(req *Request, resp *Response) {
    resp.WriteHeader(http.StatusInternalServerError, "text/plain; charset=utf-8")
    resp.Out.Write([]byte(r.Error.Error()))
}

// Action methods return this result to request a template be rendered.
type RenderTemplateResult struct {
    Template   Template
    RenderArgs map[string]interface{}
}

func (r *RenderTemplateResult) Apply(req *Request, resp *Response) {
    // Handle panics when rendering templates.
    defer func() {
        if err := recover(); err != nil {
            ERROR.Println(err)
            PlaintextErrorResult{fmt.Errorf("Template Execution Panic in %s:\n%s",
                r.Template.Name(), err)}.Apply(req, resp)
        }
    }()

    chunked := Config.BoolDefault("results.chunked", false)

    // If it's a HEAD request, throw away the bytes.
    out := io.Writer(resp.Out)
    if req.Method == "HEAD" {
        out = ioutil.Discard
    }

    // In a prod mode, write the status, render, and hope for the best.
    // (In a dev mode, always render to a temporary buffer first to avoid having
    // error pages distorted by HTML already written)
    if chunked && !DevMode {
        resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
        r.render(req, resp, out)
        return
    }

    // Render the template into a temporary buffer, to see if there was an error
    // rendering the template.  If not, then copy it into the response buffer.
    // Otherwise, template render errors may result in unpredictable HTML (and
    // would carry a 200 status code)
    var b bytes.Buffer
    r.render(req, resp, &b)
    if !chunked {
        resp.Out.Header().Set("Content-Length", strconv.Itoa(b.Len()))
    }
    resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
    b.WriteTo(out)
}

type RenderHtmlResult struct {

}

func (r RenderHtmlResult) Apply(req *Request, resp *Response) {
    resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
    resp.Out.Write([]byte(r.html))
}

type RenderJsonResult struct {

}

func (r RenderJsonResult) Apply(req *Request, resp *Response) {
    var b []byte
    var err error
    if Config.BoolDefault("results.pretty", false) {
        b, err = json.MarshalIndent(r.obj, "", "  ")
    } else {
        b, err = json.Marshal(r.obj)
    }

    if err != nil {
        ErrorResult{Error: err}.Apply(req, resp)
        return
    }

    if r.callback == "" {
        resp.WriteHeader(http.StatusOK, "application/json; charset=utf-8")
        resp.Out.Write(b)
        return
    }

    resp.WriteHeader(http.StatusOK, "application/javascript; charset=utf-8")
    resp.Out.Write([]byte(r.callback + "("))
    resp.Out.Write(b)
    resp.Out.Write([]byte(");"))
}

type RenderXmlResult struct {

}

func (r RenderXmlResult) Apply(req *Request, resp *Response) {
    var b []byte
    var err error
    if Config.BoolDefault("results.pretty", false) {
        b, err = xml.MarshalIndent(r.obj, "", "  ")
    } else {
        b, err = xml.Marshal(r.obj)
    }

    if err != nil {
        ErrorResult{Error: err}.Apply(req, resp)
        return
    }

    resp.WriteHeader(http.StatusOK, "application/xml; charset=utf-8")
    resp.Out.Write(b)
}

type RenderTextResult struct {

}

func (r RenderTextResult) Apply(req *Request, resp *Response) {
    resp.WriteHeader(http.StatusOK, "text/plain; charset=utf-8")
    resp.Out.Write([]byte(r.text))
}

type ContentDisposition string

var (
    Attachment ContentDisposition = "attachment"
    Inline     ContentDisposition = "inline"
)

type BinaryResult struct {
    Reader   io.Reader
    Name     string
    Length   int64
    Delivery ContentDisposition
    ModTime  time.Time
}

func (r *BinaryResult) Apply(req *Request, resp *Response) {
    disposition := string(r.Delivery)
    if r.Name != "" {
        disposition += fmt.Sprintf("; filename=%s", r.Name)
    }
    resp.Out.Header().Set("Content-Disposition", disposition)

    // If we have a ReadSeeker, delegate to http.ServeContent
    if rs, ok := r.Reader.(io.ReadSeeker); ok {
        // http.ServeContent doesn't know about response.ContentType, so we set the respective header.
        if resp.ContentType != "" {
            resp.Out.Header().Set("Content-Type", resp.ContentType)
        }
        http.ServeContent(resp.Out, req.Request, r.Name, r.ModTime, rs)
    } else {
        // Else, do a simple io.Copy.
        if r.Length != -1 {
            resp.Out.Header().Set("Content-Length", strconv.FormatInt(r.Length, 10))
        }
        resp.WriteHeader(http.StatusOK, ContentTypeByFilename(r.Name))
        io.Copy(resp.Out, r.Reader)
    }

    // Close the Reader if we can
    if v, ok := r.Reader.(io.Closer); ok {
        v.Close()
    }
}

type RedirectToUrlResult struct {

}

func (r *RedirectToUrlResult) Apply(req *Request, resp *Response) {
    resp.Out.Header().Set("Location", r.url)
    resp.WriteHeader(http.StatusFound, "")
}

type RedirectToActionResult struct {

}

func (r *RedirectToActionResult) Apply(req *Request, resp *Response) {
    url, err := getRedirectUrl(r.val)
    if err != nil {
        ERROR.Println("Couldn't resolve redirect:", err.Error())
        ErrorResult{Error: err}.Apply(req, resp)
        return
    }
    resp.Out.Header().Set("Location", url)
    resp.WriteHeader(http.StatusFound, "")
}