docs(config,stream,logging,handler,proxy,cache,server,ssl,middleware): 为核心模块添加详细 GoDoc 文档注释
- config: 为 Config 和所有子配置结构添加完整文档,包含使用示例和注意事项 - stream: 为负载均衡器和服务器添加详细的参数、返回值和功能说明 - logging: 为日志格式化和输出函数添加文档,说明支持的变量替换 - handler: 为路由器、静态文件和 sendfile 处理器添加文档 - proxy: 为健康检查器和代理功能添加完整文档 - cache/server/ssl/middleware: 补充相关模块的文档注释 - config.example.yaml: 添加可信代理配置、加密套件示例,更新压缩级别说明 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
038c0639fd
commit
f2352ab9cc
@ -54,7 +54,12 @@ server:
|
||||
# protocols: # TLS 版本(有效值: TLSv1.2, TLSv1.3)
|
||||
# - "TLSv1.2"
|
||||
# - "TLSv1.3"
|
||||
# ciphers: [] # 加密套件(仅 TLS 1.2 有效)
|
||||
# ciphers: # 加密套件(仅 TLS 1.2 有效,TLS 1.3 使用内置套件)
|
||||
# - ECDHE-ECDSA-AES256-GCM-SHA384
|
||||
# - ECDHE-RSA-AES256-GCM-SHA384
|
||||
# - ECDHE-ECDSA-CHACHA20-POLY1305
|
||||
# - ECDHE-RSA-CHACHA20-POLY1305
|
||||
# # 拒绝不安全套件:含 RC4、DES、3DES、CBC 的配置将报错
|
||||
# ocsp_stapling: false # OCSP Stapling
|
||||
# hsts: # HTTP Strict Transport Security
|
||||
# max_age: 31536000 # 过期时间(秒)
|
||||
@ -68,6 +73,7 @@ server:
|
||||
allow: [] # 允许的 IP/CIDR 列表
|
||||
deny: [] # 拒绝的 IP/CIDR 列表
|
||||
default: "allow" # 默认动作(有效值: allow, deny)
|
||||
trusted_proxies: [] # 可信代理 CIDR 列表,用于 X-Forwarded-For 解析
|
||||
|
||||
# 速率限制
|
||||
rate_limit:
|
||||
@ -91,7 +97,7 @@ server:
|
||||
# 安全头部
|
||||
headers:
|
||||
x_frame_options: "DENY" # 防止点击劫持(有效值: DENY, SAMEORIGIN, 空表示禁用)
|
||||
x_content_type_options: "nosniff" # 防止 MIME 嗅探
|
||||
x_content_type_options: "nosniff" # 防止 MIME 嗅探(有效值:nosniff,空表示禁用)
|
||||
referrer_policy: "strict-origin-when-cross-origin" # 引用策略(有效值: no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url)
|
||||
# content_security_policy: "default-src 'self'" # 内容安全策略 CSP
|
||||
# permissions_policy: "geolocation=(), microphone=()" # 权限策略
|
||||
@ -105,12 +111,12 @@ server:
|
||||
# 响应压缩配置
|
||||
compression:
|
||||
type: "gzip" # 压缩类型(有效值: gzip, brotli, both,空表示禁用)
|
||||
level: 6 # 压缩级别(范围 1-9,值越大压缩率越高但速度越慢)
|
||||
level: 6 # 压缩级别(范围 0-9,0=不压缩,1=最快,9=最高压缩率)
|
||||
min_size: 1024 # 最小压缩大小(字节,小于此值不压缩)
|
||||
gzip_static: false # 启用预压缩文件支持(自动查找 .gz/.br 文件)
|
||||
gzip_static_extensions: # 预压缩文件扩展名
|
||||
- ".gz"
|
||||
- ".br"
|
||||
- ".gz"
|
||||
types: # 可压缩的 MIME 类型
|
||||
- "text/html"
|
||||
- "text/css"
|
||||
@ -202,7 +208,6 @@ performance:
|
||||
max_entries: 10000 # 最大缓存条目
|
||||
max_size: 268435456 # 内存上限(字节,256MB)
|
||||
inactive: 20s # 未访问淘汰时间
|
||||
lru_eviction: true # 启用 LRU 淘汰
|
||||
transport: # HTTP Transport 连接池
|
||||
max_idle_conns: 100 # 最大空闲连接
|
||||
max_idle_conns_per_host: 32 # 每主机空闲连接
|
||||
|
||||
@ -1,4 +1,14 @@
|
||||
// Package app 提供应用程序的启动和运行逻辑。
|
||||
// Package app 提供应用程序功能的测试。
|
||||
//
|
||||
// 该文件测试应用程序模块的各项功能,包括:
|
||||
// - 应用创建和配置
|
||||
// - 信号处理(SIGTERM、SIGHUP、SIGUSR1等)
|
||||
// - 配置重载
|
||||
// - 日志重开
|
||||
// - 版本输出
|
||||
// - 优雅关闭
|
||||
//
|
||||
// 作者:xfy
|
||||
package app
|
||||
|
||||
import (
|
||||
|
||||
10
internal/cache/cache_test.go
vendored
10
internal/cache/cache_test.go
vendored
@ -1,3 +1,13 @@
|
||||
// Package cache 提供缓存功能的测试。
|
||||
//
|
||||
// 该文件测试缓存模块的各项功能,包括:
|
||||
// - 文件缓存创建和配置
|
||||
// - 代理缓存规则和匹配
|
||||
// - 缓存设置和获取
|
||||
// - 过期和淘汰策略
|
||||
// - 路径匹配功能
|
||||
//
|
||||
// 作者:xfy
|
||||
package cache
|
||||
|
||||
import (
|
||||
|
||||
51
internal/cache/file_cache.go
vendored
51
internal/cache/file_cache.go
vendored
@ -205,6 +205,9 @@ func (c *FileCache) removeEntry(entry *FileEntry) {
|
||||
}
|
||||
|
||||
// evictIfNeeded 根据限制淘汰条目。
|
||||
//
|
||||
// 检查当前缓存是否超过条目数或内存大小限制,
|
||||
// 如果超过则调用 evictLRU 淘汰最久未使用的条目。
|
||||
func (c *FileCache) evictIfNeeded() {
|
||||
// 按条目数淘汰
|
||||
for c.lruList.Len() > int(c.maxEntries) && c.maxEntries > 0 {
|
||||
@ -218,6 +221,9 @@ func (c *FileCache) evictIfNeeded() {
|
||||
}
|
||||
|
||||
// evictLRU 淘汰最久未使用的条目。
|
||||
//
|
||||
// 从 LRU 链表尾部移除条目并删除。
|
||||
// 如果链表为空则不执行任何操作。
|
||||
func (c *FileCache) evictLRU() {
|
||||
if c.lruList.Len() == 0 {
|
||||
return
|
||||
@ -257,10 +263,17 @@ func (c *FileCache) Stats() FileCacheStats {
|
||||
|
||||
// FileCacheStats 文件缓存统计。
|
||||
type FileCacheStats struct {
|
||||
Entries int64
|
||||
// Entries 当前缓存条目数量
|
||||
Entries int64
|
||||
|
||||
// MaxEntries 最大缓存条目数限制
|
||||
MaxEntries int64
|
||||
Size int64
|
||||
MaxSize int64
|
||||
|
||||
// Size 当前缓存使用的内存大小(字节)
|
||||
Size int64
|
||||
|
||||
// MaxSize 最大内存使用限制(字节)
|
||||
MaxSize int64
|
||||
}
|
||||
|
||||
// ProxyCacheRule 代理缓存规则。
|
||||
@ -423,7 +436,20 @@ func (c *ProxyCache) MatchRule(path, method string, status int) *ProxyCacheRule
|
||||
return nil
|
||||
}
|
||||
|
||||
// pathMatch 路径匹配(支持前缀和精确匹配)。
|
||||
// pathMatch 检查路径是否匹配指定模式。
|
||||
//
|
||||
// 支持以下匹配模式:
|
||||
// - "*":匹配所有路径
|
||||
// - 以 "*" 结尾:前缀匹配(如 "/api/*" 匹配 "/api/xxx")
|
||||
// - 以 "/" 结尾:目录前缀匹配
|
||||
// - 其他:精确匹配
|
||||
//
|
||||
// 参数:
|
||||
// - pattern: 匹配模式,支持通配符
|
||||
// - path: 待检查的路径
|
||||
//
|
||||
// 返回值:
|
||||
// - bool: true 表示匹配,false 表示不匹配
|
||||
func pathMatch(pattern, path string) bool {
|
||||
if pattern == "*" {
|
||||
return true
|
||||
@ -442,6 +468,13 @@ func pathMatch(pattern, path string) bool {
|
||||
}
|
||||
|
||||
// contains 检查字符串切片是否包含某值。
|
||||
//
|
||||
// 参数:
|
||||
// - slice: 字符串切片
|
||||
// - val: 待查找的值
|
||||
//
|
||||
// 返回值:
|
||||
// - bool: true 表示包含,false 表示不包含
|
||||
func contains(slice []string, val string) bool {
|
||||
for _, s := range slice {
|
||||
if s == val {
|
||||
@ -452,6 +485,13 @@ func contains(slice []string, val string) bool {
|
||||
}
|
||||
|
||||
// containsInt 检查整数切片是否包含某值。
|
||||
//
|
||||
// 参数:
|
||||
// - slice: 整数切片
|
||||
// - val: 待查找的值
|
||||
//
|
||||
// 返回值:
|
||||
// - bool: true 表示包含,false 表示不包含
|
||||
func containsInt(slice []int, val int) bool {
|
||||
for _, i := range slice {
|
||||
if i == val {
|
||||
@ -489,6 +529,9 @@ func (c *ProxyCache) Stats() ProxyCacheStats {
|
||||
|
||||
// ProxyCacheStats 代理缓存统计。
|
||||
type ProxyCacheStats struct {
|
||||
// Entries 当前缓存条目数量
|
||||
Entries int
|
||||
|
||||
// Pending 正在等待缓存生成的请求数量
|
||||
Pending int
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -120,10 +120,9 @@ func DefaultConfig() *Config {
|
||||
IdleTimeout: 60 * time.Second,
|
||||
},
|
||||
FileCache: FileCacheConfig{
|
||||
MaxEntries: 10000,
|
||||
MaxSize: 256 * 1024 * 1024, // 256MB
|
||||
Inactive: 20 * time.Second,
|
||||
LRUEviction: true,
|
||||
MaxEntries: 10000,
|
||||
MaxSize: 256 * 1024 * 1024, // 256MB
|
||||
Inactive: 20 * time.Second,
|
||||
},
|
||||
Transport: TransportConfig{
|
||||
MaxIdleConns: 100,
|
||||
@ -234,7 +233,12 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) {
|
||||
for _, proto := range cfg.Server.SSL.Protocols {
|
||||
fmt.Fprintf(&buf, " # - \"%s\"\n", proto)
|
||||
}
|
||||
buf.WriteString(" # ciphers: [] # 加密套件(仅 TLS 1.2 有效)\n")
|
||||
buf.WriteString(" # ciphers: # 加密套件(仅 TLS 1.2 有效,TLS 1.3 使用内置套件)\n")
|
||||
buf.WriteString(" # - ECDHE-ECDSA-AES256-GCM-SHA384\n")
|
||||
buf.WriteString(" # - ECDHE-RSA-AES256-GCM-SHA384\n")
|
||||
buf.WriteString(" # - ECDHE-ECDSA-CHACHA20-POLY1305\n")
|
||||
buf.WriteString(" # - ECDHE-RSA-CHACHA20-POLY1305\n")
|
||||
buf.WriteString(" # # 拒绝不安全套件:含 RC4、DES、3DES、CBC 的配置将报错\n")
|
||||
fmt.Fprintf(&buf, " # ocsp_stapling: %v # OCSP Stapling\n", cfg.Server.SSL.OCSPStapling)
|
||||
buf.WriteString(" # hsts: # HTTP Strict Transport Security\n")
|
||||
fmt.Fprintf(&buf, " # max_age: %d # 过期时间(秒)\n", cfg.Server.SSL.HSTS.MaxAge)
|
||||
@ -274,7 +278,7 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) {
|
||||
buf.WriteString(" # 安全头部\n")
|
||||
buf.WriteString(" headers:\n")
|
||||
fmt.Fprintf(&buf, " x_frame_options: \"%s\" # 防止点击劫持(有效值: DENY, SAMEORIGIN, 空表示禁用)\n", cfg.Server.Security.Headers.XFrameOptions)
|
||||
fmt.Fprintf(&buf, " x_content_type_options: \"%s\" # 防止 MIME 嗅探\n", cfg.Server.Security.Headers.XContentTypeOptions)
|
||||
fmt.Fprintf(&buf, " x_content_type_options: \"%s\" # 防止 MIME 嗅探(有效值:nosniff,空表示禁用)\n", cfg.Server.Security.Headers.XContentTypeOptions)
|
||||
fmt.Fprintf(&buf, " referrer_policy: \"%s\" # 引用策略(有效值: no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url)\n", cfg.Server.Security.Headers.ReferrerPolicy)
|
||||
buf.WriteString(" # content_security_policy: \"default-src 'self'\" # 内容安全策略 CSP\n")
|
||||
buf.WriteString(" # permissions_policy: \"geolocation=(), microphone=()\" # 权限策略\n")
|
||||
@ -292,7 +296,7 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) {
|
||||
buf.WriteString(" # 响应压缩配置\n")
|
||||
buf.WriteString(" compression:\n")
|
||||
fmt.Fprintf(&buf, " type: \"%s\" # 压缩类型(有效值: gzip, brotli, both,空表示禁用)\n", cfg.Server.Compression.Type)
|
||||
fmt.Fprintf(&buf, " level: %d # 压缩级别(范围 1-9,值越大压缩率越高但速度越慢)\n", cfg.Server.Compression.Level)
|
||||
fmt.Fprintf(&buf, " level: %d # 压缩级别(范围 0-9,0=不压缩,1=最快,9=最高压缩率)\n", cfg.Server.Compression.Level)
|
||||
fmt.Fprintf(&buf, " min_size: %d # 最小压缩大小(字节,小于此值不压缩)\n", cfg.Server.Compression.MinSize)
|
||||
fmt.Fprintf(&buf, " gzip_static: %v # 启用预压缩文件支持(自动查找 .gz/.br 文件)\n", cfg.Server.Compression.GzipStatic)
|
||||
buf.WriteString(" gzip_static_extensions: # 预压缩文件扩展名\n")
|
||||
@ -398,8 +402,6 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) {
|
||||
fmt.Fprintf(&buf, " max_entries: %d # 最大缓存条目\n", cfg.Performance.FileCache.MaxEntries)
|
||||
fmt.Fprintf(&buf, " max_size: %d # 内存上限(字节,%dMB)\n", cfg.Performance.FileCache.MaxSize, cfg.Performance.FileCache.MaxSize/1024/1024)
|
||||
fmt.Fprintf(&buf, " inactive: %ds # 未访问淘汰时间\n", int(cfg.Performance.FileCache.Inactive.Seconds()))
|
||||
buf.WriteString(" # Deprecated: lru_eviction 已废弃,将在未来版本中移除\n")
|
||||
fmt.Fprintf(&buf, " lru_eviction: %v # 启用 LRU 淘汰\n", cfg.Performance.FileCache.LRUEviction)
|
||||
buf.WriteString(" transport: # HTTP Transport 连接池\n")
|
||||
fmt.Fprintf(&buf, " max_idle_conns: %d # 最大空闲连接\n", cfg.Performance.Transport.MaxIdleConns)
|
||||
fmt.Fprintf(&buf, " max_idle_conns_per_host: %d # 每主机空闲连接\n", cfg.Performance.Transport.MaxIdleConnsPerHost)
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
// Package config 提供默认配置生成功能的测试。
|
||||
//
|
||||
// 该文件测试默认配置模块的各项功能,包括:
|
||||
// - DefaultConfig 默认值验证
|
||||
// - GenerateConfigYAML YAML 生成测试
|
||||
// - 性能配置默认值测试
|
||||
//
|
||||
// 作者:xfy
|
||||
package config
|
||||
|
||||
import (
|
||||
@ -7,6 +15,7 @@ import (
|
||||
)
|
||||
|
||||
func TestDefaultConfig(t *testing.T) {
|
||||
// TestDefaultConfig 测试默认配置生成。
|
||||
cfg := DefaultConfig()
|
||||
|
||||
// 验证 Listen 默认值
|
||||
@ -55,6 +64,7 @@ func TestDefaultConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGenerateConfigYAML(t *testing.T) {
|
||||
// TestGenerateConfigYAML 测试 YAML 配置生成。
|
||||
cfg := DefaultConfig()
|
||||
|
||||
yamlData, err := GenerateConfigYAML(cfg)
|
||||
@ -82,6 +92,7 @@ func TestGenerateConfigYAML(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultConfigPerformance(t *testing.T) {
|
||||
// TestDefaultConfigPerformance 测试性能配置默认值。
|
||||
cfg := DefaultConfig()
|
||||
|
||||
// 验证 GoroutinePool 默认值
|
||||
@ -108,9 +119,7 @@ func TestDefaultConfigPerformance(t *testing.T) {
|
||||
if cfg.Performance.FileCache.Inactive != 20*time.Second {
|
||||
t.Errorf("FileCache.Inactive 期望 20s, 实际 %v", cfg.Performance.FileCache.Inactive)
|
||||
}
|
||||
if !cfg.Performance.FileCache.LRUEviction {
|
||||
t.Errorf("FileCache.LRUEviction 期望 true, 实际 %v", cfg.Performance.FileCache.LRUEviction)
|
||||
}
|
||||
// 注意: LRUEviction 已废弃,不再验证
|
||||
|
||||
// 验证 Transport 默认值
|
||||
if cfg.Performance.Transport.MaxIdleConns != 100 {
|
||||
|
||||
@ -1,4 +1,17 @@
|
||||
// Package config 提供 YAML 配置文件的解析、验证和默认配置生成功能。
|
||||
//
|
||||
// 该文件测试配置验证模块的各项功能,包括:
|
||||
// - 服务器配置验证
|
||||
// - 代理配置验证
|
||||
// - SSL 配置验证
|
||||
// - 认证配置验证
|
||||
// - 速率限制验证
|
||||
// - 压缩配置验证
|
||||
// - 访问控制验证
|
||||
// - Stream 配置验证
|
||||
// - 性能配置验证
|
||||
//
|
||||
// 作者:xfy
|
||||
package config
|
||||
|
||||
import (
|
||||
@ -7,6 +20,7 @@ import (
|
||||
)
|
||||
|
||||
func TestValidateServer(t *testing.T) {
|
||||
// TestValidateServer 测试服务器配置验证。
|
||||
tests := []struct {
|
||||
name string
|
||||
config ServerConfig
|
||||
@ -85,6 +99,7 @@ func TestValidateServer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateProxy(t *testing.T) {
|
||||
// TestValidateProxy 测试代理配置验证。
|
||||
tests := []struct {
|
||||
name string
|
||||
config ProxyConfig
|
||||
@ -176,6 +191,7 @@ func TestValidateProxy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateSSL(t *testing.T) {
|
||||
// TestValidateSSL 测试 SSL 配置验证。
|
||||
tests := []struct {
|
||||
name string
|
||||
config SSLConfig
|
||||
@ -287,6 +303,7 @@ func TestValidateSSL(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateAuth(t *testing.T) {
|
||||
// TestValidateAuth 测试认证配置验证。
|
||||
tests := []struct {
|
||||
name string
|
||||
config AuthConfig
|
||||
@ -387,6 +404,7 @@ func TestValidateAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateRateLimit(t *testing.T) {
|
||||
// TestValidateRateLimit 测试速率限制配置验证。
|
||||
tests := []struct {
|
||||
name string
|
||||
config RateLimitConfig
|
||||
@ -471,6 +489,7 @@ func TestValidateRateLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateCompression(t *testing.T) {
|
||||
// TestValidateCompression 测试压缩配置验证。
|
||||
tests := []struct {
|
||||
name string
|
||||
config CompressionConfig
|
||||
@ -574,6 +593,7 @@ func TestValidateCompression(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateAccess(t *testing.T) {
|
||||
// TestValidateAccess 测试访问控制配置验证。
|
||||
tests := []struct {
|
||||
name string
|
||||
config AccessConfig
|
||||
@ -675,6 +695,7 @@ func TestValidateAccess(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateStatic(t *testing.T) {
|
||||
// TestValidateStatic 测试静态文件配置验证。
|
||||
tests := []struct {
|
||||
name string
|
||||
config StaticConfig
|
||||
@ -732,6 +753,7 @@ func TestValidateStatic(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateSecurity(t *testing.T) {
|
||||
// TestValidateSecurity 测试安全配置验证。
|
||||
tests := []struct {
|
||||
name string
|
||||
config SecurityConfig
|
||||
@ -813,6 +835,7 @@ func TestValidateSecurity(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateStream(t *testing.T) {
|
||||
// TestValidateStream 测试 Stream 代理配置验证。
|
||||
tests := []struct {
|
||||
name string
|
||||
config StreamConfig
|
||||
@ -820,7 +843,7 @@ func TestValidateStream(t *testing.T) {
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid tcp stream",
|
||||
name: "有效 TCP Stream",
|
||||
config: StreamConfig{
|
||||
Listen: ":3306",
|
||||
Protocol: "tcp",
|
||||
@ -832,7 +855,7 @@ func TestValidateStream(t *testing.T) {
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid udp stream",
|
||||
name: "有效 UDP Stream",
|
||||
config: StreamConfig{
|
||||
Listen: ":53",
|
||||
Protocol: "udp",
|
||||
@ -844,7 +867,7 @@ func TestValidateStream(t *testing.T) {
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty listen",
|
||||
name: "监听地址为空",
|
||||
config: StreamConfig{
|
||||
Listen: "",
|
||||
Protocol: "tcp",
|
||||
@ -853,7 +876,7 @@ func TestValidateStream(t *testing.T) {
|
||||
errMsg: "listen 地址必填",
|
||||
},
|
||||
{
|
||||
name: "invalid protocol",
|
||||
name: "无效协议类型",
|
||||
config: StreamConfig{
|
||||
Listen: ":3306",
|
||||
Protocol: "http",
|
||||
@ -862,7 +885,7 @@ func TestValidateStream(t *testing.T) {
|
||||
errMsg: "无效的协议类型",
|
||||
},
|
||||
{
|
||||
name: "no targets",
|
||||
name: "无目标地址",
|
||||
config: StreamConfig{
|
||||
Listen: ":3306",
|
||||
Protocol: "tcp",
|
||||
@ -874,7 +897,7 @@ func TestValidateStream(t *testing.T) {
|
||||
errMsg: "upstream.targets 至少需要一个目标地址",
|
||||
},
|
||||
{
|
||||
name: "empty target addr",
|
||||
name: "目标地址为空",
|
||||
config: StreamConfig{
|
||||
Listen: ":3306",
|
||||
Protocol: "tcp",
|
||||
@ -908,6 +931,7 @@ func TestValidateStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidatePerformance(t *testing.T) {
|
||||
// TestValidatePerformance 测试性能配置验证。
|
||||
tests := []struct {
|
||||
name string
|
||||
config PerformanceConfig
|
||||
|
||||
@ -29,39 +29,91 @@ type Router struct {
|
||||
router *router.Router
|
||||
}
|
||||
|
||||
// NewRouter 创建路由器
|
||||
// NewRouter 创建路由器。
|
||||
//
|
||||
// 初始化并返回一个新的 Router 实例,底层使用 fasthttp/router 实现。
|
||||
//
|
||||
// 返回值:
|
||||
// - *Router: 新创建的路由器实例
|
||||
//
|
||||
// 使用示例:
|
||||
//
|
||||
// r := handler.NewRouter()
|
||||
// r.GET("/api", myHandler)
|
||||
func NewRouter() *Router {
|
||||
return &Router{
|
||||
router: router.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// GET 注册 GET 路由
|
||||
// GET 注册 GET 路由。
|
||||
//
|
||||
// 将指定路径映射到对应的处理器函数。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 路由路径,支持参数化路径如 /user/{id}
|
||||
// - handler: 请求处理函数
|
||||
func (r *Router) GET(path string, handler fasthttp.RequestHandler) {
|
||||
r.router.GET(path, handler)
|
||||
}
|
||||
|
||||
// POST 注册 POST 路由
|
||||
// POST 注册 POST 路由。
|
||||
//
|
||||
// 将指定路径映射到对应的处理器函数。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 路由路径,支持参数化路径
|
||||
// - handler: 请求处理函数
|
||||
func (r *Router) POST(path string, handler fasthttp.RequestHandler) {
|
||||
r.router.POST(path, handler)
|
||||
}
|
||||
|
||||
// PUT 注册 PUT 路由
|
||||
// PUT 注册 PUT 路由。
|
||||
//
|
||||
// 将指定路径映射到对应的处理器函数。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 路由路径
|
||||
// - handler: 请求处理函数
|
||||
func (r *Router) PUT(path string, handler fasthttp.RequestHandler) {
|
||||
r.router.PUT(path, handler)
|
||||
}
|
||||
|
||||
// DELETE 注册 DELETE 路由
|
||||
// DELETE 注册 DELETE 路由。
|
||||
//
|
||||
// 将指定路径映射到对应的处理器函数。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 路由路径
|
||||
// - handler: 请求处理函数
|
||||
func (r *Router) DELETE(path string, handler fasthttp.RequestHandler) {
|
||||
r.router.DELETE(path, handler)
|
||||
}
|
||||
|
||||
// HEAD 注册 HEAD 路由
|
||||
// HEAD 注册 HEAD 路由。
|
||||
//
|
||||
// 将指定路径映射到对应的处理器函数。
|
||||
//
|
||||
// 参数:
|
||||
// - path: 路由路径
|
||||
// - handler: 请求处理函数
|
||||
func (r *Router) HEAD(path string, handler fasthttp.RequestHandler) {
|
||||
r.router.HEAD(path, handler)
|
||||
}
|
||||
|
||||
// Handler 返回路由处理器
|
||||
// Handler 返回路由处理器。
|
||||
//
|
||||
// 获取可用于 fasthttp.Server 的请求处理器。
|
||||
// 该处理器会根据注册的路由规则分发请求。
|
||||
//
|
||||
// 返回值:
|
||||
// - fasthttp.RequestHandler: 请求处理器函数
|
||||
//
|
||||
// 使用示例:
|
||||
//
|
||||
// server := &fasthttp.Server{
|
||||
// Handler: r.Handler(),
|
||||
// }
|
||||
func (r *Router) Handler() fasthttp.RequestHandler {
|
||||
return r.router.Handler
|
||||
}
|
||||
|
||||
@ -1,4 +1,16 @@
|
||||
// Package handler 提供路由器的测试。
|
||||
// Package handler 提供路由器功能的测试。
|
||||
//
|
||||
// 该文件测试路由器模块的各项功能,包括:
|
||||
// - GET 路由注册
|
||||
// - POST 路由注册
|
||||
// - PUT 路由注册
|
||||
// - DELETE 路由注册
|
||||
// - HEAD 路由注册
|
||||
// - 多方法路由区分
|
||||
// - 多路由注册
|
||||
// - 未匹配路由处理
|
||||
//
|
||||
// 作者:xfy
|
||||
package handler
|
||||
|
||||
import (
|
||||
|
||||
@ -34,7 +34,30 @@ const (
|
||||
)
|
||||
|
||||
// SendFile 零拷贝文件传输。
|
||||
// 大文件使用系统调用直接从文件传输到 socket,避免用户空间拷贝。
|
||||
//
|
||||
// 大文件使用系统调用直接从文件传输到 socket,避免用户空间拷贝,
|
||||
// 从而减少 CPU 和内存开销,提升传输性能。
|
||||
//
|
||||
// 参数:
|
||||
// - ctx: fasthttp 请求上下文,用于获取底层连接
|
||||
// - file: 要传输的文件对象
|
||||
// - offset: 文件起始偏移量(字节)
|
||||
// - length: 传输长度(字节),-1 表示传输到文件末尾
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 传输过程中的错误
|
||||
//
|
||||
// 注意事项:
|
||||
// - 小于 8KB 的文件使用普通 io.Copy
|
||||
// - Linux 使用 sendfile 系统调用
|
||||
// - macOS 和 Windows 使用 fallback 方式
|
||||
//
|
||||
// 使用示例:
|
||||
//
|
||||
// file, _ := os.Open("large_file.bin")
|
||||
// defer file.Close()
|
||||
// info, _ := file.Stat()
|
||||
// err := SendFile(ctx, file, 0, info.Size())
|
||||
func SendFile(ctx *fasthttp.RequestCtx, file *os.File, offset, length int64) error {
|
||||
// 小文件使用普通 io.Copy
|
||||
if length < MinSendfileSize {
|
||||
@ -58,12 +81,29 @@ func SendFile(ctx *fasthttp.RequestCtx, file *os.File, offset, length int64) err
|
||||
}
|
||||
|
||||
// getNetConn 从 fasthttp.RequestCtx 获取底层 net.Conn。
|
||||
//
|
||||
// 参数:
|
||||
// - ctx: fasthttp 请求上下文
|
||||
//
|
||||
// 返回值:
|
||||
// - net.Conn: 底层网络连接,如果无法获取则返回 nil
|
||||
func getNetConn(ctx *fasthttp.RequestCtx) net.Conn {
|
||||
// fasthttp 内部使用 net.Conn,通过接口获取
|
||||
return ctx.Conn()
|
||||
}
|
||||
|
||||
// copyFile 普通文件拷贝(fallback)。
|
||||
//
|
||||
// 使用 io.Copy 进行文件传输,适用于不支持 sendfile 的平台或小文件。
|
||||
//
|
||||
// 参数:
|
||||
// - ctx: fasthttp 请求上下文,作为写入目标
|
||||
// - file: 源文件对象
|
||||
// - offset: 文件起始偏移量
|
||||
// - length: 传输长度,0 表示拷贝到文件末尾
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 拷贝过程中的错误
|
||||
func copyFile(ctx *fasthttp.RequestCtx, file *os.File, offset, length int64) error {
|
||||
if offset > 0 {
|
||||
if _, err := file.Seek(offset, io.SeekStart); err != nil {
|
||||
@ -82,6 +122,17 @@ func copyFile(ctx *fasthttp.RequestCtx, file *os.File, offset, length int64) err
|
||||
}
|
||||
|
||||
// platformSendfile 平台特定的 sendfile 实现。
|
||||
//
|
||||
// 根据运行平台选择合适的零拷贝传输方式。
|
||||
//
|
||||
// 参数:
|
||||
// - conn: 目标网络连接
|
||||
// - file: 源文件对象
|
||||
// - offset: 文件起始偏移量
|
||||
// - length: 传输长度
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 传输错误或不支持时返回 ENOTSUP
|
||||
func platformSendfile(conn net.Conn, file *os.File, offset, length int64) error {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
@ -98,6 +149,17 @@ func platformSendfile(conn net.Conn, file *os.File, offset, length int64) error
|
||||
}
|
||||
|
||||
// linuxSendfile Linux sendfile 系统调用。
|
||||
//
|
||||
// 使用 Linux 特有的 sendfile 系统调用实现零拷贝传输。
|
||||
//
|
||||
// 参数:
|
||||
// - conn: 目标网络连接
|
||||
// - fileFd: 源文件描述符
|
||||
// - offset: 文件起始偏移量
|
||||
// - length: 传输长度
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 系统调用错误
|
||||
func linuxSendfile(conn net.Conn, fileFd uintptr, offset, length int64) error {
|
||||
socketFd, err := getSocketFd(conn)
|
||||
if err != nil {
|
||||
@ -124,6 +186,19 @@ func linuxSendfile(conn net.Conn, fileFd uintptr, offset, length int64) error {
|
||||
}
|
||||
|
||||
// getSocketFd 获取 socket 文件描述符。
|
||||
//
|
||||
// 从网络连接中提取底层的文件描述符,用于 sendfile 系统调用。
|
||||
//
|
||||
// 参数:
|
||||
// - conn: 网络连接对象
|
||||
//
|
||||
// 返回值:
|
||||
// - uintptr: 文件描述符
|
||||
// - error: 不支持的连接类型或获取失败时返回错误
|
||||
//
|
||||
// 支持的连接类型:
|
||||
// - *net.TCPConn
|
||||
// - *net.UnixConn
|
||||
func getSocketFd(conn net.Conn) (uintptr, error) {
|
||||
switch c := conn.(type) {
|
||||
case *net.TCPConn:
|
||||
|
||||
@ -1,3 +1,18 @@
|
||||
// Package handler 提供 Sendfile 功能的测试。
|
||||
//
|
||||
// 该文件测试 Sendfile 模块的各项功能,包括:
|
||||
// - 最小 Sendfile 大小
|
||||
// - 平台 Sendfile 行为
|
||||
// - 文件复制功能
|
||||
// - 非 Linux 平台行为
|
||||
// - 套接字文件描述符获取
|
||||
// - 小文件发送
|
||||
// - 带偏移量发送
|
||||
// - 零长度文件
|
||||
// - 网络连接获取
|
||||
// - 错误处理
|
||||
//
|
||||
// 作者:xfy
|
||||
package handler
|
||||
|
||||
import (
|
||||
|
||||
@ -32,6 +32,11 @@ import (
|
||||
// StaticHandler 静态文件处理器。
|
||||
//
|
||||
// 提供静态文件服务,支持目录索引、文件缓存和零拷贝传输。
|
||||
//
|
||||
// 注意事项:
|
||||
// - 自动处理目录遍历攻击防护(拒绝包含 ".." 的路径)
|
||||
// - 并发安全,可在多个 goroutine 中使用
|
||||
// - 大文件(>= 8KB)自动启用零拷贝传输
|
||||
type StaticHandler struct {
|
||||
// root 静态文件根目录
|
||||
root string
|
||||
@ -49,7 +54,21 @@ type StaticHandler struct {
|
||||
gzipStatic *compression.GzipStatic
|
||||
}
|
||||
|
||||
// NewStaticHandler 创建静态文件处理器
|
||||
// NewStaticHandler 创建静态文件处理器。
|
||||
//
|
||||
// 初始化并返回一个新的静态文件处理器实例。
|
||||
//
|
||||
// 参数:
|
||||
// - root: 静态文件根目录路径
|
||||
// - index: 索引文件列表,当请求目录时依次查找(如 ["index.html", "index.htm"])
|
||||
// - useSendfile: 是否启用零拷贝传输(大文件优化)
|
||||
//
|
||||
// 返回值:
|
||||
// - *StaticHandler: 新创建的静态文件处理器
|
||||
//
|
||||
// 使用示例:
|
||||
//
|
||||
// handler := handler.NewStaticHandler("/var/www", []string{"index.html"}, true)
|
||||
func NewStaticHandler(root string, index []string, useSendfile bool) *StaticHandler {
|
||||
return &StaticHandler{
|
||||
root: root,
|
||||
@ -58,19 +77,54 @@ func NewStaticHandler(root string, index []string, useSendfile bool) *StaticHand
|
||||
}
|
||||
}
|
||||
|
||||
// SetFileCache 设置文件缓存
|
||||
// SetFileCache 设置文件缓存。
|
||||
//
|
||||
// 为静态文件处理器启用文件缓存功能。
|
||||
// 缓存可以显著提升小文件的访问性能。
|
||||
//
|
||||
// 参数:
|
||||
// - fc: 文件缓存实例
|
||||
//
|
||||
// 注意事项:
|
||||
// - 仅对小于 1MB 的文件启用缓存
|
||||
// - 缓存会自动检测文件修改并更新
|
||||
func (h *StaticHandler) SetFileCache(fc *cache.FileCache) {
|
||||
h.fileCache = fc
|
||||
}
|
||||
|
||||
// SetGzipStatic 设置预压缩文件支持
|
||||
// SetGzipStatic 设置预压缩文件支持。
|
||||
//
|
||||
// 启用后,对于匹配扩展名的请求,优先发送 .gz 预压缩文件。
|
||||
//
|
||||
// 参数:
|
||||
// - enabled: 是否启用预压缩支持
|
||||
// - extensions: 需要支持预压缩的文件扩展名列表(如 [".html", ".css", ".js"])
|
||||
//
|
||||
// 使用示例:
|
||||
//
|
||||
// handler.SetGzipStatic(true, []string{".html", ".css", ".js"})
|
||||
func (h *StaticHandler) SetGzipStatic(enabled bool, extensions []string) {
|
||||
if enabled {
|
||||
h.gzipStatic = compression.NewGzipStatic(true, h.root, extensions)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 处理静态文件请求
|
||||
// Handle 处理静态文件请求。
|
||||
//
|
||||
// 根据请求路径查找并返回对应的静态文件。
|
||||
// 支持目录索引文件、缓存查找和零拷贝传输。
|
||||
//
|
||||
// 参数:
|
||||
// - ctx: fasthttp 请求上下文
|
||||
//
|
||||
// 处理流程:
|
||||
// 1. 安全检查:防止目录遍历攻击
|
||||
// 2. 检查文件/目录是否存在
|
||||
// 3. 如果是目录,尝试查找索引文件
|
||||
// 4. 尝试发送预压缩文件
|
||||
// 5. 尝试从缓存获取
|
||||
// 6. 大文件使用零拷贝传输
|
||||
// 7. 读取文件并存入缓存
|
||||
func (h *StaticHandler) Handle(ctx *fasthttp.RequestCtx) {
|
||||
path := string(ctx.Path())
|
||||
|
||||
@ -107,7 +161,14 @@ func (h *StaticHandler) Handle(ctx *fasthttp.RequestCtx) {
|
||||
h.serveFile(ctx, filePath, info)
|
||||
}
|
||||
|
||||
// serveFile 提供文件服务,支持缓存和零拷贝传输
|
||||
// serveFile 提供文件服务,支持缓存和零拷贝传输。
|
||||
//
|
||||
// 内部方法,负责实际的文件发送逻辑。
|
||||
//
|
||||
// 参数:
|
||||
// - ctx: fasthttp 请求上下文
|
||||
// - filePath: 文件绝对路径
|
||||
// - info: 文件信息(用于判断文件大小和修改时间)
|
||||
func (h *StaticHandler) serveFile(ctx *fasthttp.RequestCtx, filePath string, info os.FileInfo) {
|
||||
// 尝试发送预压缩文件
|
||||
if h.gzipStatic != nil {
|
||||
|
||||
@ -1,4 +1,20 @@
|
||||
// Package handler 提供静态文件处理器的测试。
|
||||
// Package handler 提供静态文件处理器功能的测试。
|
||||
//
|
||||
// 该文件测试静态文件处理器模块的各项功能,包括:
|
||||
// - 正常文件访问
|
||||
// - 嵌套路径文件
|
||||
// - 目录索引文件
|
||||
// - 路径遍历安全检查
|
||||
// - 索引文件优先级
|
||||
// - 构造函数
|
||||
// - 文件缓存设置
|
||||
// - Gzip 静态文件设置
|
||||
// - HEAD 请求处理
|
||||
// - 预压缩文件支持
|
||||
// - 大文件处理
|
||||
// - 符号链接处理
|
||||
//
|
||||
// 作者:xfy
|
||||
package handler
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
// Package http3 提供 HTTP/3 适配器功能的测试。
|
||||
//
|
||||
// 该文件测试 HTTP/3 适配器模块的各项功能,包括:
|
||||
// - 适配器创建和初始化
|
||||
// - HTTP 请求到 fasthttp 请求的转换
|
||||
// - fasthttp 响应到 HTTP 响应的转换
|
||||
// - 请求方法、URI、头部、请求体的转换
|
||||
// - 完整请求响应流程
|
||||
//
|
||||
// 作者:xfy
|
||||
package http3
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
// Package http3 提供 HTTP/3 服务器功能的测试。
|
||||
//
|
||||
// 该文件测试 HTTP/3 服务器模块的各项功能,包括:
|
||||
// - 服务器创建和配置验证
|
||||
// - Alt-Svc 头部生成
|
||||
// - 服务器统计信息获取
|
||||
// - 运行状态检查
|
||||
// - 服务器停止和优雅停止
|
||||
//
|
||||
// 作者:xfy
|
||||
package http3
|
||||
|
||||
import (
|
||||
|
||||
@ -1,4 +1,13 @@
|
||||
// Package loadbalance provides load balancing algorithms for the Lolly HTTP server.
|
||||
// Package loadbalance 提供负载均衡算法的测试。
|
||||
//
|
||||
// 该文件测试负载均衡模块的各项功能,包括:
|
||||
// - 轮询算法
|
||||
// - 加权轮询算法
|
||||
// - 最少连接算法
|
||||
// - IP 哈希算法
|
||||
// - 一致性哈希算法
|
||||
//
|
||||
// 作者:xfy
|
||||
package loadbalance
|
||||
|
||||
import (
|
||||
|
||||
@ -163,9 +163,14 @@ func (c *ConsistentHash) GetVirtualNodes() int {
|
||||
|
||||
// Stats 返回一致性哈希统计信息。
|
||||
type ConsistentHashStats struct {
|
||||
VirtualNodes int // 虚拟节点数
|
||||
CircleSize int // 哈希环大小
|
||||
SortedHashes int // 排序哈希值数量
|
||||
// VirtualNodes 每个目标的虚拟节点数量
|
||||
VirtualNodes int
|
||||
|
||||
// CircleSize 哈希环中的节点总数
|
||||
CircleSize int
|
||||
|
||||
// SortedHashes 排序后的哈希值数量
|
||||
SortedHashes int
|
||||
}
|
||||
|
||||
// GetStats 返回统计信息。
|
||||
|
||||
@ -78,6 +78,17 @@ func New(cfg *config.LoggingConfig) *Logger {
|
||||
}
|
||||
|
||||
// getOutput 获取输出目标(stdout/stderr/文件)。
|
||||
//
|
||||
// 根据路径返回对应的输出 writer:
|
||||
// - "stdout" 或空字符串:返回 os.Stdout
|
||||
// - "stderr":返回 os.Stderr
|
||||
// - 其他:尝试打开文件,失败返回 os.Stdout
|
||||
//
|
||||
// 参数:
|
||||
// - path: 输出路径
|
||||
//
|
||||
// 返回值:
|
||||
// - io.Writer: 输出 writer
|
||||
func getOutput(path string) io.Writer {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" || path == "stdout" {
|
||||
@ -128,6 +139,26 @@ func (l *Logger) LogAccess(ctx *fasthttp.RequestCtx, status int, size int64, dur
|
||||
}
|
||||
|
||||
// formatAccessLog 根据模板格式化访问日志。
|
||||
//
|
||||
// 支持以下变量替换:
|
||||
// - $remote_addr: 客户端地址
|
||||
// - $remote_user: 认证用户
|
||||
// - $request: 请求方法和路径
|
||||
// - $status: HTTP 状态码
|
||||
// - $body_bytes_sent: 响应体大小
|
||||
// - $request_time: 请求处理时间
|
||||
// - $http_referer: Referer 头
|
||||
// - $http_user_agent: User-Agent 头
|
||||
// - $time: 当前时间
|
||||
//
|
||||
// 参数:
|
||||
// - ctx: FastHTTP 请求上下文
|
||||
// - status: HTTP 状态码
|
||||
// - size: 响应体大小
|
||||
// - duration: 请求处理时间
|
||||
//
|
||||
// 返回值:
|
||||
// - string: 格式化后的日志字符串
|
||||
func (l *Logger) formatAccessLog(ctx *fasthttp.RequestCtx, status int, size int64, duration time.Duration) string {
|
||||
// 获取认证用户名,无认证时为 "-"
|
||||
remoteUser := "-"
|
||||
@ -208,6 +239,16 @@ func Debug() *zerolog.Event {
|
||||
}
|
||||
|
||||
// parseLevel 解析日志级别。
|
||||
//
|
||||
// 将字符串级别转换为 zerolog.Level。
|
||||
// 支持的级别:debug, info, warn, error(不区分大小写)。
|
||||
// 未知级别默认返回 info。
|
||||
//
|
||||
// 参数:
|
||||
// - level: 日志级别字符串
|
||||
//
|
||||
// 返回值:
|
||||
// - zerolog.Level: 解析后的日志级别
|
||||
func parseLevel(level string) zerolog.Level {
|
||||
switch strings.ToLower(level) {
|
||||
case "debug":
|
||||
|
||||
@ -1,3 +1,14 @@
|
||||
// Package logging 提供日志功能的测试。
|
||||
//
|
||||
// 该文件测试日志模块的各项功能,包括:
|
||||
// - 日志级别解析
|
||||
// - 日志记录器创建
|
||||
// - 访问日志记录
|
||||
// - 错误日志记录
|
||||
// - 自定义格式
|
||||
// - 文件输出
|
||||
//
|
||||
// 作者:xfy
|
||||
package logging
|
||||
|
||||
import (
|
||||
|
||||
@ -23,10 +23,17 @@ import (
|
||||
|
||||
// AccessLog 访问日志中间件,记录请求方法、路径、状态码、响应大小和处理时间。
|
||||
type AccessLog struct {
|
||||
// logger 日志记录器实例,用于输出访问日志
|
||||
logger *logging.Logger
|
||||
}
|
||||
|
||||
// New 创建访问日志中间件。
|
||||
//
|
||||
// 参数:
|
||||
// - cfg: 日志配置,包含输出路径、格式等设置
|
||||
//
|
||||
// 返回值:
|
||||
// - *AccessLog: 访问日志中间件实例
|
||||
func New(cfg *config.LoggingConfig) *AccessLog {
|
||||
return &AccessLog{
|
||||
logger: logging.New(cfg),
|
||||
@ -34,11 +41,20 @@ func New(cfg *config.LoggingConfig) *AccessLog {
|
||||
}
|
||||
|
||||
// Name 返回中间件名称。
|
||||
//
|
||||
// 返回值:
|
||||
// - string: 中间件名称 "accesslog"
|
||||
func (a *AccessLog) Name() string {
|
||||
return "accesslog"
|
||||
}
|
||||
|
||||
// Process 包装 handler,在请求处理后记录访问日志。
|
||||
//
|
||||
// 参数:
|
||||
// - next: 下一个请求处理器
|
||||
//
|
||||
// 返回值:
|
||||
// - fasthttp.RequestHandler: 包装后的请求处理器
|
||||
func (a *AccessLog) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
start := time.Now()
|
||||
@ -49,6 +65,9 @@ func (a *AccessLog) Process(next fasthttp.RequestHandler) fasthttp.RequestHandle
|
||||
}
|
||||
|
||||
// Close 关闭日志文件。
|
||||
//
|
||||
// 返回值:
|
||||
// - error: 关闭失败时返回错误,成功返回 nil
|
||||
func (a *AccessLog) Close() error {
|
||||
return a.logger.Close()
|
||||
}
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
// Package accesslog 提供访问日志功能的测试。
|
||||
//
|
||||
// 该文件测试访问日志模块的各项功能,包括:
|
||||
// - 访问日志中间件创建
|
||||
// - 请求处理和响应记录
|
||||
// - 请求持续时间记录
|
||||
// - 日志格式化
|
||||
//
|
||||
// 作者:xfy
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
|
||||
@ -56,6 +56,13 @@ type CompressionMiddleware struct {
|
||||
}
|
||||
|
||||
// New 创建压缩中间件。
|
||||
//
|
||||
// 参数:
|
||||
// - cfg: 压缩配置,包含算法类型、压缩级别、最小压缩大小等
|
||||
//
|
||||
// 返回值:
|
||||
// - *CompressionMiddleware: 压缩中间件实例
|
||||
// - error: 配置无效时返回错误
|
||||
func New(cfg *config.CompressionConfig) (*CompressionMiddleware, error) {
|
||||
if cfg == nil {
|
||||
cfg = &config.CompressionConfig{
|
||||
@ -139,6 +146,12 @@ func (m *CompressionMiddleware) Name() string {
|
||||
}
|
||||
|
||||
// Process 应用压缩中间件。
|
||||
//
|
||||
// 参数:
|
||||
// - next: 下一个请求处理器
|
||||
//
|
||||
// 返回值:
|
||||
// - fasthttp.RequestHandler: 包装后的请求处理器
|
||||
func (m *CompressionMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
// 检查客户端是否支持压缩
|
||||
@ -203,6 +216,12 @@ func (m *CompressionMiddleware) Process(next fasthttp.RequestHandler) fasthttp.R
|
||||
}
|
||||
|
||||
// isCompressible 检查 MIME 类型是否可压缩。
|
||||
//
|
||||
// 参数:
|
||||
// - contentType: 内容类型(MIME 类型)
|
||||
//
|
||||
// 返回值:
|
||||
// - bool: 是否可压缩
|
||||
func (m *CompressionMiddleware) isCompressible(contentType string) bool {
|
||||
// 移除 charset 等参数
|
||||
ct := contentType
|
||||
@ -227,6 +246,12 @@ func (m *CompressionMiddleware) isCompressible(contentType string) bool {
|
||||
}
|
||||
|
||||
// compressGzip 使用 gzip 压缩数据。
|
||||
//
|
||||
// 参数:
|
||||
// - data: 待压缩的原始数据
|
||||
//
|
||||
// 返回值:
|
||||
// - []byte: 压缩后的数据
|
||||
func (m *CompressionMiddleware) compressGzip(data []byte) []byte {
|
||||
w := m.gzipPool.Get().(*gzip.Writer)
|
||||
defer m.gzipPool.Put(w)
|
||||
@ -240,6 +265,12 @@ func (m *CompressionMiddleware) compressGzip(data []byte) []byte {
|
||||
}
|
||||
|
||||
// compressBrotli 使用 brotli 压缩数据。
|
||||
//
|
||||
// 参数:
|
||||
// - data: 待压缩的原始数据
|
||||
//
|
||||
// 返回值:
|
||||
// - []byte: 压缩后的数据
|
||||
func (m *CompressionMiddleware) compressBrotli(data []byte) []byte {
|
||||
w := m.brotliPool.Get().(*brotli.Writer)
|
||||
defer m.brotliPool.Put(w)
|
||||
@ -253,16 +284,25 @@ func (m *CompressionMiddleware) compressBrotli(data []byte) []byte {
|
||||
}
|
||||
|
||||
// Types 返回可压缩的 MIME 类型列表。
|
||||
//
|
||||
// 返回值:
|
||||
// - []string: 可压缩的 MIME 类型列表
|
||||
func (m *CompressionMiddleware) Types() []string {
|
||||
return m.types
|
||||
}
|
||||
|
||||
// Level 返回压缩级别。
|
||||
//
|
||||
// 返回值:
|
||||
// - int: 压缩级别(1-9)
|
||||
func (m *CompressionMiddleware) Level() int {
|
||||
return m.level
|
||||
}
|
||||
|
||||
// MinSize 返回最小压缩大小。
|
||||
//
|
||||
// 返回值:
|
||||
// - int: 最小压缩大小(字节)
|
||||
func (m *CompressionMiddleware) MinSize() int {
|
||||
return m.minSize
|
||||
}
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
// Package compression 提供压缩功能的测试。
|
||||
//
|
||||
// 该文件测试压缩中间件模块的各项功能,包括:
|
||||
// - 压缩中间件创建
|
||||
// - gzip 和 brotli 压缩
|
||||
// - 可压缩类型检查
|
||||
// - 压缩级别配置
|
||||
// - 响应处理
|
||||
//
|
||||
// 作者:xfy
|
||||
package compression
|
||||
|
||||
import (
|
||||
|
||||
@ -1,4 +1,14 @@
|
||||
// Package compression 提供 gzip_static 预压缩文件测试。
|
||||
// Package compression 提供 gzip_static 预压缩文件功能的测试。
|
||||
//
|
||||
// 该文件测试 gzip_static 模块的各项功能,包括:
|
||||
// - Brotli 和 Gzip 文件优先级
|
||||
// - Gzip 回退机制
|
||||
// - Accept-Encoding 头解析
|
||||
// - 扩展名检查
|
||||
// - 路径遍历防护
|
||||
// - Vary 头设置
|
||||
//
|
||||
// 作者:xfy
|
||||
package compression
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
// Package middleware 提供中间件链功能的测试。
|
||||
//
|
||||
// 该文件测试中间件链模块的各项功能,包括:
|
||||
// - 中间件链创建
|
||||
// - 空链处理
|
||||
// - 单中间件包装
|
||||
// - 多中间件执行顺序
|
||||
// - 中间件修改响应
|
||||
//
|
||||
// 作者:xfy
|
||||
package middleware
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
// Package rewrite 提供 URL 重写功能的测试。
|
||||
//
|
||||
// 该文件测试 URL 重写模块的各项功能,包括:
|
||||
// - 重写规则解析
|
||||
// - 正则表达式匹配
|
||||
// - 重定向和重写
|
||||
// - 规则链执行
|
||||
// - ReDoS 防护
|
||||
//
|
||||
// 作者:xfy
|
||||
package rewrite
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
// Package security 提供访问控制功能的测试。
|
||||
//
|
||||
// 该文件测试访问控制模块的各项功能,包括:
|
||||
// - 访问控制器创建
|
||||
// - IP 地址检查
|
||||
// - 允许列表和拒绝列表
|
||||
// - CIDR 解析
|
||||
// - 列表更新和统计
|
||||
//
|
||||
// 作者:xfy
|
||||
package security
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
// Package security 提供基本认证功能的测试。
|
||||
//
|
||||
// 该文件测试基本认证模块的各项功能,包括:
|
||||
// - 基本认证创建和配置
|
||||
// - 用户认证验证
|
||||
// - 密码哈希(bcrypt/argon2id)
|
||||
// - 用户添加和删除
|
||||
// - 凭据提取
|
||||
//
|
||||
// 作者:xfy
|
||||
package security
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
// Package security 提供安全头部功能的测试。
|
||||
//
|
||||
// 该文件测试安全头部模块的各项功能,包括:
|
||||
// - 安全头部中间件创建
|
||||
// - 各种安全头部设置(CSP、HSTS、X-Frame-Options等)
|
||||
// - 配置更新
|
||||
// - 默认和严格配置
|
||||
// - HSTS 值格式化
|
||||
//
|
||||
// 作者:xfy
|
||||
package security
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,14 @@
|
||||
// Package security 提供速率限制功能的测试。
|
||||
//
|
||||
// 该文件测试速率限制模块的各项功能,包括:
|
||||
// - 速率限制器创建
|
||||
// - 令牌桶算法
|
||||
// - 令牌补充机制
|
||||
// - 计数器重置
|
||||
// - 连接数限制
|
||||
// - 统计信息获取
|
||||
//
|
||||
// 作者:xfy
|
||||
package security
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,14 @@
|
||||
// Package security 提供滑动窗口速率限制功能的测试。
|
||||
//
|
||||
// 该文件测试滑动窗口限流模块的各项功能,包括:
|
||||
// - 滑动窗口限流器创建
|
||||
// - 精确和近似模式
|
||||
// - 请求允许和拒绝
|
||||
// - 计数器重置
|
||||
// - 过期清理
|
||||
// - 统计信息获取
|
||||
//
|
||||
// 作者:xfy
|
||||
package security
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
// Package netutil 提供网络工具功能的测试。
|
||||
//
|
||||
// 该文件测试网络工具模块的 IP 相关功能,包括:
|
||||
// - 客户端 IP 提取
|
||||
// - X-Forwarded-For 头解析
|
||||
// - X-Real-IP 头解析
|
||||
// - 远程地址解析
|
||||
//
|
||||
// 作者:xfy
|
||||
package netutil
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
// Package netutil 提供 URL 工具功能的测试。
|
||||
//
|
||||
// 该文件测试网络工具模块的 URL 相关功能,包括:
|
||||
// - 目标 URL 解析
|
||||
// - 默认端口添加
|
||||
// - TLS 检测
|
||||
// - 主机提取
|
||||
//
|
||||
// 作者:xfy
|
||||
package netutil
|
||||
|
||||
import "testing"
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
// Package proxy provides reverse proxy functionality for the Lolly HTTP server.
|
||||
// Package proxy 提供反向代理功能,支持 HTTP、WebSocket 和流式代理。
|
||||
//
|
||||
// 此文件实现了针对后端目标的健康检查功能,支持
|
||||
// 主动健康检查(定期 HTTP 探测)和被动健康检查
|
||||
// (基于观察到的失败标记目标为不健康)。
|
||||
//
|
||||
// 主要功能:
|
||||
// - 定期向后端发送健康检查请求
|
||||
// - 根据响应状态更新目标健康状态
|
||||
// - 支持被动健康检查(基于请求失败)
|
||||
//
|
||||
// 作者:xfy
|
||||
//
|
||||
//go:generate go test -v ./...
|
||||
package proxy
|
||||
|
||||
@ -27,7 +34,7 @@ import (
|
||||
// 返回 2xx 状态码的目标被标记为健康;
|
||||
// 超时、连接失败或非 2xx 响应将其标记为不健康。
|
||||
//
|
||||
// Example usage:
|
||||
// 使用示例:
|
||||
//
|
||||
// targets := []*loadbalance.Target{
|
||||
// {URL: "http://backend1:8080"},
|
||||
@ -46,13 +53,26 @@ import (
|
||||
// checker.Start()
|
||||
// defer checker.Stop()
|
||||
type HealthChecker struct {
|
||||
targets []*loadbalance.Target
|
||||
// targets 需要检查的后端目标列表
|
||||
targets []*loadbalance.Target
|
||||
|
||||
// interval 健康检查间隔时间
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
path string
|
||||
stopCh chan struct{}
|
||||
running atomic.Bool
|
||||
client *fasthttp.Client
|
||||
|
||||
// timeout 单次检查超时时间
|
||||
timeout time.Duration
|
||||
|
||||
// path 健康检查请求路径
|
||||
path string
|
||||
|
||||
// stopCh 停止信号通道
|
||||
stopCh chan struct{}
|
||||
|
||||
// running 运行状态标志
|
||||
running atomic.Bool
|
||||
|
||||
// client HTTP 客户端,用于发送健康检查请求
|
||||
client *fasthttp.Client
|
||||
}
|
||||
|
||||
// NewHealthChecker 使用指定的目标和配置创建一个新的 HealthChecker。
|
||||
|
||||
@ -1,4 +1,18 @@
|
||||
// Package proxy 提供健康检查的测试。
|
||||
// Package proxy 提供健康检查功能的测试。
|
||||
//
|
||||
// 该文件测试健康检查模块的各项功能,包括:
|
||||
// - 健康检查器创建
|
||||
// - 默认值应用
|
||||
// - 自定义配置
|
||||
// - 负值配置处理
|
||||
// - 零值配置处理
|
||||
// - 启动和停止控制
|
||||
// - 目标健康检查
|
||||
// - 超时处理
|
||||
// - 连接失败处理
|
||||
// - 标记不健康
|
||||
//
|
||||
// 作者:xfy
|
||||
package proxy
|
||||
|
||||
import (
|
||||
|
||||
@ -47,15 +47,33 @@ import (
|
||||
)
|
||||
|
||||
// Proxy 表示反向代理实例,负责将 HTTP 请求转发到后端目标。
|
||||
//
|
||||
// 它为每个后端目标管理连接池,并提供负载均衡功能。
|
||||
//
|
||||
// 注意事项:
|
||||
// - 所有公开方法均为并发安全
|
||||
// - 使用前需确保 targets 中至少有一个健康目标
|
||||
type Proxy struct {
|
||||
targets []*loadbalance.Target
|
||||
clients map[string]*fasthttp.HostClient // key: target URL
|
||||
balancer loadbalance.Balancer
|
||||
config *config.ProxyConfig
|
||||
cache *cache.ProxyCache // 代理缓存(可选)
|
||||
healthChecker *HealthChecker // 健康检查器(用于被动检查)
|
||||
mu sync.RWMutex
|
||||
// targets 后端目标列表,用于负载均衡选择
|
||||
targets []*loadbalance.Target
|
||||
|
||||
// clients 每个目标对应的 HostClient 连接池映射(key: target URL)
|
||||
clients map[string]*fasthttp.HostClient
|
||||
|
||||
// balancer 负载均衡器实例
|
||||
balancer loadbalance.Balancer
|
||||
|
||||
// config 代理配置,包含超时、请求头等设置
|
||||
config *config.ProxyConfig
|
||||
|
||||
// cache 代理缓存实例,用于缓存响应(可选)
|
||||
cache *cache.ProxyCache
|
||||
|
||||
// healthChecker 健康检查器,用于被动健康检查
|
||||
healthChecker *HealthChecker
|
||||
|
||||
// mu 保护并发访问的读写锁
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewProxy 使用给定的配置和后台目标创建一个新的反向代理实例。
|
||||
@ -171,7 +189,7 @@ func createHostClient(targetURL string, timeout config.ProxyTimeout, transportCf
|
||||
MaxIdleConnDuration: maxIdleConnDuration,
|
||||
MaxConns: maxConns,
|
||||
MaxConnWaitTimeout: timeout.Connect,
|
||||
RetryIf: nil, // Disable automatic retries
|
||||
RetryIf: nil, // 禁用自动重试
|
||||
DisablePathNormalizing: false,
|
||||
SecureErrorLogMessage: false,
|
||||
}
|
||||
|
||||
@ -1,6 +1,21 @@
|
||||
// Package proxy provides reverse proxy functionality for the Lolly HTTP server.
|
||||
// Package proxy 提供反向代理功能的测试。
|
||||
//
|
||||
//go:generate go test -v ./...
|
||||
// 该文件测试代理模块的各项功能,包括:
|
||||
// - 代理创建和配置
|
||||
// - 目标选择
|
||||
// - 请求转发
|
||||
// - 请求头修改
|
||||
// - 响应头修改
|
||||
// - 客户端 IP 提取
|
||||
// - 目标更新
|
||||
// - WebSocket 请求检测
|
||||
// - 负载均衡器创建
|
||||
// - HostClient 创建
|
||||
// - 健康检查器设置
|
||||
// - 代理缓存功能
|
||||
// - 被动健康检查
|
||||
//
|
||||
// 作者:xfy
|
||||
package proxy
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,17 @@
|
||||
// Package proxy 提供 WebSocket 代理功能的测试。
|
||||
//
|
||||
// 该文件测试 WebSocket 代理模块的各项功能,包括:
|
||||
// - 桥接器创建
|
||||
// - 桥接器关闭
|
||||
// - 空连接处理
|
||||
// - 连接关闭错误检测
|
||||
// - 目标地址拨号
|
||||
// - URL 解析
|
||||
// - 数据复制
|
||||
// - 双向数据转发
|
||||
// - 超时错误处理
|
||||
//
|
||||
// 作者:xfy
|
||||
package proxy
|
||||
|
||||
import (
|
||||
|
||||
@ -206,7 +206,7 @@ func (p *GoroutinePool) startWorker() {
|
||||
task(nil)
|
||||
|
||||
case <-idleTimer.C:
|
||||
// 穴闲超时,退出 worker(保持最小数量)
|
||||
// 空闲超时,退出 worker(保持最小数量)
|
||||
atomic.AddInt32(&p.idleWorkers, -1)
|
||||
if atomic.LoadInt32(&p.workers) > p.minWorkers {
|
||||
return
|
||||
@ -243,12 +243,23 @@ func (p *GoroutinePool) Stats() PoolStats {
|
||||
//
|
||||
// 用于监控池的运行状态,包括 worker 数量和队列状态。
|
||||
type PoolStats struct {
|
||||
Workers int32 // 当前活跃 worker 数量
|
||||
IdleWorkers int32 // 当前空闲 worker 数量
|
||||
MaxWorkers int32 // 最大 worker 数量上限
|
||||
MinWorkers int32 // 最小 worker 数量下限
|
||||
QueueLen int32 // 当前队列中的任务数
|
||||
QueueCap int32 // 队列容量上限
|
||||
// Workers 当前活跃 worker 数量
|
||||
Workers int32
|
||||
|
||||
// IdleWorkers 当前空闲 worker 数量
|
||||
IdleWorkers int32
|
||||
|
||||
// MaxWorkers 最大 worker 数量上限
|
||||
MaxWorkers int32
|
||||
|
||||
// MinWorkers 最小 worker 数量下限
|
||||
MinWorkers int32
|
||||
|
||||
// QueueLen 当前队列中的任务数
|
||||
QueueLen int32
|
||||
|
||||
// QueueCap 队列容量上限
|
||||
QueueCap int32
|
||||
}
|
||||
|
||||
// WrapHandler 使用 Goroutine 池包装 fasthttp 处理器。
|
||||
|
||||
@ -1,3 +1,14 @@
|
||||
// Package server 提供 Goroutine 池功能的测试。
|
||||
//
|
||||
// 该文件测试 Goroutine 池的各项功能,包括:
|
||||
// - 池的创建和配置
|
||||
// - 启动和停止控制
|
||||
// - 任务提交和执行
|
||||
// - 并发提交处理
|
||||
// - Handler 包装功能
|
||||
// - 统计信息收集
|
||||
//
|
||||
// 作者:xfy
|
||||
package server
|
||||
|
||||
import (
|
||||
|
||||
@ -678,7 +678,13 @@ func (s *Server) getProxyCacheStats() ProxyCacheStats {
|
||||
return total
|
||||
}
|
||||
|
||||
// registerStaticHandler registers static file handler.
|
||||
// registerStaticHandler 注册静态文件处理器。
|
||||
//
|
||||
// 为路由器注册静态文件服务,支持文件缓存和预压缩文件。
|
||||
//
|
||||
// 参数:
|
||||
// - router: 路由器实例,用于注册路由规则
|
||||
// - cfg: 服务器配置,包含静态文件和压缩设置
|
||||
func (s *Server) registerStaticHandler(router *handler.Router, cfg *config.ServerConfig) {
|
||||
staticHandler := handler.NewStaticHandler(
|
||||
cfg.Static.Root,
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
// Package server 提供 HTTP 服务器功能的测试。
|
||||
//
|
||||
// 该文件测试服务器模块的各项功能,包括:
|
||||
// - 服务器创建和初始化
|
||||
// - 启动和停止控制
|
||||
// - 优雅关闭
|
||||
// - 中间件链构建
|
||||
// - 请求统计追踪
|
||||
// - 监听器管理
|
||||
// - TLS 配置
|
||||
// - 代理缓存统计
|
||||
//
|
||||
// 作者:xfy
|
||||
package server
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
// Package server 提供状态处理器功能的测试。
|
||||
//
|
||||
// 该文件测试状态处理器模块的各项功能,包括:
|
||||
// - 状态处理器创建
|
||||
// - CIDR 和单 IP 白名单配置
|
||||
// - 无效 IP 格式处理
|
||||
// - 访问控制逻辑
|
||||
// - 客户端 IP 提取
|
||||
// - 状态信息收集
|
||||
// - Goroutine 池统计
|
||||
// - 文件缓存统计
|
||||
//
|
||||
// 作者:xfy
|
||||
package server
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
// Package server 提供优雅升级功能的测试。
|
||||
//
|
||||
// 该文件测试优雅升级模块的各项功能,包括:
|
||||
// - UpgradeManager 的创建和配置
|
||||
// - 子进程检测
|
||||
// - PID 文件的读写
|
||||
// - 监听器继承
|
||||
// - 进程通知机制
|
||||
// - 平滑关闭等待
|
||||
// - 错误处理场景
|
||||
//
|
||||
// 作者:xfy
|
||||
package server
|
||||
|
||||
import (
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
// Package ssl 提供 OCSP(在线证书状态协议)功能的测试。
|
||||
//
|
||||
// 该文件测试 OCSP 模块的各项功能,包括:
|
||||
// - OCSP 管理器创建和配置
|
||||
// - OCSP 响应获取
|
||||
// - 证书状态检查
|
||||
// - 过期响应处理
|
||||
// - OCSP 配置默认值
|
||||
//
|
||||
// 作者:xfy
|
||||
package ssl
|
||||
|
||||
import (
|
||||
@ -380,7 +390,7 @@ func TestOCSPManagerRegisterCertificate(t *testing.T) {
|
||||
// If it fails, that's also OK - graceful degradation
|
||||
}
|
||||
|
||||
// generateTestCertWithOCSP generates a self-signed certificate for testing.
|
||||
// generateTestCertWithOCSP 生成用于测试的自签名证书。
|
||||
// If ocspServer is provided, it will be included in the certificate.
|
||||
func generateTestCertWithOCSP(t *testing.T, ocspServer []string) ([]byte, []byte) {
|
||||
t.Helper()
|
||||
|
||||
@ -190,7 +190,7 @@ func NewMultiTLSManager(configs map[string]*config.SSLConfig, defaultCfg *config
|
||||
configs: make(map[string]*tls.Config),
|
||||
}
|
||||
|
||||
// Load each certificate
|
||||
// 加载每个证书
|
||||
for name, cfg := range configs {
|
||||
tlsCfg, err := createTLSConfig(cfg)
|
||||
if err != nil {
|
||||
@ -199,7 +199,7 @@ func NewMultiTLSManager(configs map[string]*config.SSLConfig, defaultCfg *config
|
||||
manager.configs[name] = tlsCfg
|
||||
}
|
||||
|
||||
// Load default config if provided
|
||||
// 如果提供了默认配置,则加载
|
||||
if defaultCfg != nil {
|
||||
tlsCfg, err := createTLSConfig(defaultCfg)
|
||||
if err != nil {
|
||||
@ -236,7 +236,7 @@ func (m *TLSManager) GetTLSConfigForHost(host string) *tls.Config {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
// Remove port from host if present
|
||||
// 从主机名中移除端口(如果存在)
|
||||
for i := 0; i < len(host); i++ {
|
||||
if host[i] == ':' {
|
||||
host = host[:i]
|
||||
@ -261,14 +261,14 @@ func (m *TLSManager) GetCertificate() func(*tls.ClientHelloInfo) (*tls.Certifica
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
// Look for matching server name
|
||||
// 查找匹配的服务器名称
|
||||
if cfg, ok := m.configs[hello.ServerName]; ok {
|
||||
if len(cfg.Certificates) > 0 {
|
||||
return &cfg.Certificates[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to default
|
||||
// 回退到默认配置
|
||||
if m.defaultCfg != nil && len(m.defaultCfg.Certificates) > 0 {
|
||||
return &m.defaultCfg.Certificates[0], nil
|
||||
}
|
||||
@ -331,7 +331,7 @@ func (m *TLSManager) getConfigForClientWithOCSP(hello *tls.ClientHelloInfo) (*tl
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
// Get the base config
|
||||
// 获取基础配置
|
||||
var baseCfg *tls.Config
|
||||
if hello.ServerName != "" {
|
||||
if cfg, ok := m.configs[hello.ServerName]; ok {
|
||||
@ -347,13 +347,13 @@ func (m *TLSManager) getConfigForClientWithOCSP(hello *tls.ClientHelloInfo) (*tl
|
||||
return baseCfg, nil
|
||||
}
|
||||
|
||||
// Create a copy of the config with OCSP response attached
|
||||
// 创建配置副本并附加 OCSP 响应
|
||||
cfgCopy := baseCfg.Clone()
|
||||
|
||||
// Attach OCSP response to the certificate
|
||||
cert := &cfgCopy.Certificates[0]
|
||||
if len(cert.Certificate) > 0 {
|
||||
// Parse the leaf certificate to get serial number
|
||||
// 解析叶子证书以获取序列号
|
||||
leafCert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err == nil {
|
||||
serial := leafCert.SerialNumber.String()
|
||||
@ -487,7 +487,7 @@ func extractPEMBlock(data []byte) ([]byte, []byte) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Extract and decode the PEM block
|
||||
// 提取并解码 PEM 块
|
||||
blockData := data[start : start+end+len(endMarker)]
|
||||
rest := data[start+end+len(endMarker):]
|
||||
|
||||
@ -629,7 +629,7 @@ func parseCipherSuites(ciphers []string) ([]uint16, error) {
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown cipher suite: %s", c)
|
||||
}
|
||||
// Check for insecure cipher suites
|
||||
// 检查不安全的加密套件
|
||||
if isInsecureCipher(id) {
|
||||
return nil, fmt.Errorf("insecure cipher suite %s is not allowed", c)
|
||||
}
|
||||
@ -735,7 +735,7 @@ func ValidateKey(keyPath string) error {
|
||||
return fmt.Errorf("failed to read key: %w", err)
|
||||
}
|
||||
|
||||
// Key validation happens during tls.LoadX509KeyPair
|
||||
// 密钥验证在 tls.LoadX509KeyPair 期间进行
|
||||
// This is a preliminary check that the file exists and is readable
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
// Package ssl 提供 SSL/TLS 功能的测试。
|
||||
//
|
||||
// 该文件测试 SSL 模块的各项功能,包括:
|
||||
// - TLS 管理器创建和配置
|
||||
// - TLS 版本和加密套件解析
|
||||
// - 证书验证
|
||||
// - 多证书管理
|
||||
// - TLS 配置获取
|
||||
//
|
||||
// 作者:xfy
|
||||
package ssl
|
||||
|
||||
import (
|
||||
|
||||
@ -42,17 +42,34 @@ type Balancer interface {
|
||||
Select(targets []*Target) *Target
|
||||
}
|
||||
|
||||
// roundRobin 简单轮询。
|
||||
// roundRobin 简单轮询负载均衡器。
|
||||
//
|
||||
// 使用原子计数器实现线程安全的轮询选择,每次选择后计数器递增,
|
||||
// 确保请求均匀分布到所有健康目标。
|
||||
type roundRobin struct {
|
||||
counter uint64
|
||||
}
|
||||
|
||||
// newRoundRobin 创建轮询均衡器。
|
||||
//
|
||||
// 返回实现了简单轮询算法的 Balancer 接口实例。
|
||||
//
|
||||
// 返回值:
|
||||
// - Balancer: 轮询负载均衡器实例
|
||||
func newRoundRobin() Balancer {
|
||||
return &roundRobin{}
|
||||
}
|
||||
|
||||
// Select 选择下一个目标。
|
||||
//
|
||||
// 从健康目标列表中按轮询顺序选择一个目标。
|
||||
// 只选择标记为健康的目标,如果无健康目标则返回 nil。
|
||||
//
|
||||
// 参数:
|
||||
// - targets: 目标服务器列表
|
||||
//
|
||||
// 返回值:
|
||||
// - *Target: 选中的目标服务器,无可用目标时返回 nil
|
||||
func (r *roundRobin) Select(targets []*Target) *Target {
|
||||
// 过滤健康目标
|
||||
healthy := make([]*Target, 0)
|
||||
@ -68,15 +85,32 @@ func (r *roundRobin) Select(targets []*Target) *Target {
|
||||
return healthy[idx%uint64(len(healthy))]
|
||||
}
|
||||
|
||||
// leastConn 最少连接。
|
||||
// leastConn 最少连接负载均衡器。
|
||||
//
|
||||
// 跟踪每个目标服务器的活跃连接数,总是选择连接数最少的目标。
|
||||
// 适合连接持续时间差异较大的场景。
|
||||
type leastConn struct{}
|
||||
|
||||
// newLeastConn 创建最少连接均衡器。
|
||||
//
|
||||
// 返回实现了最少连接算法的 Balancer 接口实例。
|
||||
//
|
||||
// 返回值:
|
||||
// - Balancer: 最少连接负载均衡器实例
|
||||
func newLeastConn() Balancer {
|
||||
return &leastConn{}
|
||||
}
|
||||
|
||||
// Select 选择连接最少的目标。
|
||||
//
|
||||
// 遍历所有健康目标,选择当前连接数最少的目标服务器。
|
||||
// 只选择标记为健康的目标,如果无健康目标则返回 nil。
|
||||
//
|
||||
// 参数:
|
||||
// - targets: 目标服务器列表
|
||||
//
|
||||
// 返回值:
|
||||
// - *Target: 选中的目标服务器,无可用目标时返回 nil
|
||||
func (l *leastConn) Select(targets []*Target) *Target {
|
||||
var selected *Target
|
||||
var minConns int64 = -1
|
||||
@ -93,17 +127,35 @@ func (l *leastConn) Select(targets []*Target) *Target {
|
||||
return selected
|
||||
}
|
||||
|
||||
// weightedRoundRobin 加权轮询。
|
||||
// weightedRoundRobin 加权轮询负载均衡器。
|
||||
//
|
||||
// 根据目标服务器的权重分配请求,权重高的目标获得更多请求。
|
||||
// 使用原子计数器确保线程安全,支持不同权重的目标混合使用。
|
||||
type weightedRoundRobin struct {
|
||||
counter uint64
|
||||
}
|
||||
|
||||
// newWeightedRoundRobin 创建加权轮询均衡器。
|
||||
//
|
||||
// 返回实现了加权轮询算法的 Balancer 接口实例。
|
||||
//
|
||||
// 返回值:
|
||||
// - Balancer: 加权轮询负载均衡器实例
|
||||
func newWeightedRoundRobin() Balancer {
|
||||
return &weightedRoundRobin{}
|
||||
}
|
||||
|
||||
// Select 基于权重分布选择目标。
|
||||
//
|
||||
// 根据目标服务器的权重进行轮询选择,权重决定被选中的概率。
|
||||
// 权重为 0 或负数的目标按权重 1 处理。
|
||||
// 只选择标记为健康的目标,如果无健康目标则返回 nil。
|
||||
//
|
||||
// 参数:
|
||||
// - targets: 目标服务器列表
|
||||
//
|
||||
// 返回值:
|
||||
// - *Target: 选中的目标服务器,无可用目标时返回 nil
|
||||
func (w *weightedRoundRobin) Select(targets []*Target) *Target {
|
||||
healthy := make([]*Target, 0)
|
||||
for _, t := range targets {
|
||||
@ -145,20 +197,48 @@ func (w *weightedRoundRobin) Select(targets []*Target) *Target {
|
||||
return healthy[len(healthy)-1]
|
||||
}
|
||||
|
||||
// ipHash IP 哈希。
|
||||
// ipHash IP 哈希负载均衡器。
|
||||
//
|
||||
// 基于客户端 IP 地址计算哈希值,将同一 IP 的请求分配到固定目标。
|
||||
// 适合需要会话保持的场景,确保相同客户端总是被路由到同一服务器。
|
||||
type ipHash struct{}
|
||||
|
||||
// newIPHash 创建 IP 哈希均衡器。
|
||||
//
|
||||
// 返回实现了 IP 哈希算法的 Balancer 接口实例。
|
||||
//
|
||||
// 返回值:
|
||||
// - Balancer: IP 哈希负载均衡器实例
|
||||
func newIPHash() Balancer {
|
||||
return &ipHash{}
|
||||
}
|
||||
|
||||
// Select 默认选择(IP Hash 需要具体 IP)。
|
||||
//
|
||||
// 当无客户端 IP 时,使用空字符串进行哈希计算。
|
||||
// 通常应使用 SelectByIP 方法指定客户端 IP。
|
||||
//
|
||||
// 参数:
|
||||
// - targets: 目标服务器列表
|
||||
//
|
||||
// 返回值:
|
||||
// - *Target: 选中的目标服务器,无可用目标时返回 nil
|
||||
func (i *ipHash) Select(targets []*Target) *Target {
|
||||
return i.SelectByIP(targets, "")
|
||||
}
|
||||
|
||||
// SelectByIP 基于客户端 IP 哈希选择目标。
|
||||
//
|
||||
// 使用 FNV-64a 算法计算客户端 IP 的哈希值,对目标数取模选择目标。
|
||||
// 同一 IP 总是映射到同一目标(除非目标列表变化)。
|
||||
// 只选择标记为健康的目标,如果无健康目标则返回 nil。
|
||||
//
|
||||
// 参数:
|
||||
// - targets: 目标服务器列表
|
||||
// - clientIP: 客户端 IP 地址字符串
|
||||
//
|
||||
// 返回值:
|
||||
// - *Target: 选中的目标服务器,无可用目标时返回 nil
|
||||
func (i *ipHash) SelectByIP(targets []*Target, clientIP string) *Target {
|
||||
healthy := make([]*Target, 0)
|
||||
for _, t := range targets {
|
||||
@ -397,6 +477,13 @@ func (s *Server) Start() error {
|
||||
}
|
||||
|
||||
// acceptLoop 接受连接循环。
|
||||
//
|
||||
// 在单独的 goroutine 中运行,持续接受 TCP 连接。
|
||||
// 当服务器停止时,该函数返回。
|
||||
//
|
||||
// 参数:
|
||||
// - addr: 监听地址
|
||||
// - listener: TCP 监听器实例
|
||||
func (s *Server) acceptLoop(addr string, listener net.Listener) {
|
||||
for s.running.Load() {
|
||||
conn, err := listener.Accept()
|
||||
@ -413,6 +500,16 @@ func (s *Server) acceptLoop(addr string, listener net.Listener) {
|
||||
}
|
||||
|
||||
// handleConnection 处理单个连接。
|
||||
//
|
||||
// 处理客户端连接的完整生命周期:
|
||||
// 1. 选择上游目标
|
||||
// 2. 建立到目标服务器的连接
|
||||
// 3. 在客户端和目标之间双向转发数据
|
||||
// 4. 处理连接关闭和错误
|
||||
//
|
||||
// 参数:
|
||||
// - clientConn: 客户端连接
|
||||
// - addr: 监听地址
|
||||
func (s *Server) handleConnection(clientConn net.Conn, addr string) {
|
||||
defer func() {
|
||||
_ = clientConn.Close()
|
||||
@ -491,6 +588,9 @@ func (h *HealthChecker) Start() {
|
||||
}
|
||||
|
||||
// check 执行健康检查。
|
||||
//
|
||||
// 尝试 TCP 连接到所有目标,根据连接结果更新健康状态。
|
||||
// 连接成功标记为健康,连接失败标记为不健康。
|
||||
func (h *HealthChecker) check() {
|
||||
for _, target := range h.upstream.targets {
|
||||
conn, err := net.DialTimeout("tcp", target.addr, h.timeout)
|
||||
@ -549,34 +649,71 @@ func (s *Server) Stats() Stats {
|
||||
|
||||
// Stats Stream 服务器统计。
|
||||
type Stats struct {
|
||||
// Connections 当前活跃连接数量
|
||||
Connections int64
|
||||
Listeners int
|
||||
Upstreams int
|
||||
|
||||
// Listeners 当前监听器数量(TCP + UDP)
|
||||
Listeners int
|
||||
|
||||
// Upstreams 上游配置数量
|
||||
Upstreams int
|
||||
}
|
||||
|
||||
// udpSession UDP 会话,管理客户端到后端的映射
|
||||
// udpSession UDP 会话,管理客户端到后端的映射。
|
||||
//
|
||||
// 每个 UDP 会话代表一个客户端与一个后端目标的映射关系。
|
||||
// 会话包含客户端地址、后端连接、最后活跃时间等信息。
|
||||
// 会话在空闲超时后自动清理。
|
||||
type udpSession struct {
|
||||
// clientAddr 客户端 UDP 地址
|
||||
clientAddr *net.UDPAddr
|
||||
// targetConn 到后端目标的连接
|
||||
targetConn net.Conn
|
||||
// lastActive 最后活跃时间
|
||||
lastActive time.Time
|
||||
mu sync.RWMutex
|
||||
srv *udpServer
|
||||
closeOnce sync.Once
|
||||
// mu 保护 lastActive 的读写锁
|
||||
mu sync.RWMutex
|
||||
// srv 所属的 UDP 服务器
|
||||
srv *udpServer
|
||||
// closeOnce 确保只关闭一次
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
// udpServer UDP 服务器,管理多个客户端会话
|
||||
// udpServer UDP 服务器,管理多个客户端会话。
|
||||
//
|
||||
// 负责监听 UDP 端口,管理客户端会话的生命周期。
|
||||
// 支持会话自动过期清理和优雅停止。
|
||||
type udpServer struct {
|
||||
conn *net.UDPConn
|
||||
// conn UDP 连接
|
||||
conn *net.UDPConn
|
||||
// sessions 客户端会话映射,键为 sessionKey
|
||||
sessions map[string]*udpSession
|
||||
mu sync.RWMutex
|
||||
running atomic.Bool
|
||||
// mu 保护 sessions 的读写锁
|
||||
mu sync.RWMutex
|
||||
// running 运行状态标志
|
||||
running atomic.Bool
|
||||
// upstream 上游配置
|
||||
upstream *Upstream
|
||||
timeout time.Duration
|
||||
stopCh chan struct{}
|
||||
wg sync.WaitGroup
|
||||
// timeout 会话空闲超时时间
|
||||
timeout time.Duration
|
||||
// stopCh 停止信号通道
|
||||
stopCh chan struct{}
|
||||
// wg 等待 goroutine 结束
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// newUDPServer 创建新的 UDP 服务器
|
||||
// newUDPServer 创建新的 UDP 服务器。
|
||||
//
|
||||
// 根据 UDP 连接、上游配置和超时时间创建 UDP 服务器实例。
|
||||
// 如果超时时间小于等于 0,使用默认 60 秒。
|
||||
//
|
||||
// 参数:
|
||||
// - conn: UDP 连接
|
||||
// - upstream: 上游配置
|
||||
// - timeout: 会话空闲超时时间
|
||||
//
|
||||
// 返回值:
|
||||
// - *udpServer: 创建的 UDP 服务器实例
|
||||
func newUDPServer(conn *net.UDPConn, upstream *Upstream, timeout time.Duration) *udpServer {
|
||||
if timeout <= 0 {
|
||||
timeout = 60 * time.Second // 默认 60 秒超时
|
||||
@ -590,12 +727,29 @@ func newUDPServer(conn *net.UDPConn, upstream *Upstream, timeout time.Duration)
|
||||
}
|
||||
}
|
||||
|
||||
// sessionKey 从 UDP 地址生成会话键
|
||||
// sessionKey 从 UDP 地址生成会话键。
|
||||
//
|
||||
// 使用 UDP 地址的字符串表示作为会话映射的唯一键。
|
||||
//
|
||||
// 参数:
|
||||
// - addr: UDP 地址
|
||||
//
|
||||
// 返回值:
|
||||
// - string: 会话键字符串
|
||||
func sessionKey(addr *net.UDPAddr) string {
|
||||
return addr.String()
|
||||
}
|
||||
|
||||
// getSession 获取现有会话(线程安全)
|
||||
// getSession 获取现有会话(线程安全)。
|
||||
//
|
||||
// 根据客户端地址查找现有会话,如果找到则更新最后活跃时间。
|
||||
// 如果会话不存在返回 nil。
|
||||
//
|
||||
// 参数:
|
||||
// - clientAddr: 客户端 UDP 地址
|
||||
//
|
||||
// 返回值:
|
||||
// - *udpSession: 找到的会话,不存在时返回 nil
|
||||
func (s *udpServer) getSession(clientAddr *net.UDPAddr) *udpSession {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
@ -613,7 +767,21 @@ func (s *udpServer) getSession(clientAddr *net.UDPAddr) *udpSession {
|
||||
return session
|
||||
}
|
||||
|
||||
// getOrCreateSession 获取或创建会话(线程安全)
|
||||
// getOrCreateSession 获取或创建会话(线程安全)。
|
||||
//
|
||||
// 首先尝试获取现有会话,如果不存在则创建新会话:
|
||||
// 1. 选择后端目标
|
||||
// 2. 建立到目标的 UDP 连接
|
||||
// 3. 创建会话并启动响应监听 goroutine
|
||||
//
|
||||
// 使用双重检查锁定模式避免重复创建会话。
|
||||
//
|
||||
// 参数:
|
||||
// - clientAddr: 客户端 UDP 地址
|
||||
//
|
||||
// 返回值:
|
||||
// - *udpSession: 获取或创建的会话
|
||||
// - error: 创建失败时返回错误
|
||||
func (s *udpServer) getOrCreateSession(clientAddr *net.UDPAddr) (*udpSession, error) {
|
||||
// 先尝试获取现有会话
|
||||
session := s.getSession(clientAddr)
|
||||
@ -669,7 +837,12 @@ func (s *udpServer) getOrCreateSession(clientAddr *net.UDPAddr) (*udpSession, er
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// removeSession 移除会话(线程安全)
|
||||
// removeSession 移除会话(线程安全)。
|
||||
//
|
||||
// 关闭会话并删除会话映射。
|
||||
//
|
||||
// 参数:
|
||||
// - clientAddr: 客户端 UDP 地址
|
||||
func (s *udpServer) removeSession(clientAddr *net.UDPAddr) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@ -681,7 +854,10 @@ func (s *udpServer) removeSession(clientAddr *net.UDPAddr) {
|
||||
}
|
||||
}
|
||||
|
||||
// close 关闭会话
|
||||
// close 关闭会话。
|
||||
//
|
||||
// 使用 sync.Once 确保会话只关闭一次。
|
||||
// 关闭后端目标连接。
|
||||
func (sess *udpSession) close() {
|
||||
sess.closeOnce.Do(func() {
|
||||
if sess.targetConn != nil {
|
||||
@ -690,7 +866,14 @@ func (sess *udpSession) close() {
|
||||
})
|
||||
}
|
||||
|
||||
// handleBackendResponse 处理后端响应并转发回客户端
|
||||
// handleBackendResponse 处理后端响应并转发回客户端。
|
||||
//
|
||||
// 在单独的 goroutine 中运行,持续监听后端响应:
|
||||
// 1. 读取后端数据
|
||||
// 2. 转发到客户端
|
||||
// 3. 处理超时和错误
|
||||
//
|
||||
// 当会话超时、连接错误或服务器停止时返回。
|
||||
func (sess *udpSession) handleBackendResponse() {
|
||||
defer sess.srv.wg.Done()
|
||||
|
||||
@ -734,7 +917,14 @@ func (sess *udpSession) handleBackendResponse() {
|
||||
}
|
||||
}
|
||||
|
||||
// serve 启动 UDP 服务循环
|
||||
// serve 启动 UDP 服务循环。
|
||||
//
|
||||
// 在单独的 goroutine 中运行,持续监听 UDP 数据报:
|
||||
// 1. 接收客户端数据报
|
||||
// 2. 获取或创建会话
|
||||
// 3. 转发数据到后端
|
||||
//
|
||||
// 当服务器停止时返回。
|
||||
func (s *udpServer) serve() {
|
||||
s.running.Store(true)
|
||||
|
||||
@ -772,7 +962,10 @@ func (s *udpServer) serve() {
|
||||
}
|
||||
}
|
||||
|
||||
// startCleanupTicker 启动定期清理过期会话的 ticker
|
||||
// startCleanupTicker 启动定期清理过期会话的 ticker。
|
||||
//
|
||||
// 每 10 秒执行一次清理,移除空闲超时的会话。
|
||||
// 当服务器停止时返回。
|
||||
func (s *udpServer) startCleanupTicker() {
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
@ -787,7 +980,10 @@ func (s *udpServer) startCleanupTicker() {
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupExpiredSessions 清理过期会话
|
||||
// cleanupExpiredSessions 清理过期会话。
|
||||
//
|
||||
// 遍历所有会话,移除空闲时间超过 timeout 的会话。
|
||||
// 必须在持有写锁的情况下调用。
|
||||
func (s *udpServer) cleanupExpiredSessions() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@ -805,7 +1001,9 @@ func (s *udpServer) cleanupExpiredSessions() {
|
||||
}
|
||||
}
|
||||
|
||||
// stop 停止 UDP 服务器
|
||||
// stop 停止 UDP 服务器。
|
||||
//
|
||||
// 设置停止标志,关闭所有会话,等待 goroutine 结束,关闭连接。
|
||||
func (s *udpServer) stop() {
|
||||
s.running.Store(false)
|
||||
close(s.stopCh)
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
// Package stream 提供 TCP/UDP 流代理功能的测试。
|
||||
//
|
||||
// 该文件测试流代理模块的各项功能,包括:
|
||||
// - 服务器创建和初始化
|
||||
// - 上游配置和负载均衡
|
||||
// - TCP 和 UDP 监听
|
||||
// - 健康检查
|
||||
// - 连接统计
|
||||
//
|
||||
// 作者:xfy
|
||||
package stream
|
||||
|
||||
import (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user