Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 15:45 UTC
httpcaddyfile: Add `RegisterDirectiveOrder` function for plugin authors (#5865)
* httpcaddyfile: Add `RegisterDirectiveOrder` function for plugin authors * Set up Positional enum * Linter doesn't like a switch on an enum with default * Update caddyconfig/httpcaddyfile/directives.go Co-authored-by: Matt Holt <mholt@users.noreply.github.com> --------- Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Francis Lavoie committed
Mar 6, 2024 at 14:41 UTC
258d9061401091c014c247d39d33d07bc178c66c
2 files changed
+89
-15
caddyconfig/httpcaddyfile/directives.go
+82
-8
@@ -27,18 +27,25 @@ import (
27
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
28
)
29
30
-// directiveOrder specifies the order
31
-// to apply directives in HTTP routes.
30
+// defaultDirectiveOrder specifies the default order
31
+// to apply directives in HTTP routes. This must only
32
+// consist of directives that are included in Caddy's
33
+// standard distribution.
34
//
33
-// The root directive goes first in case rewrites or
34
-// redirects depend on existence of files, i.e. the
35
-// file matcher, which must know the root first.
35
+// e.g. The 'root' directive goes near the start in
36
+// case rewrites or redirects depend on existence of
37
+// files, i.e. the file matcher, which must know the
38
+// root first.
39
//
37
-// The header directive goes second so that headers
38
-// can be manipulated before doing redirects.
39
-var directiveOrder = []string{
40
+// e.g. The 'header' directive goes before 'redir' so
41
+// that headers can be manipulated before doing redirects.
42
+//
43
+// e.g. The 'respond' directive is near the end because it
44
+// writes a response and terminates the middleware chain.
45
+var defaultDirectiveOrder = []string{
46
"tracing",
47
48
+ // set variables that may be used by other directives
49
"map",
50
"vars",
51
"fs",
@@ -85,6 +92,11 @@ var directiveOrder = []string{
92
"acme_server",
93
}
94
95
+// directiveOrder specifies the order to apply directives
96
+// in HTTP routes, after being modified by either the
97
+// plugins or by the user via the "order" global option.
98
+var directiveOrder = defaultDirectiveOrder
99
+
100
// directiveIsOrdered returns true if dir is
101
// a known, ordered (sorted) directive.
102
func directiveIsOrdered(dir string) bool {
@@ -131,6 +143,58 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
143
})
144
}
145
146
+// RegisterDirectiveOrder registers the default order for a
147
+// directive from a plugin.
148
+//
149
+// This is useful when a plugin has a well-understood place
150
+// it should run in the middleware pipeline, and it allows
151
+// users to avoid having to define the order themselves.
152
+//
153
+// The directive dir may be placed in the position relative
154
+// to ('before' or 'after') a directive included in Caddy's
155
+// standard distribution. It cannot be relative to another
156
+// plugin's directive.
157
+//
158
+// EXPERIMENTAL: This API may change or be removed.
159
+func RegisterDirectiveOrder(dir string, position Positional, standardDir string) {
160
+ // check if directive was already ordered
161
+ if directiveIsOrdered(dir) {
162
+ panic("directive '" + dir + "' already ordered")
163
+ }
164
+
165
+ if position != Before && position != After {
166
+ panic("the 2nd argument must be either 'before' or 'after', got '" + position + "'")
167
+ }
168
+
169
+ // check if directive exists in standard distribution, since
170
+ // we can't allow plugins to depend on one another; we can't
171
+ // guarantee the order that plugins are loaded in.
172
+ foundStandardDir := false
173
+ for _, d := range defaultDirectiveOrder {
174
+ if d == standardDir {
175
+ foundStandardDir = true
176
+ }
177
+ }
178
+ if !foundStandardDir {
179
+ panic("the 3rd argument '" + standardDir + "' must be a directive that exists in the standard distribution of Caddy")
180
+ }
181
+
182
+ // insert directive into proper position
183
+ newOrder := directiveOrder
184
+ for i, d := range newOrder {
185
+ if d != standardDir {
186
+ continue
187
+ }
188
+ if position == Before {
189
+ newOrder = append(newOrder[:i], append([]string{dir}, newOrder[i:]...)...)
190
+ } else if position == After {
191
+ newOrder = append(newOrder[:i+1], append([]string{dir}, newOrder[i+1:]...)...)
192
+ }
193
+ break
194
+ }
195
+ directiveOrder = newOrder
196
+}
197
+
198
// RegisterGlobalOption registers a unique global option opt with
199
// an associated unmarshaling (setup) function. When the global
200
// option opt is encountered in a Caddyfile, setupFunc will be
@@ -555,6 +619,16 @@ func (sb serverBlock) isAllHTTP() bool {
619
return true
620
}
621
622
+// Positional are the supported modes for ordering directives.
623
+type Positional string
624
+
625
+const (
626
+ Before Positional = "before"
627
+ After Positional = "after"
628
+ First Positional = "first"
629
+ Last Positional = "last"
630
+)
631
+
632
type (
633
// UnmarshalFunc is a function which can unmarshal Caddyfile
634
// tokens into zero or more config values using a Helper type.
caddyconfig/httpcaddyfile/options.go
+7
-7
@@ -107,7 +107,7 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
107
if !d.Next() {
108
return nil, d.ArgErr()
109
}
110
- pos := d.Val()
110
+ pos := Positional(d.Val())
111
112
newOrder := directiveOrder
113
@@ -121,22 +121,22 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
121
122
// act on the positional
123
switch pos {
124
- case "first":
124
+ case First:
125
newOrder = append([]string{dirName}, newOrder...)
126
if d.NextArg() {
127
return nil, d.ArgErr()
128
}
129
directiveOrder = newOrder
130
return newOrder, nil
131
- case "last":
131
+ case Last:
132
newOrder = append(newOrder, dirName)
133
if d.NextArg() {
134
return nil, d.ArgErr()
135
}
136
directiveOrder = newOrder
137
return newOrder, nil
138
- case "before":
139
- case "after":
138
+ case Before:
139
+ case After:
140
default:
141
return nil, d.Errf("unknown positional '%s'", pos)
142
}
@@ -153,9 +153,9 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
153
// insert directive into proper position
154
for i, d := range newOrder {
155
if d == otherDir {
156
- if pos == "before" {
156
+ if pos == Before {
157
newOrder = append(newOrder[:i], append([]string{dirName}, newOrder[i:]...)...)
158
- } else if pos == "after" {
158
+ } else if pos == After {
159
newOrder = append(newOrder[:i+1], append([]string{dirName}, newOrder[i+1:]...)...)
160
}
161
break