lolly/internal/server/server.go
xfy e2c37e2bf8 feat(server,proxy,loadbalance): 集成反向代理和虚拟主机模式
- server: 集成反向代理路由,支持单服务器和虚拟主机两种模式
- loadbalance: 使用 atomic.Bool 替代 bool 实现并发安全的健康状态
- proxy: 适配 atomic.Bool,移除 HealthChecker 不必要的互斥锁
- config: 添加服务器超时配置字段,验证负载均衡算法
- 新增 algorithms.go 提供算法验证函数
- 新增 config.example.yaml 配置示例文件

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-03 09:26:20 +08:00

198 lines
4.8 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 server
import (
"context"
"time"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/config"
"rua.plus/lolly/internal/handler"
"rua.plus/lolly/internal/loadbalance"
"rua.plus/lolly/internal/logging"
"rua.plus/lolly/internal/middleware"
"rua.plus/lolly/internal/proxy"
)
// Server HTTP 服务器
type Server struct {
config *config.Config
fastServer *fasthttp.Server
handler fasthttp.RequestHandler
running bool
healthCheckers []*proxy.HealthChecker // 新增
}
// New 创建服务器
func New(cfg *config.Config) *Server {
return &Server{config: cfg}
}
// Start 启动服务器
func (s *Server) Start() error {
logging.Init(s.config.Logging.Error.Level, true)
if s.config.HasServers() {
return s.startVHostMode()
}
return s.startSingleMode()
}
// startSingleMode 单服务器模式
func (s *Server) startSingleMode() error {
router := handler.NewRouter()
// 注册代理路由
s.registerProxyRoutes(router, &s.config.Server)
// 静态文件服务(作为 fallback
staticHandler := handler.NewStaticHandler(
s.config.Server.Static.Root,
s.config.Server.Static.Index,
)
router.GET("/{filepath:*}", staticHandler.Handle)
router.HEAD("/{filepath:*}", staticHandler.Handle)
chain := middleware.NewChain()
s.handler = chain.Apply(router.Handler())
s.fastServer = &fasthttp.Server{
Name: "lolly",
Handler: s.handler,
ReadTimeout: s.config.Server.ReadTimeout,
WriteTimeout: s.config.Server.WriteTimeout,
IdleTimeout: s.config.Server.IdleTimeout,
MaxConnsPerIP: s.config.Server.MaxConnsPerIP,
MaxRequestsPerConn: s.config.Server.MaxRequestsPerConn,
}
s.running = true
return s.fastServer.ListenAndServe(s.config.Server.Listen)
}
// startVHostMode 虚拟主机模式
func (s *Server) startVHostMode() error {
vhostMgr := NewVHostManager()
for i := range s.config.Servers {
router := handler.NewRouter()
s.registerProxyRoutes(router, &s.config.Servers[i])
// 静态文件
staticHandler := handler.NewStaticHandler(
s.config.Servers[i].Static.Root,
s.config.Servers[i].Static.Index,
)
router.GET("/{filepath:*}", staticHandler.Handle)
router.HEAD("/{filepath:*}", staticHandler.Handle)
vhostMgr.AddHost(s.config.Servers[i].Name, router.Handler())
}
// 默认主机
if s.config.HasDefaultServer() {
router := handler.NewRouter()
s.registerProxyRoutes(router, &s.config.Server)
staticHandler := handler.NewStaticHandler(
s.config.Server.Static.Root,
s.config.Server.Static.Index,
)
router.GET("/{filepath:*}", staticHandler.Handle)
vhostMgr.SetDefault(router.Handler())
}
s.handler = vhostMgr.Handler()
s.fastServer = &fasthttp.Server{
Name: "lolly",
Handler: s.handler,
ReadTimeout: s.config.Server.ReadTimeout,
WriteTimeout: s.config.Server.WriteTimeout,
IdleTimeout: s.config.Server.IdleTimeout,
MaxConnsPerIP: s.config.Server.MaxConnsPerIP,
MaxRequestsPerConn: s.config.Server.MaxRequestsPerConn,
}
s.running = true
return s.fastServer.ListenAndServe(s.config.Server.Listen)
}
// registerProxyRoutes 注册代理路由
func (s *Server) registerProxyRoutes(router *handler.Router, serverCfg *config.ServerConfig) {
for i := range serverCfg.Proxy {
proxyCfg := &serverCfg.Proxy[i]
// 转换目标
targets := make([]*loadbalance.Target, len(proxyCfg.Targets))
for j, t := range proxyCfg.Targets {
targets[j] = &loadbalance.Target{
URL: t.URL,
Weight: t.Weight,
}
targets[j].Healthy.Store(true)
}
p, err := proxy.NewProxy(proxyCfg, targets)
if err != nil {
logging.Error().Msg("创建代理失败: " + err.Error())
continue
}
// 启动健康检查
if proxyCfg.HealthCheck.Interval > 0 {
hc := proxy.NewHealthChecker(targets, &proxyCfg.HealthCheck)
hc.Start()
s.healthCheckers = append(s.healthCheckers, hc)
}
router.GET(proxyCfg.Path, p.ServeHTTP)
router.POST(proxyCfg.Path, p.ServeHTTP)
router.PUT(proxyCfg.Path, p.ServeHTTP)
router.DELETE(proxyCfg.Path, p.ServeHTTP)
router.HEAD(proxyCfg.Path, p.ServeHTTP)
}
}
// Stop 快速停止服务器
func (s *Server) Stop() error {
s.running = false
// 停止健康检查器
for _, hc := range s.healthCheckers {
hc.Stop()
}
if s.fastServer != nil {
return s.fastServer.Shutdown()
}
return nil
}
// GracefulStop 优雅停止
func (s *Server) GracefulStop(timeout time.Duration) error {
s.running = false
// 停止健康检查器
for _, hc := range s.healthCheckers {
hc.Stop()
}
if s.fastServer != nil {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
done := make(chan struct{})
go func() {
s.fastServer.Shutdown()
close(done)
}()
select {
case <-done:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
return nil
}