caddy-website/src/docs/markdown/extending-caddy/placeholders.md

134 lines
3.8 KiB
Markdown
Raw Normal View History

2024-07-25 15:40:43 -05:00
---
title: "Placeholder Support"
---
2024-07-25 15:48:03 -05:00
# Placeholders
2024-07-25 15:40:43 -05:00
2024-07-25 15:54:47 -05:00
In Caddy, placeholders are a feature of the individual plugins. They are not parsed at config time, but instead preserved, and replaced at runtime.
2024-07-25 15:48:03 -05:00
This means that if you wish for your plugin to support placeholders, you must explicitly add support for it.
## Placeholder Parsing Rules & Gotchas
2024-07-25 15:54:47 -05:00
If you wish to use placeholders in your Caddy plugin, you must accept placeholders strings, informat `{foo}` as valid configuration values, and parse them at runtime
2024-07-25 15:40:43 -05:00
2024-07-25 15:54:47 -05:00
However, Placeholders-like strings which do start with a dollar sign (`{$foo}`), are evaulated at Caddyfile parse time, and do not need to be dealt with by your plugin. These are technically not placeholders, but config-time env-var substitution, they just happen to share the `{}` syntax.
2024-07-25 15:48:03 -05:00
It is therefore important to understand that `{env.HOST}` is inherently different from something like `{$HOST}`
2024-07-25 15:40:43 -05:00
As an example, see the following caddyfile:
```
:8080 {
respond {$HOST} 200
}
:8081 {
respond {env.HOST} 200
}
```
When you adapt this Caddyfile with `HOST=example caddy adapt` you will get
```
{
"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
}
]
}
]
}
}
}
}
}
```
Importantly, look at the `"body"` field in both `srv0` and `srv1`.
Since `srv0` used `{$ENV}`, the special environmental variable placeholder with `$`, as it is parsed during Caddyfile parse time.
Since `srv1` used `{env.HOST}`, a standard placeholder, it was parsed as a normal string value for "body" field of the respond directive's config.
2024-07-25 15:48:03 -05:00
This means that down the line, the handler plugins will receive both `example` and `{env.Host}` respectively in their configurations.
2024-07-25 15:40:43 -05:00
## Using Placeholders in your Plugin
2024-07-25 15:48:03 -05:00
#### How to parse Placeholders in your Unmarshaler
2024-07-25 15:40:43 -05:00
Placeholders should be parsed as their raw values when parsing caddyfiles, just like any other string value
```go
func (g *Gizmo) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.Next()
if !d.Args(&g.Name) {
// not enough args
return d.ArgErr()
}
2024-07-25 15:48:03 -05:00
}
2024-07-25 15:40:43 -05:00
```
2024-07-25 15:48:03 -05:00
#### How to resolve Placeholders during Serve or Match
2024-07-25 15:40:43 -05:00
2024-07-25 15:48:03 -05:00
In order to now correctly read our `g.Name` placeholder, in a plugin matcher or middleware, we must extract the replacer from the context, and use that replacer on our saved placeholder string.
2024-07-25 15:40:43 -05:00
```go
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)
}
```
2024-07-25 15:48:03 -05:00
#### How to resolve Placeholders at Provision time
2024-07-25 15:40:43 -05:00
2024-07-25 15:48:03 -05:00
If you only use global placeholders, like `env`, then you may initialize a global replacer at provision time, and use it to replace such values.
2024-07-25 15:40:43 -05:00
```go
func (g *Gizmo) Provision(ctx caddy.Context) error {
repl := caddy.NewReplacer()
g.Name = repl.ReplaceAll(g.Name,"")
return nil
}
func (g *Gizmo) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
// in this case, you don't need to replace at serve-time anymore
_, err := w.Write([]byte(g.Name))
if err != nil {
return err
}
return next.ServeHTTP(w, r)
}
```