From 79cbe7bfd06565d0e7ab0717119f78960ed54c08 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 22 Mar 2022 10:47:21 -0600 Subject: [PATCH] httpcaddyfile: Add 'vars' directive See discussion in #4650 --- caddyconfig/httpcaddyfile/builtins.go | 8 +++ caddyconfig/httpcaddyfile/directives.go | 1 + ...es.txt => map_and_vars_with_raw_types.txt} | 19 ++++++ modules/caddyhttp/vars.go | 66 +++++++++++++++++-- 4 files changed, 87 insertions(+), 7 deletions(-) rename caddytest/integration/caddyfile_adapt/{map_with_raw_types.txt => map_and_vars_with_raw_types.txt} (87%) diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go index e1430d0b..e65039d6 100644 --- a/caddyconfig/httpcaddyfile/builtins.go +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -39,6 +39,7 @@ func init() { RegisterDirective("bind", parseBind) RegisterDirective("tls", parseTLS) RegisterHandlerDirective("root", parseRoot) + RegisterHandlerDirective("vars", parseVars) RegisterHandlerDirective("redir", parseRedir) RegisterHandlerDirective("respond", parseRespond) RegisterHandlerDirective("abort", parseAbort) @@ -530,6 +531,13 @@ func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) { 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: // // redir [] [] diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index aac4f1f4..425bf192 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -40,6 +40,7 @@ var directiveOrder = []string{ "tracing", "map", + "vars", "root", "header", diff --git a/caddytest/integration/caddyfile_adapt/map_with_raw_types.txt b/caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.txt similarity index 87% rename from caddytest/integration/caddyfile_adapt/map_with_raw_types.txt rename to caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.txt index 54b2b60c..af9faf47 100644 --- a/caddytest/integration/caddyfile_adapt/map_with_raw_types.txt +++ b/caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.txt @@ -19,6 +19,14 @@ map {host} {my_placeholder} {magic_number} { # Should output two strings, second being escaped quote default "unknown domain" \""" } + +vars foo bar +vars { + abc true + def 1 + ghi 2.3 + jkl "mn op" +} ---------- { "apps": { @@ -91,6 +99,17 @@ map {host} {my_placeholder} {magic_number} { } ], "source": "{http.request.host}" + }, + { + "foo": "bar", + "handler": "vars" + }, + { + "abc": true, + "def": 1, + "ghi": 2.3, + "handler": "vars", + "jkl": "mn op" } ] } diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go index b0f10a7f..28d0ddfc 100644 --- a/modules/caddyhttp/vars.go +++ b/modules/caddyhttp/vars.go @@ -37,7 +37,7 @@ func init() { // // 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. -type VarsMiddleware map[string]string +type VarsMiddleware map[string]interface{} // CaddyModule returns the Caddy module information. 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{}) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) - for k, v := range t { + for k, v := range m { keyExpanded := repl.ReplaceAll(k, "") - valExpanded := repl.ReplaceAll(v, "") - vars[keyExpanded] = valExpanded + if valStr, ok := v.(string); ok { + v = repl.ReplaceAll(valStr, "") + } + vars[keyExpanded] = v } return next.ServeHTTP(w, r) } +// UnmarshalCaddyfile implements caddyfile.Unmarshaler. Syntax: +// +// vars [ ] { +// +// ... +// } +// +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 // requests based on variables in the context. The key is // 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 var ( - _ MiddlewareHandler = (*VarsMiddleware)(nil) - _ RequestMatcher = (*VarsMatcher)(nil) + _ MiddlewareHandler = (*VarsMiddleware)(nil) + _ caddyfile.Unmarshaler = (*VarsMiddleware)(nil) + _ RequestMatcher = (*VarsMatcher)(nil) + _ caddyfile.Unmarshaler = (*VarsMatcher)(nil) )