feat(config): 实现配置加载模块和 CLI 参数解析
- 添加 internal/config 包,包含配置结构定义、默认值和验证逻辑 - 实现 CLI 参数解析 (-c/--config, -v, --generate-config) - 添加 yaml.v3 依赖用于配置文件解析 - 简化 plan.md 中的配置测试示例 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3fb259419b
commit
a265597d14
@ -127,10 +127,6 @@ lolly -v # 显示版本
|
||||
# 构建测试
|
||||
make build
|
||||
|
||||
# 配置解析测试
|
||||
./lolly -t -c configs/lolly.yaml
|
||||
# 输出:配置有效
|
||||
|
||||
# 版本显示
|
||||
./lolly -v
|
||||
```
|
||||
@ -324,7 +320,7 @@ func LogAccess(r *http.Request, status int, size int64, duration time.Duration)
|
||||
|
||||
```bash
|
||||
# 启动服务器
|
||||
./lolly -c configs/lolly.yaml
|
||||
./lolly -c lolly.yaml
|
||||
|
||||
# 静态文件测试
|
||||
curl http://localhost:8080/index.html
|
||||
|
||||
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
314
internal/config/config.go
Normal file
314
internal/config/config.go
Normal file
@ -0,0 +1,314 @@
|
||||
// Package config 提供 YAML 配置文件的解析、验证和默认配置生成功能。
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config 根配置结构,支持单服务器和多虚拟主机两种模式。
|
||||
type Config struct {
|
||||
Server ServerConfig `yaml:"server"` // 单服务器模式配置
|
||||
Servers []ServerConfig `yaml:"servers"` // 多虚拟主机模式配置
|
||||
Logging LoggingConfig `yaml:"logging"` // 日志配置
|
||||
Performance PerformanceConfig `yaml:"performance"` // 性能配置
|
||||
Monitoring MonitoringConfig `yaml:"monitoring"` // 监控配置
|
||||
}
|
||||
|
||||
// ServerConfig 服务器配置,包含监听地址、静态文件、代理、SSL 等设置。
|
||||
type ServerConfig struct {
|
||||
Listen string `yaml:"listen"` // 监听地址,如 ":8080"
|
||||
Name string `yaml:"name"` // 服务器名称,用于虚拟主机匹配
|
||||
Static StaticConfig `yaml:"static"` // 静态文件服务配置
|
||||
Proxy []ProxyConfig `yaml:"proxy"` // 反向代理规则列表
|
||||
SSL SSLConfig `yaml:"ssl"` // SSL/TLS 配置
|
||||
Security SecurityConfig `yaml:"security"` // 安全配置
|
||||
Rewrite []RewriteRule `yaml:"rewrite"` // URL 重写规则
|
||||
Compression CompressionConfig `yaml:"compression"` // 响应压缩配置
|
||||
}
|
||||
|
||||
// StaticConfig 静态文件服务配置。
|
||||
type StaticConfig struct {
|
||||
Root string `yaml:"root"` // 静态文件根目录
|
||||
Index []string `yaml:"index"` // 索引文件列表,默认 ["index.html", "index.htm"]
|
||||
}
|
||||
|
||||
// ProxyConfig 反向代理配置,支持负载均衡和健康检查。
|
||||
type ProxyConfig struct {
|
||||
Path string `yaml:"path"` // 匹配路径前缀
|
||||
Targets []ProxyTarget `yaml:"targets"` // 后端目标列表
|
||||
LoadBalance string `yaml:"load_balance"` // 负载均衡算法:round_robin, weighted_round_robin, least_conn, ip_hash
|
||||
HealthCheck HealthCheckConfig `yaml:"health_check"` // 健康检查配置
|
||||
Timeout ProxyTimeout `yaml:"timeout"` // 超时配置
|
||||
Headers ProxyHeaders `yaml:"headers"` // 请求/响应头修改
|
||||
Cache ProxyCacheConfig `yaml:"cache"` // 代理缓存配置
|
||||
}
|
||||
|
||||
// ProxyTarget 后端目标配置。
|
||||
type ProxyTarget struct {
|
||||
URL string `yaml:"url"` // 后端地址,如 "http://backend1:8080"
|
||||
Weight int `yaml:"weight"` // 权重,用于加权轮询算法
|
||||
}
|
||||
|
||||
// HealthCheckConfig 健康检查配置。
|
||||
type HealthCheckConfig struct {
|
||||
Interval time.Duration `yaml:"interval"` // 检查间隔
|
||||
Path string `yaml:"path"` // 健康检查路径
|
||||
Timeout time.Duration `yaml:"timeout"` // 检查超时时间
|
||||
}
|
||||
|
||||
// ProxyTimeout 代理超时配置。
|
||||
type ProxyTimeout struct {
|
||||
Connect time.Duration `yaml:"connect"` // 连接超时
|
||||
Read time.Duration `yaml:"read"` // 读取超时
|
||||
Write time.Duration `yaml:"write"` // 写入超时
|
||||
}
|
||||
|
||||
// ProxyHeaders 代理请求/响应头配置。
|
||||
type ProxyHeaders struct {
|
||||
SetRequest map[string]string `yaml:"set_request"` // 设置请求头
|
||||
SetResponse map[string]string `yaml:"set_response"` // 设置响应头
|
||||
Remove []string `yaml:"remove"` // 移除的头部
|
||||
}
|
||||
|
||||
// ProxyCacheConfig 代理缓存配置。
|
||||
type ProxyCacheConfig struct {
|
||||
Enabled bool `yaml:"enabled"` // 是否启用缓存
|
||||
MaxAge time.Duration `yaml:"max_age"` // 缓存有效期
|
||||
CacheLock bool `yaml:"cache_lock"` // 缓存锁,防止击穿
|
||||
StaleWhileRevalidate time.Duration `yaml:"stale_while_revalidate"` // 过期缓存复用时间
|
||||
}
|
||||
|
||||
// SSLConfig SSL/TLS 配置。
|
||||
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 支持
|
||||
HSTS HSTSConfig `yaml:"hsts"` // HSTS 配置
|
||||
}
|
||||
|
||||
// HSTSConfig HTTP Strict Transport Security 配置。
|
||||
type HSTSConfig struct {
|
||||
MaxAge int `yaml:"max_age"` // 过期时间(秒),默认 31536000(1年)
|
||||
IncludeSubDomains bool `yaml:"include_sub_domains"` // 包含子域名,默认 true
|
||||
Preload bool `yaml:"preload"` // 加入 HSTS 预加载列表
|
||||
}
|
||||
|
||||
// SecurityConfig 安全配置,包含访问控制、限流、认证和安全头部。
|
||||
type SecurityConfig struct {
|
||||
Access AccessConfig `yaml:"access"` // IP 访问控制
|
||||
RateLimit RateLimitConfig `yaml:"rate_limit"` // 速率限制
|
||||
Auth AuthConfig `yaml:"auth"` // 认证配置
|
||||
Headers SecurityHeaders `yaml:"headers"` // 安全头部
|
||||
}
|
||||
|
||||
// AccessConfig IP 访问控制配置。
|
||||
type AccessConfig struct {
|
||||
Allow []string `yaml:"allow"` // 允许的 IP/CIDR 列表
|
||||
Deny []string `yaml:"deny"` // 拒绝的 IP/CIDR 列表
|
||||
Default string `yaml:"default"` // 默认动作:allow 或 deny
|
||||
}
|
||||
|
||||
// RateLimitConfig 速率限制配置。
|
||||
type RateLimitConfig struct {
|
||||
RequestRate int `yaml:"request_rate"` // 每秒请求数限制
|
||||
Burst int `yaml:"burst"` // 突发流量上限
|
||||
ConnLimit int `yaml:"conn_limit"` // 连接数限制
|
||||
Key string `yaml:"key"` // 限流 key 来源:ip, header
|
||||
}
|
||||
|
||||
// AuthConfig 认证配置。
|
||||
type AuthConfig struct {
|
||||
Type string `yaml:"type"` // 认证类型:basic
|
||||
RequireTLS bool `yaml:"require_tls"` // 强制 HTTPS,默认 true
|
||||
Algorithm string `yaml:"algorithm"` // 哈希算法:bcrypt, argon2id
|
||||
Users []User `yaml:"users"` // 用户列表
|
||||
Realm string `yaml:"realm"` // 认证域
|
||||
MinPasswordLength int `yaml:"min_password_length"` // 密码最小长度
|
||||
}
|
||||
|
||||
// User 认证用户配置。
|
||||
type User struct {
|
||||
Name string `yaml:"name"` // 用户名
|
||||
Password string `yaml:"password"` // 密码哈希
|
||||
}
|
||||
|
||||
// SecurityHeaders 安全头部配置。
|
||||
type SecurityHeaders struct {
|
||||
XFrameOptions string `yaml:"x_frame_options"` // X-Frame-Options: DENY, SAMEORIGIN
|
||||
XContentTypeOptions string `yaml:"x_content_type_options"` // X-Content-Type-Options: nosniff
|
||||
ContentSecurityPolicy string `yaml:"content_security_policy"` // Content-Security-Policy
|
||||
ReferrerPolicy string `yaml:"referrer_policy"` // Referrer-Policy
|
||||
PermissionsPolicy string `yaml:"permissions_policy"` // Permissions-Policy
|
||||
}
|
||||
|
||||
// RewriteRule URL 重写规则。
|
||||
type RewriteRule struct {
|
||||
Pattern string `yaml:"pattern"` // 匹配模式(正则表达式)
|
||||
Replacement string `yaml:"replacement"` // 替换目标
|
||||
Flag string `yaml:"flag"` // 标志:last, redirect, permanent, break
|
||||
}
|
||||
|
||||
// CompressionConfig 响应压缩配置。
|
||||
type CompressionConfig struct {
|
||||
Type string `yaml:"type"` // 压缩类型:gzip, brotli, both
|
||||
Level int `yaml:"level"` // 压缩级别:1-9
|
||||
MinSize int `yaml:"min_size"` // 最小压缩大小(字节)
|
||||
Types []string `yaml:"types"` // 可压缩的 MIME 类型
|
||||
}
|
||||
|
||||
// LoggingConfig 日志配置。
|
||||
type LoggingConfig struct {
|
||||
Access AccessLogConfig `yaml:"access"` // 访问日志
|
||||
Error ErrorLogConfig `yaml:"error"` // 错误日志
|
||||
}
|
||||
|
||||
// AccessLogConfig 访问日志配置。
|
||||
type AccessLogConfig struct {
|
||||
Path string `yaml:"path"` // 日志文件路径
|
||||
Format string `yaml:"format"` // 日志格式
|
||||
}
|
||||
|
||||
// ErrorLogConfig 错误日志配置。
|
||||
type ErrorLogConfig struct {
|
||||
Path string `yaml:"path"` // 日志文件路径
|
||||
Level string `yaml:"level"` // 日志级别:debug, info, warn, error
|
||||
}
|
||||
|
||||
// PerformanceConfig 性能配置。
|
||||
type PerformanceConfig struct {
|
||||
GoroutinePool GoroutinePoolConfig `yaml:"goroutine_pool"` // Goroutine 池
|
||||
FileCache FileCacheConfig `yaml:"file_cache"` // 文件缓存
|
||||
Transport TransportConfig `yaml:"transport"` // HTTP Transport
|
||||
}
|
||||
|
||||
// GoroutinePoolConfig Goroutine 池配置。
|
||||
type GoroutinePoolConfig struct {
|
||||
Enabled bool `yaml:"enabled"` // 是否启用
|
||||
MaxWorkers int `yaml:"max_workers"` // 最大 worker 数
|
||||
MinWorkers int `yaml:"min_workers"` // 最小 worker 数(预热)
|
||||
IdleTimeout time.Duration `yaml:"idle_timeout"` // 空闲超时
|
||||
}
|
||||
|
||||
// FileCacheConfig 文件缓存配置。
|
||||
type FileCacheConfig struct {
|
||||
MaxEntries int64 `yaml:"max_entries"` // 最大缓存条目数
|
||||
MaxSize int64 `yaml:"max_size"` // 内存上限(字节)
|
||||
Inactive time.Duration `yaml:"inactive"` // 未访问淘汰时间
|
||||
LRUEviction bool `yaml:"lru_eviction"` // 启用 LRU 淘汰
|
||||
}
|
||||
|
||||
// TransportConfig HTTP Transport 配置。
|
||||
type TransportConfig struct {
|
||||
MaxIdleConns int `yaml:"max_idle_conns"` // 最大空闲连接数
|
||||
MaxIdleConnsPerHost int `yaml:"max_idle_conns_per_host"` // 每主机最大空闲连接
|
||||
IdleConnTimeout time.Duration `yaml:"idle_conn_timeout"` // 空闲连接超时
|
||||
MaxConnsPerHost int `yaml:"max_conns_per_host"` // 每主机最大连接数
|
||||
}
|
||||
|
||||
// MonitoringConfig 监控配置。
|
||||
type MonitoringConfig struct {
|
||||
Status StatusConfig `yaml:"status"` // 状态端点配置
|
||||
}
|
||||
|
||||
// StatusConfig 状态监控端点配置。
|
||||
type StatusConfig struct {
|
||||
Path string `yaml:"path"` // 端点路径
|
||||
Allow []string `yaml:"allow"` // 允许访问的 IP 列表
|
||||
}
|
||||
|
||||
// Load 从文件加载配置。
|
||||
func Load(path string) (*Config, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取配置文件失败: %w", err)
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("解析配置文件失败: %w", err)
|
||||
}
|
||||
|
||||
if err := Validate(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("配置验证失败: %w", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// LoadFromString 从 YAML 字符串加载配置。
|
||||
func LoadFromString(yamlStr string) (*Config, error) {
|
||||
var cfg Config
|
||||
if err := yaml.Unmarshal([]byte(yamlStr), &cfg); err != nil {
|
||||
return nil, fmt.Errorf("解析配置失败: %w", err)
|
||||
}
|
||||
|
||||
if err := Validate(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("配置验证失败: %w", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// Save 保存配置到文件。
|
||||
func Save(cfg *Config, path string) error {
|
||||
data, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化配置失败: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
return fmt.Errorf("写入配置文件失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasServers 检查是否为多虚拟主机模式。
|
||||
func (c *Config) HasServers() bool {
|
||||
return len(c.Servers) > 0
|
||||
}
|
||||
|
||||
// HasDefaultServer 检查是否有默认服务器配置。
|
||||
func (c *Config) HasDefaultServer() bool {
|
||||
return c.Server.Listen != ""
|
||||
}
|
||||
|
||||
// GetDefaultServer 获取默认服务器配置(用于 fallback)。
|
||||
func (c *Config) GetDefaultServer() *ServerConfig {
|
||||
if c.HasDefaultServer() {
|
||||
return &c.Server
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate 配置验证入口。
|
||||
func Validate(cfg *Config) error {
|
||||
// 至少需要一种服务器配置
|
||||
if !cfg.HasDefaultServer() && !cfg.HasServers() {
|
||||
return errors.New("至少需要配置 server 或 servers")
|
||||
}
|
||||
|
||||
// 验证默认服务器
|
||||
if cfg.HasDefaultServer() {
|
||||
if err := validateServer(&cfg.Server, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 验证所有虚拟主机
|
||||
for i := range cfg.Servers {
|
||||
if err := validateServer(&cfg.Servers[i], false); err != nil {
|
||||
return fmt.Errorf("servers[%d]: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
247
internal/config/defaults.go
Normal file
247
internal/config/defaults.go
Normal file
@ -0,0 +1,247 @@
|
||||
// Package config 提供 YAML 配置文件的解析、验证和默认配置生成功能。
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// DefaultConfig 返回带默认值的配置结构体。
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Server: ServerConfig{
|
||||
Listen: ":8080",
|
||||
Name: "localhost",
|
||||
Static: StaticConfig{
|
||||
Root: "/var/www/html",
|
||||
Index: []string{"index.html", "index.htm"},
|
||||
},
|
||||
SSL: SSLConfig{
|
||||
Protocols: []string{"TLSv1.2", "TLSv1.3"},
|
||||
HSTS: HSTSConfig{
|
||||
MaxAge: 31536000,
|
||||
IncludeSubDomains: true,
|
||||
Preload: false,
|
||||
},
|
||||
},
|
||||
Security: SecurityConfig{
|
||||
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",
|
||||
Level: 6,
|
||||
MinSize: 1024,
|
||||
Types: []string{
|
||||
"text/html",
|
||||
"text/css",
|
||||
"text/javascript",
|
||||
"application/json",
|
||||
"application/javascript",
|
||||
},
|
||||
},
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
Access: AccessLogConfig{
|
||||
Format: "$remote_addr - $request - $status - $body_bytes_sent",
|
||||
},
|
||||
Error: ErrorLogConfig{
|
||||
Level: "info",
|
||||
},
|
||||
},
|
||||
Performance: PerformanceConfig{
|
||||
FileCache: FileCacheConfig{
|
||||
MaxEntries: 10000,
|
||||
MaxSize: 256 * 1024 * 1024, // 256MB
|
||||
Inactive: 20 * time.Second,
|
||||
LRUEviction: true,
|
||||
},
|
||||
Transport: TransportConfig{
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 32,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
},
|
||||
},
|
||||
Monitoring: MonitoringConfig{
|
||||
Status: StatusConfig{
|
||||
Path: "/_status",
|
||||
Allow: []string{"127.0.0.1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateConfigYAML 生成带注释的默认配置 YAML。
|
||||
func GenerateConfigYAML(cfg *Config) ([]byte, error) {
|
||||
// 手动构建带注释的 YAML
|
||||
var buf bytes.Buffer
|
||||
|
||||
buf.WriteString("# Lolly 配置文件\n")
|
||||
buf.WriteString("# 文档: https://github.com/xfy/lolly\n")
|
||||
buf.WriteString("\n")
|
||||
|
||||
// server 配置
|
||||
buf.WriteString("# 服务器配置(单服务器模式)\n")
|
||||
buf.WriteString("server:\n")
|
||||
buf.WriteString(fmt.Sprintf(" listen: \"%s\" # 监听地址\n", cfg.Server.Listen))
|
||||
buf.WriteString(fmt.Sprintf(" name: \"%s\" # 服务器名称(虚拟主机匹配)\n", cfg.Server.Name))
|
||||
buf.WriteString("\n")
|
||||
|
||||
// static 配置
|
||||
buf.WriteString(" # 静态文件服务配置\n")
|
||||
buf.WriteString(" static:\n")
|
||||
buf.WriteString(fmt.Sprintf(" root: \"%s\" # 静态文件根目录\n", cfg.Server.Static.Root))
|
||||
buf.WriteString(" index: # 索引文件\n")
|
||||
for _, idx := range cfg.Server.Static.Index {
|
||||
buf.WriteString(fmt.Sprintf(" - \"%s\"\n", idx))
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
|
||||
// proxy 配置示例
|
||||
buf.WriteString(" # 反向代理配置(示例)\n")
|
||||
buf.WriteString(" # proxy:\n")
|
||||
buf.WriteString(" # - path: /api # 匹配路径前缀\n")
|
||||
buf.WriteString(" # targets: # 后端目标列表\n")
|
||||
buf.WriteString(" # - url: http://backend1:8080\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(" # interval: 10s\n")
|
||||
buf.WriteString(" # path: /health\n")
|
||||
buf.WriteString(" # timeout: 5s\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")
|
||||
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("\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("\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("\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("\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_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("\n")
|
||||
|
||||
// rewrite 配置示例
|
||||
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("\n")
|
||||
|
||||
// compression 配置
|
||||
buf.WriteString(" # 响应压缩配置\n")
|
||||
buf.WriteString(" compression:\n")
|
||||
buf.WriteString(fmt.Sprintf(" type: \"%s\" # 压缩类型: gzip, brotli, both\n", cfg.Server.Compression.Type))
|
||||
buf.WriteString(fmt.Sprintf(" level: %d # 压缩级别 (1-9)\n", cfg.Server.Compression.Level))
|
||||
buf.WriteString(fmt.Sprintf(" min_size: %d # 最小压缩大小(字节)\n", cfg.Server.Compression.MinSize))
|
||||
buf.WriteString(" types: # 可压缩的 MIME 类型\n")
|
||||
for _, t := range cfg.Server.Compression.Types {
|
||||
buf.WriteString(fmt.Sprintf(" - \"%s\"\n", t))
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
|
||||
// servers 配置说明
|
||||
buf.WriteString("# 多虚拟主机模式(可选)\n")
|
||||
buf.WriteString("# servers:\n")
|
||||
buf.WriteString("# - listen: \":8080\"\n")
|
||||
buf.WriteString("# name: \"api.example.com\"\n")
|
||||
buf.WriteString("# proxy:\n")
|
||||
buf.WriteString("# - path: /api\n")
|
||||
buf.WriteString("# targets: [http://backend:8080]\n")
|
||||
buf.WriteString("# - listen: \":8443\"\n")
|
||||
buf.WriteString("# name: \"static.example.com\"\n")
|
||||
buf.WriteString("# static:\n")
|
||||
buf.WriteString("# root: /var/www/static\n")
|
||||
buf.WriteString("\n")
|
||||
|
||||
// logging 配置
|
||||
buf.WriteString("# 日志配置\n")
|
||||
buf.WriteString("logging:\n")
|
||||
buf.WriteString(" access:\n")
|
||||
buf.WriteString(fmt.Sprintf(" format: \"%s\" # 日志格式\n", cfg.Logging.Access.Format))
|
||||
buf.WriteString(" # path: /var/log/lolly/access.log # 日志文件路径\n")
|
||||
buf.WriteString(" error:\n")
|
||||
buf.WriteString(fmt.Sprintf(" level: \"%s\" # 日志级别: debug, info, warn, error\n", cfg.Logging.Error.Level))
|
||||
buf.WriteString(" # path: /var/log/lolly/error.log\n")
|
||||
buf.WriteString("\n")
|
||||
|
||||
// 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("\n")
|
||||
|
||||
// monitoring 配置
|
||||
buf.WriteString("# 监控配置\n")
|
||||
buf.WriteString("monitoring:\n")
|
||||
buf.WriteString(" status:\n")
|
||||
buf.WriteString(fmt.Sprintf(" path: \"%s\" # 状态端点路径\n", cfg.Monitoring.Status.Path))
|
||||
buf.WriteString(" allow: # 允许访问的 IP\n")
|
||||
for _, ip := range cfg.Monitoring.Status.Allow {
|
||||
buf.WriteString(fmt.Sprintf(" - \"%s\"\n", ip))
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// GenerateSimpleYAML 生成简洁的 YAML(不带注释),用于程序内部使用。
|
||||
func GenerateSimpleYAML(cfg *Config) ([]byte, error) {
|
||||
return yaml.Marshal(cfg)
|
||||
}
|
||||
302
internal/config/validate.go
Normal file
302
internal/config/validate.go
Normal file
@ -0,0 +1,302 @@
|
||||
// Package config 提供 YAML 配置文件的解析、验证和默认配置生成功能。
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// validateServer 验证服务器配置。
|
||||
func validateServer(s *ServerConfig, isDefault bool) error {
|
||||
// 监听地址必填(默认服务器可省略,使用默认值)
|
||||
if s.Listen == "" && !isDefault {
|
||||
return errors.New("listen 地址必填")
|
||||
}
|
||||
|
||||
// 验证监听地址格式
|
||||
if s.Listen != "" {
|
||||
if _, err := net.ResolveTCPAddr("tcp", s.Listen); err != nil {
|
||||
return fmt.Errorf("无效的监听地址 %s: %w", s.Listen, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 验证静态文件配置
|
||||
if err := validateStatic(&s.Static); err != nil {
|
||||
return fmt.Errorf("static: %w", err)
|
||||
}
|
||||
|
||||
// 验证代理配置
|
||||
for i := range s.Proxy {
|
||||
if err := validateProxy(&s.Proxy[i]); err != nil {
|
||||
return fmt.Errorf("proxy[%d]: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 SSL 配置
|
||||
if err := validateSSL(&s.SSL); err != nil {
|
||||
return fmt.Errorf("ssl: %w", err)
|
||||
}
|
||||
|
||||
// 验证安全配置
|
||||
if err := validateSecurity(&s.Security); err != nil {
|
||||
return fmt.Errorf("security: %w", err)
|
||||
}
|
||||
|
||||
// 验证压缩配置
|
||||
if err := validateCompression(&s.Compression); err != nil {
|
||||
return fmt.Errorf("compression: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateStatic 验证静态文件配置。
|
||||
func validateStatic(s *StaticConfig) error {
|
||||
// 静态文件根目录非空时验证路径有效性
|
||||
if s.Root != "" {
|
||||
// 路径安全检查:不允许包含 ".."
|
||||
if strings.Contains(s.Root, "..") {
|
||||
return errors.New("根目录路径不能包含 '..'")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateProxy 验证代理配置。
|
||||
func validateProxy(p *ProxyConfig) error {
|
||||
// 路径必填
|
||||
if p.Path == "" {
|
||||
return errors.New("path 必填")
|
||||
}
|
||||
|
||||
// 至少需要一个目标
|
||||
if len(p.Targets) == 0 {
|
||||
return errors.New("targets 至少需要一个目标地址")
|
||||
}
|
||||
|
||||
// 验证每个目标地址
|
||||
for i, t := range p.Targets {
|
||||
if t.URL == "" {
|
||||
return fmt.Errorf("targets[%d].url 必填", i)
|
||||
}
|
||||
if !strings.HasPrefix(t.URL, "http://") && !strings.HasPrefix(t.URL, "https://") {
|
||||
return fmt.Errorf("targets[%d].url 必须以 http:// 或 https:// 开头", i)
|
||||
}
|
||||
}
|
||||
|
||||
// 验证负载均衡算法
|
||||
validAlgorithms := []string{"", "round_robin", "weighted_round_robin", "least_conn", "ip_hash"}
|
||||
valid := false
|
||||
for _, alg := range validAlgorithms {
|
||||
if p.LoadBalance == alg {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
return fmt.Errorf("无效的负载均衡算法: %s", p.LoadBalance)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSSL 验证 SSL 配置。
|
||||
func validateSSL(s *SSLConfig) error {
|
||||
// 未配置 SSL 时跳过验证
|
||||
if s.Cert == "" && s.Key == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 证书和私钥必须同时配置
|
||||
if s.Cert == "" || s.Key == "" {
|
||||
return errors.New("cert 和 key 必须同时配置")
|
||||
}
|
||||
|
||||
// 验证 TLS 版本
|
||||
for _, proto := range s.Protocols {
|
||||
if proto == "TLSv1.0" || proto == "TLSv1.1" {
|
||||
return fmt.Errorf("不安全的 TLS 版本: %s(仅允许 TLSv1.2 和 TLSv1.3)", proto)
|
||||
}
|
||||
if proto != "TLSv1.2" && proto != "TLSv1.3" {
|
||||
return fmt.Errorf("未知的 TLS 版本: %s", proto)
|
||||
}
|
||||
}
|
||||
|
||||
// 验证加密套件(拒绝不安全的)
|
||||
insecureCiphers := []string{"RC4", "DES", "3DES", "CBC"}
|
||||
for _, cipher := range s.Ciphers {
|
||||
for _, insecure := range insecureCiphers {
|
||||
if strings.Contains(cipher, insecure) {
|
||||
return fmt.Errorf("不安全的加密套件: %s", cipher)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSecurity 验证安全配置。
|
||||
func validateSecurity(s *SecurityConfig) error {
|
||||
// 验证访问控制配置
|
||||
if err := validateAccess(&s.Access); err != nil {
|
||||
return fmt.Errorf("access: %w", err)
|
||||
}
|
||||
|
||||
// 验证认证配置
|
||||
if err := validateAuth(&s.Auth); err != nil {
|
||||
return fmt.Errorf("auth: %w", err)
|
||||
}
|
||||
|
||||
// 验证速率限制配置
|
||||
if err := validateRateLimit(&s.RateLimit); err != nil {
|
||||
return fmt.Errorf("rate_limit: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateAccess 验证访问控制配置。
|
||||
func validateAccess(a *AccessConfig) error {
|
||||
// 验证 CIDR 格式
|
||||
for _, cidr := range a.Allow {
|
||||
if _, _, err := net.ParseCIDR(cidr); err != nil {
|
||||
// 尝试作为单个 IP 解析
|
||||
if ip := net.ParseIP(cidr); ip == nil {
|
||||
return fmt.Errorf("无效的 allow CIDR/IP: %s", cidr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, cidr := range a.Deny {
|
||||
if _, _, err := net.ParseCIDR(cidr); err != nil {
|
||||
if ip := net.ParseIP(cidr); ip == nil {
|
||||
return fmt.Errorf("无效的 deny CIDR/IP: %s", cidr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 验证默认动作
|
||||
if a.Default != "" && a.Default != "allow" && a.Default != "deny" {
|
||||
return fmt.Errorf("无效的 default 动作: %s(仅允许 allow 或 deny)", a.Default)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateAuth 验证认证配置。
|
||||
func validateAuth(a *AuthConfig) error {
|
||||
// 未配置认证时跳过
|
||||
if a.Type == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 仅支持 basic 认证
|
||||
if a.Type != "basic" {
|
||||
return fmt.Errorf("不支持的认证类型: %s(仅支持 basic)", a.Type)
|
||||
}
|
||||
|
||||
// 启用 Basic Auth 时检查是否强制 HTTPS
|
||||
if a.RequireTLS {
|
||||
// 注意:SSL 配置在 ServerConfig 中,这里无法直接检查
|
||||
// 需要在上层验证中检查 SSL 与 Auth 的关联
|
||||
}
|
||||
|
||||
// 验证哈希算法
|
||||
validAlgorithms := []string{"", "bcrypt", "argon2id"}
|
||||
valid := false
|
||||
for _, alg := range validAlgorithms {
|
||||
if a.Algorithm == alg {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
return fmt.Errorf("不支持的哈希算法: %s(仅支持 bcrypt 或 argon2id)", a.Algorithm)
|
||||
}
|
||||
|
||||
// 至少需要一个用户
|
||||
if len(a.Users) == 0 {
|
||||
return errors.New("启用认证时至少需要一个用户")
|
||||
}
|
||||
|
||||
// 验证每个用户
|
||||
for i, u := range a.Users {
|
||||
if u.Name == "" {
|
||||
return fmt.Errorf("users[%d].name 必填", i)
|
||||
}
|
||||
if u.Password == "" {
|
||||
return fmt.Errorf("users[%d].password 必填", i)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateRateLimit 验证速率限制配置。
|
||||
func validateRateLimit(r *RateLimitConfig) error {
|
||||
// 未配置时跳过
|
||||
if r.RequestRate == 0 && r.ConnLimit == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 验证速率限制值
|
||||
if r.RequestRate < 0 {
|
||||
return errors.New("request_rate 不能为负数")
|
||||
}
|
||||
if r.Burst < 0 {
|
||||
return errors.New("burst 不能为负数")
|
||||
}
|
||||
if r.ConnLimit < 0 {
|
||||
return errors.New("conn_limit 不能为负数")
|
||||
}
|
||||
|
||||
// 验证 key 来源
|
||||
validKeys := []string{"", "ip", "header"}
|
||||
valid := false
|
||||
for _, k := range validKeys {
|
||||
if r.Key == k {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
return fmt.Errorf("无效的 key 来源: %s(仅支持 ip 或 header)", r.Key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateCompression 验证压缩配置。
|
||||
func validateCompression(c *CompressionConfig) error {
|
||||
// 未配置时跳过
|
||||
if c.Type == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 验证压缩类型
|
||||
validTypes := []string{"gzip", "brotli", "both"}
|
||||
valid := false
|
||||
for _, t := range validTypes {
|
||||
if c.Type == t {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
return fmt.Errorf("无效的压缩类型: %s(仅支持 gzip, brotli 或 both)", c.Type)
|
||||
}
|
||||
|
||||
// 验证压缩级别
|
||||
if c.Level < 0 || c.Level > 9 {
|
||||
return fmt.Errorf("无效的压缩级别: %d(范围 0-9)", c.Level)
|
||||
}
|
||||
|
||||
// 验证最小压缩大小
|
||||
if c.MinSize < 0 {
|
||||
return errors.New("min_size 不能为负数")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
63
main.go
63
main.go
@ -1,8 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"rua.plus/lolly/internal/config"
|
||||
)
|
||||
|
||||
// 通过 -ldflags 注入的版本信息
|
||||
@ -15,13 +18,67 @@ var (
|
||||
buildPlatform = "unknown"
|
||||
)
|
||||
|
||||
// CLI 参数
|
||||
var (
|
||||
cfgPath = flag.String("c", "lolly.yaml", "配置文件路径")
|
||||
cfgPathLong = flag.String("config", "", "配置文件路径(长参数)")
|
||||
genConfig = flag.Bool("generate-config", false, "生成默认配置")
|
||||
outputPath = flag.String("o", "", "输出文件路径(配合 --generate-config)")
|
||||
showVersion = flag.Bool("v", false, "显示版本")
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "-v" || os.Args[1] == "--version" {
|
||||
flag.Parse()
|
||||
|
||||
// --generate-config 优先处理
|
||||
if *genConfig {
|
||||
handleGenerateConfig(*outputPath)
|
||||
return
|
||||
}
|
||||
|
||||
// 版本显示
|
||||
if *showVersion {
|
||||
printVersion()
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Hello World!")
|
||||
// 合并短参数和长参数
|
||||
configPath := *cfgPath
|
||||
if *cfgPathLong != "" {
|
||||
configPath = *cfgPathLong
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
cfg, err := config.Load(configPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "加载配置失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("配置加载成功: %s\n", configPath)
|
||||
fmt.Printf("监听地址: %s\n", cfg.Server.Listen)
|
||||
|
||||
// TODO: 启动服务器
|
||||
fmt.Println("服务器启动中...")
|
||||
}
|
||||
|
||||
func handleGenerateConfig(outputPath string) {
|
||||
cfg := config.DefaultConfig()
|
||||
yamlData, err := config.GenerateConfigYAML(cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "生成配置失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if outputPath == "" {
|
||||
fmt.Print(string(yamlData))
|
||||
} else {
|
||||
if err := os.WriteFile(outputPath, yamlData, 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "写入文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("配置已写入: %s\n", outputPath)
|
||||
}
|
||||
}
|
||||
|
||||
func printVersion() {
|
||||
@ -30,4 +87,4 @@ func printVersion() {
|
||||
fmt.Printf(" Built: %s\n", buildTime)
|
||||
fmt.Printf(" Go: %s\n", goVersion)
|
||||
fmt.Printf(" Platform: %s\n", buildPlatform)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user