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 }