httpcaddyfile: Add 'vars' directive

See discussion in #4650
This commit is contained in:
Matthew Holt 2022-03-22 10:47:21 -06:00
parent 55b4c12e04
commit 79cbe7bfd0
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
4 changed files with 87 additions and 7 deletions

View file

@ -39,6 +39,7 @@ func init() {
RegisterDirective("bind", parseBind) RegisterDirective("bind", parseBind)
RegisterDirective("tls", parseTLS) RegisterDirective("tls", parseTLS)
RegisterHandlerDirective("root", parseRoot) RegisterHandlerDirective("root", parseRoot)
RegisterHandlerDirective("vars", parseVars)
RegisterHandlerDirective("redir", parseRedir) RegisterHandlerDirective("redir", parseRedir)
RegisterHandlerDirective("respond", parseRespond) RegisterHandlerDirective("respond", parseRespond)
RegisterHandlerDirective("abort", parseAbort) RegisterHandlerDirective("abort", parseAbort)
@ -530,6 +531,13 @@ func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
return caddyhttp.VarsMiddleware{"root": root}, nil return caddyhttp.VarsMiddleware{"root": root}, nil
} }
// parseVars parses the vars directive. See its UnmarshalCaddyfile method for syntax.
func parseVars(h Helper) (caddyhttp.MiddlewareHandler, error) {
v := new(caddyhttp.VarsMiddleware)
err := v.UnmarshalCaddyfile(h.Dispenser)
return v, err
}
// parseRedir parses the redir directive. Syntax: // parseRedir parses the redir directive. Syntax:
// //
// redir [<matcher>] <to> [<code>] // redir [<matcher>] <to> [<code>]

View file

@ -40,6 +40,7 @@ var directiveOrder = []string{
"tracing", "tracing",
"map", "map",
"vars",
"root", "root",
"header", "header",

View file

@ -19,6 +19,14 @@ map {host} {my_placeholder} {magic_number} {
# Should output two strings, second being escaped quote # Should output two strings, second being escaped quote
default "unknown domain" \""" default "unknown domain" \"""
} }
vars foo bar
vars {
abc true
def 1
ghi 2.3
jkl "mn op"
}
---------- ----------
{ {
"apps": { "apps": {
@ -91,6 +99,17 @@ map {host} {my_placeholder} {magic_number} {
} }
], ],
"source": "{http.request.host}" "source": "{http.request.host}"
},
{
"foo": "bar",
"handler": "vars"
},
{
"abc": true,
"def": 1,
"ghi": 2.3,
"handler": "vars",
"jkl": "mn op"
} }
] ]
} }

View file

@ -37,7 +37,7 @@ func init() {
// //
// The key is the variable name, and the value is the value of the // The key is the variable name, and the value is the value of the
// variable. Both the name and value may use or contain placeholders. // variable. Both the name and value may use or contain placeholders.
type VarsMiddleware map[string]string type VarsMiddleware map[string]interface{}
// CaddyModule returns the Caddy module information. // CaddyModule returns the Caddy module information.
func (VarsMiddleware) CaddyModule() caddy.ModuleInfo { func (VarsMiddleware) CaddyModule() caddy.ModuleInfo {
@ -47,17 +47,67 @@ func (VarsMiddleware) CaddyModule() caddy.ModuleInfo {
} }
} }
func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error { func (m VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
vars := r.Context().Value(VarsCtxKey).(map[string]interface{}) vars := r.Context().Value(VarsCtxKey).(map[string]interface{})
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
for k, v := range t { for k, v := range m {
keyExpanded := repl.ReplaceAll(k, "") keyExpanded := repl.ReplaceAll(k, "")
valExpanded := repl.ReplaceAll(v, "") if valStr, ok := v.(string); ok {
vars[keyExpanded] = valExpanded v = repl.ReplaceAll(valStr, "")
}
vars[keyExpanded] = v
} }
return next.ServeHTTP(w, r) return next.ServeHTTP(w, r)
} }
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. Syntax:
//
// vars [<name> <val>] {
// <name> <val>
// ...
// }
//
func (m *VarsMiddleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if *m == nil {
*m = make(VarsMiddleware)
}
nextVar := func(headerLine bool) error {
if headerLine {
// header line is optional
if !d.NextArg() {
return nil
}
}
varName := d.Val()
if !d.NextArg() {
return d.ArgErr()
}
varValue := d.ScalarVal()
(*m)[varName] = varValue
if d.NextArg() {
return d.ArgErr()
}
return nil
}
for d.Next() {
if err := nextVar(true); err != nil {
return err
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
if err := nextVar(false); err != nil {
return err
}
}
}
return nil
}
// VarsMatcher is an HTTP request matcher which can match // VarsMatcher is an HTTP request matcher which can match
// requests based on variables in the context. The key is // requests based on variables in the context. The key is
// the name of the variable, and the values are possible // the name of the variable, and the values are possible
@ -261,6 +311,8 @@ func SetVar(ctx context.Context, key string, value interface{}) {
// Interface guards // Interface guards
var ( var (
_ MiddlewareHandler = (*VarsMiddleware)(nil) _ MiddlewareHandler = (*VarsMiddleware)(nil)
_ RequestMatcher = (*VarsMatcher)(nil) _ caddyfile.Unmarshaler = (*VarsMiddleware)(nil)
_ RequestMatcher = (*VarsMatcher)(nil)
_ caddyfile.Unmarshaler = (*VarsMatcher)(nil)
) )