diff --git a/docs/plan.md b/docs/plan.md index 6eeb281..8b46cd6 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -1424,6 +1424,263 @@ Phase 6: --- +## 第七阶段:功能完善 + +### 目标 + +补齐项目缺失功能,提升完整性和生产可用性。 + +### 背景分析 + +通过代码审查发现以下缺失功能: + +| 功能 | 当前状态 | 优先级 | 说明 | +|------|----------|--------|------| +| WebSocket 代理 | ⚠️ 返回 501 | P0 | 现代Web应用必备 | +| 状态监控端点 | ⚠️ 配置已有,处理器缺失 | P1 | 运维必需 | +| 代理缓存集成 | ⚠️ 缓存模块未集成 | P1 | 性能优化关键 | +| Brotli 压缩 | ⚠️ 降级为 gzip | P2 | 需引入依赖 | +| UDP Stream | ⚠️ Accept 返回 EOF | P2 | 需重写处理逻辑 | +| OCSP Stapling | ❌ 未实现 | P3 | 安全增强(可选)| + +### 任务列表 + +#### 7.1 WebSocket 代理 (P0) + +**问题**:`proxy.go:344-349` 当前返回 501 Not Implemented。 + +**实现方案**: + +```go +// internal/proxy/websocket.go + +// WebSocketBridge WebSocket 桥接器。 +type WebSocketBridge struct { + clientConn net.Conn + targetConn net.Conn +} + +// Bridge 双向转发 WebSocket 数据。 +func (b *WebSocketBridge) Bridge() error { + // 使用 io.Copy 双向转发 + // 客户端 → 后端 + // 后端 → 客户端 +} +``` + +**实现要点**: + +- 使用 `ctx.Hijack()` 获取底层 TCP 连接 +- 建立到后端的 TCP 连接 +- 发送 HTTP 升级请求 +- 启动双向 io.Copy 数据转发 +- 处理连接关闭和错误 + +**修改文件**: +- `internal/proxy/proxy.go:341-349` - 调用 WebSocket 桥接 +- 新增 `internal/proxy/websocket.go` - WebSocket 桥接逻辑 + +#### 7.2 状态监控端点 (P1) + +**问题**:`config.go:222-231` 已定义配置,但未实现处理器。 + +**实现方案**: + +```go +// internal/server/status.go + +// StatusHandler 状态监控处理器。 +type StatusHandler struct { + server *Server + allowed []net.IPNet +} + +// Status 状态响应结构。 +type Status struct { + Version string `json:"version"` + Uptime time.Duration `json:"uptime"` + Connections int64 `json:"connections"` + Requests int64 `json:"requests"` + BytesSent int64 `json:"bytes_sent"` + BytesReceived int64 `json:"bytes_received"` +} + +// ServeHTTP 返回 JSON 格式状态。 +func (h *StatusHandler) ServeHTTP(ctx *fasthttp.RequestCtx) { + // 1. 检查 IP 访问权限 + // 2. 收集状态数据 + // 3. 返回 JSON 响应 +} +``` + +**修改文件**: +- 新增 `internal/server/status.go` - 状态处理器 +- `internal/server/server.go:54,95` - 注册状态路由 + +#### 7.3 代理缓存集成 (P1) + +**问题**:`cache/file_cache.go` 已实现 `ProxyCache`,但未与代理集成。 + +**实现方案**: + +```go +// internal/proxy/proxy.go + +// Proxy 添加缓存字段。 +type Proxy struct { + // ... 现有字段 + cache *cache.ProxyCache // 新增 +} + +// ServeHTTP 集成缓存逻辑。 +func (p *Proxy) ServeHTTP(ctx *fasthttp.RequestCtx) { + // 1. 生成缓存键 + key := p.cacheKey(ctx) + + // 2. 尝试获取缓存 + if entry, ok, stale := p.cache.Get(key); ok { + // 返回缓存响应 + p.serveFromCache(ctx, entry, stale) + return + } + + // 3. 检查缓存锁(防击穿) + if waitCh := p.cache.AcquireLock(key); waitCh != nil { + <-waitCh // 等待其他请求生成缓存 + // 重新获取缓存 + } + + // 4. 请求后端 + // 5. 存入缓存 +} +``` + +**修改文件**: +- `internal/proxy/proxy.go` - 添加缓存逻辑 +- `internal/server/server.go:129-161` - 传递缓存配置 + +#### 7.4 Brotli 压缩 (P2) + +**问题**:`compression.go:209-214` 降级为 gzip。 + +**实现方案**: + +```go +// internal/middleware/compression/compression.go + +import "github.com/andybalholm/brotli" + +// compressBrotli 使用 brotli 压缩数据。 +func (m *CompressionMiddleware) compressBrotli(data []byte) []byte { + var buf bytes.Buffer + w := brotli.NewWriterLevel(&buf, m.level) + w.Write(data) + w.Close() + return buf.Bytes() +} +``` + +**实现要点**: + +- 添加依赖:`go get github.com/andybalholm/brotli` +- 添加 brotli 缓冲池 +- 更新 `compressBrotli` 方法 + +**修改文件**: +- `internal/middleware/compression/compression.go` + +#### 7.5 UDP Stream 完善 (P2) + +**问题**:`stream.go:388-391` `udpListener.Accept()` 返回 EOF。 + +**原因分析**:UDP 是数据报协议,不能像 TCP 那样 Accept 连接。 + +**实现方案**: + +```go +// internal/stream/stream.go + +// udpServer UDP 服务器。 +type udpServer struct { + conn *net.UDPConn + sessions map[string]*udpSession // 客户端地址 → 会话 + mu sync.RWMutex +} + +// udpSession UDP 会话。 +type udpSession struct { + clientAddr *net.UDPAddr + targetConn net.Conn + lastActive time.Time +} + +// serveUDP 处理 UDP 数据报。 +func (s *udpServer) serveUDP() { + buf := make([]byte, 65535) + for { + n, clientAddr, err := s.conn.ReadFromUDP(buf) + if err != nil { + continue + } + + // 查找或创建会话 + session := s.getOrCreateSession(clientAddr) + + // 转发数据到后端 + session.targetConn.Write(buf[:n]) + } +} +``` + +**修改文件**: +- `internal/stream/stream.go:199-217,383-401` - 重写 UDP 处理 + +#### 7.6 OCSP Stapling (P3 - 可选) + +**问题**:`ssl.go` 未实现 OCSP Stapling。 + +**实现方案**: + +- 在 TLS 配置中添加 `GetConfigForClient` 回调 +- 定期查询 OCSP 服务器并缓存响应 +- 在 TLS 握手中附加 OCSP 响应 + +**注意**:此功能实现复杂,可作为后续迭代项。 + +### 验证方法 + +```bash +# WebSocket 测试 +wscat -c ws://localhost:8080/ws + +# 状态监控测试 +curl http://localhost:8080/_status + +# 代理缓存测试(检查响应头) +curl -I http://localhost:8080/api/data +# 第二次请求应返回 X-Cache-Status: HIT + +# Brotli 压缩测试 +curl -H "Accept-Encoding: br" -I http://localhost:8080/index.html +# 检查 Content-Encoding: br + +# UDP Stream 测试 +dig @localhost example.com # 通过 lolly 代理 DNS +``` + +### 文件依赖关系图 + +``` +Phase 7: + internal/proxy/proxy.go → internal/proxy/websocket.go(新增) + internal/proxy/proxy.go → internal/cache/file_cache.go + internal/server/server.go → internal/server/status.go(新增) + internal/middleware/compression/compression.go → github.com/andybalholm/brotli + internal/stream/stream.go → 重写 UDP 处理 +``` + +--- + ## 总体进度追踪 | 阶段 | 状态 | 主要功能 | @@ -1434,6 +1691,7 @@ Phase 6: | Phase 4 | ✅ 完成 | SSL/TLS、安全控制 | | Phase 5 | ✅ 完成 | 重写、压缩、缓存、日志 | | Phase 6 | ✅ 完成 | Stream、性能优化、热升级 | +| Phase 7 | 🚧 待开始 | 功能完善(WebSocket、缓存集成、监控端点) | **Phase 2 技术选型变更**: - HTTP 库:使用 [fasthttp](https://github.com/valyala/fasthttp) 替代 `net/http`(性能提升 6 倍) diff --git a/internal/app/app.go b/internal/app/app.go index b43f485..4dbb1b2 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -11,6 +11,7 @@ import ( "rua.plus/lolly/internal/config" "rua.plus/lolly/internal/logging" "rua.plus/lolly/internal/server" + "rua.plus/lolly/internal/stream" ) // 版本信息,通过 -ldflags 注入。 @@ -30,12 +31,13 @@ var ( // App 应用程序结构。 type App struct { - cfgPath string - cfg *config.Config - srv *server.Server - upgradeMgr *server.UpgradeManager - pidFile string - logFile string // 日志文件路径(用于重新打开) + cfgPath string + cfg *config.Config + srv *server.Server + streamSrv *stream.Server // Stream 服务器(可选) + upgradeMgr *server.UpgradeManager + pidFile string + logFile string // 日志文件路径(用于重新打开) } // NewApp 创建应用程序。 @@ -117,9 +119,48 @@ func (a *App) Run() int { fmt.Printf("配置加载成功: %s\n", a.cfgPath) fmt.Printf("监听地址: %s\n", cfg.Server.Listen) - // 创建服务器 + // 创建 HTTP 服务器 a.srv = server.New(cfg) + // 创建 Stream 服务器(如果配置了) + if len(cfg.Stream) > 0 { + a.streamSrv = stream.NewServer() + for _, sc := range cfg.Stream { + // 转换目标配置 + targets := make([]stream.TargetSpec, len(sc.Upstream.Targets)) + for i, t := range sc.Upstream.Targets { + targets[i] = stream.TargetSpec{ + Addr: t.Addr, + Weight: t.Weight, + } + } + + // 添加上游配置 + if err := a.streamSrv.AddUpstream(sc.Listen, targets, sc.Upstream.LoadBalance, stream.HealthCheckSpec{}); err != nil { + fmt.Fprintf(os.Stderr, "添加 Stream 上游失败: %v\n", err) + } + + // 监听端口 + if sc.Protocol == "udp" { + if err := a.streamSrv.ListenUDP(sc.Listen); err != nil { + fmt.Fprintf(os.Stderr, "监听 UDP %s 失败: %v\n", sc.Listen, err) + } + } else { + if err := a.streamSrv.ListenTCP(sc.Listen); err != nil { + fmt.Fprintf(os.Stderr, "监听 TCP %s 失败: %v\n", sc.Listen, err) + } + } + } + + // 启动 Stream 服务器 + go func() { + fmt.Println("Stream 服务器启动中...") + if err := a.streamSrv.Start(); err != nil { + fmt.Fprintf(os.Stderr, "Stream 服务器启动失败: %v\n", err) + } + }() + } + // 创建升级管理器 a.upgradeMgr = server.NewUpgradeManager(a.srv) if a.pidFile != "" { @@ -131,10 +172,10 @@ func (a *App) Run() int { sigChan := make(chan os.Signal, 1) a.setupSignalHandlers(sigChan) - // 启动服务器 + // 启动 HTTP 服务器 errChan := make(chan error, 1) go func() { - fmt.Println("服务器启动中...") + fmt.Println("HTTP 服务器启动中...") if err := a.srv.Start(); err != nil { errChan <- err } diff --git a/internal/config/config.go b/internal/config/config.go index ab94061..958544d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,6 +14,7 @@ import ( type Config struct { Server ServerConfig `yaml:"server"` // 单服务器模式配置 Servers []ServerConfig `yaml:"servers"` // 多虚拟主机模式配置 + Stream []StreamConfig `yaml:"stream"` // TCP/UDP Stream 代理配置 Logging LoggingConfig `yaml:"logging"` // 日志配置 Performance PerformanceConfig `yaml:"performance"` // 性能配置 Monitoring MonitoringConfig `yaml:"monitoring"` // 监控配置 @@ -230,6 +231,25 @@ type StatusConfig struct { Allow []string `yaml:"allow"` // 允许访问的 IP 列表 } +// StreamConfig TCP/UDP Stream 代理配置。 +type StreamConfig struct { + Listen string `yaml:"listen"` // 监听地址,如 ":3306" + Protocol string `yaml:"protocol"` // 协议:tcp 或 udp + Upstream StreamUpstream `yaml:"upstream"` // 上游配置 +} + +// StreamUpstream Stream 上游配置。 +type StreamUpstream struct { + Targets []StreamTarget `yaml:"targets"` // 目标列表 + LoadBalance string `yaml:"load_balance"` // 负载均衡算法 +} + +// StreamTarget Stream 目标配置。 +type StreamTarget struct { + Addr string `yaml:"addr"` // 目标地址,如 "mysql1:3306" + Weight int `yaml:"weight"` // 权重 +} + // Load 从文件加载配置。 func Load(path string) (*Config, error) { data, err := os.ReadFile(path) diff --git a/internal/handler/static.go b/internal/handler/static.go index 8e114bf..18757e2 100644 --- a/internal/handler/static.go +++ b/internal/handler/static.go @@ -1,22 +1,35 @@ package handler import ( + "mime" "os" "path/filepath" "strings" "github.com/valyala/fasthttp" + "rua.plus/lolly/internal/cache" ) // StaticHandler 静态文件处理器 type StaticHandler struct { - root string - index []string + root string + index []string + useSendfile bool // 是否启用零拷贝传输 + fileCache *cache.FileCache // 文件缓存(可选) } // NewStaticHandler 创建静态文件处理器 -func NewStaticHandler(root string, index []string) *StaticHandler { - return &StaticHandler{root: root, index: index} +func NewStaticHandler(root string, index []string, useSendfile bool) *StaticHandler { + return &StaticHandler{ + root: root, + index: index, + useSendfile: useSendfile, + } +} + +// SetFileCache 设置文件缓存 +func (h *StaticHandler) SetFileCache(fc *cache.FileCache) { + h.fileCache = fc } // Handle 处理静态文件请求 @@ -43,8 +56,8 @@ func (h *StaticHandler) Handle(ctx *fasthttp.RequestCtx) { if info.IsDir() { for _, idx := range h.index { idxPath := filepath.Join(filePath, idx) - if _, err := os.Stat(idxPath); err == nil { - fasthttp.ServeFile(ctx, idxPath) + if idxInfo, err := os.Stat(idxPath); err == nil && !idxInfo.IsDir() { + h.serveFile(ctx, idxPath, idxInfo) return } } @@ -53,5 +66,50 @@ func (h *StaticHandler) Handle(ctx *fasthttp.RequestCtx) { } // 直接返回文件 - fasthttp.ServeFile(ctx, filePath) + h.serveFile(ctx, filePath, info) +} + +// serveFile 提供文件服务,支持缓存和零拷贝传输 +func (h *StaticHandler) serveFile(ctx *fasthttp.RequestCtx, filePath string, info os.FileInfo) { + // 尝试从缓存获取 + if h.fileCache != nil { + if entry, ok := h.fileCache.Get(filePath); ok { + // 检查文件是否被修改 + if entry.ModTime.Equal(info.ModTime()) { + // 缓存命中且文件未修改 + ctx.Response.SetBody(entry.Data) + ctx.Response.Header.SetContentType(mime.TypeByExtension(filepath.Ext(filePath))) + return + } + // 文件已修改,删除旧缓存 + h.fileCache.Delete(filePath) + } + } + + // 大文件使用零拷贝传输 + if h.useSendfile && info.Size() >= MinSendfileSize { + file, err := os.Open(filePath) + if err == nil { + defer file.Close() + if err := SendFile(ctx, file, 0, info.Size()); err == nil { + return + } + // sendfile 失败,fallback 到 ServeFile + } + } + + // 读取文件内容 + data, err := os.ReadFile(filePath) + if err != nil { + ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError) + return + } + + // 存入缓存(仅对小文件缓存) + if h.fileCache != nil && info.Size() < 1024*1024 { // < 1MB + h.fileCache.Set(filePath, data, info.Size(), info.ModTime()) + } + + ctx.Response.SetBody(data) + ctx.Response.Header.SetContentType(mime.TypeByExtension(filepath.Ext(filePath))) } diff --git a/internal/handler/static_test.go b/internal/handler/static_test.go index 97ba136..65011dc 100644 --- a/internal/handler/static_test.go +++ b/internal/handler/static_test.go @@ -12,7 +12,7 @@ import ( // newTestHandler 创建测试用的静态文件处理器 func newTestHandler(t *testing.T, root string) *StaticHandler { t.Helper() - return NewStaticHandler(root, []string{"index.html", "index.htm"}) + return NewStaticHandler(root, []string{"index.html", "index.htm"}, false) // 测试时禁用 sendfile } // newTestContext 创建测试用的 fasthttp 请求上下文 @@ -375,7 +375,7 @@ func TestNewStaticHandler(t *testing.T) { t.Run("正常创建", func(t *testing.T) { root := "/var/www" index := []string{"index.html", "index.htm"} - handler := NewStaticHandler(root, index) + handler := NewStaticHandler(root, index, true) if handler == nil { t.Fatal("NewStaticHandler() 返回 nil") @@ -389,7 +389,7 @@ func TestNewStaticHandler(t *testing.T) { }) t.Run("空索引列表", func(t *testing.T) { - handler := NewStaticHandler("/var/www", nil) + handler := NewStaticHandler("/var/www", nil, false) if handler == nil { t.Fatal("NewStaticHandler() 返回 nil") } diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index ec5b13a..b61004f 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -40,6 +40,7 @@ import ( "time" "github.com/valyala/fasthttp" + "rua.plus/lolly/internal/cache" "rua.plus/lolly/internal/config" "rua.plus/lolly/internal/loadbalance" ) @@ -51,6 +52,7 @@ type Proxy struct { clients map[string]*fasthttp.HostClient // key: target URL balancer loadbalance.Balancer config *config.ProxyConfig + cache *cache.ProxyCache // 代理缓存(可选) mu sync.RWMutex } @@ -96,6 +98,18 @@ func NewProxy(cfg *config.ProxyConfig, targets []*loadbalance.Target) (*Proxy, e p.clients[target.URL] = client } + // 初始化代理缓存(如果启用) + if cfg.Cache.Enabled { + rules := make([]cache.ProxyCacheRule, 0) + if cfg.Cache.MaxAge > 0 { + rules = append(rules, cache.ProxyCacheRule{ + Path: cfg.Path, + MaxAge: cfg.Cache.MaxAge, + }) + } + p.cache = cache.NewProxyCache(rules, cfg.Cache.CacheLock, cfg.Cache.StaleWhileRevalidate) + } + return p, nil } diff --git a/internal/server/server.go b/internal/server/server.go index 31927a1..4205eeb 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -2,15 +2,20 @@ package server import ( "context" + "fmt" "time" "github.com/valyala/fasthttp" + "rua.plus/lolly/internal/cache" "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/middleware/accesslog" + "rua.plus/lolly/internal/middleware/compression" + "rua.plus/lolly/internal/middleware/rewrite" + "rua.plus/lolly/internal/middleware/security" "rua.plus/lolly/internal/proxy" ) @@ -22,6 +27,8 @@ type Server struct { running bool healthCheckers []*proxy.HealthChecker accessLogMiddleware *accesslog.AccessLog + pool *GoroutinePool // Goroutine 池(可选) + fileCache *cache.FileCache // 文件缓存(可选) } // New 创建服务器 @@ -29,10 +36,97 @@ func New(cfg *config.Config) *Server { return &Server{config: cfg} } +// buildMiddlewareChain 构建中间件链 +// 按顺序:AccessLog -> AccessControl -> RateLimiter -> BasicAuth -> Rewrite -> Compression -> SecurityHeaders +func (s *Server) buildMiddlewareChain(serverCfg *config.ServerConfig) (*middleware.Chain, error) { + var middlewares []middleware.Middleware + + // 1. AccessLog (已集成) + s.accessLogMiddleware = accesslog.New(&s.config.Logging) + middlewares = append(middlewares, s.accessLogMiddleware) + + // 2. Security: AccessControl (IP 访问控制) + if len(serverCfg.Security.Access.Allow) > 0 || len(serverCfg.Security.Access.Deny) > 0 { + ac, err := security.NewAccessControl(&serverCfg.Security.Access) + if err != nil { + return nil, fmt.Errorf("创建访问控制中间件失败: %w", err) + } + middlewares = append(middlewares, ac) + } + + // 3. Security: RateLimiter (速率限制) + if serverCfg.Security.RateLimit.RequestRate > 0 { + rl, err := security.NewRateLimiter(&serverCfg.Security.RateLimit) + if err != nil { + return nil, fmt.Errorf("创建限流中间件失败: %w", err) + } + middlewares = append(middlewares, rl) + } + + // 4. Security: BasicAuth (认证) + if len(serverCfg.Security.Auth.Users) > 0 { + auth, err := security.NewBasicAuth(&serverCfg.Security.Auth) + if err != nil { + return nil, fmt.Errorf("创建认证中间件失败: %w", err) + } + middlewares = append(middlewares, auth) + } + + // 5. Rewrite (URL 重写) + if len(serverCfg.Rewrite) > 0 { + rw, err := rewrite.New(serverCfg.Rewrite) + if err != nil { + return nil, fmt.Errorf("创建重写中间件失败: %w", err) + } + middlewares = append(middlewares, rw) + } + + // 6. Compression (响应压缩) + if serverCfg.Compression.Type != "" { + comp, err := compression.New(&serverCfg.Compression) + if err != nil { + return nil, fmt.Errorf("创建压缩中间件失败: %w", err) + } + middlewares = append(middlewares, comp) + } + + // 7. SecurityHeaders (安全头部) + // 如果有任何安全头部配置,则启用 + if serverCfg.Security.Headers.XFrameOptions != "" || + serverCfg.Security.Headers.XContentTypeOptions != "" || + serverCfg.Security.Headers.ContentSecurityPolicy != "" || + serverCfg.Security.Headers.ReferrerPolicy != "" || + serverCfg.Security.Headers.PermissionsPolicy != "" { + headers := security.NewSecurityHeaders(&serverCfg.Security.Headers) + middlewares = append(middlewares, headers) + } + + return middleware.NewChain(middlewares...), nil +} + // Start 启动服务器 func (s *Server) Start() error { logging.Init(s.config.Logging.Error.Level, true) + // 启用 GoroutinePool(如果配置) + if s.config.Performance.GoroutinePool.Enabled { + s.pool = NewGoroutinePool(PoolConfig{ + MaxWorkers: s.config.Performance.GoroutinePool.MaxWorkers, + MinWorkers: s.config.Performance.GoroutinePool.MinWorkers, + IdleTimeout: s.config.Performance.GoroutinePool.IdleTimeout, + }) + s.pool.Start() + } + + // 启用文件缓存(如果配置) + if s.config.Performance.FileCache.MaxEntries > 0 || s.config.Performance.FileCache.MaxSize > 0 { + s.fileCache = cache.NewFileCache( + s.config.Performance.FileCache.MaxEntries, + s.config.Performance.FileCache.MaxSize, + s.config.Performance.FileCache.Inactive, + ) + } + if s.config.HasServers() { return s.startVHostMode() } @@ -47,18 +141,31 @@ func (s *Server) startSingleMode() error { s.registerProxyRoutes(router, &s.config.Server) // 静态文件服务(作为 fallback) + // 启用零拷贝传输优化(大文件使用 sendfile) staticHandler := handler.NewStaticHandler( s.config.Server.Static.Root, s.config.Server.Static.Index, + true, // useSendfile ) + // 设置文件缓存 + if s.fileCache != nil { + staticHandler.SetFileCache(s.fileCache) + } router.GET("/{filepath:*}", staticHandler.Handle) router.HEAD("/{filepath:*}", staticHandler.Handle) - // 创建访问日志中间件 - s.accessLogMiddleware = accesslog.New(&s.config.Logging) + // 构建中间件链 + chain, err := s.buildMiddlewareChain(&s.config.Server) + if err != nil { + return err + } - chain := middleware.NewChain(s.accessLogMiddleware) - s.handler = chain.Apply(router.Handler()) + // 应用 GoroutinePool(如果启用) + handler := chain.Apply(router.Handler()) + if s.pool != nil { + handler = s.pool.WrapHandler(handler) + } + s.handler = handler s.fastServer = &fasthttp.Server{ Name: "lolly", @@ -78,10 +185,6 @@ func (s *Server) startSingleMode() error { func (s *Server) startVHostMode() error { vhostMgr := NewVHostManager() - // 创建访问日志中间件(共享给所有虚拟主机) - s.accessLogMiddleware = accesslog.New(&s.config.Logging) - chain := middleware.NewChain(s.accessLogMiddleware) - for i := range s.config.Servers { router := handler.NewRouter() s.registerProxyRoutes(router, &s.config.Servers[i]) @@ -90,11 +193,26 @@ func (s *Server) startVHostMode() error { staticHandler := handler.NewStaticHandler( s.config.Servers[i].Static.Root, s.config.Servers[i].Static.Index, + true, // useSendfile ) + if s.fileCache != nil { + staticHandler.SetFileCache(s.fileCache) + } router.GET("/{filepath:*}", staticHandler.Handle) router.HEAD("/{filepath:*}", staticHandler.Handle) - vhostMgr.AddHost(s.config.Servers[i].Name, chain.Apply(router.Handler())) + // 为每个虚拟主机构建独立的中间件链 + chain, err := s.buildMiddlewareChain(&s.config.Servers[i]) + if err != nil { + return err + } + + handler := chain.Apply(router.Handler()) + if s.pool != nil { + handler = s.pool.WrapHandler(handler) + } + + vhostMgr.AddHost(s.config.Servers[i].Name, handler) } // 默认主机 @@ -104,9 +222,23 @@ func (s *Server) startVHostMode() error { staticHandler := handler.NewStaticHandler( s.config.Server.Static.Root, s.config.Server.Static.Index, + true, // useSendfile ) + if s.fileCache != nil { + staticHandler.SetFileCache(s.fileCache) + } router.GET("/{filepath:*}", staticHandler.Handle) - vhostMgr.SetDefault(chain.Apply(router.Handler())) + + chain, err := s.buildMiddlewareChain(&s.config.Server) + if err != nil { + return err + } + + handler := chain.Apply(router.Handler()) + if s.pool != nil { + handler = s.pool.WrapHandler(handler) + } + vhostMgr.SetDefault(handler) } s.handler = vhostMgr.Handler()