fix(proxy,stream,server): Phase 8 问题修复与功能完善

- WebSocket 代理集成:handleWebSocket 现调用 ProxyWebSocket 实现
- 删除 UDP Stream 冗余代码:移除 udpListener 类型及相关测试
- 热升级监听器继承:改用 net.Listen + Serve 模式支持监听器传递
- 代码格式修复:注释格式调整、字段对齐、文件末尾换行符

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-03 14:28:00 +08:00
parent 95030cd68a
commit ac9153f09d
31 changed files with 318 additions and 167 deletions

View File

@ -1681,18 +1681,6 @@ Phase 7:
---
## 总体进度追踪
| 阶段 | 状态 | 主要功能 |
| ------- | ------ | ------------------------- |
| Phase 1 | ✅ 完成 | 项目骨架、配置系统 |
| Phase 2 | ✅ 完成 | HTTP 核心、静态文件、路由 |
| Phase 3 | ✅ 完成 | 反向代理、负载均衡 |
| Phase 4 | ✅ 完成 | SSL/TLS、安全控制 |
| Phase 5 | ✅ 完成 | 重写、压缩、缓存、日志 |
| Phase 6 | ✅ 完成 | Stream、性能优化、热升级 |
| Phase 7 | ✅ 完成 | 功能完善WebSocket、缓存集成、监控端点、Brotli、UDP、OCSP |
**Phase 2 技术选型变更**
- HTTP 库:使用 [fasthttp](https://github.com/valyala/fasthttp) 替代 `net/http`(性能提升 6 倍)
- 日志库:使用 [zerolog](https://github.com/rs/zerolog)(零分配,~40ns/op
@ -1714,3 +1702,170 @@ Phase 7:
- 性能优化:`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
```
---
## 总体进度追踪(更新)
| 阶段 | 状态 | 主要功能 |
| ------- | ------ | ------------------------- |
| Phase 1 | ✅ 完成 | 项目骨架、配置系统 |
| Phase 2 | ✅ 完成 | HTTP 核心、静态文件、路由 |
| Phase 3 | ✅ 完成 | 反向代理、负载均衡 |
| Phase 4 | ✅ 完成 | SSL/TLS、安全控制 |
| Phase 5 | ✅ 完成 | 重写、压缩、缓存、日志 |
| Phase 6 | ✅ 完成 | Stream、性能优化、热升级 |
| Phase 7 | ✅ 完成 | 功能完善 |
| Phase 8 | ✅ 完成 | 问题修复WebSocket集成、UDP清理、热升级修复|
**Phase 8 完成日期**2026-04-03
**修复内容**
1. WebSocket 代理集成 - `handleWebSocket` 现调用 `ProxyWebSocket`
2. UDP Stream 冗余代码 - 删除 `udpListener` 类型及相关测试
3. 热升级监听器继承 - 改用 `net.Listen` + `Serve` 模式,支持监听器继承

View File

@ -6,6 +6,7 @@
// - 配置加载和版本信息
//
// 主要用途:
//
// 用于启动和管理服务器进程,处理系统信号和运行时操作。
//
// 注意事项:
@ -18,6 +19,7 @@ package app
import (
"fmt"
"net"
"os"
"os/signal"
"syscall"
@ -76,6 +78,9 @@ type App struct {
// logFile 日志文件路径(用于重新打开)
logFile string
// listeners 继承的监听器(热升级时使用)
listeners []net.Listener
}
// NewApp 创建应用程序。
@ -145,6 +150,13 @@ func (a *App) Run() int {
// 检查是否是子进程(热升级)
if os.Getenv("GRACEFUL_UPGRADE") == "1" {
fmt.Println("检测到热升级模式,继承父进程监听器")
// 创建升级管理器以获取继承的监听器
a.upgradeMgr = server.NewUpgradeManager(nil)
listeners, err := a.upgradeMgr.GetInheritedListeners()
if err == nil && len(listeners) > 0 {
// 暂时保存监听器,等服务器创建后再设置
a.listeners = listeners
}
}
cfg, err := config.Load(a.cfgPath)
@ -160,6 +172,11 @@ func (a *App) Run() int {
// 创建 HTTP 服务器
a.srv = server.New(cfg)
// 如果有继承的监听器,设置到服务器
if len(a.listeners) > 0 {
a.srv.SetListeners(a.listeners)
}
// 创建 Stream 服务器(如果配置了)
if len(cfg.Stream) > 0 {
a.streamSrv = stream.NewServer()

View File

@ -6,6 +6,7 @@
// - 缓存统计和生命周期管理
//
// 主要用途:
//
// 用于缓存静态文件内容和代理响应,减少磁盘 I/O 和上游请求,提升服务性能。
//
// 注意事项:

View File

@ -6,6 +6,7 @@
// - 配置文件的加载、保存和验证方法
//
// 主要用途:
//
// 用于定义和管理服务器的完整配置,支持单服务器和多虚拟主机两种模式。
//
// 注意事项:

View File

@ -5,6 +5,7 @@
// - GenerateConfigYAML 函数:生成带注释的示例配置文件
//
// 主要用途:
//
// 用于生成默认配置和示例配置文件,便于用户快速上手。
//
// 注意事项:

View File

@ -7,6 +7,7 @@
// - 压缩配置验证(类型、级别、最小大小)
//
// 主要用途:
//
// 用于验证用户提供的配置是否符合要求,确保服务器启动前配置有效。
//
// 注意事项:

View File

@ -5,6 +5,7 @@
// - 路由器创建和处理器获取
//
// 主要用途:
//
// 用于管理 HTTP 请求的路由分发,将请求路径映射到对应的处理器。
//
// 注意事项:

View File

@ -6,6 +6,7 @@
// - 缓冲池管理
//
// 主要用途:
//
// 用于优化大文件传输性能,通过零拷贝技术减少 CPU 和内存开销。
//
// 注意事项:

View File

@ -6,6 +6,7 @@
// - 文件缓存和零拷贝传输优化
//
// 主要用途:
//
// 用于提供静态文件服务,支持缓存和零拷贝传输优化。
//
// 注意事项:

View File

@ -6,6 +6,7 @@
// - ValidAlgorithms 有效算法列表
//
// 主要用途:
//
// 用于定义负载均衡的标准接口和目标结构,支持多种负载均衡算法。
//
// 注意事项:

View File

@ -5,6 +5,7 @@
// - Chain 结构体:实现中间件的链式调用
//
// 主要用途:
//
// 用于构建和管理 HTTP 请求处理中间件链,支持灵活的组合和顺序控制。
//
// 注意事项:
@ -66,6 +67,7 @@ func NewChain(middlewares ...Middleware) *Chain {
// - fasthttp.RequestHandler: 包装后的请求处理器
//
// 执行顺序:
//
// 如果中间件链为 [A, B, C],最终处理器为 H则执行顺序为
// A -> B -> C -> H -> C -> B -> A
func (c *Chain) Apply(final fasthttp.RequestHandler) fasthttp.RequestHandler {

View File

@ -43,6 +43,7 @@ import (
"rua.plus/lolly/internal/cache"
"rua.plus/lolly/internal/config"
"rua.plus/lolly/internal/loadbalance"
"rua.plus/lolly/internal/logging"
)
// Proxy 表示反向代理实例,负责将 HTTP 请求转发到后端目标。
@ -365,13 +366,14 @@ func isWebSocketRequest(ctx *fasthttp.RequestCtx) bool {
}
// handleWebSocket 处理 WebSocket 升级请求。
// 目前返回 501 Not Implemented因为 WebSocket 代理需要
// HTTP 之外的特殊处理。
func (p *Proxy) handleWebSocket(ctx *fasthttp.RequestCtx, target *loadbalance.Target, client *fasthttp.HostClient) {
// WebSocket 代理需要原始 TCP 连接处理,
// 这超出了基本 HTTP 代理的范围。
// 后续可以使用 TCP 桥接实现
ctx.Error("WebSocket proxying not implemented", fasthttp.StatusNotImplemented)
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)
}
}
// UpdateTargets 更新代理目标并重新初始化客户端。

View File

@ -8,6 +8,7 @@
// - Goroutine 池的性能优化
//
// 主要用途:
//
// 用于启动和管理 HTTP 服务器,处理客户端请求并转发到上游服务或静态文件。
//
// 注意事项:
@ -153,6 +154,7 @@ func (s *Server) SetListeners(listeners []net.Listener) {
// buildMiddlewareChain 构建中间件链。
//
// 根据服务器配置按顺序构建中间件链,顺序为:
//
// AccessLog -> AccessControl -> RateLimiter -> BasicAuth -> Rewrite -> Compression -> SecurityHeaders
//
// 参数:
@ -342,6 +344,13 @@ func (s *Server) startSingleMode() error {
s.running = true
// 创建监听器并保存,用于热升级
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}
// 检查是否配置了 SSL/TLS
if s.config.Server.SSL.Cert != "" && s.config.Server.SSL.Key != "" {
var err error
@ -350,10 +359,10 @@ func (s *Server) startSingleMode() error {
return fmt.Errorf("创建 TLS 管理器失败: %w", err)
}
s.fastServer.TLSConfig = s.tlsManager.GetTLSConfig()
return s.fastServer.ListenAndServeTLS(s.config.Server.Listen, "", "")
return s.fastServer.ServeTLS(ln, "", "")
}
return s.fastServer.ListenAndServe(s.config.Server.Listen)
return s.fastServer.Serve(ln)
}
// startVHostMode 虚拟主机模式启动。
@ -453,6 +462,13 @@ func (s *Server) startVHostMode() error {
s.running = true
// 创建监听器并保存,用于热升级
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}
// 检查是否配置了 SSL/TLS
if s.config.Server.SSL.Cert != "" && s.config.Server.SSL.Key != "" {
var err error
@ -461,10 +477,10 @@ func (s *Server) startVHostMode() error {
return fmt.Errorf("创建 TLS 管理器失败: %w", err)
}
s.fastServer.TLSConfig = s.tlsManager.GetTLSConfig()
return s.fastServer.ListenAndServeTLS(s.config.Server.Listen, "", "")
return s.fastServer.ServeTLS(ln, "", "")
}
return s.fastServer.ListenAndServe(s.config.Server.Listen)
return s.fastServer.Serve(ln)
}
// registerProxyRoutes 注册代理路由。

View File

@ -6,6 +6,7 @@
// - 默认主机 fallback 机制
//
// 主要用途:
//
// 用于支持多域名虚拟主机场景,根据请求的 Host 头分发到不同的处理器。
//
// 注意事项:

View File

@ -7,6 +7,7 @@
// - OCSP Stapling 支持
//
// 主要用途:
//
// 用于管理 HTTPS 服务器的 TLS 配置,支持多证书虚拟主机。
//
// 安全默认值:

View File

@ -432,26 +432,6 @@ type Stats struct {
Upstreams int
}
// udpListener UDP 监听器包装。
type udpListener struct {
conn *net.UDPConn
}
// Accept UDP 不支持 Accept返回错误。
func (u *udpListener) Accept() (net.Conn, error) {
return nil, io.EOF
}
// Close 关闭 UDP 连接。
func (u *udpListener) Close() error {
return u.conn.Close()
}
// Addr 返回本地地址。
func (u *udpListener) Addr() net.Addr {
return u.conn.LocalAddr()
}
// udpSession UDP 会话,管理客户端到后端的映射
type udpSession struct {
clientAddr *net.UDPAddr

View File

@ -225,37 +225,6 @@ func TestHealthCheckerStartStop(t *testing.T) {
hc.Stop()
}
func TestUDPListener(t *testing.T) {
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Failed to resolve UDP address: %v", err)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
t.Fatalf("Failed to listen UDP: %v", err)
}
defer conn.Close()
ul := &udpListener{conn: conn}
// 测试 Addr
if ul.Addr() == nil {
t.Error("Expected non-nil address")
}
// 测试 Close
if err := ul.Close(); err != nil {
t.Errorf("Close failed: %v", err)
}
// 测试 Accept应该返回 io.EOF
_, err = ul.Accept()
if err == nil {
t.Error("Expected error from Accept")
}
}
func TestConcurrentConnections(t *testing.T) {
s := NewServer()