@marcel91200 / caddy Mirror · read-only / commits / 258d9061
Fork 0

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