lolly/docs/plan.md
xfy 5e19d1a5ee docs(plan): 添加 Phase 9 规划 - HTTP/3 与性能优化扩展
- 规划 HTTP/3 (QUIC) 支持,使用 quic-go 库
- 规划一致性哈希负载均衡算法
- 规划 gzip_static 预压缩支持
- 规划滑动窗口限流算法
- 更新 update-prompts.md 添加新分析任务

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 15:02:40 +08:00

2475 lines
62 KiB
Markdown
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.

# 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 配置解析
**配置结构体设计**
```go
// 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 命令行工具
**支持的命令**
```bash
lolly # 启动服务器(默认配置)
lolly -c /path/to.yaml # 指定配置文件
lolly -v # 显示版本
```
**实现方式**
- 使用 `flag` 标准库处理参数
- 信号处理:`SIGTERM``SIGINT``SIGHUP`
### 验证方法
```bash
# 构建测试
make build
# 版本显示
./lolly -v
```
---
## 第二阶段HTTP 核心功能
### 目标
实现基础 HTTP 服务器、静态文件服务、请求路由、基础日志系统。
### 技术选型
**HTTP 库**:使用 [fasthttp](https://github.com/valyala/fasthttp) 替代 `net/http`
**选择理由**
- **高性能**:比 net/http 快 6 倍
- **零分配**热点路径无内存分配GC 压力最小
- **原生支持高性能场景**:无需额外优化
**路由库**:使用 [fasthttp/router](https://github.com/fasthttp/router)。
**性能对比**
| 库 | 特点 | 性能 |
|----|------|------|
| fasthttp | 零分配,高性能 | ⭐⭐⭐⭐⭐ |
| net/http | 标准库,通用 | ⭐⭐⭐ |
**关键差异**
| net/http | fasthttp |
|----------|----------|
| `http.Handler` 接口 | `RequestHandler` 函数 |
| `ServeMux` 内置路由 | 无,需 router 库 |
| `http.Request` 对象 | `*fasthttp.RequestCtx` |
| `http.ResponseWriter` | `ctx` 同时处理读写 |
### 任务列表
#### 2.0 中间件框架(前置依赖)
**实现**
```go
// 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 服务器
**核心实现**
```go
// 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()` 方法自动等待请求完成
- 配置项:`ReadTimeout``WriteTimeout``IdleTimeout``MaxConnsPerIP`
#### 2.2 静态文件服务
**实现**
```go
// 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](https://github.com/fasthttp/router),基于 radix tree 高效匹配。
**实现**
```go
// 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:*}` |
**参数提取**
```go
func handler(ctx *fasthttp.RequestCtx) {
id := ctx.UserValue("id") // 获取路由参数
}
```
#### 2.4 多虚拟主机支持
**实现**
```go
// 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](https://github.com/rs/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 |
**实现**
```go
// 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 将扩展为完整日志系统(文件输出、自定义格式、访问/错误日志分离)
### 验证方法
```bash
# 启动服务器
./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
```go
// 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 负载均衡算法
**实现**
```go
// 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 健康检查
**实现**
```go
// 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()
```
**类型**
- **被动检查**:请求失败时标记不健康
- **主动检查**:定期发送探测请求
**配置示例**
```yaml
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 代理缓存(可选)
**实现**
```go
// internal/cache/proxy_cache.go
// ProxyCache 代理响应缓存
type ProxyCache struct {
storage CacheStorage
rules []CacheRule
}
```
### 验证方法
```bash
# 启动后端服务(用于测试)
# 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 支持
**实现**
```go
// 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,按优先级排序):
```yaml
# 默认安全加密套件,无需手动配置
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 访问控制
**实现**
```go
// 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 双栈匹配
**配置示例**
```yaml
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 请求限制
**实现**
```go
// 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 基础认证
**实现**
```go
// 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 哈希
- **密码强度**:配置验证,拒绝弱密码
**配置示例**
```yaml
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 安全头部
**实现**
```go
// 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 防护。
**配置示例**
```yaml
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=()"
```
### 验证方法
```bash
# 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 重写
**实现**
```go
// 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 // 停止匹配
)
```
**配置示例**
```yaml
rewrite:
- pattern: "^/old/(.*)$"
replacement: "/new/$1"
flag: permanent # 301
- pattern: "^/api/v1/(.*)$"
replacement: "/api/v2/$1"
flag: last
```
#### 5.2 Gzip/Brotli 压缩
**实现**
```go
// internal/compression/compression.go
// CompressionHandler 压缩中间件
type CompressionHandler struct {
types []string // 压缩的 MIME 类型
level int // 压缩级别
minSize int // 最小压缩大小
}
```
**配置示例**
```yaml
compression:
type: gzip # gzip/brotli/both
level: 6 # 1-9
min_size: 1024 # 最小 1KB 才压缩
types: [text/html, text/css, application/json]
```
#### 5.3 缓存系统
**静态文件缓存**
```go
// 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
}
```
**代理响应缓存**
```go
// 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
}
```
**缓存锁机制(防击穿)**
- 当多个请求同时请求同一个未缓存的资源时,只让一个请求去后端获取
- 其他请求等待第一个请求完成后从缓存读取
- 防止缓存击穿导致后端压力骤增
**配置示例**
```yaml
cache:
file:
max_entries: 10000
max_size: 256MB # 内存上限
inactive: 20s
lru_eviction: true # 启用 LRU 淘汰
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 实现**,增加文件输出和访问/错误日志分离。
**实现**
```go
// 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` - 请求耗时
**配置示例**
```yaml
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 状态监控端点
**实现**
```go
// internal/server/status.go
// StatusHandler 状态监控处理器
type StatusHandler struct {
server *Server
}
// 返回数据
type Status struct {
Connections int
Requests int64
BytesSent int64
BytesReceived int64
Uptime time.Duration
}
```
**配置示例**
```yaml
monitoring:
status:
path: /_status # 状态端点路径
allow: [127.0.0.1] # 仅允许本地访问
```
### 验证方法
```bash
# 重写测试
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 代理
**实现**
```go
// 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
}
```
**配置示例**
```yaml
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 优雅升级(热升级)
**实现**
```go
// internal/server/upgrade.go
// GracefulUpgrade 优雅升级
func GracefulUpgrade(newBinary string) error
// 逻辑:
// 1. 启动新进程,继承监听 socket
// 2. 新进程开始接受新连接
// 3. 旧进程停止接受新连接,完成现有请求后退出
```
**信号处理**
- `SIGUSR2`:触发升级
- `SIGWINCH`:优雅关闭 worker
#### 6.3 性能优化
**优化点**
##### 6.3.1 连接复用
```go
// http.Transport 连接池配置
transport := &http.Transport{
MaxIdleConns: 100, // 最大空闲连接数
MaxIdleConnsPerHost: 32, // 每主机最大空闲连接
IdleConnTimeout: 90 * time.Second, // 空闲连接超时
MaxConnsPerHost: 0, // 每主机最大连接数0=无限制)
}
```
##### 6.3.2 缓冲池
```go
// 使用 sync.Pool 实现分级缓冲池
var bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 32*1024) // 32KB 缓冲区
},
}
```
##### 6.3.3 零拷贝sendfile
```go
// 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 池(可选)
```go
// 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 对象池
```go
// 使用 sync.Pool 复用对象
var requestPool = sync.Pool{
New: func() interface{} {
return new(Request)
},
}
```
##### 6.3.6 代理缓存锁(防击穿)
```go
// 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` | 热升级 |
### 验证方法
```bash
# 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。
**实现方案**
```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 处理
```
---
**Phase 2 技术选型变更**
- HTTP 库:使用 [fasthttp](https://github.com/valyala/fasthttp) 替代 `net/http`(性能提升 6 倍)
- 日志库:使用 [zerolog](https://github.com/rs/zerolog)(零分配,~40ns/op
---
## 参考文档
详细功能参考 `docs/` 目录:
- HTTP 核心:`docs/03-nginx-http-core.md`
- 代理负载均衡:`docs/04-nginx-proxy-loadbalancing.md`
- SSL/HTTPS`docs/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-375` 的 `handleWebSocket` 返回 501 Not Implemented但 `websocket.go` 中已有完整的 `ProxyWebSocket` 实现。
**修改文件**`internal/proxy/proxy.go:370-375`
**修改内容**
```go
// 修改前
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-453` 的 `udpListener` 类型实现 `net.Listener` 接口,但 UDP 是无连接协议,`Accept()` 始终返回 `io.EOF`。实际 UDP 处理由 `udpServer` 完成。
**修改文件**
- `internal/stream/stream.go:435-453` - 删除 `udpListener` 类型
- `internal/stream/stream_test.go` - 删除相关测试
**删除代码**
```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` - 支持继承监听器启动
**核心修改**
```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)
```
```go
// 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)
}
}
```
### 验证方法
```bash
# 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](https://github.com/quic-go/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)
**实现**
```go
// 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)
**实现**
```go
// 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)
**实现**
```go
// 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()
}
```
**配置示例**
```yaml
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)
**实现**
```go
// 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
}
```
**配置示例**
```yaml
compression:
gzip_static: on # on / off
gzip_static_extensions: [".html", ".css", ".js", ".json", ".xml", ".svg"]
```
**与现有压缩中间件集成**
```go
// 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)
**实现**
```go
// 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
}
```
**配置示例**
```yaml
security:
rate_limit:
request_rate: 100
burst: 20
algorithm: sliding_window # token_bucket / sliding_window
sliding_window_mode: approximate # approximate / precise
```
#### 9.6 配置扩展
**新增配置结构**
```go
// 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`
**修改内容**
```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
}
```
### 验证方法
```bash
# 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. 滑动窗口限流 - 近似和精确两种模式