Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
346 lines
8.8 KiB
Go
346 lines
8.8 KiB
Go
package http
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"net/http"
|
||
"os"
|
||
"os/signal"
|
||
"strings"
|
||
"syscall"
|
||
"time"
|
||
|
||
"github.com/gofiber/fiber/v2"
|
||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||
"github.com/gofiber/fiber/v2/middleware/limiter"
|
||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||
"github.com/gofiber/fiber/v2/middleware/recover"
|
||
"github.com/subconverter-go/internal/config"
|
||
"github.com/subconverter-go/internal/logging"
|
||
)
|
||
|
||
// Server HTTP服务器封装
|
||
// 封装Fiber HTTP服务器功能,提供统一的HTTP服务接口
|
||
type Server struct {
|
||
app *fiber.App
|
||
config *config.ServerConfig
|
||
logger *logging.Logger
|
||
manager *config.ConfigManager
|
||
startTime time.Time
|
||
}
|
||
|
||
// NewServer 创建新的HTTP服务器
|
||
// 返回初始化好的Server实例
|
||
func NewServer(manager *config.ConfigManager) (*Server, error) {
|
||
if manager == nil {
|
||
return nil, fmt.Errorf("config manager cannot be nil")
|
||
}
|
||
|
||
// 获取日志记录器
|
||
logger, err := logging.NewDefaultLogger()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create logger: %v", err)
|
||
}
|
||
|
||
// 获取服务器配置
|
||
serverConfig := manager.GetServerConfig()
|
||
if serverConfig == nil {
|
||
return nil, fmt.Errorf("server config cannot be nil")
|
||
}
|
||
|
||
// 创建Fiber应用
|
||
app := fiber.New(fiber.Config{
|
||
AppName: "subconverter-go",
|
||
ServerHeader: "subconverter-go",
|
||
ReadTimeout: time.Duration(serverConfig.ReadTimeout) * time.Second,
|
||
WriteTimeout: time.Duration(serverConfig.WriteTimeout) * time.Second,
|
||
IdleTimeout: time.Duration(serverConfig.WriteTimeout) * time.Second,
|
||
BodyLimit: int(serverConfig.MaxRequestSize),
|
||
CaseSensitive: false,
|
||
StrictRouting: false,
|
||
EnablePrintRoutes: false,
|
||
})
|
||
|
||
server := &Server{
|
||
app: app,
|
||
config: serverConfig,
|
||
logger: logger,
|
||
manager: manager,
|
||
startTime: time.Now(),
|
||
}
|
||
|
||
// 设置中间件
|
||
server.setupMiddleware()
|
||
|
||
// 设置路由
|
||
server.setupRoutes()
|
||
|
||
return server, nil
|
||
}
|
||
|
||
// setupMiddleware 设置中间件
|
||
func (s *Server) setupMiddleware() {
|
||
// 恢复中间件 - 处理panic
|
||
s.app.Use(recover.New(recover.Config{
|
||
EnableStackTrace: true,
|
||
StackTraceHandler: func(c *fiber.Ctx, e any) {
|
||
s.logger.WithField("error", e).Error("Panic recovered")
|
||
},
|
||
}))
|
||
|
||
// 日志中间件
|
||
s.app.Use(logger.New(logger.Config{
|
||
Format: "${time} | ${status} | ${latency} | ${ip} | ${method} | ${path} | ${error}\n",
|
||
Output: s.logger.Logger.Out,
|
||
}))
|
||
|
||
// CORS中间件
|
||
s.setupCORS()
|
||
|
||
// 速率限制中间件
|
||
s.setupRateLimiter()
|
||
}
|
||
|
||
// setupCORS 设置CORS
|
||
func (s *Server) setupCORS() {
|
||
securityConfig := s.manager.GetSecurityConfig()
|
||
if securityConfig == nil {
|
||
securityConfig = &config.SecurityConfig{
|
||
CorsOrigins: []string{"*"},
|
||
}
|
||
}
|
||
|
||
s.app.Use(cors.New(cors.Config{
|
||
AllowOrigins: strings.Join(securityConfig.CorsOrigins, ","),
|
||
AllowMethods: "GET,POST,PUT,DELETE,OPTIONS",
|
||
AllowHeaders: "*",
|
||
MaxAge: 86400, // 24 hours
|
||
}))
|
||
}
|
||
|
||
// setupRateLimiter 设置速率限制
|
||
func (s *Server) setupRateLimiter() {
|
||
securityConfig := s.manager.GetSecurityConfig()
|
||
if securityConfig == nil || securityConfig.RateLimit <= 0 {
|
||
// 不启用速率限制
|
||
return
|
||
}
|
||
|
||
s.app.Use(limiter.New(limiter.Config{
|
||
Max: securityConfig.RateLimit,
|
||
Expiration: 60 * time.Second, // 1分钟窗口
|
||
KeyGenerator: func(c *fiber.Ctx) string {
|
||
return c.IP()
|
||
},
|
||
LimitReached: func(c *fiber.Ctx) error {
|
||
s.logger.WithField("ip", c.IP()).Warn("Rate limit exceeded")
|
||
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
|
||
"error": "Rate limit exceeded",
|
||
})
|
||
},
|
||
}))
|
||
}
|
||
|
||
// setupRoutes 设置路由
|
||
func (s *Server) setupRoutes() {
|
||
// 健康检查路由
|
||
s.app.Get("/health", s.healthCheck)
|
||
|
||
// 版本信息路由
|
||
s.app.Get("/version", s.versionInfo)
|
||
|
||
// 根路由
|
||
s.app.Get("/", s.rootHandler)
|
||
|
||
// API路由组
|
||
api := s.app.Group("/api")
|
||
|
||
// 转换相关路由
|
||
api.Get("/sub", s.convertHandler)
|
||
api.Post("/sub", s.convertHandler)
|
||
|
||
// 配置相关路由
|
||
api.Get("/config", s.getConfigHandler)
|
||
api.Post("/config", s.updateConfigHandler)
|
||
|
||
// 代理相关路由
|
||
api.Get("/proxy/validate", s.validateProxyHandler)
|
||
api.Post("/proxy/validate", s.validateProxyHandler)
|
||
|
||
// 统计信息路由
|
||
api.Get("/stats", s.statsHandler)
|
||
}
|
||
|
||
// Start 启动HTTP服务器
|
||
func (s *Server) Start() error {
|
||
s.logger.Infof("Starting HTTP server on %s:%d", s.config.Host, s.config.Port)
|
||
|
||
// 创建监听地址
|
||
addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port)
|
||
|
||
// 启动服务器
|
||
go func() {
|
||
if err := s.app.Listen(addr); err != nil && err != http.ErrServerClosed {
|
||
s.logger.WithError(err).Error("Failed to start HTTP server")
|
||
os.Exit(1)
|
||
}
|
||
}()
|
||
|
||
s.logger.Infof("HTTP server started successfully")
|
||
return nil
|
||
}
|
||
|
||
// Stop 停止HTTP服务器
|
||
func (s *Server) Stop() error {
|
||
s.logger.Info("Stopping HTTP server...")
|
||
|
||
// 创建上下文,设置5秒超时
|
||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||
defer cancel()
|
||
|
||
// 优雅关闭服务器
|
||
if err := s.app.ShutdownWithContext(ctx); err != nil {
|
||
s.logger.WithError(err).Error("Failed to shutdown HTTP server gracefully")
|
||
return err
|
||
}
|
||
|
||
s.logger.Info("HTTP server stopped successfully")
|
||
return nil
|
||
}
|
||
|
||
// WaitForShutdown 等待服务器关闭信号
|
||
func (s *Server) WaitForShutdown() {
|
||
// 创建信号通道
|
||
quit := make(chan os.Signal, 1)
|
||
|
||
// 监听中断信号
|
||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||
|
||
// 等待信号
|
||
<-quit
|
||
|
||
s.logger.Info("Received shutdown signal, shutting down server...")
|
||
|
||
// 停止服务器
|
||
if err := s.Stop(); err != nil {
|
||
s.logger.WithError(err).Error("Error during server shutdown")
|
||
}
|
||
}
|
||
|
||
// GetApp 获取Fiber应用实例
|
||
// 用于测试或扩展功能
|
||
func (s *Server) GetApp() *fiber.App {
|
||
return s.app
|
||
}
|
||
|
||
// GetUptime 获取服务器运行时间
|
||
func (s *Server) GetUptime() time.Duration {
|
||
return time.Since(s.startTime)
|
||
}
|
||
|
||
// GetStats 获取服务器统计信息
|
||
func (s *Server) GetStats() map[string]interface{} {
|
||
return map[string]interface{}{
|
||
"uptime": s.GetUptime().String(),
|
||
"start_time": s.startTime.Format(time.RFC3339),
|
||
"host": s.config.Host,
|
||
"port": s.config.Port,
|
||
"max_request_size": s.config.MaxRequestSize,
|
||
"read_timeout": s.config.ReadTimeout,
|
||
"write_timeout": s.config.WriteTimeout,
|
||
}
|
||
}
|
||
|
||
// 健康检查处理器
|
||
func (s *Server) healthCheck(c *fiber.Ctx) error {
|
||
health := map[string]interface{}{
|
||
"status": "healthy",
|
||
"uptime": s.GetUptime().String(),
|
||
"version": "1.0.0",
|
||
"host": s.config.Host,
|
||
"port": s.config.Port,
|
||
}
|
||
|
||
return c.JSON(health)
|
||
}
|
||
|
||
// 版本信息处理器
|
||
func (s *Server) versionInfo(c *fiber.Ctx) error {
|
||
version := map[string]interface{}{
|
||
"version": "1.0.0",
|
||
"name": "subconverter-go",
|
||
"description": "Proxy subscription converter service",
|
||
"author": "subconverter-go team",
|
||
"uptime": s.GetUptime().String(),
|
||
}
|
||
|
||
return c.JSON(version)
|
||
}
|
||
|
||
// 根路径处理器
|
||
func (s *Server) rootHandler(c *fiber.Ctx) error {
|
||
info := map[string]interface{}{
|
||
"service": "subconverter-go",
|
||
"version": "1.0.0",
|
||
"status": "running",
|
||
"endpoints": []string{
|
||
"GET /health - Health check",
|
||
"GET /version - Version information",
|
||
"GET /api/sub - Convert subscription",
|
||
"POST /api/sub - Convert subscription",
|
||
"GET /api/config - Get configuration",
|
||
"POST /api/config - Update configuration",
|
||
"GET /api/proxy/validate - Validate proxy",
|
||
"POST /api/proxy/validate - Validate proxy",
|
||
"GET /api/stats - Server statistics",
|
||
},
|
||
"uptime": s.GetUptime().String(),
|
||
}
|
||
|
||
return c.JSON(info)
|
||
}
|
||
|
||
// 转换处理器(占位符)
|
||
func (s *Server) convertHandler(c *fiber.Ctx) error {
|
||
s.logger.Info("Convert handler called (placeholder)")
|
||
|
||
return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{
|
||
"error": "Conversion handler not implemented yet",
|
||
})
|
||
}
|
||
|
||
// 获取配置处理器(占位符)
|
||
func (s *Server) getConfigHandler(c *fiber.Ctx) error {
|
||
s.logger.Info("Get config handler called (placeholder)")
|
||
|
||
return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{
|
||
"error": "Get config handler not implemented yet",
|
||
})
|
||
}
|
||
|
||
// 更新配置处理器(占位符)
|
||
func (s *Server) updateConfigHandler(c *fiber.Ctx) error {
|
||
s.logger.Info("Update config handler called (placeholder)")
|
||
|
||
return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{
|
||
"error": "Update config handler not implemented yet",
|
||
})
|
||
}
|
||
|
||
// 验证代理处理器(占位符)
|
||
func (s *Server) validateProxyHandler(c *fiber.Ctx) error {
|
||
s.logger.Info("Validate proxy handler called (placeholder)")
|
||
|
||
return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{
|
||
"error": "Validate proxy handler not implemented yet",
|
||
})
|
||
}
|
||
|
||
// 统计信息处理器(占位符)
|
||
func (s *Server) statsHandler(c *fiber.Ctx) error {
|
||
stats := s.GetStats()
|
||
stats["requests"] = 0 // 占位符
|
||
stats["errors"] = 0 // 占位符
|
||
|
||
return c.JSON(stats)
|
||
} |