跳转到内容

配置模型

Pingclairfile 在加载时编译一次,成为服务器实际执行的运行时状态。由此产生两个结果,也解释了本项目的大部分行为:配置能决定的事情都在第一个请求之前完成;而无法被满足的配置会让服务器停止,而不是在请求到来时妥协。

文件包含一个可选的 global options 块,后面跟着一个或多个 site block。

{
email admin@example.com
}
example.com {
encode zstd gzip
reverse_proxy 10.0.0.10:8080 10.0.0.11:8080
}
:8080 {
file_server ./public
}
  • Global options 写在文件最前面、没有名称的块中,用于配置不属于单个站点的状态:ACME 账户邮箱、Admin API、自动 HTTPS 行为、trusted proxies,以及主机名上游的 DNS 重新解析。可用选项列在指令参考
  • Site block 以地址命名:主机、端口,或两者兼具。端口属于地址的一部分,而不是另一个独立指令,因此只有一个地方需要保持两者一致。
  • Directive 是 site block 内的语句。有些接受参数列表,有些接受嵌套块,有些两者都接受。
  • 注释# 开始,延伸到行尾。
  • 含空格的值要加引号。 时长必须带单位:30s 是三十秒,而在需要时长的地方写裸数字 30 会被拒绝。

Matcher 用来选出某个 directive 要应用到哪些请求。命名 matcher 以 @name 声明,之后按名称引用:

example.com {
@api path /api/*
header @api Cache-Control "no-store"
@assets path /assets/*
header @assets Cache-Control "public, max-age=31536000, immutable"
}

handle 块按路由对行为分组,并支持不带 matcher 的兜底分支:

example.com {
handle /assets/* {
file_server ./assets
}
handle {
respond "Page Not Found" 404
}
}

Snippet 是可复用的片段,以 (name) { ... } 声明,并用 import name 引入。Snippet 也能接收调用方提供的块,并把它插入到片段中写 {block} 的位置:

(site) {
https://{args[0]} {
{block}
}
}
import site example.com {
reverse_proxy 127.0.0.1:3000
}

在被 import 的文件里定义的 snippet,对之后的 import 是可见的。放在参数列表中的 placeholder 会被拒绝,因为 directive 树无法像 token 层那样在插入后重新解析该行。

pingclair validate 会编译文件并执行语义检查:指令参数、matcher 语法、证书与密钥路径,以及诸如“哪些对端可以声明客户端身份请求头”之类的策略限制。

失败一律明确且封闭:

  • 未实现的名字会按名字拒绝。 格式定义的每个名字都会被识别,服务器未实现的会给出“功能不存在”的提示。它不会被当成拼写错误,也不会被忽略:包含这类名字的配置不会启动。
  • 无法兑现的选项会被拒绝,而不是降级。 例如在 encode 中指定 Brotli 会是编译错误,因为代理没有流式 Brotli 编码器;服务器不会静默改用 gzip。
  • 语法正确但引用了不存在材料的文件仍会被拒绝。 仓库中的 examples/full_featured.pingclair 是合法的 Caddyfile 语法,但仍然会被拒绝,而且理由正确:它指定的证书路径不存在于执行检查的机器上。

同样的检查会在加载时运行,因此重新加载时如果配置验证失败,先前的状态会保持不变。

pc service reload 会重新读取配置,而不重启进程。在启动阶段建立的进程级策略,例如 trusted_proxies,要在重启后才会生效。