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  }