feat(app): 添加信号处理和完善配置结构

- 添加 SIGTERM/SIGINT/SIGQUIT 信号处理和优雅停止逻辑
- 完善安全配置:访问控制、速率限制、认证参数
- 完善性能配置:Goroutine 池、连接限制
- 改进配置生成 YAML 格式和注释说明
- 添加编译产物 lolly 到 gitignore

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-02 14:30:24 +08:00
parent 77b0eccf4c
commit f3e5aad21e
3 changed files with 162 additions and 52 deletions

3
.gitignore vendored
View File

@ -58,4 +58,5 @@ temp/
.claude/
CLAUDE.md
lolly.yaml
config.yaml
config.yaml
lolly

View File

@ -2,8 +2,12 @@
package app
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"rua.plus/lolly/internal/config"
)
@ -18,6 +22,11 @@ var (
BuildPlatform = "unknown"
)
// 应用状态。
var (
shutdownTimeout = 30 * time.Second // 优雅停止超时时间
)
// Run 应用程序入口。
func Run(cfgPath string, genConfig bool, outputPath string, showVersion bool) int {
if genConfig {
@ -73,7 +82,61 @@ func startServer(cfgPath string) int {
fmt.Printf("配置加载成功: %s\n", cfgPath)
fmt.Printf("监听地址: %s\n", cfg.Server.Listen)
// TODO: 启动服务器
fmt.Println("服务器启动中...")
// 创建取消上下文用于优雅停止Phase 2 使用)。
_, cancel := context.WithCancel(context.Background())
defer cancel()
// 启动信号监听。
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan,
syscall.SIGTERM, // 快速停止kill 或 systemd stop
syscall.SIGINT, // 快速停止Ctrl+C
syscall.SIGQUIT, // 优雅停止
)
// TODO: 启动 HTTP 服务器Phase 2 实现)
// server := server.New(cfg)
// go server.Start()
fmt.Println("服务器启动中...HTTP 服务器待 Phase 2 实现)")
// 等待信号。
sig := waitSignal(sigChan)
// 根据信号类型决定停止方式。
switch sig {
case syscall.SIGQUIT:
// 优雅停止:等待请求完成。
fmt.Printf("\n收到 SIGQUIT优雅停止等待 %v...\n", shutdownTimeout)
// TODO: server.GracefulStop(shutdownTimeout)
time.Sleep(100 * time.Millisecond) // 模拟等待Phase 2 实现真实逻辑)
case syscall.SIGTERM, syscall.SIGINT:
// 快速停止。
fmt.Printf("\n收到 %v停止服务器...\n", sigName(sig))
// TODO: server.Stop()
}
cancel()
fmt.Println("服务器已停止")
return 0
}
// waitSignal 等待信号,返回接收到的信号。
func waitSignal(ch <-chan os.Signal) syscall.Signal {
sig := <-ch
return sig.(syscall.Signal)
}
// sigName 返回信号名称(用于日志输出)。
func sigName(sig syscall.Signal) string {
switch sig {
case syscall.SIGTERM:
return "SIGTERM"
case syscall.SIGINT:
return "SIGINT"
case syscall.SIGQUIT:
return "SIGQUIT"
default:
return fmt.Sprintf("Signal(%d)", sig)
}
}

View File

@ -20,7 +20,8 @@ func DefaultConfig() *Config {
Index: []string{"index.html", "index.htm"},
},
SSL: SSLConfig{
Protocols: []string{"TLSv1.2", "TLSv1.3"},
Protocols: []string{"TLSv1.2", "TLSv1.3"},
OCSPStapling: false,
HSTS: HSTSConfig{
MaxAge: 31536000,
IncludeSubDomains: true,
@ -28,16 +29,28 @@ func DefaultConfig() *Config {
},
},
Security: SecurityConfig{
Access: AccessConfig{
Allow: []string{},
Deny: []string{},
Default: "allow",
},
RateLimit: RateLimitConfig{
RequestRate: 0,
Burst: 0,
ConnLimit: 0,
Key: "ip",
},
Auth: AuthConfig{
RequireTLS: true,
Algorithm: "bcrypt",
Realm: "Restricted Area",
MinPasswordLength: 8,
},
Headers: SecurityHeaders{
XFrameOptions: "DENY",
XContentTypeOptions: "nosniff",
ReferrerPolicy: "strict-origin-when-cross-origin",
},
Auth: AuthConfig{
RequireTLS: true,
Algorithm: "bcrypt",
Realm: "Restricted Area",
},
},
Compression: CompressionConfig{
Type: "gzip",
@ -61,6 +74,12 @@ func DefaultConfig() *Config {
},
},
Performance: PerformanceConfig{
GoroutinePool: GoroutinePoolConfig{
Enabled: false,
MaxWorkers: 1000,
MinWorkers: 10,
IdleTimeout: 60 * time.Second,
},
FileCache: FileCacheConfig{
MaxEntries: 10000,
MaxSize: 256 * 1024 * 1024, // 256MB
@ -71,6 +90,7 @@ func DefaultConfig() *Config {
MaxIdleConns: 100,
MaxIdleConnsPerHost: 32,
IdleConnTimeout: 90 * time.Second,
MaxConnsPerHost: 0, // 0 表示不限制
},
},
Monitoring: MonitoringConfig{
@ -88,7 +108,7 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) {
var buf bytes.Buffer
buf.WriteString("# Lolly 配置文件\n")
buf.WriteString("# 文档: https://github.com/xfy/lolly\n")
// buf.WriteString("# 文档: https://github.com/xfy/lolly\n")
buf.WriteString("\n")
// server 配置
@ -109,72 +129,92 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) {
buf.WriteString("\n")
// proxy 配置示例
buf.WriteString(" # 反向代理配置(示例)\n")
buf.WriteString(" # 反向代理配置\n")
buf.WriteString(" # proxy:\n")
buf.WriteString(" # - path: /api # 匹配路径前缀\n")
buf.WriteString(" # targets: # 后端目标列表\n")
buf.WriteString(" # - path: /api # 匹配路径前缀\n")
buf.WriteString(" # targets: # 后端目标列表\n")
buf.WriteString(" # - url: http://backend1:8080\n")
buf.WriteString(" # weight: 3 # 权重(加权轮询)\n")
buf.WriteString(" # weight: 3 # 权重(加权轮询时有效\n")
buf.WriteString(" # - url: http://backend2:8080\n")
buf.WriteString(" # weight: 1\n")
buf.WriteString(" # load_balance: weighted_round_robin # 负载均衡算法\n")
buf.WriteString(" # health_check: # 健康检查\n")
buf.WriteString(" # load_balance: round_robin # 负载均衡算法: round_robin, weighted_round_robin, least_conn, ip_hash\n")
buf.WriteString(" # health_check: # 健康检查\n")
buf.WriteString(" # interval: 10s\n")
buf.WriteString(" # path: /health\n")
buf.WriteString(" # timeout: 5s\n")
buf.WriteString(" # timeout: # 超时配置\n")
buf.WriteString(" # connect: 5s # 连接超时\n")
buf.WriteString(" # read: 30s # 读取超时\n")
buf.WriteString(" # write: 30s # 写入超时\n")
buf.WriteString(" # headers: # 头部修改\n")
buf.WriteString(" # set_request: {X-Custom: value}\n")
buf.WriteString(" # set_response: {X-Server: lolly}\n")
buf.WriteString(" # remove: [X-Powered-By]\n")
buf.WriteString(" # cache: # 代理缓存\n")
buf.WriteString(" # enabled: false\n")
buf.WriteString(" # max_age: 60s\n")
buf.WriteString(" # cache_lock: true # 防止缓存击穿\n")
buf.WriteString(" # stale_while_revalidate: 30s\n")
buf.WriteString("\n")
// SSL 配置
buf.WriteString(" # SSL/TLS 配置\n")
buf.WriteString(" # ssl:\n")
buf.WriteString(" # cert: /path/to/cert.pem # 证书文件\n")
buf.WriteString(" # key: /path/to/key.pem # 私钥文件\n")
buf.WriteString(" # protocols: # TLS 版本(默认安全)\n")
buf.WriteString(" # cert: /path/to/cert.pem # 证书文件\n")
buf.WriteString(" # key: /path/to/key.pem # 私钥文件\n")
buf.WriteString(" # cert_chain: /path/to/chain.pem # 证书链文件\n")
buf.WriteString(" # protocols: # TLS 版本(有效值: TLSv1.2, TLSv1.3\n")
for _, proto := range cfg.Server.SSL.Protocols {
buf.WriteString(fmt.Sprintf(" # - \"%s\"\n", proto))
}
buf.WriteString(" # hsts: # HTTP Strict Transport Security\n")
buf.WriteString(fmt.Sprintf(" # max_age: %d # 过期时间(秒)\n", cfg.Server.SSL.HSTS.MaxAge))
buf.WriteString(fmt.Sprintf(" # include_sub_domains: %v # 包含子域名\n", cfg.Server.SSL.HSTS.IncludeSubDomains))
buf.WriteString(" # ciphers: [] # 加密套件(仅 TLS 1.2 有效)\n")
buf.WriteString(fmt.Sprintf(" # ocsp_stapling: %v # OCSP Stapling\n", cfg.Server.SSL.OCSPStapling))
buf.WriteString(" # hsts: # HTTP Strict Transport Security\n")
buf.WriteString(fmt.Sprintf(" # max_age: %d # 过期时间(秒)\n", cfg.Server.SSL.HSTS.MaxAge))
buf.WriteString(fmt.Sprintf(" # include_sub_domains: %v # 包含子域名\n", cfg.Server.SSL.HSTS.IncludeSubDomains))
buf.WriteString(fmt.Sprintf(" # preload: %v # 加入 HSTS 预加载列表\n", cfg.Server.SSL.HSTS.Preload))
buf.WriteString("\n")
// security 配置
buf.WriteString(" # 安全配置\n")
buf.WriteString(" security:\n")
buf.WriteString(" # IP 访问控制\n")
buf.WriteString(" # access:\n")
buf.WriteString(" # allow: [192.168.1.0/24] # 允许的 IP/CIDR\n")
buf.WriteString(" # deny: [192.168.2.100/32] # 拒绝的 IP/CIDR\n")
buf.WriteString(" # default: deny # 默认动作\n")
buf.WriteString(" access:\n")
buf.WriteString(" allow: [] # 允许的 IP/CIDR 列表\n")
buf.WriteString(" deny: [] # 拒绝的 IP/CIDR 列表\n")
buf.WriteString(fmt.Sprintf(" default: \"%s\" # 默认动作(有效值: allow, deny\n", cfg.Server.Security.Access.Default))
buf.WriteString("\n")
buf.WriteString(" # 速率限制\n")
buf.WriteString(" # rate_limit:\n")
buf.WriteString(" # request_rate: 100 # 每秒请求数\n")
buf.WriteString(" # burst: 200 # 突发上限\n")
buf.WriteString(" # conn_limit: 1000 # 连接数限制\n")
buf.WriteString(" rate_limit:\n")
buf.WriteString(fmt.Sprintf(" request_rate: %d # 每秒请求数0 表示不限制)\n", cfg.Server.Security.RateLimit.RequestRate))
buf.WriteString(fmt.Sprintf(" burst: %d # 突发上限\n", cfg.Server.Security.RateLimit.Burst))
buf.WriteString(fmt.Sprintf(" conn_limit: %d # 连接数限制\n", cfg.Server.Security.RateLimit.ConnLimit))
buf.WriteString(fmt.Sprintf(" key: \"%s\" # 限流 key 来源(有效值: ip, header\n", cfg.Server.Security.RateLimit.Key))
buf.WriteString("\n")
buf.WriteString(" # 认证配置(启用时强制 HTTPS\n")
buf.WriteString(" # auth:\n")
buf.WriteString(" # type: basic\n")
buf.WriteString(" # users:\n")
buf.WriteString(" # - name: admin\n")
buf.WriteString(" # password: $2b$12$... # bcrypt 哈希\n")
buf.WriteString(" # require_tls: true\n")
buf.WriteString(" # 认证配置type 为空时禁用)\n")
buf.WriteString(" auth:\n")
buf.WriteString(" type: \"\" # 认证类型(有效值: basic空表示禁用\n")
buf.WriteString(fmt.Sprintf(" require_tls: %v # 启用时强制 HTTPS\n", cfg.Server.Security.Auth.RequireTLS))
buf.WriteString(fmt.Sprintf(" algorithm: \"%s\" # 密码哈希算法(有效值: bcrypt, argon2id\n", cfg.Server.Security.Auth.Algorithm))
buf.WriteString(" users: [] # 用户列表\n")
buf.WriteString(fmt.Sprintf(" realm: \"%s\" # 认证域\n", cfg.Server.Security.Auth.Realm))
buf.WriteString(fmt.Sprintf(" min_password_length: %d # 密码最小长度\n", cfg.Server.Security.Auth.MinPasswordLength))
buf.WriteString("\n")
buf.WriteString(" # 安全头部(默认值)\n")
buf.WriteString(" # 安全头部\n")
buf.WriteString(" headers:\n")
buf.WriteString(fmt.Sprintf(" x_frame_options: \"%s\" # 防止点击劫持\n", cfg.Server.Security.Headers.XFrameOptions))
buf.WriteString(fmt.Sprintf(" x_frame_options: \"%s\" # 防止点击劫持(有效值: DENY, SAMEORIGIN\n", cfg.Server.Security.Headers.XFrameOptions))
buf.WriteString(fmt.Sprintf(" x_content_type_options: \"%s\" # 防止 MIME 嗅探\n", cfg.Server.Security.Headers.XContentTypeOptions))
buf.WriteString(fmt.Sprintf(" referrer_policy: \"%s\" # 引用策略\n", cfg.Server.Security.Headers.ReferrerPolicy))
buf.WriteString(" # content_security_policy: \"default-src 'self'\" # CSP推荐配置\n")
buf.WriteString(" # permissions_policy: \"geolocation=(), microphone=()\" # 权限策略\n")
buf.WriteString("\n")
// rewrite 配置示例
buf.WriteString(" # URL 重写规则(示例)\n")
buf.WriteString(" # URL 重写规则\n")
buf.WriteString(" # rewrite:\n")
buf.WriteString(" # - pattern: \"^/old/(.*)$\"\n")
buf.WriteString(" # replacement: /new/$1\n")
buf.WriteString(" # flag: permanent # 301 重定向\n")
buf.WriteString(" # - pattern: \"^/old/(.*)$\" # 匹配模式(正则表达式)\n")
buf.WriteString(" # replacement: /new/$1 # 替换目标\n")
buf.WriteString(" # flag: last # 标志(有效值: last, redirect, permanent, break\n")
buf.WriteString("\n")
// compression 配置
@ -217,15 +257,21 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) {
// performance 配置
buf.WriteString("# 性能配置\n")
buf.WriteString("performance:\n")
buf.WriteString(" file_cache: # 静态文件缓存\n")
buf.WriteString(fmt.Sprintf(" max_entries: %d # 最大缓存条目\n", cfg.Performance.FileCache.MaxEntries))
buf.WriteString(fmt.Sprintf(" max_size: %dMB # 内存上限\n", cfg.Performance.FileCache.MaxSize/1024/1024))
buf.WriteString(fmt.Sprintf(" inactive: %ds # 未访问淘汰时间\n", int(cfg.Performance.FileCache.Inactive.Seconds())))
buf.WriteString(fmt.Sprintf(" lru_eviction: %v # 启用 LRU 淘汰\n", cfg.Performance.FileCache.LRUEviction))
buf.WriteString(" transport: # HTTP Transport 连接池\n")
buf.WriteString(fmt.Sprintf(" max_idle_conns: %d # 最大空闲连接\n", cfg.Performance.Transport.MaxIdleConns))
buf.WriteString(fmt.Sprintf(" max_idle_conns_per_host: %d # 每主机空闲连接\n", cfg.Performance.Transport.MaxIdleConnsPerHost))
buf.WriteString(fmt.Sprintf(" idle_conn_timeout: %ds # 空闲超时\n", int(cfg.Performance.Transport.IdleConnTimeout.Seconds())))
buf.WriteString(" goroutine_pool: # Goroutine 池(处理并发请求)\n")
buf.WriteString(fmt.Sprintf(" enabled: %v # 是否启用\n", cfg.Performance.GoroutinePool.Enabled))
buf.WriteString(fmt.Sprintf(" max_workers: %d # 最大 worker 数\n", cfg.Performance.GoroutinePool.MaxWorkers))
buf.WriteString(fmt.Sprintf(" min_workers: %d # 最小 worker 数(预热)\n", cfg.Performance.GoroutinePool.MinWorkers))
buf.WriteString(fmt.Sprintf(" idle_timeout: %ds # 空闲超时\n", int(cfg.Performance.GoroutinePool.IdleTimeout.Seconds())))
buf.WriteString(" file_cache: # 静态文件缓存\n")
buf.WriteString(fmt.Sprintf(" max_entries: %d # 最大缓存条目\n", cfg.Performance.FileCache.MaxEntries))
buf.WriteString(fmt.Sprintf(" max_size: %d # 内存上限(字节,%dMB\n", cfg.Performance.FileCache.MaxSize, cfg.Performance.FileCache.MaxSize/1024/1024))
buf.WriteString(fmt.Sprintf(" inactive: %ds # 未访问淘汰时间\n", int(cfg.Performance.FileCache.Inactive.Seconds())))
buf.WriteString(fmt.Sprintf(" lru_eviction: %v # 启用 LRU 淘汰\n", cfg.Performance.FileCache.LRUEviction))
buf.WriteString(" transport: # HTTP Transport 连接池\n")
buf.WriteString(fmt.Sprintf(" max_idle_conns: %d # 最大空闲连接\n", cfg.Performance.Transport.MaxIdleConns))
buf.WriteString(fmt.Sprintf(" max_idle_conns_per_host: %d # 每主机空闲连接\n", cfg.Performance.Transport.MaxIdleConnsPerHost))
buf.WriteString(fmt.Sprintf(" idle_conn_timeout: %ds # 空闲超时\n", int(cfg.Performance.Transport.IdleConnTimeout.Seconds())))
buf.WriteString(fmt.Sprintf(" max_conns_per_host: %d # 每主机最大连接0 表示不限制)\n", cfg.Performance.Transport.MaxConnsPerHost))
buf.WriteString("\n")
// monitoring 配置