From 558f3eef1f9735ba7e953d28a63db9fcb72a95ab Mon Sep 17 00:00:00 2001 From: Mark Sargent <99003+sarge@users.noreply.github.com> Date: Mon, 23 Mar 2020 21:10:34 +1300 Subject: [PATCH 1/8] inital map implementation --- caddyconfig/httpcaddyfile/directives.go | 3 +- caddytest/integration/map_test.go | 143 ++++++++++++++++++++++++ modules/caddyhttp/map/caddyfile.go | 66 +++++++++++ modules/caddyhttp/map/map.go | 109 ++++++++++++++++++ modules/caddyhttp/standard/imports.go | 1 + 5 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 caddytest/integration/map_test.go create mode 100644 modules/caddyhttp/map/caddyfile.go create mode 100644 modules/caddyhttp/map/map.go diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index c9f4ad96ba5..97069bf2232 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -37,10 +37,9 @@ import ( // The header directive goes second so that headers // can be manipulated before doing redirects. var directiveOrder = []string{ + "map", "root", - "header", - "redir", "rewrite", diff --git a/caddytest/integration/map_test.go b/caddytest/integration/map_test.go new file mode 100644 index 00000000000..8b8d7fe7166 --- /dev/null +++ b/caddytest/integration/map_test.go @@ -0,0 +1,143 @@ +package integration + +import ( + "bytes" + "testing" + + "github.com/caddyserver/caddy/v2/caddytest" +) + +func TestMap(t *testing.T) { + + // arrange + tester := caddytest.NewTester(t) + tester.InitServer(`{ + http_port 9080 + https_port 9443 + } + + localhost:9080 { + + map http.request.method dest-name { + default unknown + GET get-called + POST post-called + } + + respond /version 200 { + body "hello from localhost {dest-name}" + } + } + `, "caddyfile") + + // act and assert + tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") + tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called") +} + +func TestMapRespondWithDefault(t *testing.T) { + + // arrange + tester := caddytest.NewTester(t) + tester.InitServer(`{ + http_port 9080 + https_port 9443 + } + + localhost:9080 { + + map http.request.method dest-name { + default unknown + GET get-called + } + + respond /version 200 { + body "hello from localhost {dest-name}" + } + } + `, "caddyfile") + + // act and assert + tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") + tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost unknown") +} + +func TestMapAsJson(t *testing.T) { + + // arrange + tester := caddytest.NewTester(t) + tester.InitServer(`{ + "apps": { + "http": { + "http_port": 9080, + "https_port": 9443, + "servers": { + "srv0": { + "listen": [ + ":9080" + ], + "routes": [ + { + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "map", + "source": "http.request.method", + "destination": "dest-name", + "default": "unknown", + "items": [ + { + "key": "GET", + "value": "get-called" + }, + { + "key": "POST", + "value": "post-called" + } + ] + } + ] + }, + { + "handle": [ + { + "body": "hello from localhost {dest-name}", + "handler": "static_response", + "status_code": 200 + } + ], + "match": [ + { + "path": [ + "/version" + ] + } + ] + } + ] + } + ], + "match": [ + { + "host": [ + "localhost" + ] + } + ], + "terminal": true + } + ] + } + } + } + } + } + `, "json") + + tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") + tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called") +} diff --git a/modules/caddyhttp/map/caddyfile.go b/modules/caddyhttp/map/caddyfile.go new file mode 100644 index 00000000000..bc536babde1 --- /dev/null +++ b/modules/caddyhttp/map/caddyfile.go @@ -0,0 +1,66 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mmap + +import ( + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + httpcaddyfile.RegisterHandlerDirective("map", parseCaddyfile) +} + +// parseCaddyfile sets up the handler for a map from +// Caddyfile tokens. Syntax: +// +// map source dest { +// [[default] value] +// [+][ []] +// } +// +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { + m := new(Handler) + + for h.Next() { + // first see if source and dest are configured + if h.NextArg() { + m.Source = h.Val() + if h.NextArg() { + m.Destination = h.Val() + } + } + + // load the rules + for h.NextBlock(0) { + key := h.Val() + if key == "default" { + args := h.RemainingArgs() + if len(args) != 1 { + return m, h.ArgErr() + } + m.Default = args[0] + } else { + args := h.RemainingArgs() + if len(args) != 1 { + return m, h.ArgErr() + } + m.Items = append(m.Items, Item{Key: key, Value: args[0]}) + } + } + } + + return m, nil +} diff --git a/modules/caddyhttp/map/map.go b/modules/caddyhttp/map/map.go new file mode 100644 index 00000000000..9995ebce2d8 --- /dev/null +++ b/modules/caddyhttp/map/map.go @@ -0,0 +1,109 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mmap + +import ( + "net/http" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func init() { + caddy.RegisterModule(Handler{}) +} + +// Handler - Map +// +// +type Handler struct { + Source string `json:"source,omitempty"` + Destination string `json:"destination,omitempty"` + Default string `json:"default,omitempty"` + Items []Item `json:"items,omitempty"` + internalMap map[interface{}]string +} + +// CaddyModule returns the Caddy module information. +func (Handler) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.map", + New: func() caddy.Module { return new(Handler) }, + } +} + +// Provision - +func (h *Handler) Provision(_ caddy.Context) error { + h.internalMap = make(map[interface{}]string) + return nil +} + +// Validate ensures h's configuration is valid. +func (h Handler) Validate() error { + + //TODO: detect and compile regular expressions + //TODO: organise a data structure to determine the order in which + // the static keys and regular expressions can be deterministically + // evaluated. Static keys first then regular expressions in order? + // Or evaluated in order of appearance? + + // load the values + for _, v := range h.Items { + h.internalMap[v.Key] = v.Value + } + return nil +} + +func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + + // get the source value, if the source value was not found do no + // replacement. + //TODO: has the potential to miss changes in variables made later + // in the request pipeline but perhaps that is a simplier mental + // model to start with. + val, ok := repl.Get(h.Source) + if ok { + lookup := func(key string) (interface{}, bool) { + if v, ok := h.internalMap[val]; ok { + return v, true + } + if h.Default != "" { + return h.Default, true + } + return "", false + } + + // add the lookup function + repl.Map(lookup) + } + + return next.ServeHTTP(w, r) +} + +// Item defines manipulations for HTTP headers. +type Item struct { + // Key + Key string `json:"key,omitempty"` + + // Value + Value string `json:"value,omitempty"` +} + +// Interface guards +var ( + _ caddy.Provisioner = (*Handler)(nil) + _ caddyhttp.MiddlewareHandler = (*Handler)(nil) +) diff --git a/modules/caddyhttp/standard/imports.go b/modules/caddyhttp/standard/imports.go index a0ccf6e71b0..dabec812ff9 100644 --- a/modules/caddyhttp/standard/imports.go +++ b/modules/caddyhttp/standard/imports.go @@ -9,6 +9,7 @@ import ( _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/zstd" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/map" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/requestbody" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy/fastcgi" From daf74e1cce76307016f962e235cd606290a2757c Mon Sep 17 00:00:00 2001 From: Mark Sargent <99003+sarge@users.noreply.github.com> Date: Tue, 24 Mar 2020 15:00:44 +1300 Subject: [PATCH 2/8] resolve the value during middleware execution --- modules/caddyhttp/map/map.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/modules/caddyhttp/map/map.go b/modules/caddyhttp/map/map.go index 9995ebce2d8..c7884a1af32 100644 --- a/modules/caddyhttp/map/map.go +++ b/modules/caddyhttp/map/map.go @@ -76,18 +76,11 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt // model to start with. val, ok := repl.Get(h.Source) if ok { - lookup := func(key string) (interface{}, bool) { - if v, ok := h.internalMap[val]; ok { - return v, true - } - if h.Default != "" { - return h.Default, true - } - return "", false + v, ok := h.internalMap[val] + if !ok && h.Default != "" { + v = h.Default } - - // add the lookup function - repl.Map(lookup) + repl.Set(h.Destination, v) } return next.ServeHTTP(w, r) @@ -97,7 +90,6 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt type Item struct { // Key Key string `json:"key,omitempty"` - // Value Value string `json:"value,omitempty"` } From 506719f828af9ea2b87f2b35992b9bf8253b9643 Mon Sep 17 00:00:00 2001 From: Mark Sargent <99003+sarge@users.noreply.github.com> Date: Fri, 27 Mar 2020 21:10:12 +1300 Subject: [PATCH 3/8] use regex instead --- caddytest/integration/map_test.go | 4 ++-- modules/caddyhttp/map/map.go | 37 ++++++++++++++----------------- replacer.go | 11 +++++++++ 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/caddytest/integration/map_test.go b/caddytest/integration/map_test.go index 8b8d7fe7166..ae1114de2d4 100644 --- a/caddytest/integration/map_test.go +++ b/caddytest/integration/map_test.go @@ -15,12 +15,12 @@ func TestMap(t *testing.T) { http_port 9080 https_port 9443 } - + localhost:9080 { map http.request.method dest-name { default unknown - GET get-called + G.T get-called POST post-called } diff --git a/modules/caddyhttp/map/map.go b/modules/caddyhttp/map/map.go index c7884a1af32..b30b9176129 100644 --- a/modules/caddyhttp/map/map.go +++ b/modules/caddyhttp/map/map.go @@ -16,6 +16,7 @@ package mmap import ( "net/http" + "regexp" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" @@ -33,7 +34,6 @@ type Handler struct { Destination string `json:"destination,omitempty"` Default string `json:"default,omitempty"` Items []Item `json:"items,omitempty"` - internalMap map[interface{}]string } // CaddyModule returns the Caddy module information. @@ -46,22 +46,14 @@ func (Handler) CaddyModule() caddy.ModuleInfo { // Provision - func (h *Handler) Provision(_ caddy.Context) error { - h.internalMap = make(map[interface{}]string) return nil } // Validate ensures h's configuration is valid. func (h Handler) Validate() error { - //TODO: detect and compile regular expressions - //TODO: organise a data structure to determine the order in which - // the static keys and regular expressions can be deterministically - // evaluated. Static keys first then regular expressions in order? - // Or evaluated in order of appearance? - - // load the values - for _, v := range h.Items { - h.internalMap[v.Key] = v.Value + for i := 0; i < len(h.Items); i++ { + h.Items[i].compiled = regexp.MustCompile(h.Items[i].Key) } return nil } @@ -71,18 +63,21 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt // get the source value, if the source value was not found do no // replacement. - //TODO: has the potential to miss changes in variables made later - // in the request pipeline but perhaps that is a simplier mental - // model to start with. - val, ok := repl.Get(h.Source) + val, ok := repl.GetString(h.Source) if ok { - v, ok := h.internalMap[val] - if !ok && h.Default != "" { - v = h.Default + found := false + for i := 0; i < len(h.Items); i++ { + if h.Items[i].compiled.Match([]byte(val)) { + found = true + repl.Set(h.Destination, h.Items[i].Value) + break + } } - repl.Set(h.Destination, v) - } + if !found && h.Default != "" { + repl.Set(h.Destination, h.Default) + } + } return next.ServeHTTP(w, r) } @@ -92,6 +87,8 @@ type Item struct { Key string `json:"key,omitempty"` // Value Value string `json:"value,omitempty"` + // compiled internal value + compiled *regexp.Regexp } // Interface guards diff --git a/replacer.go b/replacer.go index 86cd7298059..dae48ed7b0c 100644 --- a/replacer.go +++ b/replacer.go @@ -66,6 +66,17 @@ func (r *Replacer) Get(variable string) (interface{}, bool) { return nil, false } +// GetString gets a value from the replacer. It returns +// the value and whether the variable was known. +func (r *Replacer) GetString(variable string) (string, bool) { + for _, mapFunc := range r.providers { + if val, ok := mapFunc(variable); ok { + return toString(val), true + } + } + return "", false +} + // Delete removes a variable with a static value // that was created using Set. func (r *Replacer) Delete(variable string) { From 77d2846e5dffbd1753c45d358e934a705f4627ed Mon Sep 17 00:00:00 2001 From: Mark Sargent <99003+sarge@users.noreply.github.com> Date: Mon, 15 Jun 2020 19:07:10 +1200 Subject: [PATCH 4/8] pr feedback --- caddytest/integration/map_test.go | 4 ++-- modules/caddyhttp/map/caddyfile.go | 17 +++++++++++------ modules/caddyhttp/map/map.go | 21 ++++++++++----------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/caddytest/integration/map_test.go b/caddytest/integration/map_test.go index ae1114de2d4..e31b95a5391 100644 --- a/caddytest/integration/map_test.go +++ b/caddytest/integration/map_test.go @@ -91,11 +91,11 @@ func TestMapAsJson(t *testing.T) { "default": "unknown", "items": [ { - "key": "GET", + "expression": "GET", "value": "get-called" }, { - "key": "POST", + "expression": "POST", "value": "post-called" } ] diff --git a/modules/caddyhttp/map/caddyfile.go b/modules/caddyhttp/map/caddyfile.go index bc536babde1..af736c38f1e 100644 --- a/modules/caddyhttp/map/caddyfile.go +++ b/modules/caddyhttp/map/caddyfile.go @@ -25,10 +25,15 @@ func init() { // parseCaddyfile sets up the handler for a map from // Caddyfile tokens. Syntax: +// The map takes a variable and maps it into the variable. The mapping process +// will check the variable for the first succesful match against a list of regular expressions. +// If a succesful match is found the variable will contain the value. +// If no successful match is found and the is specified then the will contain the value. // -// map source dest { -// [[default] value] -// [+][ []] +// map { +// [default ] - used if not match is found +// [ ] - regular expression to match against the sourceo find and the matching replacement +// ... // } // func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { @@ -45,8 +50,8 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) // load the rules for h.NextBlock(0) { - key := h.Val() - if key == "default" { + expression := h.Val() + if expression == "default" { args := h.RemainingArgs() if len(args) != 1 { return m, h.ArgErr() @@ -57,7 +62,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) if len(args) != 1 { return m, h.ArgErr() } - m.Items = append(m.Items, Item{Key: key, Value: args[0]}) + m.Items = append(m.Items, Item{Expression: expression, Value: args[0]}) } } } diff --git a/modules/caddyhttp/map/map.go b/modules/caddyhttp/map/map.go index b30b9176129..53045c7c677 100644 --- a/modules/caddyhttp/map/map.go +++ b/modules/caddyhttp/map/map.go @@ -44,17 +44,16 @@ func (Handler) CaddyModule() caddy.ModuleInfo { } } -// Provision - +// Provision will compile all regular expressions func (h *Handler) Provision(_ caddy.Context) error { + for i := 0; i < len(h.Items); i++ { + h.Items[i].compiled = regexp.MustCompile(h.Items[i].Expression) + } return nil } // Validate ensures h's configuration is valid. func (h Handler) Validate() error { - - for i := 0; i < len(h.Items); i++ { - h.Items[i].compiled = regexp.MustCompile(h.Items[i].Key) - } return nil } @@ -67,7 +66,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt if ok { found := false for i := 0; i < len(h.Items); i++ { - if h.Items[i].compiled.Match([]byte(val)) { + if h.Items[i].compiled.MatchString(val) { found = true repl.Set(h.Destination, h.Items[i].Value) break @@ -81,13 +80,13 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt return next.ServeHTTP(w, r) } -// Item defines manipulations for HTTP headers. +// Item defines each entry in the map type Item struct { - // Key - Key string `json:"key,omitempty"` - // Value + // Expression is the regular expression searched for + Expression string `json:"expression,omitempty"` + // Value to use once the expression has been found Value string `json:"value,omitempty"` - // compiled internal value + // compiled expression, internal use compiled *regexp.Regexp } From 1c4db130da46e2ca3884cc25d4f23f1f3f34878f Mon Sep 17 00:00:00 2001 From: Mark Sargent <99003+sarge@users.noreply.github.com> Date: Mon, 15 Jun 2020 19:09:08 +1200 Subject: [PATCH 5/8] renamed mmap to maphandler --- modules/caddyhttp/map/caddyfile.go | 2 +- modules/caddyhttp/map/map.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/caddyhttp/map/caddyfile.go b/modules/caddyhttp/map/caddyfile.go index af736c38f1e..b7727ade4ac 100644 --- a/modules/caddyhttp/map/caddyfile.go +++ b/modules/caddyhttp/map/caddyfile.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package mmap +package maphandler import ( "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" diff --git a/modules/caddyhttp/map/map.go b/modules/caddyhttp/map/map.go index 53045c7c677..110281022cd 100644 --- a/modules/caddyhttp/map/map.go +++ b/modules/caddyhttp/map/map.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package mmap +package maphandler import ( "net/http" From cf080eda0273db777a6b44ba81f450bf5fdd2727 Mon Sep 17 00:00:00 2001 From: Mark Sargent <99003+sarge@users.noreply.github.com> Date: Mon, 15 Jun 2020 19:13:13 +1200 Subject: [PATCH 6/8] refactored GetString implementation --- replacer.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/replacer.go b/replacer.go index dae48ed7b0c..7691c52b57a 100644 --- a/replacer.go +++ b/replacer.go @@ -69,12 +69,8 @@ func (r *Replacer) Get(variable string) (interface{}, bool) { // GetString gets a value from the replacer. It returns // the value and whether the variable was known. func (r *Replacer) GetString(variable string) (string, bool) { - for _, mapFunc := range r.providers { - if val, ok := mapFunc(variable); ok { - return toString(val), true - } - } - return "", false + s, found := r.Get(variable) + return toString(s), found } // Delete removes a variable with a static value From 068cef0daf59a5f8254c89508dca91e4283f4b19 Mon Sep 17 00:00:00 2001 From: Mark Sargent <99003+sarge@users.noreply.github.com> Date: Mon, 15 Jun 2020 19:14:32 +1200 Subject: [PATCH 7/8] fixed mispelling --- modules/caddyhttp/map/caddyfile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/caddyhttp/map/caddyfile.go b/modules/caddyhttp/map/caddyfile.go index b7727ade4ac..373dfeb3c63 100644 --- a/modules/caddyhttp/map/caddyfile.go +++ b/modules/caddyhttp/map/caddyfile.go @@ -26,8 +26,8 @@ func init() { // parseCaddyfile sets up the handler for a map from // Caddyfile tokens. Syntax: // The map takes a variable and maps it into the variable. The mapping process -// will check the variable for the first succesful match against a list of regular expressions. -// If a succesful match is found the variable will contain the value. +// will check the variable for the first successful match against a list of regular expressions. +// If a successful match is found the variable will contain the value. // If no successful match is found and the is specified then the will contain the value. // // map { From 5a10ec1870ec31db465dd00298a2bc8a3450692d Mon Sep 17 00:00:00 2001 From: Mark Sargent <99003+sarge@users.noreply.github.com> Date: Tue, 16 Jun 2020 21:44:56 +1200 Subject: [PATCH 8/8] additional feedback --- caddyconfig/httpcaddyfile/directives.go | 2 ++ modules/caddyhttp/map/caddyfile.go | 14 ++++++------- modules/caddyhttp/map/map.go | 26 ++++++++++++++++--------- replacer.go | 4 ++-- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index 97069bf2232..ee73078c9fc 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -39,7 +39,9 @@ import ( var directiveOrder = []string{ "map", "root", + "header", + "redir", "rewrite", diff --git a/modules/caddyhttp/map/caddyfile.go b/modules/caddyhttp/map/caddyfile.go index 373dfeb3c63..57379710898 100644 --- a/modules/caddyhttp/map/caddyfile.go +++ b/modules/caddyhttp/map/caddyfile.go @@ -23,19 +23,19 @@ func init() { httpcaddyfile.RegisterHandlerDirective("map", parseCaddyfile) } -// parseCaddyfile sets up the handler for a map from -// Caddyfile tokens. Syntax: -// The map takes a variable and maps it into the variable. The mapping process -// will check the variable for the first successful match against a list of regular expressions. -// If a successful match is found the variable will contain the value. -// If no successful match is found and the is specified then the will contain the value. +// parseCaddyfile sets up the handler for a map from Caddyfile tokens. Syntax: // // map { // [default ] - used if not match is found -// [ ] - regular expression to match against the sourceo find and the matching replacement +// [ ] - regular expression to match against the source find and the matching replacement value // ... // } // +// The map takes a source variable and maps it into the dest variable. The mapping process +// will check the source variable for the first successful match against a list of regular expressions. +// If a successful match is found the dest variable will contain the replacement value. +// If no successful match is found and the default is specified then the dest will contain the default value. +// func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { m := new(Handler) diff --git a/modules/caddyhttp/map/map.go b/modules/caddyhttp/map/map.go index 110281022cd..d2a0a0ab9a5 100644 --- a/modules/caddyhttp/map/map.go +++ b/modules/caddyhttp/map/map.go @@ -26,14 +26,27 @@ func init() { caddy.RegisterModule(Handler{}) } -// Handler - Map +// Handler is a middleware that maps a source placeholder to a destination +// placeholder. // +// The mapping process happens early in the request handling lifecycle so that +// the Destination placeholder is calculated and available for substitution. +// The Items array contains pairs of regex expressions and values, the +// Source is matched against the expression, if they match then the destination +// placeholder is set to the value. +// +// The Default is optional, if no Item expression is matched then the value of +// the Default will be used. // type Handler struct { - Source string `json:"source,omitempty"` + // Source is a placeholder + Source string `json:"source,omitempty"` + // Destination is a new placeholder Destination string `json:"destination,omitempty"` - Default string `json:"default,omitempty"` - Items []Item `json:"items,omitempty"` + // Default is an optional value to use if no other was found + Default string `json:"default,omitempty"` + // Items is an array of regex expressions and values + Items []Item `json:"items,omitempty"` } // CaddyModule returns the Caddy module information. @@ -52,11 +65,6 @@ func (h *Handler) Provision(_ caddy.Context) error { return nil } -// Validate ensures h's configuration is valid. -func (h Handler) Validate() error { - return nil -} - func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) diff --git a/replacer.go b/replacer.go index 7691c52b57a..29d8e26a779 100644 --- a/replacer.go +++ b/replacer.go @@ -66,8 +66,8 @@ func (r *Replacer) Get(variable string) (interface{}, bool) { return nil, false } -// GetString gets a value from the replacer. It returns -// the value and whether the variable was known. +// GetString is the same as Get, but coerces the value to a +// string representation. func (r *Replacer) GetString(variable string) (string, bool) { s, found := r.Get(variable) return toString(s), found