github.com/Bio-core/jtree@v0.0.0-20190705165106-1d7a7e7d6272/restapi/server.go (about) 1 package restapi 2 3 import ( 4 "crypto/tls" 5 "crypto/x509" 6 "errors" 7 "io/ioutil" 8 "log" 9 "net" 10 "net/http" 11 "os" 12 "strconv" 13 "sync" 14 "sync/atomic" 15 "time" 16 17 "github.com/go-openapi/runtime/flagext" 18 "github.com/go-openapi/swag" 19 flags "github.com/jessevdk/go-flags" 20 graceful "github.com/tylerb/graceful" 21 22 "github.com/Bio-core/jtree/restapi/operations" 23 ) 24 25 const ( 26 schemeHTTP = "http" 27 schemeHTTPS = "https" 28 schemeUnix = "unix" 29 ) 30 31 var defaultSchemes []string 32 33 func init() { 34 defaultSchemes = []string{ 35 schemeHTTP, 36 } 37 } 38 39 // NewServer creates a new api jtree metadata server but does not configure it 40 func NewServer(api *operations.JtreeMetadataAPI) *Server { 41 s := new(Server) 42 43 s.shutdown = make(chan struct{}) 44 s.api = api 45 return s 46 } 47 48 // ConfigureAPI configures the API and handlers. 49 func (s *Server) ConfigureAPI() { 50 if s.api != nil { 51 s.handler = configureAPI(s.api) 52 } 53 if s.Port == 0 { 54 s.Port = c.App.Port 55 } 56 s.Host = "" 57 // c.App.Host 58 59 } 60 61 // ConfigureFlags configures the additional flags defined by the handlers. Needs to be called before the parser.Parse 62 func (s *Server) ConfigureFlags() { 63 if s.api != nil { 64 configureFlags(s.api) 65 } 66 } 67 68 // Server for the jtree metadata API 69 type Server struct { 70 EnabledListeners []string `long:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec"` 71 CleanupTimeout time.Duration `long:"cleanup-timeout" description:"grace period for which to wait before shutting down the server" default:"10s"` 72 MaxHeaderSize flagext.ByteSize `long:"max-header-size" description:"controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body." default:"1MiB"` 73 74 SocketPath flags.Filename `long:"socket-path" description:"the unix socket to listen on" default:"/var/run/jtree-metadata.sock"` 75 domainSocketL net.Listener 76 77 Host string `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"` 78 Port int `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"` 79 ListenLimit int `long:"listen-limit" description:"limit the number of outstanding requests"` 80 KeepAlive time.Duration `long:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"` 81 ReadTimeout time.Duration `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"` 82 WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"` 83 httpServerL net.Listener 84 85 TLSHost string `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"` 86 TLSPort int `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"` 87 TLSCertificate flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"` 88 TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure conections" env:"TLS_PRIVATE_KEY"` 89 TLSCACertificate flags.Filename `long:"tls-ca" description:"the certificate authority file to be used with mutual tls auth" env:"TLS_CA_CERTIFICATE"` 90 TLSListenLimit int `long:"tls-listen-limit" description:"limit the number of outstanding requests"` 91 TLSKeepAlive time.Duration `long:"tls-keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)"` 92 TLSReadTimeout time.Duration `long:"tls-read-timeout" description:"maximum duration before timing out read of the request"` 93 TLSWriteTimeout time.Duration `long:"tls-write-timeout" description:"maximum duration before timing out write of the response"` 94 httpsServerL net.Listener 95 96 api *operations.JtreeMetadataAPI 97 handler http.Handler 98 hasListeners bool 99 shutdown chan struct{} 100 shuttingDown int32 101 } 102 103 // Logf logs message either via defined user logger or via system one if no user logger is defined. 104 func (s *Server) Logf(f string, args ...interface{}) { 105 if s.api != nil && s.api.Logger != nil { 106 s.api.Logger(f, args...) 107 } else { 108 log.Printf(f, args...) 109 } 110 } 111 112 // Fatalf logs message either via defined user logger or via system one if no user logger is defined. 113 // Exits with non-zero status after printing 114 func (s *Server) Fatalf(f string, args ...interface{}) { 115 if s.api != nil && s.api.Logger != nil { 116 s.api.Logger(f, args...) 117 os.Exit(1) 118 } else { 119 log.Fatalf(f, args...) 120 } 121 } 122 123 // SetAPI configures the server with the specified API. Needs to be called before Serve 124 func (s *Server) SetAPI(api *operations.JtreeMetadataAPI) { 125 if api == nil { 126 s.api = nil 127 s.handler = nil 128 return 129 } 130 131 s.api = api 132 s.api.Logger = log.Printf 133 s.handler = configureAPI(api) 134 } 135 136 func (s *Server) hasScheme(scheme string) bool { 137 schemes := s.EnabledListeners 138 if len(schemes) == 0 { 139 schemes = defaultSchemes 140 } 141 142 for _, v := range schemes { 143 if v == scheme { 144 return true 145 } 146 } 147 return false 148 } 149 150 // Serve the api 151 func (s *Server) Serve() (err error) { 152 if !s.hasListeners { 153 if err = s.Listen(); err != nil { 154 return err 155 } 156 } 157 158 // set default handler, if none is set 159 if s.handler == nil { 160 if s.api == nil { 161 return errors.New("can't create the default handler, as no api is set") 162 } 163 164 s.SetHandler(s.api.Serve(nil)) 165 } 166 167 var wg sync.WaitGroup 168 169 if s.hasScheme(schemeUnix) { 170 domainSocket := &graceful.Server{Server: new(http.Server)} 171 domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) 172 domainSocket.Handler = s.handler 173 domainSocket.LogFunc = s.Logf 174 if int64(s.CleanupTimeout) > 0 { 175 domainSocket.Timeout = s.CleanupTimeout 176 } 177 178 configureServer(domainSocket, "unix", string(s.SocketPath)) 179 180 wg.Add(2) 181 s.Logf("Serving jtree metadata at unix://%s", s.SocketPath) 182 go func(l net.Listener) { 183 defer wg.Done() 184 if err := domainSocket.Serve(l); err != nil { 185 s.Fatalf("%v", err) 186 } 187 s.Logf("Stopped serving jtree metadata at unix://%s", s.SocketPath) 188 }(s.domainSocketL) 189 go s.handleShutdown(&wg, domainSocket) 190 } 191 192 if s.hasScheme(schemeHTTP) { 193 httpServer := &graceful.Server{Server: new(http.Server)} 194 httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) 195 httpServer.ReadTimeout = s.ReadTimeout 196 httpServer.WriteTimeout = s.WriteTimeout 197 httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) 198 httpServer.TCPKeepAlive = s.KeepAlive 199 if s.ListenLimit > 0 { 200 httpServer.ListenLimit = s.ListenLimit 201 } 202 203 if int64(s.CleanupTimeout) > 0 { 204 httpServer.Timeout = s.CleanupTimeout 205 } 206 207 httpServer.Handler = s.handler 208 httpServer.LogFunc = s.Logf 209 210 configureServer(httpServer, "http", s.httpServerL.Addr().String()) 211 212 wg.Add(2) 213 s.Logf("Serving jtree metadata at http://%s", s.httpServerL.Addr()) 214 go func(l net.Listener) { 215 defer wg.Done() 216 if err := httpServer.Serve(l); err != nil { 217 s.Fatalf("%v", err) 218 } 219 s.Logf("Stopped serving jtree metadata at http://%s", l.Addr()) 220 }(s.httpServerL) 221 go s.handleShutdown(&wg, httpServer) 222 } 223 224 if s.hasScheme(schemeHTTPS) { 225 httpsServer := &graceful.Server{Server: new(http.Server)} 226 httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) 227 httpsServer.ReadTimeout = s.TLSReadTimeout 228 httpsServer.WriteTimeout = s.TLSWriteTimeout 229 httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) 230 httpsServer.TCPKeepAlive = s.TLSKeepAlive 231 if s.TLSListenLimit > 0 { 232 httpsServer.ListenLimit = s.TLSListenLimit 233 } 234 if int64(s.CleanupTimeout) > 0 { 235 httpsServer.Timeout = s.CleanupTimeout 236 } 237 httpsServer.Handler = s.handler 238 httpsServer.LogFunc = s.Logf 239 240 // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go 241 httpsServer.TLSConfig = &tls.Config{ 242 // Causes servers to use Go's default ciphersuite preferences, 243 // which are tuned to avoid attacks. Does nothing on clients. 244 PreferServerCipherSuites: true, 245 // Only use curves which have assembly implementations 246 // https://github.com/golang/go/tree/master/src/crypto/elliptic 247 CurvePreferences: []tls.CurveID{tls.CurveP256}, 248 // Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility 249 NextProtos: []string{"http/1.1", "h2"}, 250 // https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols 251 MinVersion: tls.VersionTLS12, 252 // These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy 253 CipherSuites: []uint16{ 254 tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 255 tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 256 tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 257 tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 258 }, 259 } 260 261 if s.TLSCertificate != "" && s.TLSCertificateKey != "" { 262 httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1) 263 httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(string(s.TLSCertificate), string(s.TLSCertificateKey)) 264 } 265 266 if s.TLSCACertificate != "" { 267 caCert, caCertErr := ioutil.ReadFile(string(s.TLSCACertificate)) 268 if caCertErr != nil { 269 log.Fatal(caCertErr) 270 } 271 caCertPool := x509.NewCertPool() 272 caCertPool.AppendCertsFromPEM(caCert) 273 httpsServer.TLSConfig.ClientCAs = caCertPool 274 httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert 275 } 276 277 configureTLS(httpsServer.TLSConfig) 278 httpsServer.TLSConfig.BuildNameToCertificate() 279 280 if err != nil { 281 return err 282 } 283 284 if len(httpsServer.TLSConfig.Certificates) == 0 { 285 if s.TLSCertificate == "" { 286 if s.TLSCertificateKey == "" { 287 s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified") 288 } 289 s.Fatalf("the required flag `--tls-certificate` was not specified") 290 } 291 if s.TLSCertificateKey == "" { 292 s.Fatalf("the required flag `--tls-key` was not specified") 293 } 294 } 295 296 configureServer(httpsServer, "https", s.httpsServerL.Addr().String()) 297 298 wg.Add(2) 299 s.Logf("Serving jtree metadata at https://%s", s.httpsServerL.Addr()) 300 go func(l net.Listener) { 301 defer wg.Done() 302 if err := httpsServer.Serve(l); err != nil { 303 s.Fatalf("%v", err) 304 } 305 s.Logf("Stopped serving jtree metadata at https://%s", l.Addr()) 306 }(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig)) 307 go s.handleShutdown(&wg, httpsServer) 308 } 309 310 wg.Wait() 311 return nil 312 } 313 314 // Listen creates the listeners for the server 315 func (s *Server) Listen() error { 316 if s.hasListeners { // already done this 317 return nil 318 } 319 320 if s.hasScheme(schemeHTTPS) { 321 // Use http host if https host wasn't defined 322 if s.TLSHost == "" { 323 s.TLSHost = s.Host 324 } 325 // Use http listen limit if https listen limit wasn't defined 326 if s.TLSListenLimit == 0 { 327 s.TLSListenLimit = s.ListenLimit 328 } 329 // Use http tcp keep alive if https tcp keep alive wasn't defined 330 if int64(s.TLSKeepAlive) == 0 { 331 s.TLSKeepAlive = s.KeepAlive 332 } 333 // Use http read timeout if https read timeout wasn't defined 334 if int64(s.TLSReadTimeout) == 0 { 335 s.TLSReadTimeout = s.ReadTimeout 336 } 337 // Use http write timeout if https write timeout wasn't defined 338 if int64(s.TLSWriteTimeout) == 0 { 339 s.TLSWriteTimeout = s.WriteTimeout 340 } 341 } 342 343 if s.hasScheme(schemeUnix) { 344 domSockListener, err := net.Listen("unix", string(s.SocketPath)) 345 if err != nil { 346 return err 347 } 348 s.domainSocketL = domSockListener 349 } 350 351 if s.hasScheme(schemeHTTP) { 352 listener, err := net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port))) 353 if err != nil { 354 return err 355 } 356 357 h, p, err := swag.SplitHostPort(listener.Addr().String()) 358 if err != nil { 359 return err 360 } 361 s.Host = h 362 s.Port = p 363 s.httpServerL = listener 364 } 365 366 if s.hasScheme(schemeHTTPS) { 367 tlsListener, err := net.Listen("tcp", net.JoinHostPort(s.TLSHost, strconv.Itoa(s.TLSPort))) 368 if err != nil { 369 return err 370 } 371 372 sh, sp, err := swag.SplitHostPort(tlsListener.Addr().String()) 373 if err != nil { 374 return err 375 } 376 s.TLSHost = sh 377 s.TLSPort = sp 378 s.httpsServerL = tlsListener 379 } 380 381 s.hasListeners = true 382 return nil 383 } 384 385 // Shutdown server and clean up resources 386 func (s *Server) Shutdown() error { 387 if atomic.LoadInt32(&s.shuttingDown) != 0 { 388 s.Logf("already shutting down") 389 return nil 390 } 391 s.shutdown <- struct{}{} 392 return nil 393 } 394 395 func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { 396 defer wg.Done() 397 for { 398 select { 399 case <-s.shutdown: 400 atomic.AddInt32(&s.shuttingDown, 1) 401 server.Stop(s.CleanupTimeout) 402 <-server.StopChan() 403 s.api.ServerShutdown() 404 return 405 case <-server.StopChan(): 406 atomic.AddInt32(&s.shuttingDown, 1) 407 s.api.ServerShutdown() 408 return 409 } 410 } 411 } 412 413 // GetHandler returns a handler useful for testing 414 func (s *Server) GetHandler() http.Handler { 415 return s.handler 416 } 417 418 // SetHandler allows for setting a http handler on this server 419 func (s *Server) SetHandler(handler http.Handler) { 420 s.handler = handler 421 } 422 423 // UnixListener returns the domain socket listener 424 func (s *Server) UnixListener() (net.Listener, error) { 425 if !s.hasListeners { 426 if err := s.Listen(); err != nil { 427 return nil, err 428 } 429 } 430 return s.domainSocketL, nil 431 } 432 433 // HTTPListener returns the http listener 434 func (s *Server) HTTPListener() (net.Listener, error) { 435 if !s.hasListeners { 436 if err := s.Listen(); err != nil { 437 return nil, err 438 } 439 } 440 return s.httpServerL, nil 441 } 442 443 // TLSListener returns the https listener 444 func (s *Server) TLSListener() (net.Listener, error) { 445 if !s.hasListeners { 446 if err := s.Listen(); err != nil { 447 return nil, err 448 } 449 } 450 return s.httpsServerL, nil 451 }