Skip to content

Commit 258d906

Browse files
francislavoiemholt
andauthored
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 <[email protected]> --------- Co-authored-by: Matt Holt <[email protected]>
1 parent 69290d2 commit 258d906

File tree

2 files changed

+89
-15
lines changed

2 files changed

+89
-15
lines changed

caddyconfig/httpcaddyfile/directives.go

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,25 @@ import (
2727
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
2828
)
2929

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.
3234
//
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.
3639
//
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{
4046
"tracing",
4147

48+
// set variables that may be used by other directives
4249
"map",
4350
"vars",
4451
"fs",
@@ -85,6 +92,11 @@ var directiveOrder = []string{
8592
"acme_server",
8693
}
8794

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+
88100
// directiveIsOrdered returns true if dir is
89101
// a known, ordered (sorted) directive.
90102
func directiveIsOrdered(dir string) bool {
@@ -131,6 +143,58 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
131143
})
132144
}
133145

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+
134198
// RegisterGlobalOption registers a unique global option opt with
135199
// an associated unmarshaling (setup) function. When the global
136200
// option opt is encountered in a Caddyfile, setupFunc will be
@@ -555,6 +619,16 @@ func (sb serverBlock) isAllHTTP() bool {
555619
return true
556620
}
557621

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+
558632
type (
559633
// UnmarshalFunc is a function which can unmarshal Caddyfile
560634
// tokens into zero or more config values using a Helper type.

caddyconfig/httpcaddyfile/options.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
107107
if !d.Next() {
108108
return nil, d.ArgErr()
109109
}
110-
pos := d.Val()
110+
pos := Positional(d.Val())
111111

112112
newOrder := directiveOrder
113113

@@ -121,22 +121,22 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
121121

122122
// act on the positional
123123
switch pos {
124-
case "first":
124+
case First:
125125
newOrder = append([]string{dirName}, newOrder...)
126126
if d.NextArg() {
127127
return nil, d.ArgErr()
128128
}
129129
directiveOrder = newOrder
130130
return newOrder, nil
131-
case "last":
131+
case Last:
132132
newOrder = append(newOrder, dirName)
133133
if d.NextArg() {
134134
return nil, d.ArgErr()
135135
}
136136
directiveOrder = newOrder
137137
return newOrder, nil
138-
case "before":
139-
case "after":
138+
case Before:
139+
case After:
140140
default:
141141
return nil, d.Errf("unknown positional '%s'", pos)
142142
}
@@ -153,9 +153,9 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
153153
// insert directive into proper position
154154
for i, d := range newOrder {
155155
if d == otherDir {
156-
if pos == "before" {
156+
if pos == Before {
157157
newOrder = append(newOrder[:i], append([]string{dirName}, newOrder[i:]...)...)
158-
} else if pos == "after" {
158+
} else if pos == After {
159159
newOrder = append(newOrder[:i+1], append([]string{dirName}, newOrder[i+1:]...)...)
160160
}
161161
break

0 commit comments

Comments
 (0)