github.com/pavlo67/common@v0.5.3/common/server_http/server_http_jschmhr/jschmhr.go (about) 1 package server_http_jschmhr 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 "regexp" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/julienschmidt/httprouter" 15 16 "github.com/pavlo67/common/common/auth" 17 "github.com/pavlo67/common/common/errors" 18 "github.com/pavlo67/common/common/server_http" 19 ) 20 21 var _ server_http.Operator = &serverHTTPJschmhr{} 22 23 type serverHTTPJschmhr struct { 24 httpServer *http.Server 25 httpServeMux *httprouter.Router 26 27 port int 28 tlsCertFile string 29 tlsKeyFile string 30 31 onRequest server_http.OnRequestMiddleware 32 33 secretENVsToLower []string 34 } 35 36 func New(port int, tlsCertFile, tlsKeyFile string, secretENVs []string) (server_http.Operator, error) { 37 if port <= 0 { 38 return nil, fmt.Errorf("on server_http_jschmhr.New(): wrong port = %d", port) 39 } 40 41 var secretENVsToLower []string 42 for _, secretENV := range secretENVs { 43 secretENVsToLower = append(secretENVsToLower, strings.ToLower(secretENV)) 44 } 45 46 router := httprouter.New() 47 48 return &serverHTTPJschmhr{ 49 httpServer: &http.Server{ 50 Handler: router, 51 ReadTimeout: 60 * time.Second, 52 WriteTimeout: 60 * time.Second, 53 MaxHeaderBytes: 1 << 20, 54 55 // ReadHeaderTimeout: 60 * time.Second, 56 // IdleTimeout: 60 * time.Second, 57 // DisableGeneralOptionsHandler: false, 58 }, 59 httpServeMux: router, 60 port: port, 61 tlsCertFile: tlsCertFile, 62 tlsKeyFile: tlsKeyFile, 63 64 secretENVsToLower: secretENVsToLower, 65 }, nil 66 } 67 68 // start wraps and verbalizes http.Server.ListenAndServe method. 69 func (s *serverHTTPJschmhr) Start() error { 70 if s == nil { 71 return errors.New("no serverOp to start") 72 } 73 74 s.httpServer.Addr = ":" + strconv.Itoa(s.port) 75 l.Info("Server is starting on address ", s.httpServer.Addr) 76 77 if s.tlsCertFile != "" && s.tlsKeyFile != "" { 78 return s.httpServer.ListenAndServeTLS(s.tlsCertFile, s.tlsKeyFile) 79 } 80 81 return s.httpServer.ListenAndServe() 82 } 83 84 func (s *serverHTTPJschmhr) Shutdown(ctx context.Context) error { 85 if s == nil { 86 return errors.New("no serverOp to shutting down") 87 } 88 89 return s.Shutdown(ctx) 90 } 91 92 func (s *serverHTTPJschmhr) Addr() (port int, https bool) { 93 return s.port, s.tlsCertFile != "" && s.tlsKeyFile != "" 94 } 95 96 //func (s *serverHTTPJschmhr) ServerHTTP() *http.Server { 97 // return s.httpServer 98 //} 99 100 const onHandleMiddleware = "on serverHTTPJschmhr.HandleMiddleware()" 101 102 func (s *serverHTTPJschmhr) HandleMiddleware(onRequest server_http.OnRequestMiddleware) error { 103 if s.onRequest != nil && onRequest != nil { 104 return fmt.Errorf(onHandleMiddleware + ": can't add middlware twice") 105 } 106 107 s.onRequest = onRequest 108 return nil 109 } 110 111 const onHandleEndpoint = "on serverHTTPJschmhr.HandleEndpoint()" 112 113 func (s *serverHTTPJschmhr) HandleEndpoint(key server_http.EndpointKey, serverPath string, endpoint server_http.Endpoint) error { 114 115 method := strings.ToUpper(endpoint.Method) 116 path := endpoint.PathTemplate(serverPath) 117 118 if endpoint.WorkerHTTP == nil { 119 return errors.New(onHandleEndpoint + ": " + method + ": " + path + "\t!!! NULL workerHTTP ISN'T DISPATCHED !!!") 120 } 121 122 s.HandleOptions(key, path) 123 124 handler := func(w http.ResponseWriter, r *http.Request, paramsHR httprouter.Params) { 125 126 w.Header().Set("Access-Control-Allow-Origin", server_http.CORSAllowOrigin) 127 w.Header().Set("Access-Control-Allow-Headers", server_http.CORSAllowHeaders) 128 w.Header().Set("Access-Control-Allow-Methods", server_http.CORSAllowMethods) 129 w.Header().Set("Access-Control-Allow-Credentials", server_http.CORSAllowCredentials) 130 131 var identity *auth.Identity 132 if s.onRequest != nil { 133 var err error 134 if identity, err = s.onRequest.Identity(r); err != nil { 135 l.Error(err) 136 } 137 } 138 139 var params server_http.PathParams 140 if len(paramsHR) > 0 { 141 params = server_http.PathParams{} 142 for _, p := range paramsHR { 143 params[p.Key] = p.Value 144 } 145 } 146 147 responseData, err := endpoint.WorkerHTTP(s, r, params, identity) 148 if err != nil { 149 l.Warn(err) 150 } 151 152 if responseData.MIMEType != "" { 153 w.Header().Set("Content-Type", responseData.MIMEType) 154 } 155 w.Header().Set("Content-Length", strconv.Itoa(len(responseData.Data))) 156 if responseData.FileName != "" { 157 w.Header().Set("Content-Disposition", "attachment; filename="+responseData.FileName) 158 } 159 160 if responseData.Status > 0 { 161 w.WriteHeader(responseData.Status) 162 } else { 163 w.WriteHeader(http.StatusOK) 164 } 165 166 if _, err = w.Write(responseData.Data); err != nil { 167 l.Errorf("can't write response (%s): %s", serverPath, err) 168 } 169 } 170 171 l.Infof("%-10s: %s %s", key, method, path) 172 switch method { 173 case "GET": 174 s.httpServeMux.GET(path, handler) 175 case "POST": 176 s.httpServeMux.POST(path, handler) 177 case "PUT": 178 s.httpServeMux.PUT(path, handler) 179 case "DELETE": 180 s.httpServeMux.DELETE(path, handler) 181 default: 182 return fmt.Errorf(onHandleEndpoint+": method (%s) isn't supported", method) 183 } 184 185 return nil 186 } 187 188 func (s *serverHTTPJschmhr) HandleOptions(key server_http.EndpointKey, serverPath string) { 189 //if strlib.In(s.handledOptions, serverPath) { 190 // //l.Infof("- %#v", s.handledOptions) 191 // return 192 //} 193 194 s.httpServeMux.OPTIONS(serverPath, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 195 l.Infof("%-10s: OPTIONS %s", key, serverPath) 196 w.Header().Set("Access-Control-Allow-Origin", server_http.CORSAllowOrigin) 197 w.Header().Set("Access-Control-Allow-Headers", server_http.CORSAllowHeaders) 198 w.Header().Set("Access-Control-Allow-Methods", server_http.CORSAllowMethods) 199 w.Header().Set("Access-Control-Allow-Credentials", server_http.CORSAllowCredentials) 200 }) 201 202 //s.handledOptions = append(s.handledOptions, serverPath) 203 } 204 205 var reHTMLExt = regexp.MustCompile(`\.html?$`) 206 207 func (s *serverHTTPJschmhr) HandleFiles(key server_http.EndpointKey, serverPath string, staticPath server_http.StaticPath) error { 208 l.Infof("%-10s: FILES %s <-- %s", key, serverPath, staticPath.LocalPath) 209 210 // TODO: check localPath 211 212 if staticPath.MIMEType == nil { 213 // TODO!!! CORS 214 215 s.httpServeMux.ServeFiles(serverPath, http.Dir(staticPath.LocalPath)) 216 return nil 217 } 218 219 s.HandleOptions(key, serverPath) 220 221 //fileServer := http.FileServer(http.Dir(localPath)) 222 s.httpServeMux.GET(serverPath, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 223 w.Header().Set("Access-Control-Allow-Origin", server_http.CORSAllowOrigin) 224 w.Header().Set("Access-Control-Allow-Headers", server_http.CORSAllowHeaders) 225 w.Header().Set("Access-Control-Allow-Methods", server_http.CORSAllowMethods) 226 w.Header().Set("Access-Control-Allow-Credentials", server_http.CORSAllowCredentials) 227 228 if staticPath.MIMEType != nil && *staticPath.MIMEType != "" { 229 w.Header().Set("Content-Type", *staticPath.MIMEType) 230 } 231 232 OpenFile, err := os.Open(staticPath.LocalPath + "/" + p.ByName("filepath")) 233 defer OpenFile.Close() 234 if err != nil { 235 l.Error(err) 236 } else { 237 io.Copy(w, OpenFile) 238 } 239 240 //if mimeType != nil { 241 //} 242 //fileServer.ServeHTTP(w, r) 243 }) 244 245 return nil 246 } 247 248 // mimeTypeToSet, err = inspector.MIME(localPath+"/"+r.ExportID.PathWithParams, nil) 249 // if err != nil { 250 // l.ErrStr("can't read MIMEType for file: ", localPath+"/"+r.ExportID.PathWithParams, err) 251 // } 252 253 //func (s *serverHTTPJschmhr) HandleGetString(serverRoute, str string, mimeType *string) { 254 // s.handleFunc("GET", serverRoute, func(w http.ResponseWriter, r *http.Request, params httprouter.Content) { 255 // if mimeType != nil { 256 // // "application/javascript" 257 // w.Header().Set("Content-Type", *mimeType) 258 // } 259 // w.Write([]byte(str)) 260 // }) 261 //}