github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/swarm/api/http/error.go (about) 1 // Copyright 2017 The Spectrum Authors 2 // This file is part of the Spectrum library. 3 // 4 // The Spectrum 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 Spectrum 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 Spectrum 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/SmartMeshFoundation/Spectrum/log" 32 "github.com/SmartMeshFoundation/Spectrum/swarm/api" 33 ) 34 35 //templateMap holds a mapping of an HTTP error code to a template 36 var templateMap map[int]*template.Template 37 38 //parameters needed for formatting the correct HTML page 39 type ErrorParams struct { 40 Msg string 41 Code int 42 Timestamp string 43 template *template.Template 44 Details template.HTML 45 } 46 47 //we init the error handling right on boot time, so lookup and http response is fast 48 func init() { 49 initErrHandling() 50 } 51 52 func initErrHandling() { 53 //pages are saved as strings - get these strings 54 genErrPage := GetGenericErrorPage() 55 notFoundPage := GetNotFoundErrorPage() 56 multipleChoicesPage := GetMultipleChoicesErrorPage() 57 //map the codes to the available pages 58 tnames := map[int]string{ 59 0: genErrPage, //default 60 http.StatusBadRequest: genErrPage, 61 http.StatusNotFound: notFoundPage, 62 http.StatusMultipleChoices: multipleChoicesPage, 63 http.StatusInternalServerError: genErrPage, 64 } 65 templateMap = make(map[int]*template.Template) 66 for code, tname := range tnames { 67 //assign formatted HTML to the code 68 templateMap[code] = template.Must(template.New(fmt.Sprintf("%d", code)).Parse(tname)) 69 } 70 } 71 72 //ShowMultipeChoices is used when a user requests a resource in a manifest which results 73 //in ambiguous results. It returns a HTML page with clickable links of each of the entry 74 //in the manifest which fits the request URI ambiguity. 75 //For example, if the user requests bzz:/<hash>/read and that manifest contains entries 76 //"readme.md" and "readinglist.txt", a HTML page is returned with this two links. 77 //This only applies if the manifest has no default entry 78 func ShowMultipleChoices(w http.ResponseWriter, r *http.Request, list api.ManifestList) { 79 msg := "" 80 if list.Entries == nil { 81 ShowError(w, r, "Internal Server Error", http.StatusInternalServerError) 82 return 83 } 84 //make links relative 85 //requestURI comes with the prefix of the ambiguous path, e.g. "read" for "readme.md" and "readinglist.txt" 86 //to get clickable links, need to remove the ambiguous path, i.e. "read" 87 idx := strings.LastIndex(r.RequestURI, "/") 88 if idx == -1 { 89 ShowError(w, r, "Internal Server Error", http.StatusInternalServerError) 90 return 91 } 92 //remove ambiguous part 93 base := r.RequestURI[:idx+1] 94 for _, e := range list.Entries { 95 //create clickable link for each entry 96 msg += "<a href='" + base + e.Path + "'>" + e.Path + "</a><br/>" 97 } 98 respond(w, r, &ErrorParams{ 99 Code: http.StatusMultipleChoices, 100 Details: template.HTML(msg), 101 Timestamp: time.Now().Format(time.RFC1123), 102 template: getTemplate(http.StatusMultipleChoices), 103 }) 104 } 105 106 //ShowError is used to show an HTML error page to a client. 107 //If there is an `Accept` header of `application/json`, JSON will be returned instead 108 //The function just takes a string message which will be displayed in the error page. 109 //The code is used to evaluate which template will be displayed 110 //(and return the correct HTTP status code) 111 func ShowError(w http.ResponseWriter, r *http.Request, msg string, code int) { 112 if code == http.StatusInternalServerError { 113 log.Error(msg) 114 } 115 respond(w, r, &ErrorParams{ 116 Code: code, 117 Msg: msg, 118 Timestamp: time.Now().Format(time.RFC1123), 119 template: getTemplate(code), 120 }) 121 } 122 123 //evaluate if client accepts html or json response 124 func respond(w http.ResponseWriter, r *http.Request, params *ErrorParams) { 125 w.WriteHeader(params.Code) 126 if r.Header.Get("Accept") == "application/json" { 127 respondJson(w, params) 128 } else { 129 respondHtml(w, params) 130 } 131 } 132 133 //return a HTML page 134 func respondHtml(w http.ResponseWriter, params *ErrorParams) { 135 err := params.template.Execute(w, params) 136 if err != nil { 137 log.Error(err.Error()) 138 } 139 } 140 141 //return JSON 142 func respondJson(w http.ResponseWriter, params *ErrorParams) { 143 w.Header().Set("Content-Type", "application/json") 144 json.NewEncoder(w).Encode(params) 145 } 146 147 //get the HTML template for a given code 148 func getTemplate(code int) *template.Template { 149 if val, tmpl := templateMap[code]; tmpl { 150 return val 151 } else { 152 return templateMap[0] 153 } 154 }