github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/swarm/api/http/error.go (about)

     1  // This file is part of the go-sberex library. The go-sberex library is 
     2  // free software: you can redistribute it and/or modify it under the terms 
     3  // of the GNU Lesser General Public License as published by the Free 
     4  // Software Foundation, either version 3 of the License, or (at your option)
     5  // any later version.
     6  //
     7  // The go-sberex library is distributed in the hope that it will be useful, 
     8  // but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
    10  // General Public License <http://www.gnu.org/licenses/> for more details.
    11  
    12  /*
    13  Show nicely (but simple) formatted HTML error pages (or respond with JSON
    14  if the appropriate `Accept` header is set)) for the http package.
    15  */
    16  package http
    17  
    18  import (
    19  	"encoding/json"
    20  	"fmt"
    21  	"html/template"
    22  	"net/http"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/Sberex/go-sberex/log"
    27  	"github.com/Sberex/go-sberex/metrics"
    28  	"github.com/Sberex/go-sberex/swarm/api"
    29  )
    30  
    31  //templateMap holds a mapping of an HTTP error code to a template
    32  var templateMap map[int]*template.Template
    33  
    34  //metrics variables
    35  var (
    36  	htmlCounter = metrics.NewRegisteredCounter("api.http.errorpage.html.count", nil)
    37  	jsonCounter = metrics.NewRegisteredCounter("api.http.errorpage.json.count", nil)
    38  )
    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  
    49  //we init the error handling right on boot time, so lookup and http response is fast
    50  func init() {
    51  	initErrHandling()
    52  }
    53  
    54  func initErrHandling() {
    55  	//pages are saved as strings - get these strings
    56  	genErrPage := GetGenericErrorPage()
    57  	notFoundPage := GetNotFoundErrorPage()
    58  	multipleChoicesPage := GetMultipleChoicesErrorPage()
    59  	//map the codes to the available pages
    60  	tnames := map[int]string{
    61  		0: genErrPage, //default
    62  		http.StatusBadRequest:          genErrPage,
    63  		http.StatusNotFound:            notFoundPage,
    64  		http.StatusMultipleChoices:     multipleChoicesPage,
    65  		http.StatusInternalServerError: genErrPage,
    66  	}
    67  	templateMap = make(map[int]*template.Template)
    68  	for code, tname := range tnames {
    69  		//assign formatted HTML to the code
    70  		templateMap[code] = template.Must(template.New(fmt.Sprintf("%d", code)).Parse(tname))
    71  	}
    72  }
    73  
    74  //ShowMultipeChoices is used when a user requests a resource in a manifest which results
    75  //in ambiguous results. It returns a HTML page with clickable links of each of the entry
    76  //in the manifest which fits the request URI ambiguity.
    77  //For example, if the user requests bzz:/<hash>/read and that manifest contains entries
    78  //"readme.md" and "readinglist.txt", a HTML page is returned with this two links.
    79  //This only applies if the manifest has no default entry
    80  func ShowMultipleChoices(w http.ResponseWriter, r *http.Request, list api.ManifestList) {
    81  	msg := ""
    82  	if list.Entries == nil {
    83  		ShowError(w, r, "Internal Server Error", http.StatusInternalServerError)
    84  		return
    85  	}
    86  	//make links relative
    87  	//requestURI comes with the prefix of the ambiguous path, e.g. "read" for "readme.md" and "readinglist.txt"
    88  	//to get clickable links, need to remove the ambiguous path, i.e. "read"
    89  	idx := strings.LastIndex(r.RequestURI, "/")
    90  	if idx == -1 {
    91  		ShowError(w, r, "Internal Server Error", http.StatusInternalServerError)
    92  		return
    93  	}
    94  	//remove ambiguous part
    95  	base := r.RequestURI[:idx+1]
    96  	for _, e := range list.Entries {
    97  		//create clickable link for each entry
    98  		msg += "<a href='" + base + e.Path + "'>" + e.Path + "</a><br/>"
    99  	}
   100  	respond(w, r, &ErrorParams{
   101  		Code:      http.StatusMultipleChoices,
   102  		Details:   template.HTML(msg),
   103  		Timestamp: time.Now().Format(time.RFC1123),
   104  		template:  getTemplate(http.StatusMultipleChoices),
   105  	})
   106  }
   107  
   108  //ShowError is used to show an HTML error page to a client.
   109  //If there is an `Accept` header of `application/json`, JSON will be returned instead
   110  //The function just takes a string message which will be displayed in the error page.
   111  //The code is used to evaluate which template will be displayed
   112  //(and return the correct HTTP status code)
   113  func ShowError(w http.ResponseWriter, r *http.Request, msg string, code int) {
   114  	if code == http.StatusInternalServerError {
   115  		log.Error(msg)
   116  	}
   117  	respond(w, r, &ErrorParams{
   118  		Code:      code,
   119  		Msg:       msg,
   120  		Timestamp: time.Now().Format(time.RFC1123),
   121  		template:  getTemplate(code),
   122  	})
   123  }
   124  
   125  //evaluate if client accepts html or json response
   126  func respond(w http.ResponseWriter, r *http.Request, params *ErrorParams) {
   127  	w.WriteHeader(params.Code)
   128  	if r.Header.Get("Accept") == "application/json" {
   129  		respondJson(w, params)
   130  	} else {
   131  		respondHtml(w, params)
   132  	}
   133  }
   134  
   135  //return a HTML page
   136  func respondHtml(w http.ResponseWriter, params *ErrorParams) {
   137  	htmlCounter.Inc(1)
   138  	err := params.template.Execute(w, params)
   139  	if err != nil {
   140  		log.Error(err.Error())
   141  	}
   142  }
   143  
   144  //return JSON
   145  func respondJson(w http.ResponseWriter, params *ErrorParams) {
   146  	jsonCounter.Inc(1)
   147  	w.Header().Set("Content-Type", "application/json")
   148  	json.NewEncoder(w).Encode(params)
   149  }
   150  
   151  //get the HTML template for a given code
   152  func getTemplate(code int) *template.Template {
   153  	if val, tmpl := templateMap[code]; tmpl {
   154  		return val
   155  	} else {
   156  		return templateMap[0]
   157  	}
   158  }