v2中文文档
项目

占位符

在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时已经变成example
  • srv1使用的是{env.HOST}(全局占位符),因此在adapt到JSON时会保持原样。

这也意味着:直接写JSON配置(不使用Caddyfile)的用户不能使用{$ENV}语法。因此,插件作者应在配置Provision阶段实现占位符替换支持,下面会说明。

实现占位符支持

你不应在UnmarshalCaddyfile()中处理占位符。占位符应在更晚阶段替换:要么在Provision()阶段,要么在模块执行阶段(例如HTTP处理器的ServeHTTP()、匹配器的Match()等),并使用caddy.Replacer

示例

下面示例使用新建的replacer来处理占位符。它能访问全局占位符(如{env.HOST}),但不能访问HTTP占位符(如{http.request.uri}),因为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)
}