github.com/readium/readium-lcp-server@v0.0.0-20240101192032-6e95190e99f1/api/common_server.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 api 6 7 import ( 8 "fmt" 9 "log" 10 "net/http" 11 12 auth "github.com/abbot/go-http-auth" 13 "github.com/gorilla/mux" 14 "github.com/jeffbmartinez/delay" 15 "github.com/rs/cors" 16 "github.com/urfave/negroni" 17 18 "github.com/readium/readium-lcp-server/problem" 19 ) 20 21 const ( 22 // DO NOT FORGET to update the version 23 Software_Version = "1.9.0" 24 25 ContentType_LCP_JSON = "application/vnd.readium.lcp.license.v1.0+json" 26 ContentType_LSD_JSON = "application/vnd.readium.license.status.v1.0+json" 27 ContentType_TEXT_HTML = "text/html" 28 29 ContentType_JSON = "application/json" 30 31 ContentType_FORM_URL_ENCODED = "application/x-www-form-urlencoded" 32 ) 33 34 type ServerRouter struct { 35 R *mux.Router 36 N *negroni.Negroni 37 } 38 39 func CreateServerRouter(tplPath string) ServerRouter { 40 41 log.Println("Software Version " + Software_Version) 42 43 r := mux.NewRouter() 44 45 //handle 404 errors 46 r.NotFoundHandler = http.HandlerFunc(problem.NotFoundHandler) 47 48 // this demonstrates a panic report 49 r.HandleFunc("/panic", func(w http.ResponseWriter, req *http.Request) { 50 panic("just testing. no worries.") 51 }) 52 53 //n := negroni.Classic() == negroni.New(negroni.NewRecovery(), negroni.NewLogger(), negroni.NewStatic(...)) 54 n := negroni.New() 55 56 // HTTP client can emit requests with custom header: 57 //X-Add-Delay: 300ms 58 //X-Add-Delay: 2.5s 59 n.Use(delay.Middleware{}) 60 61 // possibly useful middlewares: 62 // https://github.com/jeffbmartinez/delay 63 64 //https://github.com/urfave/negroni#recovery 65 recovery := negroni.NewRecovery() 66 recovery.PrintStack = true 67 n.Use(recovery) 68 69 //https://github.com/urfave/negroni#logger 70 // Nov 2023, suppression of negroni logs 71 //n.Use(negroni.NewLogger()) 72 73 // debug: log request details 74 //n.Use(negroni.HandlerFunc(ExtraLogger)) 75 76 if tplPath != "" { 77 //https://github.com/urfave/negroni#static 78 n.Use(negroni.NewStatic(http.Dir(tplPath))) 79 } 80 81 // debug: log CORS details 82 //n.Use(negroni.HandlerFunc(CORSHeaders)) 83 84 // Does not insert CORS headers as intended, depends on Origin check in the HTTP request...we want the same headers, always. 85 // IMPORT "github.com/rs/cors" 86 // //https://github.com/rs/cors#parameters 87 // [cors] logs depend on the Debug option (false/true) 88 c := cors.New(cors.Options{ 89 AllowedOrigins: []string{"*"}, 90 AllowedMethods: []string{"PATCH", "HEAD", "POST", "GET", "OPTIONS", "PUT", "DELETE"}, 91 AllowedHeaders: []string{"Range", "Content-Type", "Origin", "X-Requested-With", "Accept", "Accept-Language", "Content-Language", "Authorization"}, 92 Debug: false, 93 }) 94 n.Use(c) 95 96 n.UseHandler(r) 97 98 sr := ServerRouter{ 99 R: r, 100 N: n, 101 } 102 103 return sr 104 } 105 106 func ExtraLogger(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 107 108 log.Print(" << -------------------") 109 110 fmt.Printf("%s => %s (%s)\n", r.RemoteAddr, r.URL.String(), r.RequestURI) 111 112 log.Println("method: ", r.Method, " path: ", r.URL.Path, " query: ", r.URL.RawQuery) 113 114 log.Printf("REQUEST headers: %#v", r.Header) 115 116 // before 117 next(rw, r) 118 // after 119 120 contentType := rw.Header().Get("Content-Type") 121 if contentType == problem.ContentType_PROBLEM_JSON { 122 log.Print("^^^^ " + problem.ContentType_PROBLEM_JSON + " ^^^^") 123 } 124 125 log.Printf("RESPONSE headers: %#v", rw.Header()) 126 127 log.Print(" >> -------------------") 128 } 129 130 func CORSHeaders(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 131 132 rw.Header().Add("Access-Control-Allow-Methods", "PATCH, HEAD, POST, GET, OPTIONS, PUT, DELETE") 133 rw.Header().Add("Access-Control-Allow-Credentials", "true") 134 rw.Header().Add("Access-Control-Allow-Origin", "*") 135 rw.Header().Add("Access-Control-Allow-Headers", "Range, Content-Type, Origin, X-Requested-With, Accept, Accept-Language, Content-Language, Authorization") 136 137 // before 138 next(rw, r) 139 // after 140 141 // noop 142 } 143 144 func CheckAuth(authenticator *auth.BasicAuth, w http.ResponseWriter, r *http.Request) bool { 145 var username string 146 if username = authenticator.CheckAuth(r); username == "" { 147 w.Header().Set("WWW-Authenticate", `Basic realm="`+authenticator.Realm+`"`) 148 problem.Error(w, r, problem.Problem{Detail: "User or password do not match!"}, http.StatusUnauthorized) 149 return false 150 } 151 return true 152 }