error.go 6.5 KB

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