文档
一个 项目

占位符

在 Caddy 中,占位符由各个插件按需处理;它们不会自动在所有地方生效。

这意味着如果你希望你的插件支持占位符,必须显式为其添加支持。

如果你还不熟悉占位符,请先从此处开始阅读!

占位符概览

占位符 是格式为 {foo.bar} 的字符串,用作动态配置值,稍后在运行时进行求值。

以美元符号开头的 Caddyfile 环境变量替换,例如 {$FOO},会在解析 Caddyfile 时求值,因此你的插件无需处理它们。尽管它们与占位符共享相同的 { } 语法,但它们并不是占位符。

因此,理解 {env.HOST}(一个全局占位符)本质上不同于 {$HOST}(Caddyfile 的环境变量替换)是很重要的。

例如,参见下面的 Caddyfile:

:8080 {
	respond {$HOST} 200
}

:8081 {
	respond {env.HOST} 200
}

当你使用 HOST=example caddy adapt 将此 Caddyfile 转换为 JSON 时,你会得到:

{
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [":8080"],
          "routes": [
            {
              "handle": [
                {
                  "body": "example",
                  "handler": "static_response",
                  "status_code": 200
                }
              ]
            }
          ]
        },
        "srv1": {
          "listen": [":8081"],
          "routes": [
            {
              "handle": [
                {
                  "body": "{env.HOST}",
                  "handler": "static_response",
                  "status_code": 200
                }
              ]
            }
          ]
        }
      }
    }
  }
}

特别注意 srv0srv1 中的 "body" 字段。

由于 srv0 使用了 {$HOST}(Caddyfile 环境变量替换),该值在生成 JSON 配置时的 Caddyfile 解析阶段就被处理为 example

srv1 使用的是 {env.HOST}(一个全局占位符),在适配为 JSON 时保持不变。

这也意味着以 JSON 编写配置的用户(不使用 Caddyfile)不能使用 {$ENV} 语法。因此,插件作者在配置被 provision(提供)时实现替换占位符的支持是很重要的。下面将对此进行说明。

实现占位符支持

你不应该在 UnmarshalCaddyfile() 中处理占位符。相反,占位符应在稍后被替换,或在 Provision() 步骤中,或在你的模块执行期间(例如对 HTTP 处理器的 ServeHTTP()、对匹配器的 Match() 等),使用 caddy.Replacer 来替换。

示例

这里我们使用新构造的 replacer 来处理占位符。它可以访问诸如 {env.HOST} 之类的全局占位符,但不能访问诸如 {http.request.uri} 的 HTTP 占位符,因为 provision 发生在配置加载时,而不是在请求期间。

func (g *Gizmo) Provision(ctx caddy.Context) error {
	repl := caddy.NewReplacer()
	g.Name = repl.ReplaceAll(g.Name,"")
	return nil
}

下面示例展示在 ServeHTTP 中从请求上下文 r.Context() 获取 replacer。此 replacer 既可以访问全局占位符,也可以访问每次请求的 HTTP 占位符(例如 {http.request.uri})。

func (g *Gizmo) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
	_, err := w.Write([]byte(repl.ReplaceAll(g.Name,"")))
	if err != nil {
		return err
	}
	return next.ServeHTTP(w, r)
}