X-Git-Url: https://gerrit.akraino.org/r/gitweb?a=blobdiff_plain;f=src%2Ffoundation%2Fapi%2Frevel%2Fresults.go;fp=src%2Ffoundation%2Fapi%2Frevel%2Fresults.go;h=59d050753e10759962a98a90b101e9a04cab8594;hb=1d1ee6961c93781e1187d8c7faa868da6b2f01f4;hp=0000000000000000000000000000000000000000;hpb=56dd5e0f2164b37b40ac1daa188ccc618b4cbd19;p=iec.git diff --git a/src/foundation/api/revel/results.go b/src/foundation/api/revel/results.go new file mode 100644 index 0000000..59d0507 --- /dev/null +++ b/src/foundation/api/revel/results.go @@ -0,0 +1,513 @@ +// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. +// Revel Framework source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package revel + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "reflect" + "strconv" + "strings" + "time" +) + +type Result interface { + Apply(req *Request, resp *Response) +} + +// ErrorResult structure used to 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 { + ViewArgs map[string]interface{} + Error error +} + +var resultsLog = RevelLog.New("section", "results") + +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" + } + lang, _ := r.ViewArgs[CurrentLocaleViewArg].(string) + // Get the error template. + var err error + templatePath := fmt.Sprintf("errors/%d.%s", status, format) + tmpl, err := MainTemplateLoader.TemplateLang(templatePath, lang) + + // 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) + } + templateLog.Warn("Got an error rendering template", "error", err, "template", templatePath, "lang", lang) + 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.ViewArgs == nil { + r.ViewArgs = make(map[string]interface{}) + } + r.ViewArgs["RunMode"] = RunMode + r.ViewArgs["DevMode"] = DevMode + r.ViewArgs["Error"] = revelError + r.ViewArgs["Router"] = MainRouter + + resultsLog.Info("Rendering error template", "template", templatePath, "error", revelError) + + // Render it. + var b bytes.Buffer + err = tmpl.Render(&b, r.ViewArgs) + + // If there was an error, print it in plain text. + if err != nil { + templateLog.Warn("Got an error rendering template", "error", err, "template", templatePath, "lang", lang) + showPlaintext(err) + return + } + + // need to check if we are on a websocket here + // net/http panics if we write to a hijacked connection + if req.Method == "WS" { + if err := req.WebSocket.MessageSendJSON(fmt.Sprint(revelError)); err != nil { + resultsLog.Error("Apply: Send failed", "error", err) + } + } else { + resp.WriteHeader(status, contentType) + if _, err := b.WriteTo(resp.GetWriter()); err != nil { + resultsLog.Error("Apply: Response WriteTo failed:", "error", err) + } + } + +} + +type PlaintextErrorResult struct { + Error error +} + +// Apply 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") + if _, err := resp.GetWriter().Write([]byte(r.Error.Error())); err != nil { + resultsLog.Error("Apply: Write error:", "error", err) + } +} + +// RenderTemplateResult action methods returns this result to request +// a template be rendered. +type RenderTemplateResult struct { + Template Template + ViewArgs map[string]interface{} +} + +func (r *RenderTemplateResult) Apply(req *Request, resp *Response) { + // Handle panics when rendering templates. + defer func() { + if err := recover(); err != nil { + resultsLog.Error("Apply: panic recovery", "error", 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.GetWriter()) + 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") + if err := r.renderOutput(out); err != nil { + r.renderError(err, req, resp) + } + 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) + b, err := r.ToBytes() + if err != nil { + r.renderError(err, req, resp) + return + } + + if !chunked { + resp.Out.Header().Set("Content-Length", strconv.Itoa(b.Len())) + } + resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8") + if _, err := b.WriteTo(out); err != nil { + resultsLog.Error("Apply: Response write failed", "error", err) + } +} + +// Return a byte array and or an error object if the template failed to render +func (r *RenderTemplateResult) ToBytes() (b *bytes.Buffer, err error) { + defer func() { + if rerr := recover(); rerr != nil { + resultsLog.Error("ApplyBytes: panic recovery", "recover-error", rerr) + err = fmt.Errorf("Template Execution Panic in %s:\n%s", r.Template.Name(), rerr) + } + }() + b = &bytes.Buffer{} + if err = r.renderOutput(b); err == nil { + if Config.BoolDefault("results.trim.html", false) { + b = r.compressHtml(b) + } + } + return +} + +// Output the template to the writer, catch any panics and return as an error +func (r *RenderTemplateResult) renderOutput(wr io.Writer) (err error) { + defer func() { + if rerr := recover(); rerr != nil { + resultsLog.Error("ApplyBytes: panic recovery", "recover-error", rerr) + err = fmt.Errorf("Template Execution Panic in %s:\n%s", r.Template.Name(), rerr) + } + }() + err = r.Template.Render(wr, r.ViewArgs) + return +} + +// Trimming the HTML will do the following: +// * Remove all leading & trailing whitespace on every line +// * Remove all empty lines +// * Attempt to keep formatting inside
 tags
+//
+// This is safe unless white-space: pre; is used in css for formatting.
+// Since there is no way to detect that, you will have to keep trimming off in these cases.
+func (r *RenderTemplateResult) compressHtml(b *bytes.Buffer) (b2 *bytes.Buffer) {
+
+	// Allocate length of original buffer, so we can write everything without allocating again
+	b2.Grow(b.Len())
+	insidePre := false
+	for {
+		text, err := b.ReadString('\n')
+		// Convert to lower case for finding 
 tags.
+		tl := strings.ToLower(text)
+		if strings.Contains(tl, "
") {
+			insidePre = true
+		}
+		// Trim if not inside a 
 statement
+		if !insidePre {
+			// Cut trailing/leading whitespace
+			text = strings.Trim(text, " \t\r\n")
+			if len(text) > 0 {
+				if _, err = b2.WriteString(text); err != nil {
+					resultsLog.Error("Apply: ", "error", err)
+				}
+				if _, err = b2.WriteString("\n"); err != nil {
+					resultsLog.Error("Apply: ", "error", err)
+				}
+			}
+		} else {
+			if _, err = b2.WriteString(text); err != nil {
+				resultsLog.Error("Apply: ", "error", err)
+			}
+		}
+		if strings.Contains(tl, "
") { + insidePre = false + } + // We are finished + if err != nil { + break + } + } + + return +} + +// Render the error in the response +func (r *RenderTemplateResult) renderError(err error, req *Request, resp *Response) { + compileError, found := err.(*Error) + if !found { + var templateContent []string + templateName, line, description := ParseTemplateError(err) + if templateName == "" { + templateLog.Info("Cannot determine template name to render error", "error", err) + templateName = r.Template.Name() + templateContent = r.Template.Content() + + } else { + lang, _ := r.ViewArgs[CurrentLocaleViewArg].(string) + if tmpl, err := MainTemplateLoader.TemplateLang(templateName, lang); err == nil { + templateContent = tmpl.Content() + } else { + templateLog.Info("Unable to retreive template ", "error", err) + } + } + compileError = &Error{ + Title: "Template Execution Error", + Path: templateName, + Description: description, + Line: line, + SourceLines: templateContent, + } + } + resp.Status = 500 + resultsLog.Errorf("render: Template Execution Error (in %s): %s", compileError.Path, compileError.Description) + ErrorResult{r.ViewArgs, compileError}.Apply(req, resp) +} + +type RenderHTMLResult struct { + html string +} + +func (r RenderHTMLResult) Apply(req *Request, resp *Response) { + resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8") + if _, err := resp.GetWriter().Write([]byte(r.html)); err != nil { + resultsLog.Error("Apply: Response write failed", "error", err) + } +} + +type RenderJSONResult struct { + obj interface{} + callback string +} + +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") + if _, err = resp.GetWriter().Write(b); err != nil { + resultsLog.Error("Apply: Response write failed:", "error", err) + } + return + } + + resp.WriteHeader(http.StatusOK, "application/javascript; charset=utf-8") + if _, err = resp.GetWriter().Write([]byte(r.callback + "(")); err != nil { + resultsLog.Error("Apply: Response write failed", "error", err) + } + if _, err = resp.GetWriter().Write(b); err != nil { + resultsLog.Error("Apply: Response write failed", "error", err) + } + if _, err = resp.GetWriter().Write([]byte(");")); err != nil { + resultsLog.Error("Apply: Response write failed", "error", err) + } +} + +type RenderXMLResult struct { + obj interface{} +} + +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") + if _, err = resp.GetWriter().Write(b); err != nil { + resultsLog.Error("Apply: Response write failed", "error", err) + } +} + +type RenderTextResult struct { + text string +} + +func (r RenderTextResult) Apply(req *Request, resp *Response) { + resp.WriteHeader(http.StatusOK, "text/plain; charset=utf-8") + if _, err := resp.GetWriter().Write([]byte(r.text)); err != nil { + resultsLog.Error("Apply: Response write failed", "error", err) + } +} + +type ContentDisposition string + +var ( + NoDisposition ContentDisposition = "" + 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) { + if r.Delivery != NoDisposition { + disposition := string(r.Delivery) + if r.Name != "" { + disposition += fmt.Sprintf(`; filename="%s"`, r.Name) + } + resp.Out.internalHeader.Set("Content-Disposition", disposition) + } + if resp.ContentType != "" { + resp.Out.internalHeader.Set("Content-Type", resp.ContentType) + } else { + contentType := ContentTypeByFilename(r.Name) + resp.Out.internalHeader.Set("Content-Type", contentType) + } + if content, ok := r.Reader.(io.ReadSeeker); ok && r.Length < 0 { + // get the size from the stream + // go1.6 compatibility change, go1.6 does not define constants io.SeekStart + //if size, err := content.Seek(0, io.SeekEnd); err == nil { + // if _, err = content.Seek(0, io.SeekStart); err == nil { + if size, err := content.Seek(0, 2); err == nil { + if _, err = content.Seek(0, 0); err == nil { + r.Length = size + } + } + } + + // Write stream writes the status code to the header as well + if ws := resp.GetStreamWriter(); ws != nil { + if err := ws.WriteStream(r.Name, r.Length, r.ModTime, r.Reader); err != nil { + resultsLog.Error("Apply: Response write failed", "error", err) + } + } + + // Close the Reader if we can + if v, ok := r.Reader.(io.Closer); ok { + _ = v.Close() + } +} + +type RedirectToURLResult struct { + url string +} + +func (r *RedirectToURLResult) Apply(req *Request, resp *Response) { + resp.Out.internalHeader.Set("Location", r.url) + resp.WriteHeader(http.StatusFound, "") +} + +type RedirectToActionResult struct { + val interface{} + args []interface{} +} + +func (r *RedirectToActionResult) Apply(req *Request, resp *Response) { + url, err := getRedirectURL(r.val, r.args) + if err != nil { + resultsLog.Error("Apply: Couldn't resolve redirect", "error", err) + ErrorResult{Error: err}.Apply(req, resp) + return + } + resp.Out.internalHeader.Set("Location", url) + resp.WriteHeader(http.StatusFound, "") +} + +func getRedirectURL(item interface{}, args []interface{}) (string, error) { + // Handle strings + if url, ok := item.(string); ok { + return url, nil + } + + // Handle funcs + val := reflect.ValueOf(item) + typ := reflect.TypeOf(item) + if typ.Kind() == reflect.Func && typ.NumIn() > 0 { + // Get the Controller Method + recvType := typ.In(0) + method := FindMethod(recvType, val) + if method == nil { + return "", errors.New("couldn't find method") + } + + // Construct the action string (e.g. "Controller.Method") + if recvType.Kind() == reflect.Ptr { + recvType = recvType.Elem() + } + module := ModuleFromPath(recvType.PkgPath(), true) + action := module.Namespace() + recvType.Name() + "." + method.Name + // Fetch the action path to get the defaults + pathData, found := splitActionPath(nil, action, true) + if !found { + return "", fmt.Errorf("Unable to redirect '%s', expected 'Controller.Action'", action) + } + + // Build the map for the router to reverse + // Unbind the arguments. + argsByName := make(map[string]string) + // Bind any static args first + fixedParams := len(pathData.FixedParamsByName) + methodType := pathData.TypeOfController.Method(pathData.MethodName) + + for i, argValue := range args { + Unbind(argsByName, methodType.Args[i+fixedParams].Name, argValue) + } + + actionDef := MainRouter.Reverse(action, argsByName) + if actionDef == nil { + return "", errors.New("no route for action " + action) + } + + return actionDef.String(), nil + } + + // Out of guesses + return "", errors.New("didn't recognize type: " + typ.String()) +}