func()

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
}