forked from TrueCloudLab/frostfs-rest-gw
[#1] Add basic structure and operations
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
eb642eae89
commit
9f752cd756
65 changed files with 11534 additions and 0 deletions
495
gen/restapi/server.go
Normal file
495
gen/restapi/server.go
Normal file
|
@ -0,0 +1,495 @@
|
|||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/swag"
|
||||
"golang.org/x/net/netutil"
|
||||
|
||||
"github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations"
|
||||
)
|
||||
|
||||
const (
|
||||
schemeHTTP = "http"
|
||||
schemeHTTPS = "https"
|
||||
)
|
||||
|
||||
var defaultSchemes []string
|
||||
|
||||
func init() {
|
||||
defaultSchemes = []string{
|
||||
schemeHTTP,
|
||||
}
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
EnabledListeners []string
|
||||
CleanupTimeout time.Duration
|
||||
GracefulTimeout time.Duration
|
||||
MaxHeaderSize int
|
||||
|
||||
ListenAddress string
|
||||
ListenLimit int
|
||||
KeepAlive time.Duration
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
|
||||
TLSListenAddress string
|
||||
TLSListenLimit int
|
||||
TLSKeepAlive time.Duration
|
||||
TLSReadTimeout time.Duration
|
||||
TLSWriteTimeout time.Duration
|
||||
TLSCertificate string
|
||||
TLSCertificateKey string
|
||||
TLSCACertificate string
|
||||
}
|
||||
|
||||
// NewServer creates a new api neofs rest gw server but does not configure it
|
||||
func NewServer(api *operations.NeofsRestGwAPI, cfg *ServerConfig) *Server {
|
||||
s := new(Server)
|
||||
s.EnabledListeners = cfg.EnabledListeners
|
||||
s.CleanupTimeout = cfg.CleanupTimeout
|
||||
s.GracefulTimeout = cfg.GracefulTimeout
|
||||
s.MaxHeaderSize = cfg.MaxHeaderSize
|
||||
s.ListenAddress = cfg.ListenAddress
|
||||
s.ListenLimit = cfg.ListenLimit
|
||||
s.KeepAlive = cfg.KeepAlive
|
||||
s.ReadTimeout = cfg.ReadTimeout
|
||||
s.WriteTimeout = cfg.WriteTimeout
|
||||
s.TLSListenAddress = cfg.TLSListenAddress
|
||||
if len(s.TLSListenAddress) == 0 {
|
||||
s.TLSListenAddress = s.ListenAddress
|
||||
}
|
||||
s.TLSCertificate = cfg.TLSCertificate
|
||||
s.TLSCertificateKey = cfg.TLSCertificateKey
|
||||
s.TLSCACertificate = cfg.TLSCACertificate
|
||||
s.TLSListenLimit = cfg.TLSListenLimit
|
||||
s.TLSKeepAlive = cfg.TLSKeepAlive
|
||||
s.TLSReadTimeout = cfg.TLSReadTimeout
|
||||
s.TLSWriteTimeout = cfg.TLSWriteTimeout
|
||||
s.shutdown = make(chan struct{})
|
||||
s.api = api
|
||||
s.interrupt = make(chan os.Signal, 1)
|
||||
return s
|
||||
}
|
||||
|
||||
// ConfigureAPI configures the API and handlers.
|
||||
func (s *Server) ConfigureAPI(fn func(*operations.NeofsRestGwAPI) http.Handler) {
|
||||
if s.api != nil {
|
||||
s.handler = fn(s.api)
|
||||
}
|
||||
}
|
||||
|
||||
// Server for the neofs rest gw API
|
||||
type Server struct {
|
||||
EnabledListeners []string
|
||||
CleanupTimeout time.Duration
|
||||
GracefulTimeout time.Duration
|
||||
MaxHeaderSize int
|
||||
|
||||
ListenAddress string
|
||||
ListenLimit int
|
||||
KeepAlive time.Duration
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
httpServerL net.Listener
|
||||
|
||||
TLSListenAddress string
|
||||
TLSCertificate string
|
||||
TLSCertificateKey string
|
||||
TLSCACertificate string
|
||||
TLSListenLimit int
|
||||
TLSKeepAlive time.Duration
|
||||
TLSReadTimeout time.Duration
|
||||
TLSWriteTimeout time.Duration
|
||||
httpsServerL net.Listener
|
||||
|
||||
cfgTLSFn func(tlsConfig *tls.Config)
|
||||
cfgServerFn func(s *http.Server, scheme, addr string)
|
||||
|
||||
api *operations.NeofsRestGwAPI
|
||||
handler http.Handler
|
||||
hasListeners bool
|
||||
shutdown chan struct{}
|
||||
shuttingDown int32
|
||||
interrupted bool
|
||||
interrupt chan os.Signal
|
||||
}
|
||||
|
||||
// Logf logs message either via defined user logger or via system one if no user logger is defined.
|
||||
func (s *Server) Logf(f string, args ...interface{}) {
|
||||
if s.api != nil && s.api.Logger != nil {
|
||||
s.api.Logger(f, args...)
|
||||
} else {
|
||||
log.Printf(f, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Fatalf logs message either via defined user logger or via system one if no user logger is defined.
|
||||
// Exits with non-zero status after printing
|
||||
func (s *Server) Fatalf(f string, args ...interface{}) {
|
||||
if s.api != nil && s.api.Logger != nil {
|
||||
s.api.Logger(f, args...)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
log.Fatalf(f, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) hasScheme(scheme string) bool {
|
||||
schemes := s.EnabledListeners
|
||||
if len(schemes) == 0 {
|
||||
schemes = defaultSchemes
|
||||
}
|
||||
|
||||
for _, v := range schemes {
|
||||
if v == scheme {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Serve the api
|
||||
func (s *Server) Serve() (err error) {
|
||||
if !s.hasListeners {
|
||||
if err = s.Listen(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// set default handler, if none is set
|
||||
if s.handler == nil {
|
||||
if s.api == nil {
|
||||
return errors.New("can't create the default handler, as no api is set")
|
||||
}
|
||||
|
||||
s.SetHandler(s.api.Serve(nil))
|
||||
}
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
once := new(sync.Once)
|
||||
signalNotify(s.interrupt)
|
||||
go handleInterrupt(once, s)
|
||||
|
||||
servers := []*http.Server{}
|
||||
|
||||
if s.hasScheme(schemeHTTP) {
|
||||
httpServer := new(http.Server)
|
||||
httpServer.MaxHeaderBytes = s.MaxHeaderSize
|
||||
httpServer.ReadTimeout = s.ReadTimeout
|
||||
httpServer.WriteTimeout = s.WriteTimeout
|
||||
httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0)
|
||||
if s.ListenLimit > 0 {
|
||||
s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit)
|
||||
}
|
||||
|
||||
if int64(s.CleanupTimeout) > 0 {
|
||||
httpServer.IdleTimeout = s.CleanupTimeout
|
||||
}
|
||||
|
||||
httpServer.Handler = s.handler
|
||||
|
||||
if s.cfgServerFn != nil {
|
||||
s.cfgServerFn(httpServer, "http", s.httpServerL.Addr().String())
|
||||
}
|
||||
|
||||
servers = append(servers, httpServer)
|
||||
wg.Add(1)
|
||||
s.Logf("Serving neofs rest gw at http://%s", s.httpServerL.Addr())
|
||||
go func(l net.Listener) {
|
||||
defer wg.Done()
|
||||
if err := httpServer.Serve(l); err != nil && err != http.ErrServerClosed {
|
||||
s.Fatalf("%v", err)
|
||||
}
|
||||
s.Logf("Stopped serving neofs rest gw at http://%s", l.Addr())
|
||||
}(s.httpServerL)
|
||||
}
|
||||
|
||||
if s.hasScheme(schemeHTTPS) {
|
||||
httpsServer := new(http.Server)
|
||||
httpsServer.MaxHeaderBytes = s.MaxHeaderSize
|
||||
httpsServer.ReadTimeout = s.TLSReadTimeout
|
||||
httpsServer.WriteTimeout = s.TLSWriteTimeout
|
||||
httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0)
|
||||
if s.TLSListenLimit > 0 {
|
||||
s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit)
|
||||
}
|
||||
if int64(s.CleanupTimeout) > 0 {
|
||||
httpsServer.IdleTimeout = s.CleanupTimeout
|
||||
}
|
||||
httpsServer.Handler = s.handler
|
||||
|
||||
// Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
|
||||
httpsServer.TLSConfig = &tls.Config{
|
||||
// Causes servers to use Go's default ciphersuite preferences,
|
||||
// which are tuned to avoid attacks. Does nothing on clients.
|
||||
PreferServerCipherSuites: true,
|
||||
// Only use curves which have assembly implementations
|
||||
// https://github.com/golang/go/tree/master/src/crypto/elliptic
|
||||
CurvePreferences: []tls.CurveID{tls.CurveP256},
|
||||
// Use modern tls mode https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
// https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Only_Support_Strong_Protocols
|
||||
MinVersion: tls.VersionTLS12,
|
||||
// These ciphersuites support Forward Secrecy: https://en.wikipedia.org/wiki/Forward_secrecy
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
},
|
||||
}
|
||||
|
||||
// build standard config from server options
|
||||
if s.TLSCertificate != "" && s.TLSCertificateKey != "" {
|
||||
httpsServer.TLSConfig.Certificates = make([]tls.Certificate, 1)
|
||||
httpsServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(s.TLSCertificate, s.TLSCertificateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.TLSCACertificate != "" {
|
||||
// include specified CA certificate
|
||||
caCert, caCertErr := ioutil.ReadFile(s.TLSCACertificate)
|
||||
if caCertErr != nil {
|
||||
return caCertErr
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
ok := caCertPool.AppendCertsFromPEM(caCert)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot parse CA certificate")
|
||||
}
|
||||
httpsServer.TLSConfig.ClientCAs = caCertPool
|
||||
httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
// call custom TLS configurator
|
||||
if s.cfgTLSFn != nil {
|
||||
s.cfgTLSFn(httpsServer.TLSConfig)
|
||||
}
|
||||
|
||||
if len(httpsServer.TLSConfig.Certificates) == 0 && httpsServer.TLSConfig.GetCertificate == nil {
|
||||
// after standard and custom config are passed, this ends up with no certificate
|
||||
if s.TLSCertificate == "" {
|
||||
if s.TLSCertificateKey == "" {
|
||||
s.Fatalf("the required flags `--tls-certificate` and `--tls-key` were not specified")
|
||||
}
|
||||
s.Fatalf("the required flag `--tls-certificate` was not specified")
|
||||
}
|
||||
if s.TLSCertificateKey == "" {
|
||||
s.Fatalf("the required flag `--tls-key` was not specified")
|
||||
}
|
||||
// this happens with a wrong custom TLS configurator
|
||||
s.Fatalf("no certificate was configured for TLS")
|
||||
}
|
||||
|
||||
if s.cfgServerFn != nil {
|
||||
s.cfgServerFn(httpsServer, "https", s.httpsServerL.Addr().String())
|
||||
}
|
||||
|
||||
servers = append(servers, httpsServer)
|
||||
wg.Add(1)
|
||||
s.Logf("Serving neofs rest gw at https://%s", s.httpsServerL.Addr())
|
||||
go func(l net.Listener) {
|
||||
defer wg.Done()
|
||||
if err := httpsServer.Serve(l); err != nil && err != http.ErrServerClosed {
|
||||
s.Fatalf("%v", err)
|
||||
}
|
||||
s.Logf("Stopped serving neofs rest gw at https://%s", l.Addr())
|
||||
}(tls.NewListener(s.httpsServerL, httpsServer.TLSConfig))
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go s.handleShutdown(wg, &servers)
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// The TLS configuration before HTTPS server starts.
|
||||
func (s *Server) ConfigureTLS(cfgTLS func(tlsConfig *tls.Config)) {
|
||||
s.cfgTLSFn = cfgTLS
|
||||
}
|
||||
|
||||
// As soon as server is initialized but not run yet, this function will be called.
|
||||
// If you need to modify a config, store server instance to stop it individually later, this is the place.
|
||||
// This function can be called multiple times, depending on the number of serving schemes.
|
||||
// scheme value will be set accordingly: "http", "https" or "unix".
|
||||
func (s *Server) ConfigureServer(cfgServer func(s *http.Server, scheme, addr string)) {
|
||||
s.cfgServerFn = cfgServer
|
||||
}
|
||||
|
||||
// Listen creates the listeners for the server
|
||||
func (s *Server) Listen() error {
|
||||
if s.hasListeners { // already done this
|
||||
return nil
|
||||
}
|
||||
|
||||
if s.hasScheme(schemeHTTPS) {
|
||||
// Use http listen limit if https listen limit wasn't defined
|
||||
if s.TLSListenLimit == 0 {
|
||||
s.TLSListenLimit = s.ListenLimit
|
||||
}
|
||||
// Use http tcp keep alive if https tcp keep alive wasn't defined
|
||||
if int64(s.TLSKeepAlive) == 0 {
|
||||
s.TLSKeepAlive = s.KeepAlive
|
||||
}
|
||||
// Use http read timeout if https read timeout wasn't defined
|
||||
if int64(s.TLSReadTimeout) == 0 {
|
||||
s.TLSReadTimeout = s.ReadTimeout
|
||||
}
|
||||
// Use http write timeout if https write timeout wasn't defined
|
||||
if int64(s.TLSWriteTimeout) == 0 {
|
||||
s.TLSWriteTimeout = s.WriteTimeout
|
||||
}
|
||||
}
|
||||
|
||||
if s.hasScheme(schemeHTTP) {
|
||||
listener, err := net.Listen("tcp", s.ListenAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = swag.SplitHostPort(listener.Addr().String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.httpServerL = listener
|
||||
}
|
||||
|
||||
if s.hasScheme(schemeHTTPS) {
|
||||
tlsListener, err := net.Listen("tcp", s.TLSListenAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = swag.SplitHostPort(tlsListener.Addr().String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.httpsServerL = tlsListener
|
||||
}
|
||||
|
||||
s.hasListeners = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown server and clean up resources
|
||||
func (s *Server) Shutdown() error {
|
||||
if atomic.CompareAndSwapInt32(&s.shuttingDown, 0, 1) {
|
||||
close(s.shutdown)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleShutdown(wg *sync.WaitGroup, serversPtr *[]*http.Server) {
|
||||
// wg.Done must occur last, after s.api.ServerShutdown()
|
||||
// (to preserve old behaviour)
|
||||
defer wg.Done()
|
||||
|
||||
<-s.shutdown
|
||||
|
||||
servers := *serversPtr
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), s.GracefulTimeout)
|
||||
defer cancel()
|
||||
|
||||
// first execute the pre-shutdown hook
|
||||
s.api.PreServerShutdown()
|
||||
|
||||
shutdownChan := make(chan bool)
|
||||
for i := range servers {
|
||||
server := servers[i]
|
||||
go func() {
|
||||
var success bool
|
||||
defer func() {
|
||||
shutdownChan <- success
|
||||
}()
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
// Error from closing listeners, or context timeout:
|
||||
s.Logf("HTTP server Shutdown: %v", err)
|
||||
} else {
|
||||
success = true
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait until all listeners have successfully shut down before calling ServerShutdown
|
||||
success := true
|
||||
for range servers {
|
||||
success = success && <-shutdownChan
|
||||
}
|
||||
if success {
|
||||
s.api.ServerShutdown()
|
||||
}
|
||||
}
|
||||
|
||||
// GetHandler returns a handler useful for testing
|
||||
func (s *Server) GetHandler() http.Handler {
|
||||
return s.handler
|
||||
}
|
||||
|
||||
// SetHandler allows for setting a http handler on this server
|
||||
func (s *Server) SetHandler(handler http.Handler) {
|
||||
s.handler = handler
|
||||
}
|
||||
|
||||
// HTTPListener returns the http listener
|
||||
func (s *Server) HTTPListener() (net.Listener, error) {
|
||||
if !s.hasListeners {
|
||||
if err := s.Listen(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s.httpServerL, nil
|
||||
}
|
||||
|
||||
// TLSListener returns the https listener
|
||||
func (s *Server) TLSListener() (net.Listener, error) {
|
||||
if !s.hasListeners {
|
||||
if err := s.Listen(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s.httpsServerL, nil
|
||||
}
|
||||
|
||||
func handleInterrupt(once *sync.Once, s *Server) {
|
||||
once.Do(func() {
|
||||
for range s.interrupt {
|
||||
if s.interrupted {
|
||||
s.Logf("Server already shutting down")
|
||||
continue
|
||||
}
|
||||
s.interrupted = true
|
||||
s.Logf("Shutting down... ")
|
||||
if err := s.Shutdown(); err != nil {
|
||||
s.Logf("HTTP server Shutdown: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func signalNotify(interrupt chan<- os.Signal) {
|
||||
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue