Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
caddyhttp: Add 'map' handler (#3199)
* inital map implementation * resolve the value during middleware execution * use regex instead * pr feedback * renamed mmap to maphandler * refactored GetString implementation * fixed mispelling * additional feedback
Mark Sargent committed
Jun 27, 2020 at 09:12 UTC
6004d3f779b8175d92d8eb7819ed800e8eddbff6
6 files changed
+328
caddyconfig/httpcaddyfile/directives.go
+1
@@ -37,6 +37,7 @@ import (
37
// The header directive goes second so that headers
38
// can be manipulated before doing redirects.
39
var directiveOrder = []string{
40
+ "map",
41
"root",
42
43
"header",
caddytest/integration/map_test.go
new
+143
@@ -0,0 +1,143 @@
1
+package integration
2
+
3
+import (
4
+ "bytes"
5
+ "testing"
6
+
7
+ "github.com/caddyserver/caddy/v2/caddytest"
8
+)
9
+
10
+func TestMap(t *testing.T) {
11
+
12
+ // arrange
13
+ tester := caddytest.NewTester(t)
14
+ tester.InitServer(`{
15
+ http_port 9080
16
+ https_port 9443
17
+ }
18
+
19
+ localhost:9080 {
20
+
21
+ map http.request.method dest-name {
22
+ default unknown
23
+ G.T get-called
24
+ POST post-called
25
+ }
26
+
27
+ respond /version 200 {
28
+ body "hello from localhost {dest-name}"
29
+ }
30
+ }
31
+ `, "caddyfile")
32
+
33
+ // act and assert
34
+ tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called")
35
+ tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called")
36
+}
37
+
38
+func TestMapRespondWithDefault(t *testing.T) {
39
+
40
+ // arrange
41
+ tester := caddytest.NewTester(t)
42
+ tester.InitServer(`{
43
+ http_port 9080
44
+ https_port 9443
45
+ }
46
+
47
+ localhost:9080 {
48
+
49
+ map http.request.method dest-name {
50
+ default unknown
51
+ GET get-called
52
+ }
53
+
54
+ respond /version 200 {
55
+ body "hello from localhost {dest-name}"
56
+ }
57
+ }
58
+ `, "caddyfile")
59
+
60
+ // act and assert
61
+ tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called")
62
+ tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost unknown")
63
+}
64
+
65
+func TestMapAsJson(t *testing.T) {
66
+
67
+ // arrange
68
+ tester := caddytest.NewTester(t)
69
+ tester.InitServer(`{
70
+ "apps": {
71
+ "http": {
72
+ "http_port": 9080,
73
+ "https_port": 9443,
74
+ "servers": {
75
+ "srv0": {
76
+ "listen": [
77
+ ":9080"
78
+ ],
79
+ "routes": [
80
+ {
81
+ "handle": [
82
+ {
83
+ "handler": "subroute",
84
+ "routes": [
85
+ {
86
+ "handle": [
87
+ {
88
+ "handler": "map",
89
+ "source": "http.request.method",
90
+ "destination": "dest-name",
91
+ "default": "unknown",
92
+ "items": [
93
+ {
94
+ "expression": "GET",
95
+ "value": "get-called"
96
+ },
97
+ {
98
+ "expression": "POST",
99
+ "value": "post-called"
100
+ }
101
+ ]
102
+ }
103
+ ]
104
+ },
105
+ {
106
+ "handle": [
107
+ {
108
+ "body": "hello from localhost {dest-name}",
109
+ "handler": "static_response",
110
+ "status_code": 200
111
+ }
112
+ ],
113
+ "match": [
114
+ {
115
+ "path": [
116
+ "/version"
117
+ ]
118
+ }
119
+ ]
120
+ }
121
+ ]
122
+ }
123
+ ],
124
+ "match": [
125
+ {
126
+ "host": [
127
+ "localhost"
128
+ ]
129
+ }
130
+ ],
131
+ "terminal": true
132
+ }
133
+ ]
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ `, "json")
140
+
141
+ tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called")
142
+ tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called")
143
+}
modules/caddyhttp/map/caddyfile.go
new
+71
@@ -0,0 +1,71 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package maphandler
16
+
17
+import (
18
+ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
19
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
20
+)
21
+
22
+func init() {
23
+ httpcaddyfile.RegisterHandlerDirective("map", parseCaddyfile)
24
+}
25
+
26
+// parseCaddyfile sets up the handler for a map from Caddyfile tokens. Syntax:
27
+//
28
+// map <source> <dest> {
29
+// [default <default>] - used if not match is found
30
+// [<regexp> <replacement>] - regular expression to match against the source find and the matching replacement value
31
+// ...
32
+// }
33
+//
34
+// The map takes a source variable and maps it into the dest variable. The mapping process
35
+// will check the source variable for the first successful match against a list of regular expressions.
36
+// If a successful match is found the dest variable will contain the replacement value.
37
+// If no successful match is found and the default is specified then the dest will contain the default value.
38
+//
39
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
40
+ m := new(Handler)
41
+
42
+ for h.Next() {
43
+ // first see if source and dest are configured
44
+ if h.NextArg() {
45
+ m.Source = h.Val()
46
+ if h.NextArg() {
47
+ m.Destination = h.Val()
48
+ }
49
+ }
50
+
51
+ // load the rules
52
+ for h.NextBlock(0) {
53
+ expression := h.Val()
54
+ if expression == "default" {
55
+ args := h.RemainingArgs()
56
+ if len(args) != 1 {
57
+ return m, h.ArgErr()
58
+ }
59
+ m.Default = args[0]
60
+ } else {
61
+ args := h.RemainingArgs()
62
+ if len(args) != 1 {
63
+ return m, h.ArgErr()
64
+ }
65
+ m.Items = append(m.Items, Item{Expression: expression, Value: args[0]})
66
+ }
67
+ }
68
+ }
69
+
70
+ return m, nil
71
+}
modules/caddyhttp/map/map.go
new
+105
@@ -0,0 +1,105 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package maphandler
16
+
17
+import (
18
+ "net/http"
19
+ "regexp"
20
+
21
+ "github.com/caddyserver/caddy/v2"
22
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
23
+)
24
+
25
+func init() {
26
+ caddy.RegisterModule(Handler{})
27
+}
28
+
29
+// Handler is a middleware that maps a source placeholder to a destination
30
+// placeholder.
31
+//
32
+// The mapping process happens early in the request handling lifecycle so that
33
+// the Destination placeholder is calculated and available for substitution.
34
+// The Items array contains pairs of regex expressions and values, the
35
+// Source is matched against the expression, if they match then the destination
36
+// placeholder is set to the value.
37
+//
38
+// The Default is optional, if no Item expression is matched then the value of
39
+// the Default will be used.
40
+//
41
+type Handler struct {
42
+ // Source is a placeholder
43
+ Source string `json:"source,omitempty"`
44
+ // Destination is a new placeholder
45
+ Destination string `json:"destination,omitempty"`
46
+ // Default is an optional value to use if no other was found
47
+ Default string `json:"default,omitempty"`
48
+ // Items is an array of regex expressions and values
49
+ Items []Item `json:"items,omitempty"`
50
+}
51
+
52
+// CaddyModule returns the Caddy module information.
53
+func (Handler) CaddyModule() caddy.ModuleInfo {
54
+ return caddy.ModuleInfo{
55
+ ID: "http.handlers.map",
56
+ New: func() caddy.Module { return new(Handler) },
57
+ }
58
+}
59
+
60
+// Provision will compile all regular expressions
61
+func (h *Handler) Provision(_ caddy.Context) error {
62
+ for i := 0; i < len(h.Items); i++ {
63
+ h.Items[i].compiled = regexp.MustCompile(h.Items[i].Expression)
64
+ }
65
+ return nil
66
+}
67
+
68
+func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
69
+ repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
70
+
71
+ // get the source value, if the source value was not found do no
72
+ // replacement.
73
+ val, ok := repl.GetString(h.Source)
74
+ if ok {
75
+ found := false
76
+ for i := 0; i < len(h.Items); i++ {
77
+ if h.Items[i].compiled.MatchString(val) {
78
+ found = true
79
+ repl.Set(h.Destination, h.Items[i].Value)
80
+ break
81
+ }
82
+ }
83
+
84
+ if !found && h.Default != "" {
85
+ repl.Set(h.Destination, h.Default)
86
+ }
87
+ }
88
+ return next.ServeHTTP(w, r)
89
+}
90
+
91
+// Item defines each entry in the map
92
+type Item struct {
93
+ // Expression is the regular expression searched for
94
+ Expression string `json:"expression,omitempty"`
95
+ // Value to use once the expression has been found
96
+ Value string `json:"value,omitempty"`
97
+ // compiled expression, internal use
98
+ compiled *regexp.Regexp
99
+}
100
+
101
+// Interface guards
102
+var (
103
+ _ caddy.Provisioner = (*Handler)(nil)
104
+ _ caddyhttp.MiddlewareHandler = (*Handler)(nil)
105
+)
modules/caddyhttp/standard/imports.go
+1
@@ -9,6 +9,7 @@ import (
9
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/zstd"
10
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
11
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
12
+ _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/map"
13
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/requestbody"
14
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
15
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy/fastcgi"
replacer.go
+7
@@ -66,6 +66,13 @@ func (r *Replacer) Get(variable string) (interface{}, bool) {
66
return nil, false
67
}
68
69
+// GetString is the same as Get, but coerces the value to a
70
+// string representation.
71
+func (r *Replacer) GetString(variable string) (string, bool) {
72
+ s, found := r.Get(variable)
73
+ return toString(s), found
74
+}
75
+
76
// Delete removes a variable with a static value
77
// that was created using Set.
78
func (r *Replacer) Delete(variable string) {