response.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  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. package http
  17. import (
  18. "encoding/json"
  19. "fmt"
  20. "html/template"
  21. "net/http"
  22. "strings"
  23. "time"
  24. "github.com/ethereum/go-ethereum/log"
  25. "github.com/ethereum/go-ethereum/metrics"
  26. "github.com/ethereum/go-ethereum/swarm/api"
  27. )
  28. //metrics variables
  29. var (
  30. htmlCounter = metrics.NewRegisteredCounter("api.http.errorpage.html.count", nil)
  31. jsonCounter = metrics.NewRegisteredCounter("api.http.errorpage.json.count", nil)
  32. plaintextCounter = metrics.NewRegisteredCounter("api.http.errorpage.plaintext.count", nil)
  33. )
  34. //parameters needed for formatting the correct HTML page
  35. type ResponseParams struct {
  36. Msg template.HTML
  37. Code int
  38. Timestamp string
  39. template *template.Template
  40. Details template.HTML
  41. }
  42. //ShowMultipeChoices is used when a user requests a resource in a manifest which results
  43. //in ambiguous results. It returns a HTML page with clickable links of each of the entry
  44. //in the manifest which fits the request URI ambiguity.
  45. //For example, if the user requests bzz:/<hash>/read and that manifest contains entries
  46. //"readme.md" and "readinglist.txt", a HTML page is returned with this two links.
  47. //This only applies if the manifest has no default entry
  48. func ShowMultipleChoices(w http.ResponseWriter, r *http.Request, list api.ManifestList) {
  49. log.Debug("ShowMultipleChoices", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context()))
  50. msg := ""
  51. if list.Entries == nil {
  52. RespondError(w, r, "Could not resolve", http.StatusInternalServerError)
  53. return
  54. }
  55. requestUri := strings.TrimPrefix(r.RequestURI, "/")
  56. uri, err := api.Parse(requestUri)
  57. if err != nil {
  58. RespondError(w, r, "Bad Request", http.StatusBadRequest)
  59. }
  60. uri.Scheme = "bzz-list"
  61. //request the same url just with bzz-list
  62. msg += fmt.Sprintf("Disambiguation:<br/>Your request may refer to multiple choices.<br/>Click <a class=\"orange\" href='"+"/"+uri.String()+"'>here</a> if your browser does not redirect you within 5 seconds.<script>setTimeout(\"location.href='%s';\",5000);</script><br/>", "/"+uri.String())
  63. RespondTemplate(w, r, "error", msg, http.StatusMultipleChoices)
  64. }
  65. func RespondTemplate(w http.ResponseWriter, r *http.Request, templateName, msg string, code int) {
  66. log.Debug("RespondTemplate", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context()))
  67. respond(w, r, &ResponseParams{
  68. Code: code,
  69. Msg: template.HTML(msg),
  70. Timestamp: time.Now().Format(time.RFC1123),
  71. template: TemplatesMap[templateName],
  72. })
  73. }
  74. func RespondError(w http.ResponseWriter, r *http.Request, msg string, code int) {
  75. log.Debug("RespondError", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context()))
  76. RespondTemplate(w, r, "error", msg, code)
  77. }
  78. //evaluate if client accepts html or json response
  79. func respond(w http.ResponseWriter, r *http.Request, params *ResponseParams) {
  80. w.WriteHeader(params.Code)
  81. if params.Code >= 400 {
  82. w.Header().Del("Cache-Control") //avoid sending cache headers for errors!
  83. w.Header().Del("ETag")
  84. }
  85. acceptHeader := r.Header.Get("Accept")
  86. // this cannot be in a switch form since an Accept header can be in the form of "Accept: */*, text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8"
  87. if strings.Contains(acceptHeader, "application/json") {
  88. if err := respondJSON(w, r, params); err != nil {
  89. RespondError(w, r, "Internal server error", http.StatusInternalServerError)
  90. }
  91. } else if strings.Contains(acceptHeader, "text/html") {
  92. respondHTML(w, r, params)
  93. } else {
  94. respondPlaintext(w, r, params) //returns nice errors for curl
  95. }
  96. }
  97. //return a HTML page
  98. func respondHTML(w http.ResponseWriter, r *http.Request, params *ResponseParams) {
  99. htmlCounter.Inc(1)
  100. log.Debug("respondHTML", "ruid", GetRUID(r.Context()))
  101. err := params.template.Execute(w, params)
  102. if err != nil {
  103. log.Error(err.Error())
  104. }
  105. }
  106. //return JSON
  107. func respondJSON(w http.ResponseWriter, r *http.Request, params *ResponseParams) error {
  108. jsonCounter.Inc(1)
  109. log.Debug("respondJSON", "ruid", GetRUID(r.Context()))
  110. w.Header().Set("Content-Type", "application/json")
  111. return json.NewEncoder(w).Encode(params)
  112. }
  113. //return plaintext
  114. func respondPlaintext(w http.ResponseWriter, r *http.Request, params *ResponseParams) error {
  115. plaintextCounter.Inc(1)
  116. log.Debug("respondPlaintext", "ruid", GetRUID(r.Context()))
  117. w.Header().Set("Content-Type", "text/plain")
  118. strToWrite := "Code: " + fmt.Sprintf("%d", params.Code) + "\n"
  119. strToWrite += "Message: " + string(params.Msg) + "\n"
  120. strToWrite += "Timestamp: " + params.Timestamp + "\n"
  121. _, err := w.Write([]byte(strToWrite))
  122. return err
  123. }