172 lines
4.1 KiB
Go
172 lines
4.1 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"syscall"
|
|
"time"
|
|
|
|
"otpm/config"
|
|
"otpm/middleware"
|
|
)
|
|
|
|
// Server represents the HTTP server
|
|
type Server struct {
|
|
server *http.Server
|
|
router *http.ServeMux
|
|
config *config.Config
|
|
}
|
|
|
|
// New creates a new server
|
|
func New(cfg *config.Config) *Server {
|
|
router := http.NewServeMux()
|
|
|
|
server := &http.Server{
|
|
Addr: fmt.Sprintf(":%d", cfg.Server.Port),
|
|
Handler: router,
|
|
ReadTimeout: cfg.Server.ReadTimeout,
|
|
WriteTimeout: cfg.Server.WriteTimeout,
|
|
IdleTimeout: 120 * time.Second,
|
|
}
|
|
|
|
return &Server{
|
|
server: server,
|
|
router: router,
|
|
config: cfg,
|
|
}
|
|
}
|
|
|
|
// Start starts the server
|
|
func (s *Server) Start() error {
|
|
// Apply global middleware in correct order with enhanced error handling
|
|
var handler http.Handler = s.router
|
|
|
|
// Logger should be first to capture all request details
|
|
handler = middleware.Logger(handler)
|
|
|
|
// CORS next to handle pre-flight requests
|
|
handler = middleware.CORS(handler)
|
|
|
|
// Then Timeout to enforce request deadlines
|
|
handler = middleware.Timeout(s.config.Server.Timeout)(handler)
|
|
|
|
// Recover should be outermost to catch any panics
|
|
handler = middleware.Recover(handler)
|
|
|
|
s.server.Handler = handler
|
|
|
|
// Log server configuration at startup
|
|
log.Printf("Server configuration:\n"+
|
|
"Address: %s\n"+
|
|
"Read Timeout: %v\n"+
|
|
"Write Timeout: %v\n"+
|
|
"Idle Timeout: %v\n"+
|
|
"Request Timeout: %v",
|
|
s.server.Addr,
|
|
s.server.ReadTimeout,
|
|
s.server.WriteTimeout,
|
|
s.server.IdleTimeout,
|
|
s.config.Server.Timeout,
|
|
)
|
|
|
|
// Start server in a goroutine
|
|
serverErr := make(chan error, 1)
|
|
go func() {
|
|
log.Printf("Server starting on %s", s.server.Addr)
|
|
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
serverErr <- fmt.Errorf("server error: %w", err)
|
|
}
|
|
}()
|
|
|
|
// Wait for interrupt signal or server error
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
select {
|
|
case err := <-serverErr:
|
|
return err
|
|
case <-quit:
|
|
return s.Shutdown()
|
|
}
|
|
}
|
|
|
|
// Shutdown gracefully stops the server
|
|
func (s *Server) Shutdown() error {
|
|
log.Println("Shutting down server...")
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), s.config.Server.ShutdownTimeout)
|
|
defer cancel()
|
|
|
|
if err := s.server.Shutdown(ctx); err != nil {
|
|
return fmt.Errorf("graceful shutdown failed: %w", err)
|
|
}
|
|
|
|
log.Println("Server stopped gracefully")
|
|
return nil
|
|
}
|
|
|
|
// Router returns the router
|
|
func (s *Server) Router() *http.ServeMux {
|
|
return s.router
|
|
}
|
|
|
|
// RegisterRoutes registers all routes
|
|
func (s *Server) RegisterRoutes(routes map[string]http.Handler) {
|
|
for pattern, handler := range routes {
|
|
s.router.Handle(pattern, handler)
|
|
}
|
|
}
|
|
|
|
// RegisterAuthRoutes registers routes that require authentication
|
|
func (s *Server) RegisterAuthRoutes(routes map[string]http.Handler) {
|
|
for pattern, handler := range routes {
|
|
// Apply authentication middleware
|
|
authHandler := middleware.Auth(s.config.JWT.Secret)(handler)
|
|
s.router.Handle(pattern, authHandler)
|
|
}
|
|
}
|
|
|
|
// RegisterHealthCheck registers an enhanced health check endpoint
|
|
func (s *Server) RegisterHealthCheck() {
|
|
s.router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
response := map[string]interface{}{
|
|
"status": "ok",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
"version": "1.0.0", // Hardcoded version instead of from config
|
|
"system": map[string]interface{}{
|
|
"goroutines": runtime.NumGoroutine(),
|
|
"memory": getMemoryUsage(),
|
|
},
|
|
}
|
|
|
|
// Add database status if configured
|
|
if s.config.Database.DSN != "" { // Changed from URL to DSN to match config
|
|
dbStatus := "ok"
|
|
// Removed DB ping check since we don't have DB instance in config
|
|
response["database"] = dbStatus
|
|
}
|
|
|
|
middleware.SuccessResponse(w, response)
|
|
})
|
|
}
|
|
|
|
// getMemoryUsage returns current memory usage in MB
|
|
func getMemoryUsage() map[string]interface{} {
|
|
var m runtime.MemStats
|
|
runtime.ReadMemStats(&m)
|
|
return map[string]interface{}{
|
|
"alloc_mb": bToMb(m.Alloc),
|
|
"total_alloc_mb": bToMb(m.TotalAlloc),
|
|
"sys_mb": bToMb(m.Sys),
|
|
"num_gc": m.NumGC,
|
|
}
|
|
}
|
|
|
|
func bToMb(b uint64) float64 {
|
|
return float64(b) / 1024 / 1024
|
|
}
|