Files
gpt-load/internal/handler/handler.go
2025-06-09 22:27:30 +08:00

215 lines
5.6 KiB
Go

// Package handler provides HTTP handlers for the application
package handler
import (
"net/http"
"runtime"
"time"
"gpt-load/pkg/types"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
// Handler contains dependencies for HTTP handlers
type Handler struct {
keyManager types.KeyManager
config types.ConfigManager
}
// NewHandler creates a new handler instance
func NewHandler(keyManager types.KeyManager, config types.ConfigManager) *Handler {
return &Handler{
keyManager: keyManager,
config: config,
}
}
// Health handles health check requests
func (h *Handler) Health(c *gin.Context) {
stats := h.keyManager.GetStats()
status := "healthy"
httpStatus := http.StatusOK
// Check if there are any healthy keys
if stats.HealthyKeys == 0 {
status = "unhealthy"
httpStatus = http.StatusServiceUnavailable
}
// Calculate uptime (this should be tracked from server start time)
uptime := "unknown"
if startTime, exists := c.Get("serverStartTime"); exists {
if st, ok := startTime.(time.Time); ok {
uptime = time.Since(st).String()
}
}
c.JSON(httpStatus, gin.H{
"status": status,
"timestamp": time.Now().UTC().Format(time.RFC3339),
"healthy_keys": stats.HealthyKeys,
"total_keys": stats.TotalKeys,
"uptime": uptime,
})
}
// Stats handles statistics requests
func (h *Handler) Stats(c *gin.Context) {
stats := h.keyManager.GetStats()
// Add additional system information
var m runtime.MemStats
runtime.ReadMemStats(&m)
response := gin.H{
"keys": gin.H{
"total": stats.TotalKeys,
"healthy": stats.HealthyKeys,
"blacklisted": stats.BlacklistedKeys,
"current_index": stats.CurrentIndex,
},
"requests": gin.H{
"success_count": stats.SuccessCount,
"failure_count": stats.FailureCount,
"total_count": stats.SuccessCount + stats.FailureCount,
},
"memory": gin.H{
"alloc_mb": bToMb(m.Alloc),
"total_alloc_mb": bToMb(m.TotalAlloc),
"sys_mb": bToMb(m.Sys),
"num_gc": m.NumGC,
"last_gc": time.Unix(0, int64(m.LastGC)).Format("2006-01-02 15:04:05"),
"next_gc_mb": bToMb(m.NextGC),
},
"system": gin.H{
"goroutines": runtime.NumGoroutine(),
"cpu_count": runtime.NumCPU(),
"go_version": runtime.Version(),
},
"timestamp": time.Now().UTC().Format(time.RFC3339),
}
c.JSON(http.StatusOK, response)
}
// Blacklist handles blacklist requests
func (h *Handler) Blacklist(c *gin.Context) {
blacklist := h.keyManager.GetBlacklist()
response := gin.H{
"blacklisted_keys": blacklist,
"count": len(blacklist),
"timestamp": time.Now().UTC().Format(time.RFC3339),
}
c.JSON(http.StatusOK, response)
}
// ResetKeys handles key reset requests
func (h *Handler) ResetKeys(c *gin.Context) {
// Reset blacklist
h.keyManager.ResetBlacklist()
// Reload keys from file
if err := h.keyManager.LoadKeys(); err != nil {
logrus.Errorf("Failed to reload keys: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to reload keys",
"message": err.Error(),
})
return
}
stats := h.keyManager.GetStats()
c.JSON(http.StatusOK, gin.H{
"message": "Keys reset and reloaded successfully",
"total_keys": stats.TotalKeys,
"healthy_keys": stats.HealthyKeys,
"timestamp": time.Now().UTC().Format(time.RFC3339),
})
logrus.Info("Keys reset and reloaded successfully")
}
// MethodNotAllowed handles 405 requests
func (h *Handler) MethodNotAllowed(c *gin.Context) {
c.JSON(http.StatusMethodNotAllowed, gin.H{
"error": "Method not allowed",
"path": c.Request.URL.Path,
"method": c.Request.Method,
"timestamp": time.Now().UTC().Format(time.RFC3339),
})
}
// GetConfig returns configuration information (for debugging)
func (h *Handler) GetConfig(c *gin.Context) {
// Only allow in development mode or with special header
if c.GetHeader("X-Debug-Config") != "true" {
c.JSON(http.StatusForbidden, gin.H{
"error": "Access denied",
})
return
}
serverConfig := h.config.GetServerConfig()
keysConfig := h.config.GetKeysConfig()
openaiConfig := h.config.GetOpenAIConfig()
authConfig := h.config.GetAuthConfig()
corsConfig := h.config.GetCORSConfig()
perfConfig := h.config.GetPerformanceConfig()
logConfig := h.config.GetLogConfig()
// Sanitize sensitive information
sanitizedConfig := gin.H{
"server": gin.H{
"host": serverConfig.Host,
"port": serverConfig.Port,
},
"keys": gin.H{
"file_path": keysConfig.FilePath,
"start_index": keysConfig.StartIndex,
"blacklist_threshold": keysConfig.BlacklistThreshold,
"max_retries": keysConfig.MaxRetries,
},
"openai": gin.H{
"base_url": openaiConfig.BaseURL,
"timeout": openaiConfig.Timeout,
},
"auth": gin.H{
"enabled": authConfig.Enabled,
// Don't expose the actual key
},
"cors": gin.H{
"enabled": corsConfig.Enabled,
"allowed_origins": corsConfig.AllowedOrigins,
"allowed_methods": corsConfig.AllowedMethods,
"allowed_headers": corsConfig.AllowedHeaders,
"allow_credentials": corsConfig.AllowCredentials,
},
"performance": gin.H{
"max_concurrent_requests": perfConfig.MaxConcurrentRequests,
"request_timeout": perfConfig.RequestTimeout,
"enable_gzip": perfConfig.EnableGzip,
},
"log": gin.H{
"level": logConfig.Level,
"format": logConfig.Format,
"enable_file": logConfig.EnableFile,
"file_path": logConfig.FilePath,
"enable_request": logConfig.EnableRequest,
},
"timestamp": time.Now().UTC().Format(time.RFC3339),
}
c.JSON(http.StatusOK, sanitizedConfig)
}
// Helper function to convert bytes to megabytes
func bToMb(b uint64) uint64 {
return b / 1024 / 1024
}