github.com/gobitfly/go-ethereum@v1.8.12/swarm/api/http/error.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 /* 18 Show nicely (but simple) formatted HTML error pages (or respond with JSON 19 if the appropriate `Accept` header is set)) for the http package. 20 */ 21 package http 22 23 import ( 24 "encoding/json" 25 "fmt" 26 "html/template" 27 "net/http" 28 "strings" 29 "time" 30 31 "github.com/ethereum/go-ethereum/log" 32 "github.com/ethereum/go-ethereum/metrics" 33 "github.com/ethereum/go-ethereum/swarm/api" 34 l "github.com/ethereum/go-ethereum/swarm/log" 35 ) 36 37 //templateMap holds a mapping of an HTTP error code to a template 38 var templateMap map[int]*template.Template 39 var caseErrors []CaseError 40 41 //metrics variables 42 var ( 43 htmlCounter = metrics.NewRegisteredCounter("api.http.errorpage.html.count", nil) 44 jsonCounter = metrics.NewRegisteredCounter("api.http.errorpage.json.count", nil) 45 ) 46 47 //parameters needed for formatting the correct HTML page 48 type ResponseParams struct { 49 Msg string 50 Code int 51 Timestamp string 52 template *template.Template 53 Details template.HTML 54 } 55 56 //a custom error case struct that would be used to store validators and 57 //additional error info to display with client responses. 58 type CaseError struct { 59 Validator func(*Request) bool 60 Msg func(*Request) string 61 } 62 63 //we init the error handling right on boot time, so lookup and http response is fast 64 func init() { 65 initErrHandling() 66 } 67 68 func initErrHandling() { 69 //pages are saved as strings - get these strings 70 genErrPage := GetGenericErrorPage() 71 notFoundPage := GetNotFoundErrorPage() 72 multipleChoicesPage := GetMultipleChoicesErrorPage() 73 //map the codes to the available pages 74 tnames := map[int]string{ 75 0: genErrPage, //default 76 http.StatusBadRequest: genErrPage, 77 http.StatusNotFound: notFoundPage, 78 http.StatusMultipleChoices: multipleChoicesPage, 79 http.StatusInternalServerError: genErrPage, 80 } 81 templateMap = make(map[int]*template.Template) 82 for code, tname := range tnames { 83 //assign formatted HTML to the code 84 templateMap[code] = template.Must(template.New(fmt.Sprintf("%d", code)).Parse(tname)) 85 } 86 87 caseErrors = []CaseError{ 88 { 89 Validator: func(r *Request) bool { return r.uri != nil && r.uri.Addr != "" && strings.HasPrefix(r.uri.Addr, "0x") }, 90 Msg: func(r *Request) string { 91 uriCopy := r.uri 92 uriCopy.Addr = strings.TrimPrefix(uriCopy.Addr, "0x") 93 return fmt.Sprintf(`The requested hash seems to be prefixed with '0x'. You will be redirected to the correct URL within 5 seconds.<br/> 94 Please click <a href='%[1]s'>here</a> if your browser does not redirect you.<script>setTimeout("location.href='%[1]s';",5000);</script>`, "/"+uriCopy.String()) 95 }, 96 }} 97 } 98 99 //ValidateCaseErrors is a method that process the request object through certain validators 100 //that assert if certain conditions are met for further information to log as an error 101 func ValidateCaseErrors(r *Request) string { 102 for _, err := range caseErrors { 103 if err.Validator(r) { 104 return err.Msg(r) 105 } 106 } 107 108 return "" 109 } 110 111 //ShowMultipeChoices is used when a user requests a resource in a manifest which results 112 //in ambiguous results. It returns a HTML page with clickable links of each of the entry 113 //in the manifest which fits the request URI ambiguity. 114 //For example, if the user requests bzz:/<hash>/read and that manifest contains entries 115 //"readme.md" and "readinglist.txt", a HTML page is returned with this two links. 116 //This only applies if the manifest has no default entry 117 func ShowMultipleChoices(w http.ResponseWriter, req *Request, list api.ManifestList) { 118 msg := "" 119 if list.Entries == nil { 120 Respond(w, req, "Could not resolve", http.StatusInternalServerError) 121 return 122 } 123 //make links relative 124 //requestURI comes with the prefix of the ambiguous path, e.g. "read" for "readme.md" and "readinglist.txt" 125 //to get clickable links, need to remove the ambiguous path, i.e. "read" 126 idx := strings.LastIndex(req.RequestURI, "/") 127 if idx == -1 { 128 Respond(w, req, "Internal Server Error", http.StatusInternalServerError) 129 return 130 } 131 //remove ambiguous part 132 base := req.RequestURI[:idx+1] 133 for _, e := range list.Entries { 134 //create clickable link for each entry 135 msg += "<a href='" + base + e.Path + "'>" + e.Path + "</a><br/>" 136 } 137 Respond(w, req, msg, http.StatusMultipleChoices) 138 } 139 140 //Respond is used to show an HTML page to a client. 141 //If there is an `Accept` header of `application/json`, JSON will be returned instead 142 //The function just takes a string message which will be displayed in the error page. 143 //The code is used to evaluate which template will be displayed 144 //(and return the correct HTTP status code) 145 func Respond(w http.ResponseWriter, req *Request, msg string, code int) { 146 additionalMessage := ValidateCaseErrors(req) 147 switch code { 148 case http.StatusInternalServerError: 149 log.Output(msg, log.LvlError, l.CallDepth, "ruid", req.ruid, "code", code) 150 default: 151 log.Output(msg, log.LvlDebug, l.CallDepth, "ruid", req.ruid, "code", code) 152 } 153 154 if code >= 400 { 155 w.Header().Del("Cache-Control") //avoid sending cache headers for errors! 156 w.Header().Del("ETag") 157 } 158 159 respond(w, &req.Request, &ResponseParams{ 160 Code: code, 161 Msg: msg, 162 Details: template.HTML(additionalMessage), 163 Timestamp: time.Now().Format(time.RFC1123), 164 template: getTemplate(code), 165 }) 166 } 167 168 //evaluate if client accepts html or json response 169 func respond(w http.ResponseWriter, r *http.Request, params *ResponseParams) { 170 w.WriteHeader(params.Code) 171 if r.Header.Get("Accept") == "application/json" { 172 respondJSON(w, params) 173 } else { 174 respondHTML(w, params) 175 } 176 } 177 178 //return a HTML page 179 func respondHTML(w http.ResponseWriter, params *ResponseParams) { 180 htmlCounter.Inc(1) 181 err := params.template.Execute(w, params) 182 if err != nil { 183 log.Error(err.Error()) 184 } 185 } 186 187 //return JSON 188 func respondJSON(w http.ResponseWriter, params *ResponseParams) { 189 jsonCounter.Inc(1) 190 w.Header().Set("Content-Type", "application/json") 191 json.NewEncoder(w).Encode(params) 192 } 193 194 //get the HTML template for a given code 195 func getTemplate(code int) *template.Template { 196 if val, tmpl := templateMap[code]; tmpl { 197 return val 198 } 199 return templateMap[0] 200 }