From 79cbe7bfd06565d0e7ab0717119f78960ed54c08 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 22 Mar 2022 10:47:21 -0600 Subject: httpcaddyfile: Add 'vars' directive See discussion in #4650 --- caddyconfig/httpcaddyfile/builtins.go | 8 ++ caddyconfig/httpcaddyfile/directives.go | 1 + .../map_and_vars_with_raw_types.txt | 126 +++++++++++++++++++++ .../caddyfile_adapt/map_with_raw_types.txt | 107 ----------------- modules/caddyhttp/vars.go | 66 +++++++++-- 5 files changed, 194 insertions(+), 114 deletions(-) create mode 100644 caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.txt delete mode 100644 caddytest/integration/caddyfile_adapt/map_with_raw_types.txt diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go index e1430d0..e65039d 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 aac4f1f..425bf19 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_and_vars_with_raw_types.txt b/caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.txt new file mode 100644 index 0000000..af9faf4 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/map_and_vars_with_raw_types.txt @@ -0,0 +1,126 @@ +example.com + +map {host} {my_placeholder} {magic_number} { + # Should output boolean "true" and an integer + example.com true 3 + + # Should output a string and null + foo.example.com "string value" + + # Should output two strings (quoted int) + (.*)\.example.com "${1} subdomain" "5" + + # Should output null and a string (quoted int) + ~.*\.net$ - `7` + + # Should output a float and the string "false" + ~.*\.xyz$ 123.456 "false" + + # 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": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "example.com" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "defaults": [ + "unknown domain", + "\"" + ], + "destinations": [ + "{my_placeholder}", + "{magic_number}" + ], + "handler": "map", + "mappings": [ + { + "input": "example.com", + "outputs": [ + true, + 3 + ] + }, + { + "input": "foo.example.com", + "outputs": [ + "string value", + null + ] + }, + { + "input": "(.*)\\.example.com", + "outputs": [ + "${1} subdomain", + "5" + ] + }, + { + "input_regexp": ".*\\.net$", + "outputs": [ + null, + "7" + ] + }, + { + "input_regexp": ".*\\.xyz$", + "outputs": [ + 123.456, + "false" + ] + } + ], + "source": "{http.request.host}" + }, + { + "foo": "bar", + "handler": "vars" + }, + { + "abc": true, + "def": 1, + "ghi": 2.3, + "handler": "vars", + "jkl": "mn op" + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/map_with_raw_types.txt b/caddytest/integration/caddyfile_adapt/map_with_raw_types.txt deleted file mode 100644 index 54b2b60..0000000 --- a/caddytest/integration/caddyfile_adapt/map_with_raw_types.txt +++ /dev/null @@ -1,107 +0,0 @@ -example.com - -map {host} {my_placeholder} {magic_number} { - # Should output boolean "true" and an integer - example.com true 3 - - # Should output a string and null - foo.example.com "string value" - - # Should output two strings (quoted int) - (.*)\.example.com "${1} subdomain" "5" - - # Should output null and a string (quoted int) - ~.*\.net$ - `7` - - # Should output a float and the string "false" - ~.*\.xyz$ 123.456 "false" - - # Should output two strings, second being escaped quote - default "unknown domain" \""" -} ----------- -{ - "apps": { - "http": { - "servers": { - "srv0": { - "listen": [ - ":443" - ], - "routes": [ - { - "match": [ - { - "host": [ - "example.com" - ] - } - ], - "handle": [ - { - "handler": "subroute", - "routes": [ - { - "handle": [ - { - "defaults": [ - "unknown domain", - "\"" - ], - "destinations": [ - "{my_placeholder}", - "{magic_number}" - ], - "handler": "map", - "mappings": [ - { - "input": "example.com", - "outputs": [ - true, - 3 - ] - }, - { - "input": "foo.example.com", - "outputs": [ - "string value", - null - ] - }, - { - "input": "(.*)\\.example.com", - "outputs": [ - "${1} subdomain", - "5" - ] - }, - { - "input_regexp": ".*\\.net$", - "outputs": [ - null, - "7" - ] - }, - { - "input_regexp": ".*\\.xyz$", - "outputs": [ - 123.456, - "false" - ] - } - ], - "source": "{http.request.host}" - } - ] - } - ] - } - ], - "terminal": true - } - ] - } - } - } - } -} \ No newline at end of file diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go index b0f10a7..28d0ddf 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) ) -- cgit v1.2.3