为剩余模块添加完整文档注释: - app: 应用生命周期管理 - cache: 文件缓存 - config: 配置加载器 - handler: 静态文件处理和错误页面 - http2/http3: HTTP/2 和 HTTP/3 适配器 - loadbalance: 负载均衡算法和均衡器 - middleware: bodylimit、compression、rewrite、security - mimeutil: MIME 类型检测 - netutil: URL 处理工具 - resolver: DNS 解析器 - server: 服务器升级处理 - ssl: SSL/TLS 和 OCSP - stream: 流处理 - testutil: 测试工具 - variable: 变量池和 SSL 变量 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
156 lines
4.0 KiB
Go
156 lines
4.0 KiB
Go
// Package config 提供 YAML 配置文件的解析、验证和默认配置生成功能。
|
||
//
|
||
// 该文件包含配置加载器相关的核心逻辑,包括:
|
||
// - 多文件配置合并(include 指令支持)
|
||
// - DAG-safe 循环检测(防止配置文件循环引用)
|
||
// - Glob 模式展开(支持通配符路径)
|
||
// - 深度限制(防止无限递归)
|
||
//
|
||
// 主要用途:
|
||
//
|
||
// 用于加载主配置文件及其引用的子配置文件,合并为完整配置。
|
||
//
|
||
// 注意事项:
|
||
// - 最大 include 深度为 10 层
|
||
// - 循环引用会返回错误
|
||
// - DAG 共享子配置允许(同一文件可被多处引用)
|
||
//
|
||
// 作者:xfy
|
||
package config
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
|
||
"gopkg.in/yaml.v3"
|
||
)
|
||
|
||
const maxIncludeDepth = 10
|
||
|
||
// ConfigLoader 配置加载器
|
||
type ConfigLoader struct {
|
||
loadedFiles map[string]bool // 所有已加载文件(用于跳过重复处理)
|
||
stack map[string]bool // 当前调用栈(用于 DAG 循环检测)
|
||
baseDir string
|
||
depth int
|
||
}
|
||
|
||
// NewConfigLoader 构造函数
|
||
func NewConfigLoader(mainConfigPath string) *ConfigLoader {
|
||
absPath, err := filepath.Abs(mainConfigPath)
|
||
if err != nil {
|
||
absPath = mainConfigPath
|
||
}
|
||
|
||
return &ConfigLoader{
|
||
baseDir: filepath.Dir(absPath),
|
||
loadedFiles: make(map[string]bool),
|
||
stack: make(map[string]bool),
|
||
depth: 0,
|
||
}
|
||
}
|
||
|
||
// Load 加载配置(含 DAG-safe 循环检测)
|
||
func (l *ConfigLoader) Load(path string) (*Config, error) {
|
||
// 深度限制
|
||
if l.depth > maxIncludeDepth {
|
||
return nil, fmt.Errorf("include depth exceeds maximum (%d)", maxIncludeDepth)
|
||
}
|
||
|
||
absPath, err := filepath.Abs(path)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("resolve path failed: %w", err)
|
||
}
|
||
|
||
// DAG-safe 循环检测
|
||
// 使用 stack 检测真正的循环(当前调用栈中的文件)
|
||
// 使用 loadedFiles 跳过已处理的文件(允许 DAG 共享子配置)
|
||
if l.stack[absPath] {
|
||
return nil, fmt.Errorf("circular include detected: '%s' is in current include chain", absPath)
|
||
}
|
||
|
||
// 如果文件已处理过,跳过(不报错)
|
||
if l.loadedFiles[absPath] {
|
||
return &Config{}, nil // 返回空配置,跳过重复处理
|
||
}
|
||
|
||
l.stack[absPath] = true // 加入调用栈
|
||
l.loadedFiles[absPath] = true // 标记已处理
|
||
l.depth++
|
||
|
||
// 加载文件
|
||
data, err := os.ReadFile(absPath)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("read file failed: %w", err)
|
||
}
|
||
|
||
var cfg Config
|
||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||
return nil, fmt.Errorf("parse yaml failed: %w", err)
|
||
}
|
||
|
||
// 处理 include
|
||
for _, inc := range cfg.Include {
|
||
files, err := l.expandGlob(inc.Path)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for _, f := range files {
|
||
subCfg, err := l.Load(f) // 递归
|
||
if err != nil {
|
||
return nil, fmt.Errorf("include %s: %w", f, err)
|
||
}
|
||
|
||
if err := l.merge(&cfg, subCfg, f); err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
}
|
||
|
||
// 清理调用栈
|
||
delete(l.stack, absPath)
|
||
l.depth--
|
||
return &cfg, nil
|
||
}
|
||
|
||
// merge 合并配置
|
||
func (l *ConfigLoader) merge(dst, src *Config, _ string) error {
|
||
// Server name collision(listen collision 由 validate.go 处理)
|
||
for _, newServer := range src.Servers {
|
||
for _, existing := range dst.Servers {
|
||
if newServer.Name == existing.Name {
|
||
return fmt.Errorf("server name collision: '%s'", newServer.Name)
|
||
}
|
||
}
|
||
}
|
||
dst.Servers = append(dst.Servers, src.Servers...)
|
||
|
||
// Stream collision
|
||
for _, newStream := range src.Stream {
|
||
for _, existing := range dst.Stream {
|
||
if newStream.Listen == existing.Listen {
|
||
return fmt.Errorf("stream listen collision: '%s'", newStream.Listen)
|
||
}
|
||
}
|
||
}
|
||
dst.Stream = append(dst.Stream, src.Stream...)
|
||
|
||
return nil
|
||
}
|
||
|
||
// expandGlob 展开 glob 模式
|
||
func (l *ConfigLoader) expandGlob(pattern string) ([]string, error) {
|
||
absPattern := l.resolvePath(pattern)
|
||
return filepath.Glob(absPattern)
|
||
}
|
||
|
||
// resolvePath 解析路径
|
||
func (l *ConfigLoader) resolvePath(path string) string {
|
||
if filepath.IsAbs(path) {
|
||
return path
|
||
}
|
||
return filepath.Join(l.baseDir, path)
|
||
}
|