in router/core/router.go [1076:1249]
func (r *Router) Start(ctx context.Context) error {
if r.shutdown.Load() {
return fmt.Errorf("router is shutdown. Create a new instance with router.NewRouter()")
}
if err := r.bootstrap(ctx); err != nil {
return fmt.Errorf("failed to bootstrap router: %w", err)
}
r.httpServer = newServer(&httpServerOptions{
addr: r.listenAddr,
logger: r.logger,
tlsConfig: r.tlsConfig,
tlsServerConfig: r.tlsServerConfig,
healthcheck: r.healthcheck,
baseURL: r.baseURL,
maxHeaderBytes: int(r.routerTrafficConfig.MaxHeaderBytes.Uint64()),
livenessCheckPath: r.livenessCheckPath,
readinessCheckPath: r.readinessCheckPath,
healthCheckPath: r.healthCheckPath,
})
// Start the server with the static config without polling
if r.staticExecutionConfig != nil {
if err := r.listenAndServe(); err != nil {
return err
}
if err := r.newServer(ctx, r.staticExecutionConfig); err != nil {
return err
}
defer func() {
r.httpServer.healthcheck.SetReady(true)
r.logger.Info("Server initialized and ready to serve requests",
zap.String("listen_addr", r.listenAddr),
zap.Bool("playground", r.playgroundConfig.Enabled),
zap.Bool("introspection", r.introspection),
zap.String("config_version", r.staticExecutionConfig.Version),
)
}()
if r.executionConfig != nil && r.executionConfig.Watch {
w, err := watcher.New(watcher.Options{
Logger: r.logger.With(zap.String("watcher_label", "execution_config")),
Path: r.executionConfig.Path,
Interval: r.executionConfig.WatchInterval,
Callback: func() {
if r.shutdown.Load() {
r.logger.Warn("Router is in shutdown state. Skipping config update")
return
}
data, err := os.ReadFile(r.executionConfig.Path)
if err != nil {
r.logger.Error("Failed to read config file", zap.Error(err))
return
}
r.logger.Info("Config file changed. Updating server with new config", zap.String("path", r.executionConfig.Path))
cfg, err := execution_config.UnmarshalConfig(data)
if err != nil {
r.logger.Error("Failed to unmarshal config file", zap.Error(err))
return
}
if err := r.newServer(ctx, cfg); err != nil {
r.logger.Error("Failed to update server with new config", zap.Error(err))
return
}
},
})
if err != nil {
return fmt.Errorf("failed to create watcher: %w", err)
}
go func() {
if err := w(ctx); err != nil {
r.logger.Error("Error watching execution config", zap.Error(err))
return
}
}()
r.logger.Info("Watching config file for changes. Router will hot-reload automatically without downtime",
zap.String("path", r.executionConfig.Path),
)
return nil
}
r.logger.Info("Static execution config provided. Polling and watching is disabled. Updating execution config is only possible by restarting the router")
return nil
}
// when no static config is provided and no poller is configured, we can't start the server
if r.configPoller == nil {
return fmt.Errorf("execution config fetcher not provided. Please provide a static execution config instead")
}
cfg, err := r.configPoller.GetRouterConfig(ctx)
if err != nil {
return fmt.Errorf("failed to get initial execution config: %w", err)
}
if err := r.listenAndServe(); err != nil {
r.logger.Error("Failed to start server with initial config", zap.Error(err))
return err
}
if err := r.newServer(ctx, cfg.Config); err != nil {
return err
}
if r.playgroundConfig.Enabled {
graphqlEndpointURL, err := url.JoinPath(r.baseURL, r.graphqlPath)
if err != nil {
return fmt.Errorf("failed to join graphql endpoint url: %w", err)
}
r.logger.Info("GraphQL endpoint",
zap.String("method", http.MethodPost),
zap.String("url", graphqlEndpointURL),
)
}
/**
* Server logging after features has been initialized / disabled
*/
if r.localhostFallbackInsideDocker && docker.Inside() {
r.logger.Info("localhost fallback enabled, connections that fail to connect to localhost will be retried using host.docker.internal")
}
if r.developmentMode && r.engineExecutionConfiguration.EnableRequestTracing && r.graphApiToken == "" {
r.logger.Warn("Advanced Request Tracing (ART) is enabled in development mode but requires a graph token to work in production. For more information see https://cosmo-docs.wundergraph.com/router/advanced-request-tracing-art")
}
if r.redisClient != nil {
r.logger.Info("Rate limiting enabled",
zap.Int("rate", r.rateLimit.SimpleStrategy.Rate),
zap.Int("burst", r.rateLimit.SimpleStrategy.Burst),
zap.Duration("duration", r.Config.rateLimit.SimpleStrategy.Period),
zap.Bool("rejectExceeding", r.Config.rateLimit.SimpleStrategy.RejectExceedingRequests),
)
}
r.configPoller.Subscribe(ctx, func(newConfig *nodev1.RouterConfig, oldVersion string) error {
if r.shutdown.Load() {
r.logger.Warn("Router is in shutdown state. Skipping config update")
return nil
}
if err := r.newServer(ctx, newConfig); err != nil {
return err
}
return nil
})
// Mark the server as ready
r.httpServer.healthcheck.SetReady(true)
r.logger.Info("Server initialized and ready to serve requests",
zap.String("listen_addr", r.listenAddr),
zap.Bool("playground", r.playgroundConfig.Enabled),
zap.Bool("introspection", r.introspection),
zap.String("config_version", cfg.Config.GetVersion()),
)
return nil
}