github.com/anthdm/go-ethereum@v1.8.4-0.20180412101906-60516c83b011/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 ) 35 36 //templateMap holds a mapping of an HTTP error code to a template 37 var templateMap map[int]*template.Template 38 var caseErrors []CaseError 39 40 //metrics variables 41 var ( 42 htmlCounter = metrics.NewRegisteredCounter("api.http.errorpage.html.count", nil) 43 jsonCounter = metrics.NewRegisteredCounter("api.http.errorpage.json.count", nil) 44 ) 45 46 //parameters needed for formatting the correct HTML page 47 type ErrorParams struct { 48 Msg string 49 Code int 50 Timestamp string 51 template *template.Template 52 Details template.HTML 53 } 54 55 //a custom error case struct that would be used to store validators and 56 //additional error info to display with client responses. 57 type CaseError struct { 58 Validator func(*Request) bool 59 Msg func(*Request) string 60 } 61 62 //we init the error handling right on boot time, so lookup and http response is fast 63 func init() { 64 initErrHandling() 65 } 66 67 func initErrHandling() { 68 //pages are saved as strings - get these strings 69 genErrPage := GetGenericErrorPage() 70 notFoundPage := GetNotFoundErrorPage() 71 multipleChoicesPage := GetMultipleChoicesErrorPage() 72 //map the codes to the available pages 73 tnames := map[int]string{ 74 0: genErrPage, //default 75 http.StatusBadRequest: genErrPage, 76 http.StatusNotFound: notFoundPage, 77 http.StatusMultipleChoices: multipleChoicesPage, 78 http.StatusInternalServerError: genErrPage, 79 } 80 templateMap = make(map[int]*template.Template) 81 for code, tname := range tnames { 82 //assign formatted HTML to the code 83 templateMap[code] = template.Must(template.New(fmt.Sprintf("%d", code)).Parse(tname)) 84 } 85 86 caseErrors = []CaseError{ 87 { 88 Validator: func(r *Request) bool { return r.uri != nil && r.uri.Addr != "" && strings.HasPrefix(r.uri.Addr, "0x") }, 89 Msg: func(r *Request) string { 90 uriCopy := r.uri 91 uriCopy.Addr = strings.TrimPrefix(uriCopy.Addr, "0x") 92 return fmt.Sprintf(`The requested hash seems to be prefixed with '0x'. You will be redirected to the correct URL within 5 seconds.<br/> 93 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()) 94 }, 95 }} 96 } 97 98 //ValidateCaseErrors is a method that process the request object through certain validators 99 //that assert if certain conditions are met for further information to log as an error 100 func ValidateCaseErrors(r *Request) string { 101 for _, err := range caseErrors { 102 if err.Validator(r) { 103 return err.Msg(r) 104 } 105 } 106 107 return "" 108 } 109 110 //ShowMultipeChoices is used when a user requests a resource in a manifest which results 111 //in ambiguous results. It returns a HTML page with clickable links of each of the entry 112 //in the manifest which fits the request URI ambiguity. 113 //For example, if the user requests bzz:/<hash>/read and that manifest contains entries 114 //"readme.md" and "readinglist.txt", a HTML page is returned with this two links. 115 //This only applies if the manifest has no default entry 116 func ShowMultipleChoices(w http.ResponseWriter, r *Request, list api.ManifestList) { 117 msg := "" 118 if list.Entries == nil { 119 ShowError(w, r, "Could not resolve", http.StatusInternalServerError) 120 return 121 } 122 //make links relative 123 //requestURI comes with the prefix of the ambiguous path, e.g. "read" for "readme.md" and "readinglist.txt" 124 //to get clickable links, need to remove the ambiguous path, i.e. "read" 125 idx := strings.LastIndex(r.RequestURI, "/") 126 if idx == -1 { 127 ShowError(w, r, "Internal Server Error", http.StatusInternalServerError) 128 return 129 } 130 //remove ambiguous part 131 base := r.RequestURI[:idx+1] 132 for _, e := range list.Entries { 133 //create clickable link for each entry 134 msg += "<a href='" + base + e.Path + "'>" + e.Path + "</a><br/>" 135 } 136 respond(w, &r.Request, &ErrorParams{ 137 Code: http.StatusMultipleChoices, 138 Details: template.HTML(msg), 139 Timestamp: time.Now().Format(time.RFC1123), 140 template: getTemplate(http.StatusMultipleChoices), 141 }) 142 } 143 144 //ShowError is used to show an HTML error page to a client. 145 //If there is an `Accept` header of `application/json`, JSON will be returned instead 146 //The function just takes a string message which will be displayed in the error page. 147 //The code is used to evaluate which template will be displayed 148 //(and return the correct HTTP status code) 149 func ShowError(w http.ResponseWriter, r *Request, msg string, code int) { 150 additionalMessage := ValidateCaseErrors(r) 151 if code == http.StatusInternalServerError { 152 log.Error(msg) 153 } 154 respond(w, &r.Request, &ErrorParams{ 155 Code: code, 156 Msg: msg, 157 Details: template.HTML(additionalMessage), 158 Timestamp: time.Now().Format(time.RFC1123), 159 template: getTemplate(code), 160 }) 161 } 162 163 //evaluate if client accepts html or json response 164 func respond(w http.ResponseWriter, r *http.Request, params *ErrorParams) { 165 w.WriteHeader(params.Code) 166 if r.Header.Get("Accept") == "application/json" { 167 respondJson(w, params) 168 } else { 169 respondHtml(w, params) 170 } 171 } 172 173 //return a HTML page 174 func respondHtml(w http.ResponseWriter, params *ErrorParams) { 175 htmlCounter.Inc(1) 176 err := params.template.Execute(w, params) 177 if err != nil { 178 log.Error(err.Error()) 179 } 180 } 181 182 //return JSON 183 func respondJson(w http.ResponseWriter, params *ErrorParams) { 184 jsonCounter.Inc(1) 185 w.Header().Set("Content-Type", "application/json") 186 json.NewEncoder(w).Encode(params) 187 } 188 189 //get the HTML template for a given code 190 func getTemplate(code int) *template.Template { 191 if val, tmpl := templateMap[code]; tmpl { 192 return val 193 } else { 194 return templateMap[0] 195 } 196 }