otpm/server/server.go
“xHuPo” bcd986e3f7 beta
2025-05-23 18:57:11 +08:00

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
}