- Added better error detection for HTTP server startup - Added connectivity check to verify server is actually listening - Increased wait time to 500ms for better error detection - Added warning log if server connectivity check fails (may still be starting) - Improved logging messages for server startup This should help diagnose why the HTTP server isn't starting and provide better visibility into the startup process.
153 lines
3.8 KiB
Go
153 lines
3.8 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"git.dcentral.systems/toolz/goplt/internal/health"
|
|
"git.dcentral.systems/toolz/goplt/internal/metrics"
|
|
"git.dcentral.systems/toolz/goplt/pkg/config"
|
|
"git.dcentral.systems/toolz/goplt/pkg/errorbus"
|
|
"git.dcentral.systems/toolz/goplt/pkg/logger"
|
|
)
|
|
|
|
// Server wraps the HTTP server and Gin router.
|
|
type Server struct {
|
|
httpServer *http.Server
|
|
router *gin.Engine
|
|
}
|
|
|
|
// NewServer creates a new HTTP server with all middleware and routes.
|
|
func NewServer(
|
|
cfg config.ConfigProvider,
|
|
log logger.Logger,
|
|
healthRegistry *health.Registry,
|
|
metricsRegistry *metrics.Metrics,
|
|
errorBus errorbus.ErrorPublisher,
|
|
tracer trace.TracerProvider,
|
|
) (*Server, error) {
|
|
// Set Gin mode
|
|
env := cfg.GetString("environment")
|
|
if env == "production" {
|
|
gin.SetMode(gin.ReleaseMode)
|
|
}
|
|
|
|
router := gin.New()
|
|
|
|
// Add middleware (order matters!)
|
|
// OpenTelemetry tracing should be first to capture all requests
|
|
if tracer != nil {
|
|
router.Use(otelgin.Middleware("platform", otelgin.WithTracerProvider(tracer)))
|
|
}
|
|
router.Use(RequestIDMiddleware())
|
|
router.Use(LoggingMiddleware(log))
|
|
router.Use(PanicRecoveryMiddleware(errorBus))
|
|
router.Use(metricsRegistry.HTTPMiddleware())
|
|
router.Use(CORSMiddleware())
|
|
|
|
// Request timeout middleware (optional, can be configured per route if needed)
|
|
// router.Use(TimeoutMiddleware(timeout))
|
|
|
|
// Register core routes
|
|
registerRoutes(router, healthRegistry, metricsRegistry)
|
|
|
|
// Get server configuration
|
|
port := cfg.GetInt("server.port")
|
|
if port == 0 {
|
|
port = 8080
|
|
}
|
|
host := cfg.GetString("server.host")
|
|
if host == "" {
|
|
host = "0.0.0.0"
|
|
}
|
|
|
|
readTimeout := cfg.GetDuration("server.read_timeout")
|
|
if readTimeout == 0 {
|
|
readTimeout = 30 * time.Second
|
|
}
|
|
|
|
writeTimeout := cfg.GetDuration("server.write_timeout")
|
|
if writeTimeout == 0 {
|
|
writeTimeout = 30 * time.Second
|
|
}
|
|
|
|
addr := fmt.Sprintf("%s:%d", host, port)
|
|
|
|
httpServer := &http.Server{
|
|
Addr: addr,
|
|
Handler: router,
|
|
ReadTimeout: readTimeout,
|
|
WriteTimeout: writeTimeout,
|
|
IdleTimeout: 120 * time.Second,
|
|
}
|
|
|
|
return &Server{
|
|
httpServer: httpServer,
|
|
router: router,
|
|
}, nil
|
|
}
|
|
|
|
// registerRoutes registers all core routes.
|
|
func registerRoutes(
|
|
router *gin.Engine,
|
|
healthRegistry *health.Registry,
|
|
metricsRegistry *metrics.Metrics,
|
|
) {
|
|
// Health endpoints
|
|
router.GET("/healthz", func(c *gin.Context) {
|
|
status := healthRegistry.LivenessCheck(c.Request.Context())
|
|
if status.Status == "healthy" {
|
|
c.JSON(http.StatusOK, status)
|
|
} else {
|
|
c.JSON(http.StatusServiceUnavailable, status)
|
|
}
|
|
})
|
|
|
|
router.GET("/ready", func(c *gin.Context) {
|
|
status := healthRegistry.ReadinessCheck(c.Request.Context())
|
|
if status.Status == "healthy" {
|
|
c.JSON(http.StatusOK, status)
|
|
} else {
|
|
c.JSON(http.StatusServiceUnavailable, status)
|
|
}
|
|
})
|
|
|
|
// Metrics endpoint
|
|
router.GET("/metrics", gin.WrapH(metricsRegistry.Handler()))
|
|
}
|
|
|
|
// Start starts the HTTP server.
|
|
func (s *Server) Start() error {
|
|
// ListenAndServe will block until the server is closed
|
|
// If there's an immediate error (like port in use), it will return immediately
|
|
return s.httpServer.ListenAndServe()
|
|
}
|
|
|
|
// StartAsync starts the HTTP server in a goroutine and returns a channel that signals when it's ready or errored.
|
|
func (s *Server) StartAsync() <-chan error {
|
|
errChan := make(chan error, 1)
|
|
go func() {
|
|
if err := s.Start(); err != nil && err != http.ErrServerClosed {
|
|
errChan <- err
|
|
}
|
|
close(errChan)
|
|
}()
|
|
return errChan
|
|
}
|
|
|
|
// Shutdown gracefully shuts down the HTTP server.
|
|
func (s *Server) Shutdown(ctx context.Context) error {
|
|
return s.httpServer.Shutdown(ctx)
|
|
}
|
|
|
|
// Router returns the Gin router (for adding additional routes).
|
|
func (s *Server) Router() *gin.Engine {
|
|
return s.router
|
|
}
|
|
|