github.com/hellofresh/janus@v0.0.0-20230925145208-ce8de8183c67/pkg/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	"time"
     9  
    10  	"github.com/go-chi/chi"
    11  	"github.com/hellofresh/stats-go/client"
    12  	log "github.com/sirupsen/logrus"
    13  	"go.opencensus.io/plugin/ochttp/propagation/b3"
    14  
    15  	"github.com/hellofresh/janus/pkg/api"
    16  	"github.com/hellofresh/janus/pkg/config"
    17  	"github.com/hellofresh/janus/pkg/errors"
    18  	"github.com/hellofresh/janus/pkg/loader"
    19  	"github.com/hellofresh/janus/pkg/middleware"
    20  	"github.com/hellofresh/janus/pkg/plugin"
    21  	"github.com/hellofresh/janus/pkg/proxy"
    22  	"github.com/hellofresh/janus/pkg/router"
    23  	"github.com/hellofresh/janus/pkg/web"
    24  )
    25  
    26  // Server is the Janus server
    27  type Server struct {
    28  	server                *http.Server
    29  	provider              api.Repository
    30  	register              *proxy.Register
    31  	apiLoader             *loader.APILoader
    32  	currentConfigurations *api.Configuration
    33  	configurationChan     chan api.ConfigurationChanged
    34  	stopChan              chan struct{}
    35  	globalConfig          *config.Specification
    36  	statsClient           client.Client
    37  	webServer             *web.Server
    38  	profilingEnabled      bool
    39  	profilingPublic       bool
    40  }
    41  
    42  // New creates a new instance of Server
    43  func New(opts ...Option) *Server {
    44  	s := Server{
    45  		configurationChan: make(chan api.ConfigurationChanged, 100),
    46  		stopChan:          make(chan struct{}, 1),
    47  	}
    48  
    49  	for _, opt := range opts {
    50  		opt(&s)
    51  	}
    52  
    53  	return &s
    54  }
    55  
    56  // Start starts the server
    57  func (s *Server) Start() error {
    58  	return s.StartWithContext(context.Background())
    59  }
    60  
    61  // StartWithContext starts the server and Stop/Close it when context is Done
    62  func (s *Server) StartWithContext(ctx context.Context) error {
    63  	go func() {
    64  		defer s.Close()
    65  		<-ctx.Done()
    66  		log.Info("I have to go...")
    67  		reqAcceptGraceTimeOut := time.Duration(s.globalConfig.GraceTimeOut)
    68  		if reqAcceptGraceTimeOut > 0 {
    69  			log.Infof("Waiting %s for incoming requests to cease", reqAcceptGraceTimeOut)
    70  			time.Sleep(reqAcceptGraceTimeOut)
    71  		}
    72  		log.Info("Stopping server gracefully")
    73  	}()
    74  
    75  	// Register must be initialised synchronously to avoid race condition
    76  	r := s.createRouter()
    77  	s.register = proxy.NewRegister(
    78  		proxy.WithRouter(r),
    79  		proxy.WithFlushInterval(s.globalConfig.BackendFlushInterval),
    80  		proxy.WithIdleConnectionsPerHost(s.globalConfig.MaxIdleConnsPerHost),
    81  		proxy.WithIdleConnTimeout(s.globalConfig.IdleConnTimeout),
    82  		proxy.WithIdleConnPurgeTicker(s.globalConfig.ConnPurgeInterval),
    83  		proxy.WithStatsClient(s.statsClient),
    84  		proxy.WithIsPublicEndpoint(s.globalConfig.Tracing.IsPublicEndpoint),
    85  	)
    86  
    87  	// API Loader must be initialised synchronously as well to avoid race condition
    88  	s.apiLoader = loader.NewAPILoader(s.register)
    89  
    90  	go func() {
    91  		if err := s.startHTTPServers(ctx, r); err != nil {
    92  			log.WithError(err).Fatal("Could not start http servers")
    93  		}
    94  	}()
    95  
    96  	go s.listenProviders(s.stopChan)
    97  
    98  	definitions, err := s.provider.FindAll()
    99  	if err != nil {
   100  		return fmt.Errorf("could not find all configurations from the provider: %w", err)
   101  	}
   102  
   103  	s.currentConfigurations = &api.Configuration{Definitions: definitions}
   104  	if err := s.startProvider(ctx); err != nil {
   105  		log.WithError(err).Fatal("Could not start providers")
   106  	}
   107  
   108  	event := plugin.OnStartup{
   109  		StatsClient:   s.statsClient,
   110  		Register:      s.register,
   111  		Config:        s.globalConfig,
   112  		Configuration: definitions,
   113  	}
   114  
   115  	if mgoRepo, ok := s.provider.(*api.MongoRepository); ok {
   116  		event.MongoDB = mgoRepo.DB
   117  	}
   118  
   119  	if cassRepo, ok := s.provider.(*api.CassandraRepository); ok {
   120  		event.Cassandra = cassRepo.Session
   121  	}
   122  
   123  	plugin.EmitEvent(plugin.StartupEvent, event)
   124  	s.apiLoader.RegisterAPIs(definitions)
   125  
   126  	log.Info("Janus started")
   127  
   128  	return nil
   129  }
   130  
   131  // Wait blocks until server is shut down.
   132  func (s *Server) Wait() {
   133  	<-s.stopChan
   134  }
   135  
   136  // Stop stops the server
   137  func (s *Server) Stop() {
   138  	defer log.Info("Server stopped")
   139  
   140  	graceTimeOut := time.Duration(s.globalConfig.GraceTimeOut)
   141  	ctx, cancel := context.WithTimeout(context.Background(), graceTimeOut)
   142  	defer cancel()
   143  	log.Debugf("Waiting %s seconds before killing connections...", graceTimeOut)
   144  	if err := s.server.Shutdown(ctx); err != nil {
   145  		log.WithError(err).Debug("Wait is over due to error")
   146  		s.server.Close()
   147  	}
   148  	log.Debug("Server closed")
   149  
   150  	s.stopChan <- struct{}{}
   151  }
   152  
   153  // Close closes the server
   154  func (s *Server) Close() error {
   155  	defer close(s.stopChan)
   156  	defer close(s.configurationChan)
   157  	defer s.webServer.Stop()
   158  
   159  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   160  	defer cancel()
   161  	go func(ctx context.Context) {
   162  		<-ctx.Done()
   163  		if ctx.Err() == context.Canceled {
   164  			return
   165  		} else if ctx.Err() == context.DeadlineExceeded {
   166  			panic("Timeout while stopping janus, killing instance ✝")
   167  		}
   168  	}(ctx)
   169  
   170  	return s.server.Close()
   171  }
   172  
   173  func (s *Server) startHTTPServers(ctx context.Context, r router.Router) error {
   174  	return s.listenAndServe(chi.ServerBaseContext(ctx, r))
   175  }
   176  
   177  func (s *Server) startProvider(ctx context.Context) error {
   178  	s.webServer = web.New(
   179  		web.WithConfigurations(s.currentConfigurations),
   180  		web.WithPort(s.globalConfig.Web.Port),
   181  		web.WithTLS(s.globalConfig.Web.TLS),
   182  		web.WithCredentials(s.globalConfig.Web.Credentials),
   183  		web.WithProfiler(s.profilingEnabled, s.profilingPublic),
   184  	)
   185  
   186  	if err := s.webServer.Start(); err != nil {
   187  		return fmt.Errorf("could not start Janus web API: %w", err)
   188  	}
   189  
   190  	// We're listening to the configuration changes in any case, even if provider does not implement Listener,
   191  	// so we can use "file" storage as memory - all the persistent definitions are loaded on startup,
   192  	// but then API allows to manipulate proxies in memory. Otherwise api calls just stuck because channel is busy.
   193  	go func() {
   194  		ch := make(chan api.ConfigurationMessage)
   195  		listener, providerIsListener := s.provider.(api.Listener)
   196  		if providerIsListener {
   197  			listener.Listen(ctx, ch)
   198  		}
   199  
   200  		for {
   201  			select {
   202  			case c, more := <-s.webServer.ConfigurationChan:
   203  				if !more {
   204  					return
   205  				}
   206  
   207  				s.updateConfigurations(c)
   208  				s.handleEvent(s.currentConfigurations)
   209  
   210  				if providerIsListener {
   211  					ch <- c
   212  				}
   213  			case <-ctx.Done():
   214  				close(ch)
   215  				return
   216  			}
   217  		}
   218  	}()
   219  
   220  	if watcher, ok := s.provider.(api.Watcher); ok {
   221  		watcher.Watch(ctx, s.configurationChan)
   222  	}
   223  
   224  	return nil
   225  }
   226  
   227  func (s *Server) listenProviders(stop chan struct{}) {
   228  	for {
   229  		select {
   230  		case <-stop:
   231  			return
   232  		case configMsg, ok := <-s.configurationChan:
   233  			if !ok {
   234  				return
   235  			}
   236  
   237  			if s.currentConfigurations.EqualsTo(configMsg.Configurations) {
   238  				log.Debug("Skipping same configuration")
   239  				continue
   240  			}
   241  
   242  			s.currentConfigurations.Definitions = configMsg.Configurations.Definitions
   243  			s.handleEvent(configMsg.Configurations)
   244  		}
   245  	}
   246  }
   247  
   248  func (s *Server) listenAndServe(handler http.Handler) error {
   249  	address := fmt.Sprintf(":%v", s.globalConfig.Port)
   250  	logger := log.WithField("address", address)
   251  	s.server = &http.Server{
   252  		Addr:         address,
   253  		Handler:      handler,
   254  		ReadTimeout:  s.globalConfig.RespondingTimeouts.ReadTimeout,
   255  		WriteTimeout: s.globalConfig.RespondingTimeouts.WriteTimeout,
   256  		IdleTimeout:  s.globalConfig.RespondingTimeouts.IdleTimeout,
   257  	}
   258  	listener, err := net.Listen("tcp", address)
   259  	if err != nil {
   260  		return fmt.Errorf("error opening listener: %w", err)
   261  	}
   262  
   263  	if s.globalConfig.TLS.IsHTTPS() {
   264  		s.server.Addr = fmt.Sprintf(":%v", s.globalConfig.TLS.Port)
   265  
   266  		if s.globalConfig.TLS.Redirect {
   267  			go func() {
   268  				logger.Info("Listening HTTP redirects to HTTPS")
   269  				log.Fatal(http.Serve(listener, web.RedirectHTTPS(s.globalConfig.TLS.Port)))
   270  			}()
   271  		}
   272  
   273  		logger.Info("Listening HTTPS")
   274  		return s.server.ServeTLS(listener, s.globalConfig.TLS.CertFile, s.globalConfig.TLS.KeyFile)
   275  	}
   276  
   277  	logger.Info("Certificate and certificate key were not found, defaulting to HTTP")
   278  	return s.server.Serve(listener)
   279  }
   280  
   281  func (s *Server) createRouter() router.Router {
   282  	// create router with a custom not found handler
   283  	router.DefaultOptions.NotFoundHandler = errors.NotFound
   284  	r := router.NewChiRouterWithOptions(router.DefaultOptions)
   285  
   286  	// Add RequestID middleware first if enabled, so we could use it in other middlewares, e.g. logger
   287  	if s.globalConfig.RequestID {
   288  		r.Use(middleware.RequestID)
   289  	}
   290  
   291  	// Add DebugTraceKey middleware which returns debug header with the Trace ID
   292  	if s.globalConfig.Tracing.DebugTraceKey != "" {
   293  		r.Use(middleware.DebugTrace(&b3.HTTPFormat{}, s.globalConfig.Tracing.DebugTraceKey))
   294  	}
   295  
   296  	r.Use(
   297  		middleware.NewStats(s.statsClient).Handler,
   298  		middleware.NewLogger().Handler,
   299  		middleware.NewRecovery(errors.RecoveryHandler),
   300  	)
   301  
   302  	// some routers may panic when have empty routes list, so add one dummy 404 route to avoid this
   303  	if r.RoutesCount() < 1 {
   304  		r.Any("/", errors.NotFound)
   305  	}
   306  
   307  	return r
   308  }
   309  
   310  func (s *Server) updateConfigurations(cfg api.ConfigurationMessage) {
   311  	currentDefinitions := s.currentConfigurations.Definitions
   312  
   313  	switch cfg.Operation {
   314  	case api.AddedOperation:
   315  		currentDefinitions = append(currentDefinitions, cfg.Configuration)
   316  	case api.UpdatedOperation:
   317  		for i, d := range currentDefinitions {
   318  			if d.Name == cfg.Configuration.Name {
   319  				currentDefinitions[i] = cfg.Configuration
   320  			}
   321  		}
   322  	case api.RemovedOperation:
   323  		for i, d := range currentDefinitions {
   324  			if d.Name == cfg.Configuration.Name {
   325  				copy(currentDefinitions[i:], currentDefinitions[i+1:])
   326  				// currentDefinitions[len(currentDefinitions)-1] = nil // or the zero value of T
   327  				currentDefinitions = currentDefinitions[:len(currentDefinitions)-1]
   328  			}
   329  		}
   330  	}
   331  
   332  	s.currentConfigurations.Definitions = currentDefinitions
   333  }
   334  
   335  func (s *Server) handleEvent(cfg *api.Configuration) {
   336  	log.Debug("Refreshing configuration")
   337  	newRouter := s.createRouter()
   338  
   339  	s.register.UpdateRouter(newRouter)
   340  	s.apiLoader.RegisterAPIs(cfg.Definitions)
   341  
   342  	plugin.EmitEvent(plugin.ReloadEvent, plugin.OnReload{Configurations: cfg.Definitions})
   343  
   344  	s.server.Handler = newRouter
   345  	log.Debug("Configuration refresh done")
   346  }