// Package server provides the HTTP server, routing, and middleware setup. package server import ( "context" "mal/internal/audit" "mal/internal/config" "mal/internal/observability" "net/http" "time" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/render" "go.uber.org/fx" ) var Module = fx.Options( fx.Provide(observability.NewMetrics), fx.Provide(ProvideRouter), fx.Invoke(RunServer), ) func ProvideRouter(cfg config.Config, htmlRender render.HTMLRender, metrics *observability.Metrics) *gin.Engine { if cfg.GinMode == "" { gin.SetMode(gin.ReleaseMode) } else { gin.SetMode(cfg.GinMode) } r := gin.New() r.Use(CORSMiddlewareWithConfig(cfg), RequestContextMiddleware(), audit.ContextMiddleware(), RequestLogger(metrics), gin.Recovery()) r.Static("/static", "./static") r.Static("/dist", "./dist") r.GET("/metrics", gin.WrapH(metrics.Handler())) r.HTMLRender = htmlRender return r } func RunServer(cfg config.Config, lifecycle fx.Lifecycle, r *gin.Engine) { port := cfg.Port srv := newHTTPServer(":"+port, r) lifecycle.Append(fx.Hook{ OnStart: func(context.Context) error { observability.Info( "server_start", "server", "", map[string]any{ "port": port, }, ) go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { // Avoid exiting the process from a goroutine; let the process supervisor handle restarts. observability.Error( "server_listen_error", "server", "", map[string]any{ "port": port, }, err, ) } }() return nil }, OnStop: func(ctx context.Context) error { observability.Info("server_stop", "server", "", nil) ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() return srv.Shutdown(ctx) }, }) } func newHTTPServer(addr string, handler http.Handler) *http.Server { return &http.Server{ Addr: addr, Handler: handler, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 2 * time.Minute, } } // RouteRegister is an interface that modules can implement to register their routes. type RouteRegister interface { Register(r *gin.Engine) } func RegisterRoutes(r *gin.Engine, registers []RouteRegister) { for _, reg := range registers { reg.Register(r) } } // AsRouteRegister is a helper to provide a RouteRegister to the fx group. func AsRouteRegister(f any) any { return fx.Annotate( f, fx.As(new(RouteRegister)), fx.ResultTags(`group:"routes"`), ) }