github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/swarm/api/http/error.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 /* 13 Show nicely (but simple) formatted HTML error pages (or respond with JSON 14 if the appropriate `Accept` header is set)) for the http package. 15 */ 16 package http 17 18 import ( 19 "encoding/json" 20 "fmt" 21 "html/template" 22 "net/http" 23 "strings" 24 "time" 25 26 "github.com/Sberex/go-sberex/log" 27 "github.com/Sberex/go-sberex/metrics" 28 "github.com/Sberex/go-sberex/swarm/api" 29 ) 30 31 //templateMap holds a mapping of an HTTP error code to a template 32 var templateMap map[int]*template.Template 33 34 //metrics variables 35 var ( 36 htmlCounter = metrics.NewRegisteredCounter("api.http.errorpage.html.count", nil) 37 jsonCounter = metrics.NewRegisteredCounter("api.http.errorpage.json.count", nil) 38 ) 39 40 //parameters needed for formatting the correct HTML page 41 type ErrorParams struct { 42 Msg string 43 Code int 44 Timestamp string 45 template *template.Template 46 Details template.HTML 47 } 48 49 //we init the error handling right on boot time, so lookup and http response is fast 50 func init() { 51 initErrHandling() 52 } 53 54 func initErrHandling() { 55 //pages are saved as strings - get these strings 56 genErrPage := GetGenericErrorPage() 57 notFoundPage := GetNotFoundErrorPage() 58 multipleChoicesPage := GetMultipleChoicesErrorPage() 59 //map the codes to the available pages 60 tnames := map[int]string{ 61 0: genErrPage, //default 62 http.StatusBadRequest: genErrPage, 63 http.StatusNotFound: notFoundPage, 64 http.StatusMultipleChoices: multipleChoicesPage, 65 http.StatusInternalServerError: genErrPage, 66 } 67 templateMap = make(map[int]*template.Template) 68 for code, tname := range tnames { 69 //assign formatted HTML to the code 70 templateMap[code] = template.Must(template.New(fmt.Sprintf("%d", code)).Parse(tname)) 71 } 72 } 73 74 //ShowMultipeChoices is used when a user requests a resource in a manifest which results 75 //in ambiguous results. It returns a HTML page with clickable links of each of the entry 76 //in the manifest which fits the request URI ambiguity. 77 //For example, if the user requests bzz:/<hash>/read and that manifest contains entries 78 //"readme.md" and "readinglist.txt", a HTML page is returned with this two links. 79 //This only applies if the manifest has no default entry 80 func ShowMultipleChoices(w http.ResponseWriter, r *http.Request, list api.ManifestList) { 81 msg := "" 82 if list.Entries == nil { 83 ShowError(w, r, "Internal Server Error", http.StatusInternalServerError) 84 return 85 } 86 //make links relative 87 //requestURI comes with the prefix of the ambiguous path, e.g. "read" for "readme.md" and "readinglist.txt" 88 //to get clickable links, need to remove the ambiguous path, i.e. "read" 89 idx := strings.LastIndex(r.RequestURI, "/") 90 if idx == -1 { 91 ShowError(w, r, "Internal Server Error", http.StatusInternalServerError) 92 return 93 } 94 //remove ambiguous part 95 base := r.RequestURI[:idx+1] 96 for _, e := range list.Entries { 97 //create clickable link for each entry 98 msg += "<a href='" + base + e.Path + "'>" + e.Path + "</a><br/>" 99 } 100 respond(w, r, &ErrorParams{ 101 Code: http.StatusMultipleChoices, 102 Details: template.HTML(msg), 103 Timestamp: time.Now().Format(time.RFC1123), 104 template: getTemplate(http.StatusMultipleChoices), 105 }) 106 } 107 108 //ShowError is used to show an HTML error page to a client. 109 //If there is an `Accept` header of `application/json`, JSON will be returned instead 110 //The function just takes a string message which will be displayed in the error page. 111 //The code is used to evaluate which template will be displayed 112 //(and return the correct HTTP status code) 113 func ShowError(w http.ResponseWriter, r *http.Request, msg string, code int) { 114 if code == http.StatusInternalServerError { 115 log.Error(msg) 116 } 117 respond(w, r, &ErrorParams{ 118 Code: code, 119 Msg: msg, 120 Timestamp: time.Now().Format(time.RFC1123), 121 template: getTemplate(code), 122 }) 123 } 124 125 //evaluate if client accepts html or json response 126 func respond(w http.ResponseWriter, r *http.Request, params *ErrorParams) { 127 w.WriteHeader(params.Code) 128 if r.Header.Get("Accept") == "application/json" { 129 respondJson(w, params) 130 } else { 131 respondHtml(w, params) 132 } 133 } 134 135 //return a HTML page 136 func respondHtml(w http.ResponseWriter, params *ErrorParams) { 137 htmlCounter.Inc(1) 138 err := params.template.Execute(w, params) 139 if err != nil { 140 log.Error(err.Error()) 141 } 142 } 143 144 //return JSON 145 func respondJson(w http.ResponseWriter, params *ErrorParams) { 146 jsonCounter.Inc(1) 147 w.Header().Set("Content-Type", "application/json") 148 json.NewEncoder(w).Encode(params) 149 } 150 151 //get the HTML template for a given code 152 func getTemplate(code int) *template.Template { 153 if val, tmpl := templateMap[code]; tmpl { 154 return val 155 } else { 156 return templateMap[0] 157 } 158 }