github.com/readium/readium-lcp-server@v0.0.0-20240509124024-799e77a0bbd6/problem/problem.go (about)

     1  // Copyright 2020 Readium Foundation. All rights reserved.
     2  // Use of this source code is governed by a BSD-style license
     3  // that can be found in the LICENSE file exposed on Github (readium) in the project repository.
     4  
     5  package problem
     6  
     7  // rfc 7807 : https://tools.ietf.org/html/rfc7807
     8  // problem.Type should be an URI
     9  // for example http://readium.org/readium/[lcpserver|lsdserver]/<code>
    10  // for standard http error messages use "about:blank" status in json equals http status
    11  import (
    12  	"encoding/json"
    13  	"fmt"
    14  	"log"
    15  	"net/http"
    16  	"runtime/debug"
    17  	"strings"
    18  
    19  	"github.com/readium/readium-lcp-server/logging"
    20  )
    21  
    22  const (
    23  	ContentType_PROBLEM_JSON = "application/problem+json"
    24  )
    25  
    26  type Problem struct {
    27  	Type  string `json:"type,omitempty"`
    28  	Title string `json:"title,omitempty"`
    29  	//optional
    30  	Status   int    `json:"status,omitempty"` //if present = http response code
    31  	Detail   string `json:"detail,omitempty"`
    32  	Instance string `json:"instance,omitempty"`
    33  }
    34  
    35  // Problem types
    36  const ERROR_BASE_URL = "http://readium.org/license-status-document/error/"
    37  const LICENSE_NOT_FOUND = ERROR_BASE_URL + "notfound"
    38  const SERVER_INTERNAL_ERROR = ERROR_BASE_URL + "server"
    39  const REGISTRATION_BAD_REQUEST = ERROR_BASE_URL + "registration"
    40  const RETURN_BAD_REQUEST = ERROR_BASE_URL + "return"
    41  const RETURN_EXPIRED = ERROR_BASE_URL + "return/expired"
    42  const RETURN_ALREADY = ERROR_BASE_URL + "return/already"
    43  const RENEW_BAD_REQUEST = ERROR_BASE_URL + "renew"
    44  const RENEW_REJECT = ERROR_BASE_URL + "renew/date"
    45  const CANCEL_BAD_REQUEST = ERROR_BASE_URL + "cancel"
    46  const FILTER_BAD_REQUEST = ERROR_BASE_URL + "filter"
    47  
    48  func Error(w http.ResponseWriter, r *http.Request, problem Problem, status int) {
    49  
    50  	w.Header().Set("Content-Type", ContentType_PROBLEM_JSON)
    51  	w.Header().Set("X-Content-Type-Options", "nosniff")
    52  
    53  	// must come *after* w.Header().Add()/Set(), but before w.Write()
    54  	w.WriteHeader(status)
    55  
    56  	problem.Status = status
    57  
    58  	if problem.Type == "" && status == http.StatusInternalServerError {
    59  		problem.Type = SERVER_INTERNAL_ERROR
    60  	}
    61  
    62  	if problem.Title == "" { // Title (required) matches http status by default
    63  		problem.Title = http.StatusText(status)
    64  	}
    65  
    66  	jsonError, e := json.Marshal(problem)
    67  	if e != nil {
    68  		http.Error(w, "{}", problem.Status)
    69  	}
    70  	fmt.Fprintln(w, string(jsonError))
    71  
    72  	// log the error persistently
    73  	msg := fmt.Sprintf("Error: %s (%d). %s", problem.Title, problem.Status, problem.Detail)
    74  	logging.Print(msg)
    75  
    76  	// debug only
    77  	//PrintStack()
    78  }
    79  
    80  func PrintStack() {
    81  	log.Print("####################")
    82  
    83  	//debug.PrintStack()
    84  
    85  	b := debug.Stack()
    86  	s := string(b[:])
    87  	l := strings.Index(s, "ServeHTTP")
    88  	if l > 0 {
    89  		ss := s[0:l]
    90  		log.Print(ss + " [...]")
    91  	} else {
    92  		log.Print(s)
    93  	}
    94  
    95  	log.Print("####################")
    96  }
    97  
    98  // NotFoundHandler handles 404 API errors
    99  func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
   100  	Error(w, r, Problem{Detail: r.URL.String()}, http.StatusNotFound)
   101  }