Files
statusServer/statusServer.go
2025-08-24 08:33:25 +02:00

120 lines
3.7 KiB
Go

package statusServer
import (
"fmt"
"net/http"
"strings"
"time"
"gitea.tecamino.com/paadi/statusServer/utils"
"gitea.tecamino.com/paadi/statusServer/models"
logging "gitea.tecamino.com/paadi/tecamino-logger/logging"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
// StatusServer wraps an HTTP server with PubSub over websockets.
// It manages HTTP routes, PubSub broadcasting, and runtime metrics publishing.
type StatusServer struct {
Ip string // listen address (defaults to all interfaces)
Port int // server port
Routes *gin.Engine // Gin router for HTTP endpoints
pubSub *StatusWebsocket // websocket-enabled PubSub system
log *logging.Logger // application logger
}
// NewStatusServer initializes and configures a new StatusServer instance.
//
// Setup steps:
// - Apply default values to the config.
// - Initialize logging.
// - Configure CORS middleware (including localhost + detected local IP).
// - Create PubSubWebsocket with worker/message limits.
// - Register the /status websocket endpoint.
//
// Returns the server instance or an error if initialization fails.
func NewStatusServer(config models.Config) (*StatusServer, error) {
config.Default()
// initialize logger
logger, err := logging.NewLogger("statusServer.log", &config.Log)
if err != nil {
return nil, err
}
logger.Debug("NewStatusServer", "initialize new server with allowOrigins: "+
strings.Join(config.AllowOrigins, ", ")+" listening on port: "+fmt.Sprint(config.Port))
r := gin.Default()
// always allow local dev origins (http://localhost:PORT)
config.AllowOrigins = append(config.AllowOrigins, fmt.Sprintf("http://localhost:%d", config.Port))
// detect and allow local IP in CORS origins
localIP, err := utils.GetLocalIP()
if err != nil {
logger.Error("NewStatusServer", "get local ip: "+err.Error())
} else {
config.AllowOrigins = append(config.AllowOrigins, fmt.Sprintf("http://%s:%d", localIP, config.Port))
}
// configure CORS middleware
r.Use(cors.New(cors.Config{
AllowOrigins: config.AllowOrigins,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Accept"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
// build the server
s := &StatusServer{
Ip: "0.0.0.0", // listen on all interfaces
Port: config.Port,
Routes: r,
log: logger,
}
// initialize PubSub websocket service
logger.Debug("NewStatusServer", fmt.Sprintf(
"initialize new PubSubWebsocket service with %d workers %d messages",
config.PubSub.Workers, config.PubSub.MaxMessages,
))
s.pubSub, err = NewStatusWebsocket(config.PubSub.Workers, config.PubSub.MaxMessages, logger)
if err != nil {
logger.Error("NewPubSubWebsocket", err.Error())
}
// register websocket endpoint
r.GET("/status", s.pubSub.NewConection)
r.GET("/info", s.pubSub.NewConection)
return s, nil
}
// ServeHttp starts the HTTP server
// It blocks until the HTTP server stops. Resources are cleaned up on shutdown:
// PubSub is closed and a shutdown message is logged.
func (s *StatusServer) ServeHttp() error {
s.log.Debug("ServeHttp", "start publishing runtime metrics (memory + GC count)")
// log startup
s.log.Info("ServeHttp", fmt.Sprintf("start listening on %s:%d", s.Ip, s.Port))
// configure HTTP server
srv := &http.Server{
Addr: fmt.Sprintf("%s:%d", s.Ip, s.Port),
Handler: s.Routes,
}
// log shutdown + close PubSub on exit
defer s.log.Info("ServeHttp", fmt.Sprintf("close server listening on %s:%d", s.Ip, s.Port))
defer s.pubSub.Close()
// blocking call
return srv.ListenAndServe()
}