Files
meshgo/http/http.go
T
2026-05-15 19:36:43 +08:00

156 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package http
import (
"context"
_ "embed"
"log"
"meshgo/config"
"meshgo/stats"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// Server HTTP 管理界面服务
type Server struct {
srv *http.Server
enabled bool
}
// New 创建 HTTP 服务(不启动)
func New(cfg *config.AdminConfig) *Server {
if !cfg.Enabled {
return &Server{enabled: false}
}
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
// 预留认证中间件扩展点
r.Use(AuthMiddleware(cfg))
// 路由
admin := r.Group("/admin/mqtt")
{
admin.GET("/api/stats", handleStats)
admin.GET("/api/clients", handleClients)
}
r.GET("/admin/mqtt", serveIndex)
r.GET("/", serveHome)
addr := cfg.Port
if addr == "" {
addr = ":8080"
}
return &Server{
enabled: true,
srv: &http.Server{
Addr: addr,
Handler: r,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
},
}
}
// Start 启动 HTTP 服务(goroutine
func (s *Server) Start() {
if !s.enabled {
return
}
go func() {
addr := s.srv.Addr
log.Printf("[http] 管理界面已启动: http://%s/", addr)
if err := s.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("[http] 管理界面启动失败: %v", err)
}
}()
}
// Close 优雅关闭
func (s *Server) Close() error {
if !s.enabled {
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return s.srv.Shutdown(ctx)
}
// Enabled 返回是否启用
func (s *Server) Enabled() bool { return s.enabled }
// ---------------------------------------------------------------------------
// 路由处理
// ---------------------------------------------------------------------------
// serveIndex 返回管理页面
func serveIndex(c *gin.Context) {
c.Header("Content-Type", "text/html; charset=utf-8")
c.String(http.StatusOK, indexHTML)
}
// handleStats 返回实时统计(JSON
func handleStats(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": stats.GetStats(),
})
}
// handleClients 返回在线客户端列表(JSON)
func handleClients(c *gin.Context) {
st := stats.GetStats()
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": st.Clients,
})
}
// ---------------------------------------------------------------------------
// 认证中间件(预留扩展点)
// AuthMiddleware 根据 cfg.AuthType 选择对应认证策略
// 当前支持:none
// 预留支持:basic、token
// ---------------------------------------------------------------------------
// AuthMiddleware 认证中间件工厂
func AuthMiddleware(cfg *config.AdminConfig) gin.HandlerFunc {
switch cfg.AuthType {
case "basic":
return basicAuth(cfg.Username, cfg.Password)
case "token":
// TODO: token 认证
return func(c *gin.Context) { c.Next() }
default:
// none:不做认证
return func(c *gin.Context) { c.Next() }
}
}
// basicAuth Basic 认证
func basicAuth(user, pass string) gin.HandlerFunc {
return func(c *gin.Context) {
u, p, ok := c.Request.BasicAuth()
if !ok || u != user || p != pass {
c.Header("WWW-Authenticate", `Basic realm="meshgo"`)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"code": 401,
"msg": "未授权",
})
return
}
c.Next()
}
}
// ---------------------------------------------------------------------------
// 模板(go:embed 内嵌)
// ---------------------------------------------------------------------------
//go:embed index.html
var indexHTML string