package web import ( "fmt" "html/template" "net" "os" "strings" "speedtest/config" "speedtest/internal/store" "speedtest/internal/web/handlers" "github.com/gin-gonic/gin" ) // WebServer wraps the Gin engine and its dependencies. type WebServer struct { engine *gin.Engine stores *store.Stores cfg config.Config } // NewWebServer creates a new WebServer, initializes the Gin engine and registers routes. func NewWebServer(cfg config.Config, stores *store.Stores) (*WebServer, error) { gin.SetMode(gin.ReleaseMode) engine := gin.New() engine.Use(gin.Logger()) engine.Use(gin.Recovery()) // 不信任默认代理(真实 IP 通过 X-Real-IP 头由 Caddy 注入,自行解析) _ = engine.SetTrustedProxies(nil) // 加载 HTML 模板 tmpl := template.Must(template.New("").ParseGlob("internal/web/templates/*.html")) engine.SetHTMLTemplate(tmpl) handler, err := handlers.NewHandler(stores, cfg.Speedtest) if err != nil { return nil, err } ws := &WebServer{ engine: engine, stores: stores, cfg: cfg, } // 页面 engine.GET("/", func(c *gin.Context) { c.HTML(200, "index.html", gin.H{}) }) // 测速 API api := engine.Group("/api") { api.GET("/ping", handler.Ping) api.GET("/download", handler.Download) api.POST("/upload", handler.Upload) api.POST("/result", handler.Result) api.GET("/rankings", handler.Rankings) } return ws, nil } // Start launches the HTTP server on the configured address. // Supports both TCP (e.g. ":8080") and Unix socket (e.g. "/opt/speedtest/web.sock"). func (ws *WebServer) Start() error { addr := ws.cfg.Web.Addr // Unix socket:地址以 / 开头 if strings.HasPrefix(addr, "/") { // 清理旧的 socket 文件 os.Remove(addr) listener, err := net.Listen("unix", addr) if err != nil { return fmt.Errorf("监听 Unix socket 失败 %s: %w", addr, err) } // 允许 caddy 等外部进程连接 os.Chmod(addr, 0666) return ws.engine.RunListener(listener) } // TCP 端口 return ws.engine.Run(addr) }