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  }