Placeholders 在 Caddy 中,placeholder 是由各個插件根據需要進行處理的;它們不會自動在所有地方生效。 這意味著如果你希望你的插件支援 placeholder,你必須明確地為其加入支援。 如果你還不熟悉 placeholder,請先從 這裡閱讀! Placeholders 概覽 Placeholders 是格式為 {foo.bar} 的字串,用作動態配置值,隨後在運行時進行求值。 Caddyfile 環境變數替換 以美元符號開頭(例如 {$FOO}),是在 Caddyfile 解析時進行求值的,不需要由你的插件處理。儘管它們共享相同的 { } 語法,但這些 不是 placeholder。 因此,理解 {env.HOST}(一個 全域 placeholder)與 {$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 } ] } ] } } } } } 特別注意 srv0 和 srv1 中的 "body" 欄位。 由於 srv0 使用了 {$HOST}(Caddyfile 環境變數替換),該值變成了 example,因為它是在產生 JSON 配置的 Caddyfile 解析期間處理的。 由於 srv1 使用了 {env.HOST}(全域 placeholder),它在適配為 JSON 時保持不變。 這確實意味著編寫 JSON 配置(不使用 Caddyfile)的使用者無法使用 {$ENV} 語法。出於這個原因,插件作者在配置 (provisioned) 時實現替換 placeholder 的支援是非常重要的。這將在下面說明。 實現 placeholder 支援 你不應該在 UnmarshalCaddyfile() 中處理 placeholder。相反地,placeholder 應該在稍後被替換,可以是在 Provision() 步驟中,或者在模組執行期間(例如 HTTP handler 的 ServeHTTP()、matcher 的 Match() 等),並使用 caddy.Replacer。 範例 這裡,我們使用新構建的 replacer 來處理 placeholder。它可以存取 全域 placeholder,例如 {env.HOST},但 不能 存取 HTTP placeholder(例如 {http.request.uri}),因為配置 (provisioning) 發生在配置載入時,而不是在請求期間。 func (g *Gizmo) Provision(ctx caddy.Context) error { repl := caddy.NewReplacer() g.Name = repl.ReplaceAll(g.Name,"") return nil } 這裡,我們在 ServeHTTP 期間從請求上下文 r.Context() 中獲取 replacer。這個 replacer 既可以存取全域 placeholder,又 可以存取每個請求的 HTTP placeholder(例如 {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) }