lolly/docs/plan.md
xfy ee4f7a5ca9 refactor(config): 移除已废弃的配置字段
移除以下已标记废弃的字段:
- AuthConfig.MinPasswordLength (auth)
- FileCacheConfig.LRUEviction (file_cache)
- TransportConfig.MaxIdleConns (transport)

同时清理相关的验证逻辑、默认值设置、测试代码和文档。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-08 09:50:32 +08:00

62 KiB
Raw Blame History

Lolly 实现计划

概述

目标:创建一个类似 nginx 的高性能 HTTP 服务器,纯 Go 实现YAML 配置,单二进制运行。

核心原则

  • 不是 1:1 复刻 nginx而是更现代、更易用
  • 充分利用 Go 特性goroutine、channel、标准库
  • 功能完整性优先于极致性能
  • 选择"更简单易用"的设计方案

第一阶段:项目骨架与配置系统

目标

搭建项目基础结构,实现配置解析和命令行工具。

任务列表

1.1 项目目录结构

lolly/
├── main.go           # 程序入口
├── internal/
│   ├── config/                 # 配置解析模块
│   ├── server/                 # HTTP 服务器核心
│   ├── handler/                # 请求处理器
│   ├── middleware/             # 中间件系统(框架)
│   │   ├── security/           # 安全中间件access、ratelimit、auth
│   │   ├── compression/        # 压缩中间件gzip、brotli
│   │   ├── logging/            # 日志中间件
│   │   └── rewrite/            # URL 重写中间件
│   ├── proxy/                  # 反向代理模块
│   ├── loadbalance/            # 负载均衡模块
│   ├── ssl/                    # SSL/TLS 模块
│   ├── cache/                  # 缓存模块
│   ├── stream/                 # TCP/UDP Stream 代理
│   └── logging/                # 日志系统核心
├── pkg/
│   └── utils/                  # 公共工具函数
├── go.mod
├── go.sum
├── Makefile                    # 构建脚本
└── README.md

关键文件

  • main.go - 入口点,初始化和启动逻辑
  • internal/config/config.go - 配置结构体定义和解析
  • internal/middleware/middleware.go - 中间件框架接口定义

1.2 YAML 配置解析

配置结构体设计

// internal/config/config.go

// Config 根配置结构
type Config struct {
    DefaultServer ServerConfig     `yaml:"server"`      // 默认服务器配置(单服务器场景)
    Servers       []ServerConfig   `yaml:"servers"`     // 多虚拟主机配置(可选)
    Logging       LoggingConfig    `yaml:"logging"`
    Performance   PerformanceConfig `yaml:"performance"`
}

// 配置字段语义说明:
// - 若只配置 `server` 字段,则作为单一服务器运行
// - 若配置 `servers` 字段,则按虚拟主机模式运行(按 Host 头匹配)
// - 若同时配置两者,`server` 作为默认 fallback 主机,`servers` 按名称匹配
// - 建议使用场景:简单部署用 `server`,多站点部署用 `servers`

// ServerConfig 服务器配置
type ServerConfig struct {
    Listen      string         `yaml:"listen"`       // 监听地址 ":8080"
    Name        string         `yaml:"name"`         // server_name
    Static      StaticConfig   `yaml:"static"`       // 静态文件
    Proxy       []ProxyConfig  `yaml:"proxy"`        // 反向代理规则
    SSL         SSLConfig      `yaml:"ssl"`          // SSL 配置
}

// StaticConfig 静态文件配置
type StaticConfig struct {
    Root   string   `yaml:"root"`    // 根目录
    Index  []string `yaml:"index"`   // 索引文件
}

// ProxyConfig 反向代理配置
type ProxyConfig struct {
    Path        string   `yaml:"path"`         // 路径匹配
    Target      string   `yaml:"target"`       // 目标地址
    LoadBalance string   `yaml:"load_balance"` // 负载均衡算法
}

实现要点

  • 使用 gopkg.in/yaml.v3 解析 YAML
  • 支持配置文件路径命令行参数 -c/--config
  • 配置验证:必填字段检查、路径有效性
  • 默认配置:最小配置即可运行

1.3 命令行工具

支持的命令

lolly                    # 启动服务器(默认配置)
lolly -c /path/to.yaml   # 指定配置文件
lolly -v                 # 显示版本

实现方式

  • 使用 flag 标准库处理参数
  • 信号处理:SIGTERMSIGINTSIGHUP

验证方法

# 构建测试
make build

# 版本显示
./lolly -v

第二阶段HTTP 核心功能

目标

实现基础 HTTP 服务器、静态文件服务、请求路由、基础日志系统。

技术选型

HTTP 库:使用 fasthttp 替代 net/http

选择理由

  • 高性能:比 net/http 快 6 倍
  • 零分配热点路径无内存分配GC 压力最小
  • 原生支持高性能场景:无需额外优化

路由库:使用 fasthttp/router

性能对比

特点 性能
fasthttp 零分配,高性能
net/http 标准库,通用

关键差异

net/http fasthttp
http.Handler 接口 RequestHandler 函数
ServeMux 内置路由 无,需 router 库
http.Request 对象 *fasthttp.RequestCtx
http.ResponseWriter ctx 同时处理读写

任务列表

2.0 中间件框架(前置依赖)

实现

// internal/middleware/middleware.go

import "github.com/valyala/fasthttp"

// Middleware 中间件接口
type Middleware interface {
    Name() string
    Process(next fasthttp.RequestHandler) fasthttp.RequestHandler
}

// Chain 中间件链
type Chain struct {
    middlewares []Middleware
}

// Apply 应用中间件链
func (c *Chain) Apply(final fasthttp.RequestHandler) fasthttp.RequestHandler {
    handler := final
    for i := len(c.middlewares) - 1; i >= 0; i-- {
        handler = c.middlewares[i].Process(handler)
    }
    return handler
}

设计要点

  • 定义统一的中间件接口,所有中间件实现 RequestHandler 函数签名
  • 支持链式组合,按注册顺序逆序包装(从后往前)
  • Phase 2 建立框架,后续阶段填充具体中间件实现

2.1 基础 HTTP 服务器

核心实现

// internal/server/server.go

import "github.com/valyala/fasthttp"

// Server HTTP 服务器
type Server struct {
    config     *config.Config
    fastServer *fasthttp.Server
    handler    fasthttp.RequestHandler
    running    bool
}

// Start 启动服务器
func (s *Server) Start() error {
    s.fastServer = &fasthttp.Server{
        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,
    }
    return s.fastServer.ListenAndServe(s.config.Server.Listen)
}

// Stop 快速停止服务器
func (s *Server) Stop() error {
    return s.fastServer.Shutdown()
}

// GracefulStop 优雅停止(等待请求完成)
func (s *Server) GracefulStop(timeout time.Duration) error {
    // fasthttp 的 Shutdown 本身就是优雅关闭
    return s.fastServer.Shutdown()
}

实现要点

  • 使用 fasthttp.Server 配置超时和连接限制
  • 优雅关闭:Shutdown() 方法自动等待请求完成
  • 配置项:ReadTimeoutWriteTimeoutIdleTimeoutMaxConnsPerIP

2.2 静态文件服务

实现

// internal/handler/static.go

import "github.com/valyala/fasthttp"

// StaticHandler 静态文件处理器
type StaticHandler struct {
    root  string
    index []string
}

// Handle 处理静态文件请求
func (h *StaticHandler) Handle(ctx *fasthttp.RequestCtx) {
    path := string(ctx.Path())

    // 安全检查:防止目录遍历
    if strings.Contains(path, "..") {
        ctx.Error("Forbidden", fasthttp.StatusForbidden)
        return
    }

    // 拼接文件路径
    filePath := filepath.Join(h.root, path)

    // 尝试索引文件
    if info, err := os.Stat(filePath); err == nil && info.IsDir() {
        for _, idx := range h.index {
            idxPath := filepath.Join(filePath, idx)
            if fasthttp.ServeFile(ctx, idxPath) == nil {
                return
            }
        }
    }

    // 直接返回文件
    fasthttp.ServeFile(ctx, filePath)
}

功能清单

  • 文件路径安全检查(防止目录遍历)
  • MIME 类型自动识别fasthttp 内置)
  • 索引文件支持index.html、index.htm
  • Range 请求支持fasthttp 内置)
  • 文件缓存优化(可选)

2.3 请求路由

路由库:使用 fasthttp/router,基于 radix tree 高效匹配。

实现

// internal/handler/router.go

import (
    "github.com/valyala/fasthttp"
    "github.com/fasthttp/router"
)

// Router 请求路由器
type Router struct {
    router *router.Router
}

// NewRouter 创建路由器
func NewRouter() *Router {
    return &Router{
        router: router.New(),
    }
}

// Register 注册路由
func (r *Router) Register(path string, handler fasthttp.RequestHandler) {
    r.router.GET(path, handler)
    r.router.POST(path, handler)
    r.router.PUT(path, handler)
    r.router.DELETE(path, handler)
}

// Handler 返回路由处理器
func (r *Router) Handler() fasthttp.RequestHandler {
    return r.router.Handler
}

fasthttp/router 匹配类型

类型 语法 示例
Named {name} /user/{id}
Optional {name?} /search/{q?}
Regex {name:regex} /user/{id:[0-9]+}
Catch-All {filepath:*} /files/{filepath:*}

参数提取

func handler(ctx *fasthttp.RequestCtx) {
    id := ctx.UserValue("id")  // 获取路由参数
}

2.4 多虚拟主机支持

实现

// internal/server/vhost.go

import "github.com/valyala/fasthttp"

// VHostManager 虚拟主机管理器
type VHostManager struct {
    hosts       map[string]*VirtualHost  // 按 server_name 索引
    defaultHost *VirtualHost             // 默认主机
}

// VirtualHost 虚拟主机
type VirtualHost struct {
    name    string
    handler fasthttp.RequestHandler
}

// Handler 返回虚拟主机选择器
func (v *VHostManager) Handler() fasthttp.RequestHandler {
    return func(ctx *fasthttp.RequestCtx) {
        host := string(ctx.Host())
        if vhost, ok := v.hosts[host]; ok {
            vhost.handler(ctx)
        } else if v.defaultHost != nil {
            v.defaultHost.handler(ctx)
        } else {
            ctx.Error("Host not found", fasthttp.StatusNotFound)
        }
    }
}

功能

  • Host 头选择虚拟主机
  • 默认主机 fallback
  • SNI 支持SSLPhase 4

2.5 基础日志系统Phase 2 必需)

原因:调试 Phase 2-4 功能需要日志支持,将日志系统基础版本提前实现。

选型:使用 zerolog 作为日志库。

选择理由

  • 零分配:高并发场景 GC 压力最小,性能最优
  • JSON 输出便于日志采集系统ELK、Loki解析
  • API 简洁:链式调用风格,开发体验好
  • 灵活输出:支持 stdout/stderr/文件,开发模式可选 ConsoleWriter 美化

性能对比10 条日志,禁用输出):

ns/op allocs/op
zerolog ~40ns 0
zap (structured) ~50ns 0
slog (Go 1.21+) ~200ns 5+
logrus ~2000ns 23

实现

// internal/logging/logging.go

import "github.com/rs/zerolog"

// 全局日志实例
var log zerolog.Logger

// Init 初始化日志系统
func Init(level string, pretty bool) {
    l := parseLevel(level)
    if pretty {
        // 开发模式:带颜色和格式化(性能较差,仅开发用)
        log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout})
    } else {
        // 生产模式JSON 输出
        log = zerolog.New(os.Stdout).Level(l).With().Timestamp().Logger()
    }
}

// AccessLogger 访问日志(基础版)
func LogAccess(r *http.Request, status int, size int64, duration time.Duration) {
    log.Info().
        Str("method", r.Method).
        Str("path", r.URL.Path).
        Int("status", status).
        Int64("size", size).
        Dur("duration", duration).
        Msg("request")
}

Phase 2 实现范围

  • 基础请求日志:记录请求方法、路径、状态码
  • 控制台输出开发阶段便于调试ConsoleWriter 美化)
  • Phase 5 将扩展为完整日志系统(文件输出、自定义格式、访问/错误日志分离)

验证方法

# 启动服务器
./lolly -c lolly.yaml

# 静态文件测试
curl http://localhost:8080/index.html

# 路由测试
curl http://localhost:8080/static/test.txt
curl http://localhost:8080/api/health  # 应返回 404代理未实现

第三阶段:反向代理与负载均衡

目标

实现反向代理功能、多种负载均衡算法、健康检查。

任务列表

3.1 反向代理核心

实现(基于 fasthttp

// internal/proxy/proxy.go

import "github.com/valyala/fasthttp"

// Proxy 反向代理
type Proxy struct {
    targets    []*Target
    clients    map[string]*fasthttp.HostClient  // 每个目标一个 HostClient
    balancer   Balancer
}

// Target 后端目标
type Target struct {
    URL         string   // 目标地址,如 "http://backend1:8080"
    Weight      int
    Healthy     bool
    Connections int64    // 当前连接数(原子操作)
}

// HostClient fasthttp 客户端(连接池)
// 每个 Target 对应一个 HostClient自动管理连接池

功能清单

  • 请求转发:修改请求头、请求体
  • 响应处理:修改响应头
  • 超时配置连接超时、响应超时fasthttp.HostClient 配置)
  • WebSocket 支持Upgrade 协议检测和转发
  • 错误处理:后端不可用时的响应

3.2 负载均衡算法

实现

// internal/loadbalance/balancer.go

// Balancer 负载均衡器接口
type Balancer interface {
    Select(targets []*Target) *Target
}

// RoundRobin 轮询算法
type RoundRobin struct {
    current uint64
}

// WeightedRoundRobin 权重轮询
type WeightedRoundRobin struct {
    weights []int
    current int
}

// LeastConnections 最少连接
type LeastConnections struct{}

// IPHash IP 哈希
type IPHash struct{}

算法实现

算法 说明
round_robin 简单轮询
weighted_round_robin 按权重轮询
least_conn 选择连接数最少的目标
ip_hash 按客户端 IP 哈希固定目标

3.3 健康检查

实现

// internal/proxy/health.go

// HealthChecker 健康检查器
type HealthChecker struct {
    interval   time.Duration
    timeout    time.Duration
    path       string  // 健康检查路径
    targets    []*Target
}

// Check 执行健康检查
func (h *HealthChecker) Check()

// Start 后台定期检查
func (h *HealthChecker) Start()

类型

  • 被动检查:请求失败时标记不健康
  • 主动检查:定期发送探测请求

配置示例

proxy:
  - path: /api
    targets:
      - url: http://backend1:8080
        weight: 3
      - url: http://backend2:8080
        weight: 1
    load_balance: weighted_round_robin
    health_check:
      interval: 10s
      path: /health
      timeout: 5s

3.4 代理缓存(可选)

实现

// internal/cache/proxy_cache.go

// ProxyCache 代理响应缓存
type ProxyCache struct {
    storage CacheStorage
    rules   []CacheRule
}

验证方法

# 启动后端服务(用于测试)
# backend1: python3 -m http.server 8001
# backend2: python3 -m http.server 8002

# 配置代理
# lolly.yaml:
#   proxy:
#     - path: /api
#       targets: [http://localhost:8001, http://localhost:8002]

# 测试代理
curl http://localhost:8080/api/test

# 测试负载均衡(多次请求)
for i in {1..10}; do curl http://localhost:8080/api/test; done

第四阶段:安全与 SSL/TLS

目标

实现 HTTPS 支持、访问控制、请求限制。

任务列表

4.1 SSL/TLS 支持

实现

// internal/ssl/ssl.go

// SSLConfig SSL 配置
type SSLConfig struct {
    Cert         string   `yaml:"cert"`          // 证书路径
    Key          string   `yaml:"key"`           // 私钥路径
    CertChain    string   `yaml:"cert_chain"`    // 证书链路径(可选,用于中间证书)
    Protocols    []string `yaml:"protocols"`     // TLS 版本,默认 ["TLSv1.2", "TLSv1.3"]
    Ciphers      []string `yaml:"ciphers"`       // 加密套件(仅 TLS 1.2 有效)
    OCSPStapling bool     `yaml:"ocsp_stapling"` // OCSP Stapling 支持(默认 false
}

// TLSManager TLS 管理器
type TLSManager struct {
    configs map[string]*tls.Config  // 按 server_name
}

安全默认配置

  • TLS 版本:默认仅允许 TLSv1.2 和 TLSv1.3强制禁用 TLSv1.0/TLSv1.1
  • 加密套件默认值TLS 1.2,按优先级排序):
    # 默认安全加密套件,无需手动配置
    ciphers:
      - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 # 推荐,性能好
      - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 # 推荐,更安全
      - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 # 推荐,移动端友好
      - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 # ECDSA 证书专用
      - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 # ECDSA 证书专用
      - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 # ECDSA 证书专用
    
  • TLS 1.3:自动使用 Go 标准库的安全套件,配置无效

安全校验

  • 启用 Basic Auth 时,强制要求 SSL 配置,否则拒绝启动
  • 拒绝配置不安全的加密套件(如 RC4、DES、3DES

功能清单

  • 证书加载PEM 格式,支持证书链合并
  • TLS 版本控制TLSv1.2、TLSv1.3默认禁用不安全版本
  • 加密套件配置:提供安全默认值,拒绝不安全套件
  • SSL 会话缓存减少握手开销LRU 缓存,默认 128 条)
  • SNI 支持:多证书,通过 GetCertificate 回调实现
  • HTTP/2 自动启用TLS 时自动启用
  • OCSP Stapling减少客户端 CA 查询延迟和隐私风险

4.2 IP 访问控制

实现

// internal/middleware/security/access.go

// AccessControl IP 访问控制
type AccessControl struct {
    allowList []net.IPNet
    denyList  []net.IPNet
    default   Action  // 默认动作
}

// Check 检查 IP 是否允许
func (a *AccessControl) Check(ip net.IP) bool

性能优化建议

  • 使用 CIDR 树结构radix tree优化大规模 ACL 匹配
  • 预编译匹配规则,减少运行时开销
  • 支持 IPv4 和 IPv6 双栈匹配

配置示例

security:
  access:
    allow: [192.168.1.0/24, 10.0.0.0/8, "2001:db8::/32"] # 支持 IPv6
    deny: [192.168.2.100/32]
    default: deny
    # 可选:使用高性能匹配模式
    optimize: true # 启用 CIDR 树优化(适用于 >100 条规则)

4.3 请求限制

实现

// internal/middleware/security/ratelimit.go

// RateLimiter 速率限制器(令牌桶算法)
type RateLimiter struct {
    rate     int           // 令牌生成速率(请求/秒)
    burst    int           // 桶容量(突发流量上限)
    buckets  map[string]*TokenBucket
    mu       sync.RWMutex
}

// TokenBucket 令牌桶
type TokenBucket struct {
    tokens     float64
    lastUpdate time.Time
}

// SlidingWindowLimiter 滑动窗口限流器(可选,解决边界突发问题)
type SlidingWindowLimiter struct {
    window    time.Duration
    limit     int
    requests  map[string][]time.Time
}

// ConnLimiter 连接数限制器
type ConnLimiter struct {
    max      int
    current  int
    mu       sync.Mutex
}

算法选择

算法 适用场景 特点
令牌桶 (Token Bucket) API 请求限流 允许突发流量,推荐默认使用
滑动窗口 (Sliding Window) 精确限流 解决固定窗口边界问题,无突发

功能

  • 请求速率限制(limit_req
  • 连接数限制(limit_conn
  • 按 IP 或按 key 限制
  • 超限响应429 Too Many Requests
  • 支持 Retry-After 响应头告知等待时间

4.4 基础认证

实现

// internal/middleware/security/auth.go

// BasicAuth 基础认证
type BasicAuth struct {
    users     map[string]string  // username -> hashed_password
    algorithm HashAlgorithm      // 哈希算法bcrypt默认或 argon2id
    realm     string
    requireTLS bool              // 强制 HTTPS默认 true
}

// HashAlgorithm 哈希算法类型
type HashAlgorithm int
const (
    HashBcrypt HashAlgorithm = iota  // bcrypt默认推荐
    HashArgon2id                       // Argon2id更安全计算密集
)

// Authenticate 验证认证信息
func (b *BasicAuth) Authenticate(r *http.Request) bool

安全要求

  • 强制 HTTPS:启用 Basic Auth 时必须配置 SSL否则拒绝启动
  • 安全哈希:默认使用 bcrypt成本因子 12可选 Argon2id
  • 弃用 apr1:不再支持不安全的 MD5-based apr1 哈希
  • 密码强度:配置验证,拒绝弱密码

配置示例

security:
  auth:
    type: basic
    require_tls: true # 强制 HTTPS默认 true
    algorithm: bcrypt # bcrypt默认或 argon2id
    users:
      - name: admin
        password: $2b$12$... # bcrypt 哈希(推荐)
      - name: api_user
        password: $argon2id$... # Argon2id 哈希(可选)
    realm: "Restricted Area"
    min_password_length: 12 # 密码最小长度

4.5 安全头部

实现

// internal/middleware/security/headers.go

// SecurityHeaders 安全头部配置
type SecurityHeaders struct {
    XFrameOptions        string `yaml:"x_frame_options"`         // DENY/SAMEORIGIN/ALLOW-FROM
    XContentTypeOptions  string `yaml:"x_content_type_options"`  // nosniff默认
    ContentSecurityPolicy string `yaml:"content_security_policy"` // CSP 策略
    HSTS                 HSTSConfig `yaml:"hsts"`               // HSTS 配置
    ReferrerPolicy       string `yaml:"referrer_policy"`        // 推荐值
    PermissionsPolicy    string `yaml:"permissions_policy"`     // 权限策略
}

// HSTSConfig HSTS 配置
type HSTSConfig struct {
    MaxAge            int  `yaml:"max_age"`             // 过期时间(秒),默认 315360001年
    IncludeSubDomains bool `yaml:"include_sub_domains"` // 包含子域名,默认 true
    Preload           bool `yaml:"preload"`             // HSTS 预加载列表,默认 false
}

默认安全头部

头部 默认值 说明
X-Frame-Options DENY 防止点击劫持,可配置为 SAMEORIGIN
X-Content-Type-Options nosniff 防止 MIME 类型嗅探
Content-Security-Policy 可配置 关键:防止 XSS 攻击
Strict-Transport-Security max-age=31536000; includeSubDomains HSTS强制 HTTPS
Referrer-Policy strict-origin-when-cross-origin 控制引用信息泄露
Permissions-Policy 可配置 控制浏览器功能权限

注意X-XSS-Protection 已被现代浏览器弃用,不再默认添加,重点依赖 CSP 防护。

配置示例

security:
  headers:
    x_frame_options: SAMEORIGIN # 或 DENY默认
    content_security_policy: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
    hsts:
      max_age: 31536000 # 1年
      include_sub_domains: true # 包含子域名
      preload: false # 不加入预加载列表(需用户显式启用)
    referrer_policy: strict-origin-when-cross-origin
    permissions_policy: "geolocation=(), microphone=(), camera=()"

验证方法

# HTTPS 测试
curl -k https://localhost:8443/

# IP 访问控制测试
curl --interface 192.168.1.100 http://localhost:8080/  # 应允许
curl --interface 192.168.2.100 http://localhost:8080/  # 应拒绝

# 速率限制测试
for i in {1..20}; do curl http://localhost:8080/; done  # 部分应返回 429

# 基础认证测试
curl -u admin:password http://localhost:8080/protected/

第五阶段:增强功能

目标

实现 URL 重写、压缩、缓存、日志系统。

任务列表

5.1 URL 重写

实现

// internal/rewrite/rewrite.go

// RewriteRule 重写规则
type RewriteRule struct {
    Pattern     string  // 匹配模式
    Replacement string  // 替换目标
    Flag        RewriteFlag  // last/redirect/break
}

type RewriteFlag int
const (
    FlagLast     RewriteFlag = iota  // 继续匹配其他规则
    FlagRedirect                      // 302 重定向
    FlagPermanent                     // 301 重定向
    FlagBreak                         // 停止匹配
)

配置示例

rewrite:
  - pattern: "^/old/(.*)$"
    replacement: "/new/$1"
    flag: permanent # 301
  - pattern: "^/api/v1/(.*)$"
    replacement: "/api/v2/$1"
    flag: last

5.2 Gzip/Brotli 压缩

实现

// internal/compression/compression.go

// CompressionHandler 压缩中间件
type CompressionHandler struct {
    types    []string  // 压缩的 MIME 类型
    level    int       // 压缩级别
    minSize  int       // 最小压缩大小
}

配置示例

compression:
  type: gzip # gzip/brotli/both
  level: 6 # 1-9
  min_size: 1024 # 最小 1KB 才压缩
  types: [text/html, text/css, application/json]

5.3 缓存系统

静态文件缓存

// internal/cache/file_cache.go

// FileCache 文件描述符缓存
type FileCache struct {
    maxEntries int
    maxSize    int64         // 内存上限(新增)
    inactive   time.Duration
    entries    map[string]*FileEntry
    lruList    *list.List    // LRU 淘汰链表(新增)
}

// FileEntry 缓存条目
type FileEntry struct {
    fd         *os.File
    size       int64
    modTime    time.Time
    lastAccess time.Time
}

代理响应缓存

// internal/cache/proxy_cache.go

// ProxyCache 代理缓存
type ProxyCache struct {
    storage    Storage         // 内存/磁盘存储
    rules      []CacheRule
    maxAge     time.Duration
    cacheLock  bool            // 缓存锁开关(默认 true
    lock       *sync.RWMutex   // 缓存锁,防止击穿
    pending    map[string]*chan struct{} // 正在生成的缓存项
}

// CacheRule 缓存规则
type CacheRule struct {
    Path      string
    Methods   []string
    Statuses  []int           // 可缓存的响应状态码
    MaxAge    time.Duration
}

缓存锁机制(防击穿)

  • 当多个请求同时请求同一个未缓存的资源时,只让一个请求去后端获取
  • 其他请求等待第一个请求完成后从缓存读取
  • 防止缓存击穿导致后端压力骤增

配置示例

cache:
  file:
    max_entries: 10000
    max_size: 256MB # 内存上限
    inactive: 20s

  proxy:
    enabled: true
    storage: memory # memory/disk
    max_size: 1GB
    cache_lock: true # 防止缓存击穿
    stale_while_revalidate: 60s # 过期缓存复用
    rules:
      - path: /api/cacheable
        methods: [GET]
        statuses: [200, 301, 302]
        max_age: 10m

5.4 日志系统

扩展 Phase 2 的 zerolog 实现,增加文件输出和访问/错误日志分离。

实现

// internal/logging/logging.go

import (
    "io"
    "github.com/rs/zerolog"
)

// Logger 日志管理器
type Logger struct {
    accessLog zerolog.Logger  // 访问日志
    errorLog  zerolog.Logger  // 错误日志
}

// New 创建日志管理器
func New(cfg *LoggingConfig) *Logger {
    // 访问日志stdout 或文件
    accessOut := getOutput(cfg.Access.Path)
    accessLog := zerolog.New(accessOut).With().Timestamp().Logger()

    // 错误日志stderr 或文件
    errorOut := getOutput(cfg.Error.Path)
    errorLevel := parseLevel(cfg.Error.Level)
    errorLog := zerolog.New(errorOut).Level(errorLevel).With().Timestamp().Logger()

    return &Logger{accessLog: accessLog, errorLog: errorLog}
}

// LogAccess 记录访问日志nginx 格式变量)
func (l *Logger) LogAccess(r *http.Request, status int, size int64, duration time.Duration) {
    l.accessLog.Info().
        Str("remote_addr", r.RemoteAddr).
        Str("request", fmt.Sprintf("%s %s", r.Method, r.URL.Path)).
        Int("status", status).
        Int64("body_bytes_sent", size).
        Dur("request_time", duration).
        Msg("")
}

// getOutput 获取输出目标stdout/stderr/文件)
func getOutput(path string) io.Writer {
    if path == "" {
        return os.Stdout
    }
    f, _ := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    return f
}

日志格式变量(支持 nginx 风格配置):

  • $remote_addr - 客户端 IP
  • $request - 请求行(方法 + 路径)
  • $status - 响应状态码
  • $body_bytes_sent - 响应体大小
  • $request_time - 请求耗时

配置示例

logging:
  access:
    path: /var/log/lolly/access.log  # 留空则输出到 stdout
    format: json                     # json 或 textPhase 2 ConsoleWriter
  error:
    path: /var/log/lolly/error.log   # 留空则输出到 stderr
    level: info                      # debug/info/warn/error

5.5 状态监控端点

实现

// internal/server/status.go

// StatusHandler 状态监控处理器
type StatusHandler struct {
    server *Server
}

// 返回数据
type Status struct {
    Connections   int
    Requests      int64
    BytesSent     int64
    BytesReceived int64
    Uptime        time.Duration
}

配置示例

monitoring:
  status:
    path: /_status # 状态端点路径
    allow: [127.0.0.1] # 仅允许本地访问

验证方法

# 重写测试
curl http://localhost:8080/old/page  # 应重定向到 /new/page

# 压缩测试
curl -H "Accept-Encoding: gzip" -I http://localhost:8080/index.html
# 应返回 Content-Encoding: gzip

# 缓存测试
curl -I http://localhost:8080/static/test.txt
# 应返回缓存相关头部

# 日志测试
cat /var/log/lolly/access.log

# 状态监控测试
curl http://localhost:8080/_status

第六阶段:高级功能

目标

实现 TCP/UDP Stream 代理、性能优化、优雅升级。

任务列表

6.1 TCP/UDP Stream 代理

实现

// internal/stream/stream.go

// StreamServer TCP/UDP 代理服务器
type StreamServer struct {
    listeners map[string]*net.Listener
    upstreams map[string]*StreamUpstream
}

// StreamUpstream Stream 上游
type StreamUpstream struct {
    targets  []*StreamTarget
    balancer Balancer
}

// StreamTarget Stream 目标
type StreamTarget struct {
    addr      string
    healthy   bool
}

配置示例

stream:
  - listen: 3306
    protocol: tcp
    upstream:
      targets: [mysql1:3306, mysql2:3306]
      load_balance: round_robin
  - listen: 53
    protocol: udp
    upstream:
      targets: [dns1:53, dns2:53]

6.2 优雅升级(热升级)

实现

// internal/server/upgrade.go

// GracefulUpgrade 优雅升级
func GracefulUpgrade(newBinary string) error

// 逻辑:
// 1. 启动新进程,继承监听 socket
// 2. 新进程开始接受新连接
// 3. 旧进程停止接受新连接,完成现有请求后退出

信号处理

  • SIGUSR2:触发升级
  • SIGWINCH:优雅关闭 worker

6.3 性能优化

优化点

6.3.1 连接复用
// http.Transport 连接池配置
transport := &http.Transport{
    MaxIdleConns:        100,              // 最大空闲连接数
    MaxIdleConnsPerHost: 32,               // 每主机最大空闲连接
    IdleConnTimeout:     90 * time.Second, // 空闲连接超时
    MaxConnsPerHost:     0,                // 每主机最大连接数0=无限制)
}
6.3.2 缓冲池
// 使用 sync.Pool 实现分级缓冲池
var bufferPool = sync.Pool{
    New: func() interface{} {
        return make([]byte, 32*1024) // 32KB 缓冲区
    },
}
6.3.3 零拷贝sendfile
// internal/handler/sendfile.go

// SendFile 零拷贝文件传输(仅 Linux
// 大文件(>= 8KB使用 sendfile 系统调用
func SendFile(w http.ResponseWriter, f *os.File, offset, length int64) error {
    // Linux: syscall.Sendfile
    // macOS: syscall.Sendfile不同签名
    // Windows: syscall.TransmitFile
    // 其他平台: 降级为 io.Copy
}

// 跨平台兼容方案
// - Linux: sendfile(out_fd, in_fd, offset, count)
// - macOS: sendfile(in_fd, out_fd, offset, &len, sf_hdtr, flags)
// - Windows: TransmitFile(socket, handle, bytes_to_write, ...
// - Fallback: io.CopyBuffer
6.3.4 Goroutine 池(可选)
// internal/server/pool.go

// GoroutinePool Goroutine 池配置
type GoroutinePool struct {
    maxWorkers   int           // 最大 worker 数
    minWorkers   int           // 最小 worker 数(预热)
    idleTimeout  time.Duration // 空闲超时
    taskQueue    chan Task     // 任务队列
}

// 配置示例
performance:
  goroutine_pool:
    enabled: true          // 启用池化(高 QPS 场景推荐)
    max_workers: 10000     // 最大并发数
    min_workers: 100       // 预热 worker 数
    idle_timeout: 60s      // 空闲超时
6.3.5 对象池
// 使用 sync.Pool 复用对象
var requestPool = sync.Pool{
    New: func() interface{} {
        return new(Request)
    },
}
6.3.6 代理缓存锁(防击穿)
// internal/cache/proxy_cache.go

// ProxyCache 代理缓存(增加缓存锁)
type ProxyCache struct {
    storage   Storage
    rules     []CacheRule
    maxAge    time.Duration
    lock      *sync.RWMutex     // 缓存锁,防止缓存击穿
    pending   map[string]*chan struct{} // 正在生成的缓存项
}

// 缓存锁机制:
// 1. 请求到达时检查是否有 pending 请求
// 2. 有则等待 pending 完成
// 3. 无则创建 pending生成缓存后广播

6.4 信号处理完善

完整信号支持

信号 行为
SIGTERM/SIGINT 快速停止
SIGQUIT 优雅停止
SIGHUP 重载配置
SIGUSR1 重新打开日志
SIGUSR2 热升级

验证方法

# TCP Stream 测试
# 启动 MySQL 后端
mysql -h localhost -P 3306  # 应通过 lolly 代理连接

# 热升级测试
kill -USR2 <pid>  # 触发升级
ps aux | grep lolly  # 应有两个进程

# 性能测试
# 使用 wrk 或 ab 进行压力测试
wrk -t4 -c1000 -d30s http://localhost:8080/

文件依赖关系图

Phase 1:
  cmd/lolly/main.go → internal/config/config.go
  cmd/lolly/main.go → internal/middleware/middleware.go

Phase 2:
  internal/server/server.go → internal/config/config.go
  internal/server/server.go → internal/handler/router.go
  internal/server/server.go → internal/logging/logging.go
  internal/handler/router.go → internal/handler/static.go
  internal/handler/router.go → internal/middleware/middleware.go

Phase 3:
  internal/handler/router.go → internal/proxy/proxy.go
  internal/proxy/proxy.go → internal/loadbalance/balancer.go
  internal/proxy/proxy.go → internal/proxy/health.go

Phase 4:
  internal/server/server.go → internal/ssl/ssl.go
  internal/middleware/middleware.go → internal/middleware/security/access.go
  internal/middleware/middleware.go → internal/middleware/security/ratelimit.go
  internal/middleware/middleware.go → internal/middleware/security/auth.go
  internal/middleware/middleware.go → internal/middleware/security/headers.go

Phase 5:
  internal/middleware/middleware.go → internal/middleware/rewrite/rewrite.go
  internal/middleware/middleware.go → internal/middleware/compression/compression.go
  internal/proxy/proxy.go → internal/cache/proxy_cache.go
  internal/server/server.go → internal/logging/logging.go扩展

Phase 6:
  cmd/lolly/main.go → internal/stream/stream.go
  internal/server/server.go → internal/server/upgrade.go
  internal/server/server.go → internal/server/pool.go可选

第七阶段:功能完善

目标

补齐项目缺失功能,提升完整性和生产可用性。

背景分析

通过代码审查发现以下缺失功能:

功能 当前状态 优先级 说明
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。

实现方案

// 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 已定义配置,但未实现处理器。

实现方案

// 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,但未与代理集成。

实现方案

// 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。

实现方案

// 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 连接。

实现方案

// 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 响应

注意:此功能实现复杂,可作为后续迭代项。

验证方法

# 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 处理

Phase 2 技术选型变更

  • HTTP 库:使用 fasthttp 替代 net/http(性能提升 6 倍)
  • 日志库:使用 zerolog(零分配,~40ns/op

参考文档

详细功能参考 docs/ 目录:

  • HTTP 核心:docs/03-nginx-http-core.md
  • 代理负载均衡:docs/04-nginx-proxy-loadbalancing.md
  • SSL/HTTPSdocs/05-nginx-ssl-https.md
  • URL 重写:docs/06-nginx-rewrite.md
  • 压缩缓存:docs/07-nginx-compression-caching.md
  • 日志监控:docs/08-nginx-logging-monitoring.md
  • 安全控制:docs/09-nginx-security.md
  • Stream 代理:docs/10-nginx-stream-tcp-udp.md
  • 性能优化:docs/12-nginx-performance-tuning.md

代码注释规范docs/comments.md(必须遵循)


第八阶段:问题修复与功能完善

目标

修复深度分析发现的三个遗留问题,提升项目生产可用性。

背景分析

通过代码审查发现以下问题:

问题 当前状态 优先级 说明
WebSocket 代理未集成 ⚠️ 返回 501 P0 已实现但未调用
UDP Stream 冗余代码 ⚠️ Accept 返回 EOF P1 设计问题,需删除
热升级监听器继承 ⚠️ GetListeners 返回空 P1 监听器未保存

任务列表

8.1 WebSocket 代理集成 (P0)

问题proxy.go:370-375handleWebSocket 返回 501 Not Implementedwebsocket.go 中已有完整的 ProxyWebSocket 实现。

修改文件internal/proxy/proxy.go:370-375

修改内容

// 修改前
func (p *Proxy) handleWebSocket(ctx *fasthttp.RequestCtx, target *loadbalance.Target, client *fasthttp.HostClient) {
    ctx.Error("WebSocket proxying not implemented", fasthttp.StatusNotImplemented)
}

// 修改后
func (p *Proxy) handleWebSocket(ctx *fasthttp.RequestCtx, target *loadbalance.Target, client *fasthttp.HostClient) {
    timeout := p.config.Timeout.Connect
    if timeout == 0 {
        timeout = 30 * time.Second
    }
    if err := ProxyWebSocket(ctx, target, timeout); err != nil {
        logging.Error().Msgf("WebSocket proxy error: %v", err)
    }
}

8.2 删除冗余 UDP 监听器 (P1)

问题stream.go:435-453udpListener 类型实现 net.Listener 接口,但 UDP 是无连接协议,Accept() 始终返回 io.EOF。实际 UDP 处理由 udpServer 完成。

修改文件

  • internal/stream/stream.go:435-453 - 删除 udpListener 类型
  • internal/stream/stream_test.go - 删除相关测试

删除代码

// 删除以下代码(第 435-453 行)
type udpListener struct {
    conn *net.UDPConn
}

func (u *udpListener) Accept() (net.Conn, error) {
    return nil, io.EOF
}

func (u *udpListener) Close() error {
    return u.conn.Close()
}

func (u *udpListener) Addr() net.Addr {
    return u.conn.LocalAddr()
}

8.3 热升级监听器继承 (P1)

问题

  1. Server.listeners 字段从未被赋值
  2. fasthttp.ListenAndServe() 内部创建监听器但未暴露
  3. 子进程未使用继承的监听器

修改文件

  • internal/server/server.go - 改用手动监听器管理
  • internal/app/app.go - 支持继承监听器启动

核心修改

// server.go - 使用 net.Listen + fasthttp.Serve 替代 ListenAndServe

// 创建监听器
ln, err := net.Listen("tcp", s.config.Server.Listen)
if err != nil {
    return fmt.Errorf("failed to listen: %w", err)
}
s.listeners = []net.Listener{ln}  // 保存监听器

// 使用 Serve 替代 ListenAndServe
if s.tlsConfig != nil {
    return s.fastServer.ServeTLS(ln, "", "")
}
return s.fastServer.Serve(ln)
// app.go - 子进程继承监听器
if os.Getenv("GRACEFUL_UPGRADE") == "1" {
    fmt.Println("检测到热升级模式,继承父进程监听器")
    listeners, err := a.upgradeMgr.GetInheritedListeners()
    if err == nil && len(listeners) > 0 {
        a.srv.SetListeners(listeners)
    }
}

验证方法

# 1. WebSocket 测试
wscat -c ws://localhost:8080/ws

# 2. UDP Stream 测试
go test ./internal/stream/... -v

# 3. 热升级测试
./lolly -c config.yaml &
kill -USR2 <PID>
# 验证新进程接管,旧进程优雅退出

# 4. 完整测试
go test ./... -race
go build ./...

文件依赖关系图

Phase 8:
  internal/proxy/proxy.go → internal/proxy/websocket.go调用
  internal/stream/stream.go → 删除 udpListener
  internal/server/server.go → net.Listen + fasthttp.Serve
  internal/app/app.go → GetInheritedListeners

第九阶段HTTP/3 支持与性能优化扩展

目标

实现 HTTP/3 (QUIC) 协议支持,扩展负载均衡算法和限流机制,提升项目协议完整性和性能优化能力。

背景分析

通过与 nginx 功能对比分析,发现以下功能缺失:

功能 当前状态 优先级 说明
HTTP/3 (QUIC) 未实现 P0 新一代 HTTP 协议,性能提升 20-30%
一致性哈希负载均衡 未实现 P1 缓存代理场景关键功能
gzip_static 预压缩 未实现 P2 静态资源优化,减少 CPU 开销
滑动窗口限流 未实现 P2 更精确的限流算法

技术选型

HTTP/3 库:使用 quic-go

选择理由

  • 官方支持Go 生态系统中最成熟的 QUIC 实现
  • HTTP/3 完整实现:支持 http3.Server与标准库接口兼容
  • 活跃维护:定期更新,跟进 IETF QUIC 规范
  • 性能优秀0-RTT、连接迁移等特性支持完善

版本要求github.com/quic-go/quic-go v0.48.2(支持 Go 1.21+

关键挑战

挑战 说明 解决方案
接口不兼容 fasthttp 与 quic-go 接口不同 编写适配层转换请求格式
Handler 复用 需让 HTTP/3 使用现有 handler 链 adapter.go 将 http.Handler 转换为 fasthttp.RequestCtx
TLS 配置共享 QUIC 内置 TLS需复用现有证书 从 ssl 模块获取 tls.Config

任务列表

9.1 HTTP/3 服务器核心 (P0)

实现

// internal/http3/server.go

import (
    "github.com/quic-go/quic-go"
    "github.com/quic-go/quic-go/http3"
)

// Server HTTP/3 服务器。
type Server struct {
    config     *config.HTTP3Config
    http3Server *http3.Server
    handler    fasthttp.RequestHandler
    adapter    *Adapter
}

// Start 启动 HTTP/3 服务器。
func (s *Server) Start() error {
    // 1. 创建 UDP 监听器
    listener, err := quic.ListenAddrEarly(s.config.Listen, s.tlsConfig, s.quicConfig)
    if err != nil {
        return fmt.Errorf("failed to listen QUIC: %w", err)
    }

    // 2. 创建 HTTP/3 服务器
    s.http3Server = &http3.Server{
        Handler: s.adapter.Wrap(s.handler),
    }

    return s.http3Server.Serve(listener)
}

// Stop 停止 HTTP/3 服务器。
func (s *Server) Stop() error {
    if s.http3Server != nil {
        return s.http3Server.Close()
    }
    return nil
}

实现要点

  • 使用 quic.ListenAddrEarly 支持 0-RTT
  • 复用 internal/ssl 模块的 TLS 配置
  • 配置 Alt-Svc 响应头告知客户端可用 HTTP/3
  • 支持优雅关闭

9.2 HTTP/3 请求适配层 (P0)

实现

// internal/http3/adapter.go

// Adapter 将 fasthttp.RequestHandler 适配为 http.Handler。
type Adapter struct{}

// Wrap 包装 fasthttp handler 为 http.Handler。
func (a *Adapter) Wrap(handler fasthttp.RequestHandler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 1. 创建 fasthttp.RequestCtx
        ctx := &fasthttp.RequestCtx{}

        // 2. 转换请求
        a.convertRequest(r, ctx)

        // 3. 调用 fasthttp handler
        handler(ctx)

        // 4. 转换响应
        a.convertResponse(ctx, w)
    })
}

// convertRequest 将 net/http.Request 转换为 fasthttp.RequestCtx。
func (a *Adapter) convertRequest(r *http.Request, ctx *fasthttp.RequestCtx) {
    // 方法
    ctx.Request.Header.SetMethod(r.Method)

    // URI
    ctx.Request.SetRequestURI(r.URL.String())

    // 头部
    for k, v := range r.Header {
        for _, vv := range v {
            ctx.Request.Header.Add(k, vv)
        }
    }

    // 请求体
    if r.Body != nil {
        body, _ := io.ReadAll(r.Body)
        ctx.Request.SetBody(body)
    }

    // 远程地址
    ctx.SetRemoteAddr(r.RemoteAddr)
}

// convertResponse 将 fasthttp.RequestCtx 响应写入 http.ResponseWriter。
func (a *Adapter) convertResponse(ctx *fasthttp.RequestCtx, w http.ResponseWriter) {
    // 状态码
    w.WriteHeader(ctx.Response.StatusCode())

    // 头部
    ctx.Response.Header.VisitAll(func(k, v []byte) {
        w.Header().Add(string(k), string(v))
    })

    // 响应体
    w.Write(ctx.Response.Body())
}

性能优化

  • 使用 sync.Pool 复用 RequestCtx 对象
  • 避免不必要的内存分配
  • 流式处理大请求体

9.3 一致性哈希负载均衡 (P1)

实现

// internal/loadbalance/consistent_hash.go

// ConsistentHash 一致性哈希负载均衡器。
type ConsistentHash struct {
    // virtualNodes 虚拟节点数,默认 150
    virtualNodes int

    // circle 哈希环key 为哈希值value 为目标
    circle map[uint64]*Target

    // sortedHashes 排序后的哈希值列表,用于二分查找
    sortedHashes []uint64

    // hashKey 哈希键来源
    hashKey string // "ip", "uri", "header:xxx"

    // mu 读写锁
    mu sync.RWMutex
}

// Select 根据哈希键选择目标。
func (c *ConsistentHash) Select(targets []*Target) *Target {
    return c.SelectByKey(targets, "")
}

// SelectByKey 根据指定键选择目标。
func (c *ConsistentHash) SelectByKey(targets []*Target, key string) *Target {
    c.mu.RLock()
    defer c.mu.RUnlock()

    // 1. 计算键的哈希值
    hash := c.hashKey(key)

    // 2. 二分查找最近的节点
    idx := sort.Search(len(c.sortedHashes), func(i int) bool {
        return c.sortedHashes[i] >= hash
    })

    // 3. 环形回绕
    if idx >= len(c.sortedHashes) {
        idx = 0
    }

    return c.circle[c.sortedHashes[idx]]
}

// AddTarget 添加目标到哈希环。
func (c *ConsistentHash) AddTarget(target *Target) {
    c.mu.Lock()
    defer c.mu.Unlock()

    for i := 0; i < c.virtualNodes; i++ {
        key := fmt.Sprintf("%s#%d", target.URL, i)
        hash := c.hashKey(key)
        c.circle[hash] = target
        c.sortedHashes = append(c.sortedHashes, hash)
    }

    sort.Slice(c.sortedHashes, func(i, j int) bool {
        return c.sortedHashes[i] < c.sortedHashes[j]
    })
}

// hashKey 计算哈希值(使用 FNV-64a
func (c *ConsistentHash) hashKey(key string) uint64 {
    h := fnv.New64a()
    h.Write([]byte(key))
    return h.Sum64()
}

配置示例

proxy:
  - path: /api
    targets:
      - url: http://backend1:8080
      - url: http://backend2:8080
      - url: http://backend3:8080
    load_balance: consistent_hash
    hash_key: ip  # ip / uri / header:X-User-ID
    virtual_nodes: 150

9.4 gzip_static 预压缩支持 (P2)

实现

// internal/middleware/compression/gzip_static.go

// GzipStatic 预压缩文件支持。
type GzipStatic struct {
    // enabled 是否启用
    enabled bool

    // root 静态文件根目录
    root string

    // extensions 支持的扩展名
    extensions []string // 默认 [".html", ".css", ".js", ".json", ".xml"]
}

// ServeFile 发送预压缩文件(如果存在)。
func (g *GzipStatic) ServeFile(ctx *fasthttp.RequestCtx, filePath string) bool {
    if !g.enabled {
        return false
    }

    // 1. 检查客户端是否支持 gzip
    if !bytes.Contains(ctx.Request.Header.Peek("Accept-Encoding"), []byte("gzip")) {
        return false
    }

    // 2. 检查扩展名
    if !g.matchExtension(filePath) {
        return false
    }

    // 3. 检查预压缩文件是否存在
    gzPath := filePath + ".gz"
    if _, err := os.Stat(filepath.Join(g.root, gzPath)); err != nil {
        return false
    }

    // 4. 发送预压缩文件
    ctx.Response.Header.Set("Content-Encoding", "gzip")
    ctx.Response.Header.Set("Vary", "Accept-Encoding")
    fasthttp.ServeFile(ctx, filepath.Join(g.root, gzPath))
    return true
}

配置示例

compression:
  gzip_static: on  # on / off
  gzip_static_extensions: [".html", ".css", ".js", ".json", ".xml", ".svg"]

与现有压缩中间件集成

// compression.go 中添加预压缩检查
func (m *CompressionMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
    return func(ctx *fasthttp.RequestCtx) {
        // 1. 尝试发送预压缩文件
        if m.gzipStatic.ServeFile(ctx, string(ctx.Path())) {
            return
        }

        // 2. 回退到实时压缩
        // ... 现有逻辑
    }
}

9.5 滑动窗口限流算法 (P2)

实现

// internal/middleware/security/sliding_window.go

// SlidingWindowLimiter 滑动窗口限流器。
type SlidingWindowLimiter struct {
    // window 窗口大小
    window time.Duration

    // limit 窗口内最大请求数
    limit int

    // precise 是否使用精确模式
    precise bool

    // counters 窗口计数器key 为窗口起始时间
    counters map[int64]*windowCounter

    // mu 读写锁
    mu sync.RWMutex
}

// windowCounter 窗口计数器。
type windowCounter struct {
    count int64
    // precise 模式下记录每个请求时间
    timestamps []time.Time
}

// Allow 检查是否允许请求。
func (s *SlidingWindowLimiter) Allow(key string) bool {
    if s.precise {
        return s.allowPrecise(key)
    }
    return s.allowApproximate(key)
}

// allowApproximate 近似滑动窗口(推荐,内存 O(1))。
func (s *SlidingWindowLimiter) allowApproximate(key string) bool {
    s.mu.Lock()
    defer s.mu.Unlock()

    now := time.Now()
    currentWindow := now.UnixNano() / int64(s.window)
    prevWindow := currentWindow - 1

    // 获取当前窗口计数
    current, ok := s.counters[currentWindow]
    if !ok {
        current = &windowCounter{}
        s.counters[currentWindow] = current
    }

    // 获取上一个窗口计数
    prev, ok := s.counters[prevWindow]

    // 计算滑动窗口内的请求数
    // 公式:当前窗口计数 × 1.0 + 上一窗口计数 × (1 - 当前窗口已过比例)
    var count int64
    if ok {
        elapsed := float64(now.UnixNano()%int64(s.window)) / float64(s.window)
        count = current.count + int64(float64(prev.count)*(1-elapsed))
    } else {
        count = current.count
    }

    if count >= int64(s.limit) {
        return false
    }

    current.count++
    return true
}

// allowPrecise 精确滑动窗口(内存 O(n),精确限流)。
func (s *SlidingWindowLimiter) allowPrecise(key string) bool {
    s.mu.Lock()
    defer s.mu.Unlock()

    now := time.Now()
    windowStart := now.Add(-s.window)

    // 清理过期的时间戳
    valid := make([]time.Time, 0, len(s.timestamps))
    for _, t := range s.timestamps {
        if t.After(windowStart) {
            valid = append(valid, t)
        }
    }
    s.timestamps = valid

    // 检查是否超过限制
    if len(s.timestamps) >= s.limit {
        return false
    }

    s.timestamps = append(s.timestamps, now)
    return true
}

配置示例

security:
  rate_limit:
    request_rate: 100
    burst: 20
    algorithm: sliding_window  # token_bucket / sliding_window
    sliding_window_mode: approximate  # approximate / precise

9.6 配置扩展

新增配置结构

// internal/config/config.go

// HTTP3Config HTTP/3 配置。
type HTTP3Config struct {
    Enabled       bool          `yaml:"enabled"`         // 是否启用 HTTP/3
    Listen        string        `yaml:"listen"`          // UDP 监听地址,如 ":443"
    MaxStreams    int           `yaml:"max_streams"`     // 最大并发流
    IdleTimeout   time.Duration `yaml:"idle_timeout"`    // 空闲超时
    Enable0RTT    bool          `yaml:"enable_0rtt"`     // 启用 0-RTT
}

// LoadBalanceConfig 负载均衡扩展配置。
type LoadBalanceConfig struct {
    HashKey       string `yaml:"hash_key"`        // 一致性哈希键ip / uri / header:xxx
    VirtualNodes  int    `yaml:"virtual_nodes"`   // 虚拟节点数,默认 150
}

// CompressionConfig 扩展。
type CompressionConfig struct {
    // ... 现有字段
    GzipStatic          bool     `yaml:"gzip_static"`            // 启用预压缩
    GzipStaticExtensions []string `yaml:"gzip_static_extensions"` // 预压缩扩展名
}

// RateLimitConfig 扩展。
type RateLimitConfig struct {
    // ... 现有字段
    Algorithm         string `yaml:"algorithm"`          // token_bucket / sliding_window
    SlidingWindowMode string `yaml:"sliding_window_mode"` // approximate / precise
}

9.7 app.go 集成

修改文件internal/app/app.go

修改内容

// Run 启动应用(修改后)。
func (a *App) Run() error {
    // 1. 启动 TCP Server (HTTP/1.1/2)
    go func() {
        if err := a.srv.Start(); err != nil {
            logging.Error().Msgf("HTTP server error: %v", err)
        }
    }()

    // 2. 如果启用 HTTP/3启动 UDP Server
    if a.config.HTTP3.Enabled {
        go func() {
            if err := a.http3Server.Start(); err != nil {
                logging.Error().Msgf("HTTP/3 server error: %v", err)
            }
        }()
    }

    // 3. 如果配置了 Stream启动 Stream Server
    // ... 现有逻辑

    return nil
}

// Shutdown 关闭应用(修改后)。
func (a *App) Shutdown() error {
    var errs []error

    // 关闭 HTTP/3 Server
    if a.http3Server != nil {
        if err := a.http3Server.Stop(); err != nil {
            errs = append(errs, err)
        }
    }

    // 关闭 TCP Server
    if err := a.srv.Stop(); err != nil {
        errs = append(errs, err)
    }

    // ... 其他关闭逻辑

    if len(errs) > 0 {
        return fmt.Errorf("shutdown errors: %v", errs)
    }
    return nil
}

验证方法

# 1. HTTP/3 测试
# 启动服务器(配置 http3.enabled: true
./lolly -c config.yaml

# 使用 curl 测试 HTTP/3
curl --http3 https://localhost:8443/

# 检查 Alt-Svc 头
curl -I https://localhost:8443/
# 应返回 Alt-Svc: h3=":443"

# 2. 一致性哈希测试
# 配置 load_balance: consistent_hash
for i in {1..10}; do
    curl http://localhost:8080/api/test
done
# 相同 IP 的请求应路由到同一后端

# 3. gzip_static 测试
# 创建预压缩文件
gzip -k static/index.html

# 测试请求
curl -H "Accept-Encoding: gzip" -I http://localhost:8080/index.html
# 应返回 Content-Encoding: gzip且响应时间更快

# 4. 滑动窗口限流测试
# 配置 algorithm: sliding_window
for i in {1..120}; do
    curl http://localhost:8080/
done
# 应有部分请求返回 429

# 5. 完整测试
go test ./... -race
go build ./...

文件依赖关系图

Phase 9:
  internal/http3/server.go → internal/http3/adapter.go新增
  internal/http3/server.go → internal/ssl/ssl.goTLS 配置复用)
  internal/app/app.go → internal/http3/server.go集成

  internal/loadbalance/balancer.go → internal/loadbalance/consistent_hash.go新增

  internal/middleware/compression/compression.go → internal/middleware/compression/gzip_static.go新增

  internal/middleware/security/ratelimit.go → internal/middleware/security/sliding_window.go新增

  internal/config/config.go → 扩展配置结构

总体进度追踪(更新)

阶段 状态 主要功能
Phase 1 完成 项目骨架、配置系统
Phase 2 完成 HTTP 核心、静态文件、路由
Phase 3 完成 反向代理、负载均衡
Phase 4 完成 SSL/TLS、安全控制
Phase 5 完成 重写、压缩、缓存、日志
Phase 6 完成 Stream、性能优化、热升级
Phase 7 完成 功能完善
Phase 8 完成 问题修复WebSocket集成、UDP清理、热升级修复
Phase 9 🔄 计划中 HTTP/3、一致性哈希、gzip_static、滑动窗口限流

Phase 8 完成日期2026-04-03

修复内容

  1. WebSocket 代理集成 - handleWebSocket 现调用 ProxyWebSocket
  2. UDP Stream 冗余代码 - 删除 udpListener 类型及相关测试
  3. 热升级监听器继承 - 改用 net.Listen + Serve 模式,支持监听器继承

Phase 9 计划内容

  1. HTTP/3 (QUIC) 支持 - 使用 quic-go双栈并行架构
  2. 一致性哈希负载均衡 - 支持可配置哈希键ip/uri/header
  3. gzip_static 预压缩 - 静态资源优化
  4. 滑动窗口限流 - 近似和精确两种模式