mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
map: Reimplement; multiple outputs; optimize
This commit is contained in:
parent
023d702f30
commit
25d2b4bf29
6 changed files with 246 additions and 147 deletions
|
@ -75,10 +75,10 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
||||||
// load <paths...>
|
// load <paths...>
|
||||||
// ca <acme_ca_endpoint>
|
// ca <acme_ca_endpoint>
|
||||||
// ca_root <pem_file>
|
// ca_root <pem_file>
|
||||||
// dns <provider_name>
|
// dns <provider_name> [...]
|
||||||
// on_demand
|
// on_demand
|
||||||
// eab <key_id> <mac_key>
|
// eab <key_id> <mac_key>
|
||||||
// issuer <module_name> ...
|
// issuer <module_name> [...]
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
func parseTLS(h Helper) ([]ConfigValue, error) {
|
func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
|
|
|
@ -430,7 +430,7 @@ func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expe
|
||||||
|
|
||||||
body := string(bytes)
|
body := string(bytes)
|
||||||
|
|
||||||
if !strings.Contains(body, expectedBody) {
|
if body != expectedBody {
|
||||||
tc.t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
tc.t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMap(t *testing.T) {
|
func TestMap(t *testing.T) {
|
||||||
|
|
||||||
// arrange
|
// arrange
|
||||||
tester := caddytest.NewTester(t)
|
tester := caddytest.NewTester(t)
|
||||||
tester.InitServer(`{
|
tester.InitServer(`{
|
||||||
|
@ -18,25 +17,24 @@ func TestMap(t *testing.T) {
|
||||||
|
|
||||||
localhost:9080 {
|
localhost:9080 {
|
||||||
|
|
||||||
map http.request.method dest-name {
|
map {http.request.method} {dest-1} {dest-2} {
|
||||||
default unknown
|
default unknown
|
||||||
G.T get-called
|
~G.T get-called
|
||||||
POST post-called
|
POST post-called foobar
|
||||||
}
|
}
|
||||||
|
|
||||||
respond /version 200 {
|
respond /version 200 {
|
||||||
body "hello from localhost {dest-name}"
|
body "hello from localhost {dest-1} {dest-2}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, "caddyfile")
|
`, "caddyfile")
|
||||||
|
|
||||||
// act and assert
|
// act and assert
|
||||||
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called")
|
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")
|
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called foobar")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMapRespondWithDefault(t *testing.T) {
|
func TestMapRespondWithDefault(t *testing.T) {
|
||||||
|
|
||||||
// arrange
|
// arrange
|
||||||
tester := caddytest.NewTester(t)
|
tester := caddytest.NewTester(t)
|
||||||
tester.InitServer(`{
|
tester.InitServer(`{
|
||||||
|
@ -46,9 +44,9 @@ func TestMapRespondWithDefault(t *testing.T) {
|
||||||
|
|
||||||
localhost:9080 {
|
localhost:9080 {
|
||||||
|
|
||||||
map http.request.method dest-name {
|
map {http.request.method} {dest-name} {
|
||||||
default unknown
|
default unknown
|
||||||
GET get-called
|
GET get-called
|
||||||
}
|
}
|
||||||
|
|
||||||
respond /version 200 {
|
respond /version 200 {
|
||||||
|
@ -63,80 +61,75 @@ func TestMapRespondWithDefault(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMapAsJson(t *testing.T) {
|
func TestMapAsJson(t *testing.T) {
|
||||||
|
|
||||||
// arrange
|
// arrange
|
||||||
tester := caddytest.NewTester(t)
|
tester := caddytest.NewTester(t)
|
||||||
tester.InitServer(`{
|
tester.InitServer(`
|
||||||
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
"http": {
|
"http": {
|
||||||
"http_port": 9080,
|
"http_port": 9080,
|
||||||
"https_port": 9443,
|
"https_port": 9443,
|
||||||
"servers": {
|
"servers": {
|
||||||
"srv0": {
|
"srv0": {
|
||||||
"listen": [
|
"listen": [
|
||||||
":9080"
|
":9080"
|
||||||
],
|
],
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
|
||||||
"handler": "map",
|
|
||||||
"source": "http.request.method",
|
|
||||||
"destination": "dest-name",
|
|
||||||
"default": "unknown",
|
|
||||||
"items": [
|
|
||||||
{
|
{
|
||||||
"expression": "GET",
|
"handler": "subroute",
|
||||||
"value": "get-called"
|
"routes": [
|
||||||
},
|
{
|
||||||
{
|
"handle": [
|
||||||
"expression": "POST",
|
{
|
||||||
"value": "post-called"
|
"handler": "map",
|
||||||
|
"source": "{http.request.method}",
|
||||||
|
"destinations": ["dest-name"],
|
||||||
|
"defaults": ["unknown"],
|
||||||
|
"mappings": [
|
||||||
|
{
|
||||||
|
"input": "GET",
|
||||||
|
"outputs": ["get-called"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "POST",
|
||||||
|
"outputs": ["post-called"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "hello from localhost {dest-name}",
|
||||||
|
"handler": "static_response",
|
||||||
|
"status_code": 200
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"path": ["/version"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
}
|
"match": [
|
||||||
]
|
{
|
||||||
},
|
"host": ["localhost"]
|
||||||
{
|
}
|
||||||
"handle": [
|
],
|
||||||
{
|
"terminal": true
|
||||||
"body": "hello from localhost {dest-name}",
|
|
||||||
"handler": "static_response",
|
|
||||||
"status_code": 200
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/version"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}`, "json")
|
||||||
`, "json")
|
|
||||||
|
|
||||||
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called")
|
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")
|
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called")
|
||||||
|
|
|
@ -99,7 +99,7 @@ func TestDefaultSNI(t *testing.T) {
|
||||||
|
|
||||||
// act and assert
|
// act and assert
|
||||||
// makes a request with no sni
|
// makes a request with no sni
|
||||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
|
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
|
func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
|
||||||
|
@ -204,7 +204,6 @@ func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
|
func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
|
||||||
|
|
||||||
// arrange
|
// arrange
|
||||||
tester := caddytest.NewTester(t)
|
tester := caddytest.NewTester(t)
|
||||||
tester.InitServer(`
|
tester.InitServer(`
|
||||||
|
@ -273,7 +272,7 @@ func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
|
||||||
|
|
||||||
// act and assert
|
// act and assert
|
||||||
// makes a request with no sni
|
// makes a request with no sni
|
||||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
|
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHttpOnlyOnDomainWithSNI(t *testing.T) {
|
func TestHttpOnlyOnDomainWithSNI(t *testing.T) {
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
package maphandler
|
package maphandler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
)
|
)
|
||||||
|
@ -23,49 +25,75 @@ func init() {
|
||||||
httpcaddyfile.RegisterHandlerDirective("map", parseCaddyfile)
|
httpcaddyfile.RegisterHandlerDirective("map", parseCaddyfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseCaddyfile sets up the handler for a map from Caddyfile tokens. Syntax:
|
// parseCaddyfile sets up the map handler from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// map <source> <dest> {
|
// map [<matcher>] <source> <destinations...> {
|
||||||
// [default <default>] - used if not match is found
|
// [~]<input> <outputs...>
|
||||||
// [<regexp> <replacement>] - regular expression to match against the source find and the matching replacement value
|
// default <defaults...>
|
||||||
// ...
|
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// The map takes a source variable and maps it into the dest variable. The mapping process
|
// If the input value is prefixed with a tilde (~), then the input will be parsed as a
|
||||||
// will check the source variable for the first successful match against a list of regular expressions.
|
// regular expression.
|
||||||
// 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.
|
|
||||||
//
|
//
|
||||||
|
// The number of outputs for each mapping must not be more than the number of destinations.
|
||||||
|
// However, for convenience, there may be fewer outputs than destinations and any missing
|
||||||
|
// outputs will be filled in implicitly.
|
||||||
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
m := new(Handler)
|
var handler Handler
|
||||||
|
|
||||||
for h.Next() {
|
for h.Next() {
|
||||||
// first see if source and dest are configured
|
// source
|
||||||
if h.NextArg() {
|
if !h.NextArg() {
|
||||||
m.Source = h.Val()
|
return nil, h.ArgErr()
|
||||||
if h.NextArg() {
|
}
|
||||||
m.Destination = h.Val()
|
handler.Source = h.Val()
|
||||||
}
|
|
||||||
|
// destinations
|
||||||
|
handler.Destinations = h.RemainingArgs()
|
||||||
|
if len(handler.Destinations) == 0 {
|
||||||
|
return nil, h.Err("missing destination argument(s)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// load the rules
|
// mappings
|
||||||
for h.NextBlock(0) {
|
for h.NextBlock(0) {
|
||||||
expression := h.Val()
|
// defaults are a special case
|
||||||
if expression == "default" {
|
if h.Val() == "default" {
|
||||||
args := h.RemainingArgs()
|
if len(handler.Defaults) > 0 {
|
||||||
if len(args) != 1 {
|
return nil, h.Err("defaults already defined")
|
||||||
return m, h.ArgErr()
|
|
||||||
}
|
}
|
||||||
m.Default = args[0]
|
handler.Defaults = h.RemainingArgs()
|
||||||
} else {
|
for len(handler.Defaults) < len(handler.Destinations) {
|
||||||
args := h.RemainingArgs()
|
handler.Defaults = append(handler.Defaults, "")
|
||||||
if len(args) != 1 {
|
|
||||||
return m, h.ArgErr()
|
|
||||||
}
|
}
|
||||||
m.Items = append(m.Items, Item{Expression: expression, Value: args[0]})
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// every other line maps one input to one or more outputs
|
||||||
|
in := h.Val()
|
||||||
|
outs := h.RemainingArgs()
|
||||||
|
|
||||||
|
// cannot have more outputs than destinations
|
||||||
|
if len(outs) > len(handler.Destinations) {
|
||||||
|
return nil, h.Err("too many outputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// for convenience, can have fewer outputs than destinations, but the
|
||||||
|
// underlying handler won't accept that, so we fill in empty values
|
||||||
|
for len(outs) < len(handler.Destinations) {
|
||||||
|
outs = append(outs, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the mapping
|
||||||
|
mapping := Mapping{Outputs: outs}
|
||||||
|
if strings.HasPrefix(in, "~") {
|
||||||
|
mapping.InputRegexp = in[1:]
|
||||||
|
} else {
|
||||||
|
mapping.Input = in
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.Mappings = append(handler.Mappings, mapping)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, nil
|
return handler, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,11 @@
|
||||||
package maphandler
|
package maphandler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
|
@ -26,27 +29,26 @@ func init() {
|
||||||
caddy.RegisterModule(Handler{})
|
caddy.RegisterModule(Handler{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler is a middleware that maps a source placeholder to a destination
|
// Handler implements a middleware that maps inputs to outputs. Specifically, it
|
||||||
// placeholder.
|
// compares a source value against the map inputs, and for one that matches, it
|
||||||
//
|
// applies the output values to each destination. Destinations become placeholder
|
||||||
// The mapping process happens early in the request handling lifecycle so that
|
// names.
|
||||||
// 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.
|
|
||||||
//
|
//
|
||||||
|
// Mapped placeholders are not evaluated until they are used, so even for very
|
||||||
|
// large mappings, this handler is quite efficient.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
// Source is a placeholder
|
// Source is the placeholder from which to get the input value.
|
||||||
Source string `json:"source,omitempty"`
|
Source string `json:"source,omitempty"`
|
||||||
// Destination is a new placeholder
|
|
||||||
Destination string `json:"destination,omitempty"`
|
// Destinations are the placeholders in which to store the outputs.
|
||||||
// Default is an optional value to use if no other was found
|
Destinations []string `json:"destinations,omitempty"`
|
||||||
Default string `json:"default,omitempty"`
|
|
||||||
// Items is an array of regex expressions and values
|
// Mappings from source values (inputs) to destination values (outputs).
|
||||||
Items []Item `json:"items,omitempty"`
|
// The first matching mapping will be applied.
|
||||||
|
Mappings []Mapping `json:"mappings,omitempty"`
|
||||||
|
|
||||||
|
// If no mappings match, the default value will be applied (optional).
|
||||||
|
Defaults []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaddyModule returns the Caddy module information.
|
// CaddyModule returns the Caddy module information.
|
||||||
|
@ -57,10 +59,52 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provision will compile all regular expressions
|
// Provision sets up h.
|
||||||
func (h *Handler) Provision(_ caddy.Context) error {
|
func (h *Handler) Provision(_ caddy.Context) error {
|
||||||
for i := 0; i < len(h.Items); i++ {
|
for j, dest := range h.Destinations {
|
||||||
h.Items[i].compiled = regexp.MustCompile(h.Items[i].Expression)
|
h.Destinations[j] = strings.Trim(dest, "{}")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, m := range h.Mappings {
|
||||||
|
if m.InputRegexp == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m.Input != "" {
|
||||||
|
return fmt.Errorf("mapping %d has both input and input_regexp fields specified, which is confusing", i)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
h.Mappings[i].re, err = regexp.Compile(m.InputRegexp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("compiling regexp for mapping %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: improve efficiency even further by using an actual map type
|
||||||
|
// for the non-regexp mappings, OR sort them and do a binary search
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate ensures that h is configured properly.
|
||||||
|
func (h *Handler) Validate() error {
|
||||||
|
nDest, nDef := len(h.Destinations), len(h.Defaults)
|
||||||
|
if nDef > 0 && nDef != nDest {
|
||||||
|
return fmt.Errorf("%d destinations != %d defaults", nDest, nDef)
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]int)
|
||||||
|
for i, m := range h.Mappings {
|
||||||
|
// prevent duplicate mappings
|
||||||
|
if prev, ok := seen[m.Input]; ok {
|
||||||
|
return fmt.Errorf("mapping %d has a duplicate input '%s' previously used with mapping %d", i, m.Input, prev)
|
||||||
|
}
|
||||||
|
seen[m.Input] = i
|
||||||
|
|
||||||
|
// ensure mappings have 1:1 output-to-destination correspondence
|
||||||
|
nOut := len(m.Outputs)
|
||||||
|
if nOut != nDest {
|
||||||
|
return fmt.Errorf("mapping %d has %d outputs but there are %d destinations defined", i, nOut, nDest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -68,38 +112,73 @@ func (h *Handler) Provision(_ caddy.Context) error {
|
||||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
|
||||||
// get the source value, if the source value was not found do no
|
// defer work until a variable is actually evaluated by using replacer's Map callback
|
||||||
// replacement.
|
repl.Map(func(key string) (interface{}, bool) {
|
||||||
val, ok := repl.GetString(h.Source)
|
// return early if the variable is not even a configured destination
|
||||||
if ok {
|
destIdx := h.destinationIndex(key)
|
||||||
found := false
|
if destIdx < 0 {
|
||||||
for i := 0; i < len(h.Items); i++ {
|
return nil, false
|
||||||
if h.Items[i].compiled.MatchString(val) {
|
}
|
||||||
found = true
|
|
||||||
repl.Set(h.Destination, h.Items[i].Value)
|
input := repl.ReplaceAll(h.Source, "")
|
||||||
break
|
|
||||||
|
// find the first mapping matching the input and return
|
||||||
|
// the requested destination/output value
|
||||||
|
for _, m := range h.Mappings {
|
||||||
|
log.Printf("MAPPING: %+v", m)
|
||||||
|
if m.re != nil {
|
||||||
|
if m.re.MatchString(input) {
|
||||||
|
return m.Outputs[destIdx], true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if input == m.Input {
|
||||||
|
log.Printf("RETURNING: %s", m.Outputs[destIdx])
|
||||||
|
return m.Outputs[destIdx], true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found && h.Default != "" {
|
// fall back to default if no match
|
||||||
repl.Set(h.Destination, h.Default)
|
if len(h.Defaults) > destIdx {
|
||||||
|
return h.Defaults[destIdx], true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return nil, true
|
||||||
|
})
|
||||||
|
|
||||||
return next.ServeHTTP(w, r)
|
return next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item defines each entry in the map
|
// destinationIndex returns the positional index of the destination
|
||||||
type Item struct {
|
// is name is a known destination; otherwise it returns -1.
|
||||||
// Expression is the regular expression searched for
|
func (h Handler) destinationIndex(name string) int {
|
||||||
Expression string `json:"expression,omitempty"`
|
for i, dest := range h.Destinations {
|
||||||
// Value to use once the expression has been found
|
if dest == name {
|
||||||
Value string `json:"value,omitempty"`
|
return i
|
||||||
// compiled expression, internal use
|
}
|
||||||
compiled *regexp.Regexp
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mapping describes a mapping from input to outputs.
|
||||||
|
type Mapping struct {
|
||||||
|
// The input value to match. Must be distinct from other mappings.
|
||||||
|
// Mutually exclusive to input_regexp.
|
||||||
|
Input string `json:"input,omitempty"`
|
||||||
|
|
||||||
|
// The input regular expression to match. Mutually exclusive to input.
|
||||||
|
InputRegexp string `json:"input_regexp,omitempty"`
|
||||||
|
|
||||||
|
// Upon a match with the input, each output is positionally correlated
|
||||||
|
// with each destination of the parent handler.
|
||||||
|
Outputs []string `json:"outputs,omitempty"`
|
||||||
|
|
||||||
|
re *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ caddy.Provisioner = (*Handler)(nil)
|
_ caddy.Provisioner = (*Handler)(nil)
|
||||||
|
_ caddy.Validator = (*Handler)(nil)
|
||||||
_ caddyhttp.MiddlewareHandler = (*Handler)(nil)
|
_ caddyhttp.MiddlewareHandler = (*Handler)(nil)
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue