error.go 6.7 KB

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