github.com/readium/readium-lcp-server@v0.0.0-20240101192032-6e95190e99f1/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 }