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
Refactor Caddyfile adapter and module registration
Use piles from which to draw config values. Module values can return their name, so now we can do two-way mapping from value to name and name to value; whereas before we could only map name to value. This was problematic with the Caddyfile adapter since it receives values and needs to know the name to put in the config.
Matthew Holt committed
Aug 21, 2019 at 10:46 UTC
c9980fd3671d873a7197a5ac4d6ac9d6b046abb6
46 files changed
+1480
-1073
admin.go
+8
-3
@@ -95,9 +95,9 @@ func StartAdmin(initialConfigJSON []byte) error {
95
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
96
///// END PPROF STUFF //////
97
98
- for _, m := range GetModules("admin") {
99
- routes := m.New().([]AdminRoute)
100
- for _, route := range routes {
98
+ for _, m := range GetModules("admin.routers") {
99
+ adminrtr := m.New().(AdminRouter)
100
+ for _, route := range adminrtr.Routes() {
101
mux.Handle(route.Pattern, route)
102
}
103
}
@@ -146,6 +146,11 @@ func StopAdmin() error {
146
return nil
147
}
148
149
+// AdminRouter is a type which can return routes for the admin API.
150
+type AdminRouter interface {
151
+ Routes() []AdminRoute
152
+}
153
+
154
// AdminRoute represents a route for the admin endpoint.
155
type AdminRoute struct {
156
http.Handler
caddyconfig/caddyfile/adapter.go
+1
-7
@@ -37,14 +37,12 @@ func (a Adapter) Adapt(body []byte, options map[string]string) ([]byte, []caddyc
37
options = make(map[string]string)
38
}
39
40
- directives := a.ServerType.ValidDirectives()
41
-
40
filename := options["filename"]
41
if filename == "" {
42
filename = "Caddyfile"
43
}
44
47
- serverBlocks, err := Parse(filename, bytes.NewReader(body), directives)
45
+ serverBlocks, err := Parse(filename, bytes.NewReader(body))
46
if err != nil {
47
return nil, nil, err
48
}
@@ -77,10 +75,6 @@ type Unmarshaler interface {
75
76
// ServerType is a type that can evaluate a Caddyfile and set up a caddy config.
77
type ServerType interface {
80
- // ValidDirectives returns a list of the
81
- // server type's recognized directives.
82
- ValidDirectives() []string
83
-
78
// Setup takes the server blocks which
79
// contain tokens, as well as options
80
// (e.g. CLI flags) and creates a Caddy
caddyconfig/caddyfile/dispenser.go
+19
-11
@@ -31,6 +31,7 @@ type Dispenser struct {
31
}
32
33
// NewDispenser returns a Dispenser filled with the given tokens.
34
+// TODO: Get rid of the filename argument; it seems pointless here
35
func NewDispenser(filename string, tokens []Token) *Dispenser {
36
return &Dispenser{
37
filename: filename,
@@ -51,15 +52,15 @@ func (d *Dispenser) Next() bool {
52
}
53
54
// Prev moves to the previous token. It does the inverse
54
-// of Next(). Generally, this should only be used in
55
-// special cases such as deleting a token from the slice
56
-// that d is iterating. In that case, without using Prev(),
57
-// the dispenser would be pointing at the wrong token since
58
-// deleting a token implicitly advances the cursor.
55
+// of Next(), except this function may decrement the cursor
56
+// to -1 so that the next call to Next() points to the
57
+// first token; this allows dispensing to "start over". This
58
+// method returns true if the cursor ends up pointing to a
59
+// valid token.
60
func (d *Dispenser) Prev() bool {
60
- if d.cursor > 0 {
61
+ if d.cursor > -1 {
62
d.cursor--
62
- return true
63
+ return d.cursor > -1
64
}
65
return false
66
}
@@ -223,8 +224,7 @@ func (d *Dispenser) RemainingArgs() []string {
224
// "directive" whether that be to the end of the line or
225
// the end of a block that starts at the end of the line.
226
func (d *Dispenser) NewFromNextTokens() *Dispenser {
226
- var tkns []Token
227
- tkns = append(tkns, d.Token())
227
+ tkns := []Token{d.Token()}
228
for d.NextArg() {
229
tkns = append(tkns, d.Token())
230
}
@@ -245,10 +245,14 @@ func (d *Dispenser) NewFromNextTokens() *Dispenser {
245
246
// Token returns the current token.
247
func (d *Dispenser) Token() Token {
248
- if d.cursor < 0 || d.cursor >= len(d.tokens) {
248
+ return d.TokenAt(d.cursor)
249
+}
250
+
251
+func (d *Dispenser) TokenAt(cursor int) Token {
252
+ if cursor < 0 || cursor >= len(d.tokens) {
253
return Token{}
254
}
251
- return d.tokens[d.cursor]
255
+ return d.tokens[cursor]
256
}
257
258
// Cursor returns the current cursor (token index).
@@ -256,6 +260,10 @@ func (d *Dispenser) Cursor() int {
260
return d.cursor
261
}
262
263
+func (d *Dispenser) Reset() {
264
+ d.cursor = -1
265
+}
266
+
267
// ArgErr returns an argument error, meaning that another
268
// argument was expected but not found. In other words,
269
// a line break or open curly brace was encountered instead of
caddyconfig/caddyfile/parse.go
+71
-41
@@ -28,12 +28,12 @@ import (
28
// Directives that do not appear in validDirectives will cause
29
// an error. If you do not want to check for valid directives,
30
// pass in nil instead.
31
-func Parse(filename string, input io.Reader, validDirectives []string) ([]ServerBlock, error) {
31
+func Parse(filename string, input io.Reader) ([]ServerBlock, error) {
32
tokens, err := allTokens(input)
33
if err != nil {
34
return nil, err
35
}
36
- p := parser{Dispenser: NewDispenser(filename, tokens), validDirectives: validDirectives}
36
+ p := parser{Dispenser: NewDispenser(filename, tokens)}
37
return p.parseAll()
38
}
39
@@ -56,9 +56,9 @@ func allTokens(input io.Reader) ([]Token, error) {
56
type parser struct {
57
*Dispenser
58
block ServerBlock // current server block being parsed
59
- validDirectives []string // a directive must be valid or it's an error
59
eof bool // if we encounter a valid EOF in a hard place
60
definedSnippets map[string][]Token
61
+ nesting int
62
}
63
64
func (p *parser) parseAll() ([]ServerBlock, error) {
@@ -72,14 +72,16 @@ func (p *parser) parseAll() ([]ServerBlock, error) {
72
if len(p.block.Keys) > 0 {
73
blocks = append(blocks, p.block)
74
}
75
+ if p.nesting > 0 {
76
+ return blocks, p.EOFErr()
77
+ }
78
}
79
80
return blocks, nil
81
}
82
83
func (p *parser) parseOne() error {
81
- p.block = ServerBlock{Tokens: make(map[string][]Token)}
82
-
84
+ p.block = ServerBlock{}
85
return p.begin()
86
}
87
@@ -186,7 +188,7 @@ func (p *parser) blockContents() error {
188
return err
189
}
190
189
- // Only look for close curly brace if there was an opening
191
+ // only look for close curly brace if there was an opening
192
if errOpenCurlyBrace == nil {
193
err = p.closeCurlyBrace()
194
if err != nil {
@@ -205,6 +207,7 @@ func (p *parser) directives() error {
207
for p.Next() {
208
// end of server block
209
if p.Val() == "}" {
210
+ // p.nesting has already been decremented
211
break
212
}
213
@@ -218,11 +221,15 @@ func (p *parser) directives() error {
221
continue
222
}
223
221
- // normal case: parse a directive on this line
224
+ // normal case: parse a directive as a new segment
225
+ // (a "segment" is a line which starts with a directive
226
+ // and which ends at the end of the line or at the end of
227
+ // the block that is opened at the end of the line)
228
if err := p.directive(); err != nil {
229
return err
230
}
231
}
232
+
233
return nil
234
}
235
@@ -345,25 +352,24 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
352
// are loaded into the current server block for later use
353
// by directive setup functions.
354
func (p *parser) directive() error {
348
- dir := replaceEnvVars(p.Val())
349
- nesting := 0
355
+ // evaluate any env vars in directive token
356
+ p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
357
351
- if !p.validDirective(dir) {
352
- return p.Errf("Unknown directive '%s'", dir)
353
- }
358
+ // a segment is a list of tokens associated with this directive
359
+ var segment Segment
360
355
- // The directive itself is appended as a relevant token
356
- p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor])
361
+ // the directive itself is appended as a relevant token
362
+ segment = append(segment, p.Token())
363
364
for p.Next() {
365
if p.Val() == "{" {
360
- nesting++
361
- } else if p.isNewLine() && nesting == 0 {
366
+ p.nesting++
367
+ } else if p.isNewLine() && p.nesting == 0 {
368
p.cursor-- // read too far
369
break
364
- } else if p.Val() == "}" && nesting > 0 {
365
- nesting--
366
- } else if p.Val() == "}" && nesting == 0 {
370
+ } else if p.Val() == "}" && p.nesting > 0 {
371
+ p.nesting--
372
+ } else if p.Val() == "}" && p.nesting == 0 {
373
return p.Err("Unexpected '}' because no matching opening brace")
374
} else if p.Val() == "import" && p.isNewLine() {
375
if err := p.doImport(); err != nil {
@@ -373,12 +379,15 @@ func (p *parser) directive() error {
379
continue
380
}
381
p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
376
- p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor])
382
+ segment = append(segment, p.Token())
383
}
384
379
- if nesting > 0 {
385
+ p.block.Segments = append(p.block.Segments, segment)
386
+
387
+ if p.nesting > 0 {
388
return p.EOFErr()
389
}
390
+
391
return nil
392
}
393
@@ -404,19 +413,6 @@ func (p *parser) closeCurlyBrace() error {
413
return nil
414
}
415
407
-// validDirective returns true if dir is in p.validDirectives.
408
-func (p *parser) validDirective(dir string) bool {
409
- if p.validDirectives == nil {
410
- return true
411
- }
412
- for _, d := range p.validDirectives {
413
- if d == dir {
414
- return true
415
- }
416
- }
417
- return false
418
-}
419
-
416
// replaceEnvVars replaces environment variables that appear in the token
417
// and understands both the $UNIX and %WINDOWS% syntaxes.
418
func replaceEnvVars(s string) string {
@@ -447,13 +443,6 @@ func replaceEnvReferences(s, refStart, refEnd string) string {
443
return s
444
}
445
450
-// ServerBlock associates any number of keys (usually addresses
451
-// of some sort) with tokens (grouped by directive name).
452
-type ServerBlock struct {
453
- Keys []string
454
- Tokens map[string][]Token
455
-}
456
-
446
func (p *parser) isSnippet() (bool, string) {
447
keys := p.block.Keys
448
// A snippet block is a single key with parens. Nothing else qualifies.
@@ -480,6 +469,7 @@ func (p *parser) snippetTokens() ([]Token, error) {
469
}
470
}
471
if p.Val() == "{" {
472
+ p.nesting++
473
count++
474
}
475
tokens = append(tokens, p.tokens[p.cursor])
@@ -490,3 +480,43 @@ func (p *parser) snippetTokens() ([]Token, error) {
480
}
481
return tokens, nil
482
}
483
+
484
+// ServerBlock associates any number of keys from the
485
+// head of the server block with tokens, which are
486
+// grouped by segments.
487
+type ServerBlock struct {
488
+ Keys []string
489
+ Segments []Segment
490
+}
491
+
492
+// DispenseDirective returns a dispenser that contains
493
+// all the tokens in the server block.
494
+func (sb ServerBlock) DispenseDirective(dir string) *Dispenser {
495
+ var tokens []Token
496
+ for _, seg := range sb.Segments {
497
+ if len(seg) > 0 && seg[0].Text == dir {
498
+ tokens = append(tokens, seg...)
499
+ }
500
+ }
501
+ return NewDispenser("", tokens)
502
+}
503
+
504
+// Segment is a list of tokens which begins with a directive
505
+// and ends at the end of the directive (either at the end of
506
+// the line, or at the end of a block it opens).
507
+type Segment []Token
508
+
509
+// Directive returns the directive name for the segment.
510
+// The directive name is the text of the first token.
511
+func (s Segment) Directive() string {
512
+ if len(s) > 0 {
513
+ return s[0].Text
514
+ }
515
+ return ""
516
+}
517
+
518
+// NewDispenser returns a dispenser for this
519
+// segment's tokens.
520
+func (s Segment) NewDispenser() *Dispenser {
521
+ return NewDispenser("", s)
522
+}
caddyconfig/caddyfile/parse_test.go
+72
-109
@@ -22,6 +22,8 @@ import (
22
"testing"
23
)
24
25
+// TODO: re-enable all tests
26
+
27
func TestAllTokens(t *testing.T) {
28
input := strings.NewReader("a b c\nd e")
29
expected := []string{"a", "b", "c", "d", "e"}
@@ -53,84 +55,67 @@ func TestParseOneAndImport(t *testing.T) {
55
input string
56
shouldErr bool
57
keys []string
56
- tokens map[string]int // map of directive name to number of tokens expected
58
+ numTokens []int // number of tokens to expect in each segment
59
}{
60
{`localhost`, false, []string{
61
"localhost",
60
- }, map[string]int{}},
62
+ }, []int{}},
63
64
{`localhost
65
dir1`, false, []string{
66
"localhost",
65
- }, map[string]int{
66
- "dir1": 1,
67
- }},
67
+ }, []int{1}},
68
69
{`localhost:1234
70
dir1 foo bar`, false, []string{
71
"localhost:1234",
72
- }, map[string]int{
73
- "dir1": 3,
74
- }},
72
+ }, []int{3},
73
+ },
74
75
{`localhost {
76
dir1
77
}`, false, []string{
78
"localhost",
80
- }, map[string]int{
81
- "dir1": 1,
82
- }},
79
+ }, []int{1}},
80
81
{`localhost:1234 {
82
dir1 foo bar
83
dir2
84
}`, false, []string{
85
"localhost:1234",
89
- }, map[string]int{
90
- "dir1": 3,
91
- "dir2": 1,
92
- }},
86
+ }, []int{3, 1}},
87
88
{`http://localhost https://localhost
89
dir1 foo bar`, false, []string{
90
"http://localhost",
91
"https://localhost",
98
- }, map[string]int{
99
- "dir1": 3,
100
- }},
92
+ }, []int{3}},
93
94
{`http://localhost https://localhost {
95
dir1 foo bar
96
}`, false, []string{
97
"http://localhost",
98
"https://localhost",
107
- }, map[string]int{
108
- "dir1": 3,
109
- }},
99
+ }, []int{3}},
100
101
{`http://localhost, https://localhost {
102
dir1 foo bar
103
}`, false, []string{
104
"http://localhost",
105
"https://localhost",
116
- }, map[string]int{
117
- "dir1": 3,
118
- }},
106
+ }, []int{3}},
107
108
{`http://localhost, {
109
}`, true, []string{
110
"http://localhost",
123
- }, map[string]int{}},
111
+ }, []int{}},
112
113
{`host1:80, http://host2.com
114
dir1 foo bar
115
dir2 baz`, false, []string{
116
"host1:80",
117
"http://host2.com",
130
- }, map[string]int{
131
- "dir1": 3,
132
- "dir2": 2,
133
- }},
118
+ }, []int{3, 2}},
119
120
{`http://host1.com,
121
http://host2.com,
@@ -138,7 +123,7 @@ func TestParseOneAndImport(t *testing.T) {
123
"http://host1.com",
124
"http://host2.com",
125
"https://host3.com",
141
- }, map[string]int{}},
126
+ }, []int{}},
127
128
{`http://host1.com:1234, https://host2.com
129
dir1 foo {
@@ -147,10 +132,7 @@ func TestParseOneAndImport(t *testing.T) {
132
dir2`, false, []string{
133
"http://host1.com:1234",
134
"https://host2.com",
150
- }, map[string]int{
151
- "dir1": 6,
152
- "dir2": 1,
153
- }},
135
+ }, []int{6, 1}},
136
137
{`127.0.0.1
138
dir1 {
@@ -160,34 +142,25 @@ func TestParseOneAndImport(t *testing.T) {
142
foo bar
143
}`, false, []string{
144
"127.0.0.1",
163
- }, map[string]int{
164
- "dir1": 5,
165
- "dir2": 5,
166
- }},
145
+ }, []int{5, 5}},
146
147
{`localhost
148
dir1 {
149
foo`, true, []string{
150
"localhost",
172
- }, map[string]int{
173
- "dir1": 3,
174
- }},
151
+ }, []int{3}},
152
153
{`localhost
154
dir1 {
155
}`, false, []string{
156
"localhost",
180
- }, map[string]int{
181
- "dir1": 3,
182
- }},
157
+ }, []int{3}},
158
159
{`localhost
160
dir1 {
161
} }`, true, []string{
162
"localhost",
188
- }, map[string]int{
189
- "dir1": 3,
190
- }},
163
+ }, []int{}},
164
165
{`localhost
166
dir1 {
@@ -197,48 +170,38 @@ func TestParseOneAndImport(t *testing.T) {
170
}
171
dir2 foo bar`, false, []string{
172
"localhost",
200
- }, map[string]int{
201
- "dir1": 7,
202
- "dir2": 3,
203
- }},
173
+ }, []int{7, 3}},
174
205
- {``, false, []string{}, map[string]int{}},
175
+ {``, false, []string{}, []int{}},
176
177
{`localhost
178
dir1 arg1
179
import testdata/import_test1.txt`, false, []string{
180
"localhost",
211
- }, map[string]int{
212
- "dir1": 2,
213
- "dir2": 3,
214
- "dir3": 1,
215
- }},
181
+ }, []int{2, 3, 1}},
182
183
{`import testdata/import_test2.txt`, false, []string{
184
"host1",
219
- }, map[string]int{
220
- "dir1": 1,
221
- "dir2": 2,
222
- }},
185
+ }, []int{1, 2}},
186
224
- {`import testdata/import_test1.txt testdata/import_test2.txt`, true, []string{}, map[string]int{}},
187
+ {`import testdata/import_test1.txt testdata/import_test2.txt`, true, []string{}, []int{}},
188
226
- {`import testdata/not_found.txt`, true, []string{}, map[string]int{}},
189
+ {`import testdata/not_found.txt`, true, []string{}, []int{}},
190
228
- {`""`, false, []string{}, map[string]int{}},
191
+ {`""`, false, []string{}, []int{}},
192
230
- {``, false, []string{}, map[string]int{}},
193
+ {``, false, []string{}, []int{}},
194
195
// test cases found by fuzzing!
233
- {`import }{$"`, true, []string{}, map[string]int{}},
234
- {`import /*/*.txt`, true, []string{}, map[string]int{}},
235
- {`import /???/?*?o`, true, []string{}, map[string]int{}},
236
- {`import /??`, true, []string{}, map[string]int{}},
237
- {`import /[a-z]`, true, []string{}, map[string]int{}},
238
- {`import {$}`, true, []string{}, map[string]int{}},
239
- {`import {%}`, true, []string{}, map[string]int{}},
240
- {`import {$$}`, true, []string{}, map[string]int{}},
241
- {`import {%%}`, true, []string{}, map[string]int{}},
196
+ {`import }{$"`, true, []string{}, []int{}},
197
+ {`import /*/*.txt`, true, []string{}, []int{}},
198
+ {`import /???/?*?o`, true, []string{}, []int{}},
199
+ {`import /??`, true, []string{}, []int{}},
200
+ {`import /[a-z]`, true, []string{}, []int{}},
201
+ {`import {$}`, true, []string{}, []int{}},
202
+ {`import {%}`, true, []string{}, []int{}},
203
+ {`import {$$}`, true, []string{}, []int{}},
204
+ {`import {%%}`, true, []string{}, []int{}},
205
} {
206
result, err := testParseOne(test.input)
207
@@ -261,15 +224,16 @@ func TestParseOneAndImport(t *testing.T) {
224
}
225
}
226
264
- if len(result.Tokens) != len(test.tokens) {
265
- t.Errorf("Test %d: Expected %d directives, had %d",
266
- i, len(test.tokens), len(result.Tokens))
227
+ if len(result.Segments) != len(test.numTokens) {
228
+ t.Errorf("Test %d: Expected %d segments, had %d",
229
+ i, len(test.numTokens), len(result.Segments))
230
continue
231
}
269
- for directive, tokens := range result.Tokens {
270
- if len(tokens) != test.tokens[directive] {
271
- t.Errorf("Test %d, directive '%s': Expected %d tokens, counted %d",
272
- i, directive, test.tokens[directive], len(tokens))
232
+
233
+ for j, seg := range result.Segments {
234
+ if len(seg) != test.numTokens[j] {
235
+ t.Errorf("Test %d, segment %d: Expected %d tokens, counted %d",
236
+ i, j, test.numTokens[j], len(seg))
237
continue
238
}
239
}
@@ -289,12 +253,12 @@ func TestRecursiveImport(t *testing.T) {
253
t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
254
return false
255
}
292
- if len(got.Tokens) != 2 {
293
- t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens))
256
+ if len(got.Segments) != 2 {
257
+ t.Errorf("got wrong number of segments: expect 2, got %d", len(got.Segments))
258
return false
259
}
296
- if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["dir2"]) != 2 {
297
- t.Errorf("got unexpect tokens: %v", got.Tokens)
260
+ if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 2 {
261
+ t.Errorf("got unexpect tokens: %v", got.Segments)
262
return false
263
}
264
return true
@@ -384,12 +348,12 @@ func TestDirectiveImport(t *testing.T) {
348
t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
349
return false
350
}
387
- if len(got.Tokens) != 2 {
388
- t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens))
351
+ if len(got.Segments) != 2 {
352
+ t.Errorf("got wrong number of segments: expect 2, got %d", len(got.Segments))
353
return false
354
}
391
- if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["proxy"]) != 8 {
392
- t.Errorf("got unexpect tokens: %v", got.Tokens)
355
+ if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 8 {
356
+ t.Errorf("got unexpect tokens: %v", got.Segments)
357
return false
358
}
359
return true
@@ -557,21 +521,21 @@ func TestEnvironmentReplacement(t *testing.T) {
521
if actual, expected := blocks[0].Keys[0], ":8080"; expected != actual {
522
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
523
}
560
- if actual, expected := blocks[0].Tokens["dir1"][1].Text, "foobar"; expected != actual {
524
+ if actual, expected := blocks[0].Segments[0][1].Text, "foobar"; expected != actual {
525
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
526
}
527
528
// combined windows env vars in argument
529
p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}")
530
blocks, _ = p.parseAll()
567
- if actual, expected := blocks[0].Tokens["dir1"][1].Text, "servername.com/foobar"; expected != actual {
531
+ if actual, expected := blocks[0].Segments[0][1].Text, "servername.com/foobar"; expected != actual {
532
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
533
}
534
535
// malformed env var (windows)
536
p = testParser(":1234\ndir1 {%ADDRESS}")
537
blocks, _ = p.parseAll()
574
- if actual, expected := blocks[0].Tokens["dir1"][1].Text, "{%ADDRESS}"; expected != actual {
538
+ if actual, expected := blocks[0].Segments[0][1].Text, "{%ADDRESS}"; expected != actual {
539
t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
540
}
541
@@ -585,22 +549,18 @@ func TestEnvironmentReplacement(t *testing.T) {
549
// in quoted field
550
p = testParser(":1234\ndir1 \"Test {$FOOBAR} test\"")
551
blocks, _ = p.parseAll()
588
- if actual, expected := blocks[0].Tokens["dir1"][1].Text, "Test foobar test"; expected != actual {
552
+ if actual, expected := blocks[0].Segments[0][1].Text, "Test foobar test"; expected != actual {
553
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
554
}
555
556
// after end token
557
p = testParser(":1234\nanswer \"{{ .Name }} {$FOOBAR}\"")
558
blocks, _ = p.parseAll()
595
- if actual, expected := blocks[0].Tokens["answer"][1].Text, "{{ .Name }} foobar"; expected != actual {
559
+ if actual, expected := blocks[0].Segments[0][1].Text, "{{ .Name }} foobar"; expected != actual {
560
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
561
}
562
}
563
600
-func testParser(input string) parser {
601
- return parser{Dispenser: newTestDispenser(input)}
602
-}
603
-
564
func TestSnippets(t *testing.T) {
565
p := testParser(`
566
(common) {
@@ -617,7 +577,7 @@ func TestSnippets(t *testing.T) {
577
}
578
for _, b := range blocks {
579
t.Log(b.Keys)
620
- t.Log(b.Tokens)
580
+ t.Log(b.Segments)
581
}
582
if len(blocks) != 1 {
583
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
@@ -625,16 +585,15 @@ func TestSnippets(t *testing.T) {
585
if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
586
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
587
}
628
- if len(blocks[0].Tokens) != 2 {
629
- t.Fatalf("Server block should have tokens from import")
588
+ if len(blocks[0].Segments) != 2 {
589
+ t.Fatalf("Server block should have tokens from import, got: %+v", blocks[0])
590
}
631
- if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual {
591
+ if actual, expected := blocks[0].Segments[0][0].Text, "gzip"; expected != actual {
592
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
593
}
634
- if actual, expected := blocks[0].Tokens["errors"][1].Text, "stderr"; expected != actual {
594
+ if actual, expected := blocks[0].Segments[1][1].Text, "stderr"; expected != actual {
595
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
596
}
637
-
597
}
598
599
func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) {
@@ -666,9 +625,9 @@ func TestImportedFilesIgnoreNonDirectiveImportTokens(t *testing.T) {
625
}
626
for _, b := range blocks {
627
t.Log(b.Keys)
669
- t.Log(b.Tokens)
628
+ t.Log(b.Segments)
629
}
671
- auth := blocks[0].Tokens["basicauth"]
630
+ auth := blocks[0].Segments[0]
631
line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text
632
if line != "basicauth / import password" {
633
// Previously, it would be changed to:
@@ -701,7 +660,7 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
660
}
661
for _, b := range blocks {
662
t.Log(b.Keys)
704
- t.Log(b.Tokens)
663
+ t.Log(b.Segments)
664
}
665
if len(blocks) != 1 {
666
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
@@ -709,10 +668,14 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
668
if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
669
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
670
}
712
- if len(blocks[0].Tokens) != 1 {
671
+ if len(blocks[0].Segments) != 1 {
672
t.Fatalf("Server block should have tokens from import")
673
}
715
- if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual {
674
+ if actual, expected := blocks[0].Segments[0][0].Text, "gzip"; expected != actual {
675
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
676
}
677
}
678
+
679
+func testParser(input string) parser {
680
+ return parser{Dispenser: newTestDispenser(input)}
681
+}
caddyconfig/httpcaddyfile/addresses.go
+72
-90
@@ -22,7 +22,7 @@ import (
22
"strconv"
23
"strings"
24
25
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
25
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
26
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
27
"github.com/mholt/certmagic"
28
)
@@ -73,8 +73,8 @@ import (
73
// repetition may be undesirable, so call consolidateAddrMappings() to map
74
// multiple addresses to the same lists of server blocks (a many:many mapping).
75
// (Doing this is essentially a map-reduce technique.)
76
-func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.ServerBlock) (map[string][]caddyfile.ServerBlock, error) {
77
- sbmap := make(map[string][]caddyfile.ServerBlock)
76
+func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock) (map[string][]serverBlock, error) {
77
+ sbmap := make(map[string][]serverBlock)
78
79
for i, sblock := range originalServerBlocks {
80
// within a server block, we need to map all the listener addresses
@@ -83,7 +83,7 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.
83
// key of a server block as its own, but without having to repeat its
84
// contents in cases where multiple keys really can be served together
85
addrToKeys := make(map[string][]string)
86
- for j, key := range sblock.Keys {
86
+ for j, key := range sblock.block.Keys {
87
// a key can have multiple listener addresses if there are multiple
88
// arguments to the 'bind' directive (although they will all have
89
// the same port, since the port is defined by the key or is implicit
@@ -105,9 +105,12 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.
105
// server block are only the ones which use the address; but
106
// the contents (tokens) are of course the same
107
for addr, keys := range addrToKeys {
108
- sbmap[addr] = append(sbmap[addr], caddyfile.ServerBlock{
109
- Keys: keys,
110
- Tokens: sblock.Tokens,
108
+ sbmap[addr] = append(sbmap[addr], serverBlock{
109
+ block: caddyfile.ServerBlock{
110
+ Keys: keys,
111
+ Segments: sblock.block.Segments,
112
+ },
113
+ pile: sblock.pile,
114
})
115
}
116
}
@@ -123,7 +126,7 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.
126
// entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
127
// association from multiple addresses to multiple server blocks; i.e. each element of
128
// the returned slice) becomes a server definition in the output JSON.
126
-func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]caddyfile.ServerBlock) []sbAddrAssociation {
129
+func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
130
var sbaddrs []sbAddrAssociation
131
for addr, sblocks := range addrToServerBlocks {
132
// we start with knowing that at least this address
@@ -151,11 +154,12 @@ func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]ca
154
return sbaddrs
155
}
156
154
-func (st *ServerType) listenerAddrsForServerBlockKey(sblock caddyfile.ServerBlock, key string) ([]string, error) {
155
- addr, err := standardizeAddress(key)
157
+func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string) ([]string, error) {
158
+ addr, err := ParseAddress(key)
159
if err != nil {
160
return nil, fmt.Errorf("parsing key: %v", err)
161
}
162
+ addr = addr.Normalize()
163
164
lnPort := defaultPort
165
if addr.Port != "" {
@@ -168,11 +172,8 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock caddyfile.ServerBloc
172
173
// the bind directive specifies hosts, but is optional
174
var lnHosts []string
171
- for i, token := range sblock.Tokens["bind"] {
172
- if i == 0 {
173
- continue
174
- }
175
- lnHosts = append(lnHosts, token.Text)
175
+ for _, cfgVal := range sblock.pile["bind"] {
176
+ lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
177
}
178
if len(lnHosts) == 0 {
179
lnHosts = []string{""}
@@ -205,7 +206,53 @@ type Address struct {
206
Original, Scheme, Host, Port, Path string
207
}
208
208
-// String returns a human-friendly print of the address.
209
+// ParseAddress parses an address string into a structured format with separate
210
+// scheme, host, port, and path portions, as well as the original input string.
211
+func ParseAddress(str string) (Address, error) {
212
+ httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
213
+
214
+ input := str
215
+
216
+ // Split input into components (prepend with // to force host portion by default)
217
+ if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") {
218
+ str = "//" + str
219
+ }
220
+
221
+ u, err := url.Parse(str)
222
+ if err != nil {
223
+ return Address{}, err
224
+ }
225
+
226
+ // separate host and port
227
+ host, port, err := net.SplitHostPort(u.Host)
228
+ if err != nil {
229
+ host, port, err = net.SplitHostPort(u.Host + ":")
230
+ if err != nil {
231
+ host = u.Host
232
+ }
233
+ }
234
+
235
+ // see if we can set port based off scheme
236
+ if port == "" {
237
+ if u.Scheme == "http" {
238
+ port = httpPort
239
+ } else if u.Scheme == "https" {
240
+ port = httpsPort
241
+ }
242
+ }
243
+
244
+ // error if scheme and port combination violate convention
245
+ if (u.Scheme == "http" && port == httpsPort) || (u.Scheme == "https" && port == httpPort) {
246
+ return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input)
247
+ }
248
+
249
+ return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
250
+}
251
+
252
+// TODO: which of the methods on Address are even used?
253
+
254
+// String returns a human-readable form of a. It will
255
+// be a cleaned-up and filled-out URL string.
256
func (a Address) String() string {
257
if a.Host == "" && a.Port == "" {
258
return ""
@@ -235,16 +282,7 @@ func (a Address) String() string {
282
return s
283
}
284
238
-// VHost returns a sensible concatenation of Host:Port/Path from a.
239
-// It's basically the a.Original but without the scheme.
240
-func (a Address) VHost() string {
241
- if idx := strings.Index(a.Original, "://"); idx > -1 {
242
- return a.Original[idx+3:]
243
- }
244
- return a.Original
245
-}
246
-
247
-// Normalize normalizes URL: turn scheme and host names into lower case
285
+// Normalize returns a normalized version of a.
286
func (a Address) Normalize() Address {
287
path := a.Path
288
if !caseSensitivePath {
@@ -266,8 +304,8 @@ func (a Address) Normalize() Address {
304
}
305
}
306
269
-// Key is similar to String, just replaces scheme and host values with modified values.
270
-// Unlike String it doesn't add anything default (scheme, port, etc)
307
+// Key returns a string form of a, much like String() does, but this
308
+// method doesn't add anything default that wasn't in the original.
309
func (a Address) Key() string {
310
res := ""
311
if a.Scheme != "" {
@@ -276,11 +314,11 @@ func (a Address) Key() string {
314
if a.Host != "" {
315
res += a.Host
316
}
279
- if a.Port != "" {
280
- if strings.HasPrefix(a.Original[len(res):], ":"+a.Port) {
281
- // insert port only if the original has its own explicit port
282
- res += ":" + a.Port
283
- }
317
+ // insert port only if the original has its own explicit port
318
+ if a.Port != "" &&
319
+ len(a.Original) >= len(res) &&
320
+ strings.HasPrefix(a.Original[len(res):], ":"+a.Port) {
321
+ res += ":" + a.Port
322
}
323
if a.Path != "" {
324
res += a.Path
@@ -288,63 +326,7 @@ func (a Address) Key() string {
326
return res
327
}
328
291
-// standardizeAddress parses an address string into a structured format with separate
292
-// scheme, host, port, and path portions, as well as the original input string.
293
-func standardizeAddress(str string) (Address, error) {
294
- httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
295
-
296
- input := str
297
-
298
- // Split input into components (prepend with // to assert host by default)
299
- if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") {
300
- str = "//" + str
301
- }
302
- u, err := url.Parse(str)
303
- if err != nil {
304
- return Address{}, err
305
- }
306
-
307
- // separate host and port
308
- host, port, err := net.SplitHostPort(u.Host)
309
- if err != nil {
310
- host, port, err = net.SplitHostPort(u.Host + ":")
311
- if err != nil {
312
- host = u.Host
313
- }
314
- }
315
-
316
- // see if we can set port based off scheme
317
- if port == "" {
318
- if u.Scheme == "http" {
319
- port = httpPort
320
- } else if u.Scheme == "https" {
321
- port = httpsPort
322
- }
323
- }
324
-
325
- // repeated or conflicting scheme is confusing, so error
326
- if u.Scheme != "" && (port == "http" || port == "https") {
327
- return Address{}, fmt.Errorf("[%s] scheme specified twice in address", input)
328
- }
329
-
330
- // error if scheme and port combination violate convention
331
- if (u.Scheme == "http" && port == httpsPort) || (u.Scheme == "https" && port == httpPort) {
332
- return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input)
333
- }
334
-
335
- // standardize http and https ports to their respective port numbers
336
- if port == "http" {
337
- u.Scheme = "http"
338
- port = httpPort
339
- } else if port == "https" {
340
- u.Scheme = "https"
341
- port = httpsPort
342
- }
343
-
344
- return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
345
-}
346
-
329
const (
330
defaultPort = "2015"
349
- caseSensitivePath = false
331
+ caseSensitivePath = false // TODO: Used?
332
)
caddyconfig/httpcaddyfile/addresses_test.go
+79
-42
@@ -1,22 +1,11 @@
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
-
1
package httpcaddyfile
2
17
-import "testing"
3
+import (
4
+ "strings"
5
+ "testing"
6
+)
7
19
-func TestStandardizeAddress(t *testing.T) {
8
+func TestParseAddress(t *testing.T) {
9
for i, test := range []struct {
10
input string
11
scheme, host, port, path string
@@ -31,14 +20,15 @@ func TestStandardizeAddress(t *testing.T) {
20
{`[::1]`, "", "::1", "", "", false},
21
{`[::1]:1234`, "", "::1", "1234", "", false},
22
{`:`, "", "", "", "", false},
34
- {`localhost:http`, "http", "localhost", "80", "", false},
35
- {`localhost:https`, "https", "localhost", "443", "", false},
36
- {`:http`, "http", "", "80", "", false},
37
- {`:https`, "https", "", "443", "", false},
23
+ {`:http`, "", "", "", "", true},
24
+ {`:https`, "", "", "", "", true},
25
+ {`localhost:http`, "", "", "", "", true}, // using service name in port is verboten, as of Go 1.12.8
26
+ {`localhost:https`, "", "", "", "", true},
27
{`http://localhost:https`, "", "", "", "", true}, // conflict
28
{`http://localhost:http`, "", "", "", "", true}, // repeated scheme
40
- {`http://localhost:443`, "", "", "", "", true}, // not conventional
41
- {`https://localhost:80`, "", "", "", "", true}, // not conventional
29
+ {`host:https/path`, "", "", "", "", true},
30
+ {`http://localhost:443`, "", "", "", "", true}, // not conventional
31
+ {`https://localhost:80`, "", "", "", "", true}, // not conventional
32
{`http://localhost`, "http", "localhost", "80", "", false},
33
{`https://localhost`, "https", "localhost", "443", "", false},
34
{`http://127.0.0.1`, "http", "127.0.0.1", "80", "", false},
@@ -58,10 +48,9 @@ func TestStandardizeAddress(t *testing.T) {
48
{`http://host/path`, "http", "host", "80", "/path", false},
49
{`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
50
{`host:80/path`, "", "host", "80", "/path", false},
61
- {`host:https/path`, "https", "host", "443", "/path", false},
51
{`/path`, "", "", "", "/path", false},
52
} {
64
- actual, err := standardizeAddress(test.input)
53
+ actual, err := ParseAddress(test.input)
54
55
if err != nil && !test.shouldErr {
56
t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
@@ -88,24 +77,6 @@ func TestStandardizeAddress(t *testing.T) {
77
}
78
}
79
91
-func TestAddressVHost(t *testing.T) {
92
- for i, test := range []struct {
93
- addr Address
94
- expected string
95
- }{
96
- {Address{Original: "host:1234"}, "host:1234"},
97
- {Address{Original: "host:1234/foo"}, "host:1234/foo"},
98
- {Address{Original: "host/foo"}, "host/foo"},
99
- {Address{Original: "http://host/foo"}, "host/foo"},
100
- {Address{Original: "https://host/foo"}, "host/foo"},
101
- } {
102
- actual := test.addr.VHost()
103
- if actual != test.expected {
104
- t.Errorf("Test %d: expected '%s' but got '%s'", i, test.expected, actual)
105
- }
106
- }
107
-}
108
-
80
func TestAddressString(t *testing.T) {
81
for i, test := range []struct {
82
addr Address
@@ -127,3 +98,69 @@ func TestAddressString(t *testing.T) {
98
}
99
}
100
}
101
+
102
+func TestKeyNormalization(t *testing.T) {
103
+ testCases := []struct {
104
+ input string
105
+ expect string
106
+ }{
107
+ {
108
+ input: "http://host:1234/path",
109
+ expect: "http://host:1234/path",
110
+ },
111
+ {
112
+ input: "HTTP://A/ABCDEF",
113
+ expect: "http://a/ABCDEF",
114
+ },
115
+ {
116
+ input: "A/ABCDEF",
117
+ expect: "a/ABCDEF",
118
+ },
119
+ {
120
+ input: "A:2015/Path",
121
+ expect: "a:2015/Path",
122
+ },
123
+ {
124
+ input: ":80",
125
+ expect: ":80",
126
+ },
127
+ {
128
+ input: ":443",
129
+ expect: ":443",
130
+ },
131
+ {
132
+ input: ":1234",
133
+ expect: ":1234",
134
+ },
135
+ {
136
+ input: "",
137
+ expect: "",
138
+ },
139
+ {
140
+ input: ":",
141
+ expect: "",
142
+ },
143
+ {
144
+ input: "[::]",
145
+ expect: "::",
146
+ },
147
+ }
148
+ for i, tc := range testCases {
149
+ addr, err := ParseAddress(tc.input)
150
+ if err != nil {
151
+ t.Errorf("Test %d: Parsing address '%s': %v", i, tc.input, err)
152
+ continue
153
+ }
154
+ expect := tc.expect
155
+ if !caseSensitivePath {
156
+ // every other part of the address should be lowercased when normalized,
157
+ // so simply lower-case the whole thing to do case-insensitive comparison
158
+ // of the path as well
159
+ expect = strings.ToLower(expect)
160
+ }
161
+ if actual := addr.Normalize().Key(); actual != expect {
162
+ t.Errorf("Test %d: Normalized key for address '%s' was '%s' but expected '%s'", i, tc.input, actual, expect)
163
+ }
164
+
165
+ }
166
+}
caddyconfig/httpcaddyfile/builtins.go
+177
-179
@@ -19,239 +19,237 @@ import (
19
"fmt"
20
"html"
21
"net/http"
22
+ "reflect"
23
23
- "github.com/caddyserver/caddy/v2/caddyconfig"
24
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
25
- "github.com/caddyserver/caddy/v2/modules/caddyhttp"
24
+ "github.com/caddyserver/caddy/caddyconfig"
25
+ "github.com/caddyserver/caddy/modules/caddyhttp"
26
"github.com/caddyserver/caddy/v2/modules/caddytls"
27
)
28
29
-func (st *ServerType) parseRoot(
30
- tkns []caddyfile.Token,
31
- matcherDefs map[string]map[string]json.RawMessage,
32
- warnings *[]caddyconfig.Warning,
33
-) ([]caddyhttp.Route, error) {
34
- var routes []caddyhttp.Route
35
-
36
- matchersAndTokens, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
37
- if err != nil {
38
- return nil, err
39
- }
40
-
41
- for _, mst := range matchersAndTokens {
42
- d := caddyfile.NewDispenser("Caddyfile", mst.tokens)
43
-
44
- var root string
45
- for d.Next() {
46
- if !d.NextArg() {
47
- return nil, d.ArgErr()
48
- }
49
- root = d.Val()
50
- if d.NextArg() {
51
- return nil, d.ArgErr()
52
- }
53
- }
54
-
55
- varsHandler := caddyhttp.VarsMiddleware{"root": root}
56
- route := caddyhttp.Route{
57
- Handle: []json.RawMessage{
58
- caddyconfig.JSONModuleObject(varsHandler, "handler", "vars", warnings),
59
- },
60
- }
61
- if mst.matcherSet != nil {
62
- route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
63
- }
29
+func init() {
30
+ RegisterDirective("bind", parseBind)
31
+ RegisterDirective("root", parseRoot)
32
+ RegisterDirective("tls", parseTLS)
33
+ RegisterHandlerDirective("redir", parseRedir)
34
+}
35
65
- routes = append(routes, route)
36
+func parseBind(h Helper) ([]ConfigValue, error) {
37
+ var lnHosts []string
38
+ for h.Next() {
39
+ lnHosts = append(lnHosts, h.RemainingArgs()...)
40
}
67
-
68
- return routes, nil
41
+ return h.NewBindAddresses(lnHosts), nil
42
}
43
71
-func (st *ServerType) parseRedir(
72
- tkns []caddyfile.Token,
73
- matcherDefs map[string]map[string]json.RawMessage,
74
- warnings *[]caddyconfig.Warning,
75
-) ([]caddyhttp.Route, error) {
76
- var routes []caddyhttp.Route
44
+func parseRoot(h Helper) ([]ConfigValue, error) {
45
+ if !h.Next() {
46
+ return nil, h.ArgErr()
47
+ }
48
78
- matchersAndTokens, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
49
+ matcherSet, ok, err := h.MatcherToken()
50
if err != nil {
51
return nil, err
52
}
82
-
83
- for _, mst := range matchersAndTokens {
84
- var route caddyhttp.Route
85
-
86
- d := caddyfile.NewDispenser("Caddyfile", mst.tokens)
87
-
88
- for d.Next() {
89
- if !d.NextArg() {
90
- return nil, d.ArgErr()
91
- }
92
- to := d.Val()
93
-
94
- var code string
95
- if d.NextArg() {
96
- code = d.Val()
97
- }
98
- if code == "permanent" {
99
- code = "301"
100
- }
101
- if code == "temporary" || code == "" {
102
- code = "307"
103
- }
104
- var body string
105
- if code == "meta" {
106
- // Script tag comes first since that will better imitate a redirect in the browser's
107
- // history, but the meta tag is a fallback for most non-JS clients.
108
- const metaRedir = `<!DOCTYPE html>
109
-<html>
110
- <head>
111
- <title>Redirecting...</title>
112
- <script>window.location.replace("%s");</script>
113
- <meta http-equiv="refresh" content="0; URL='%s'">
114
- </head>
115
- <body>Redirecting to <a href="%s">%s</a>...</body>
116
-</html>
117
-`
118
- safeTo := html.EscapeString(to)
119
- body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
120
- }
121
-
122
- handler := caddyhttp.StaticResponse{
123
- StatusCode: caddyhttp.WeakString(code),
124
- Headers: http.Header{"Location": []string{to}},
125
- Body: body,
126
- }
127
-
128
- route.Handle = append(route.Handle,
129
- caddyconfig.JSONModuleObject(handler, "handler", "static_response", warnings))
130
- }
131
-
132
- if mst.matcherSet != nil {
133
- route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
134
- }
135
-
136
- routes = append(routes, route)
53
+ if !ok {
54
+ // no matcher token; oops
55
+ h.Dispenser.Prev()
56
}
57
139
- return routes, nil
140
-}
141
-
142
-func (st *ServerType) parseTLSAutomationManager(d *caddyfile.Dispenser) (caddytls.ACMEManagerMaker, error) {
143
- var m caddytls.ACMEManagerMaker
144
-
145
- for d.Next() {
146
- firstLine := d.RemainingArgs()
147
- if len(firstLine) == 1 && firstLine[0] != "off" {
148
- m.Email = firstLine[0]
149
- }
150
-
151
- var hasBlock bool
152
- for d.NextBlock() {
153
- hasBlock = true
154
- switch d.Val() {
155
- case "ca":
156
- arg := d.RemainingArgs()
157
- if len(arg) != 1 {
158
- return m, d.ArgErr()
159
- }
160
- m.CA = arg[0]
161
- // TODO: other properties
162
- }
163
- }
58
+ if !h.NextArg() {
59
+ return nil, h.ArgErr()
60
+ }
61
+ root := h.Val()
62
+ if h.NextArg() {
63
+ return nil, h.ArgErr()
64
+ }
65
165
- // a naked tls directive is not allowed
166
- if len(firstLine) == 0 && !hasBlock {
167
- return m, d.ArgErr()
168
- }
66
+ varsHandler := caddyhttp.VarsMiddleware{"root": root}
67
+ route := caddyhttp.Route{
68
+ HandlersRaw: []json.RawMessage{
69
+ caddyconfig.JSONModuleObject(varsHandler, "handler", "vars", nil),
70
+ },
71
+ }
72
+ if matcherSet != nil {
73
+ route.MatcherSetsRaw = []map[string]json.RawMessage{matcherSet}
74
}
75
171
- return m, nil
76
+ return h.NewVarsRoute(route), nil
77
}
78
174
-func (st *ServerType) parseTLSCerts(d *caddyfile.Dispenser) (map[string]caddytls.CertificateLoader, error) {
79
+func parseTLS(h Helper) ([]ConfigValue, error) {
80
+ var configVals []ConfigValue
81
+
82
+ cp := new(caddytls.ConnectionPolicy)
83
var fileLoader caddytls.FileLoader
84
var folderLoader caddytls.FolderLoader
177
-
178
- for d.Next() {
179
- // file loader
180
- firstLine := d.RemainingArgs()
181
- if len(firstLine) == 2 {
85
+ var mgr caddytls.ACMEManagerMaker
86
+ var off bool
87
+
88
+ for h.Next() {
89
+ // file certificate loader
90
+ firstLine := h.RemainingArgs()
91
+ switch len(firstLine) {
92
+ case 0:
93
+ case 1:
94
+ if firstLine[0] == "off" {
95
+ off = true
96
+ } else {
97
+ mgr.Email = firstLine[0]
98
+ }
99
+ case 2:
100
fileLoader = append(fileLoader, caddytls.CertKeyFilePair{
101
Certificate: firstLine[0],
102
Key: firstLine[1],
185
- // TODO: tags, for enterprise module's certificate selection
103
+ // TODO: add tags, for enterprise module's certificate selection
104
})
105
+ default:
106
+ return nil, h.ArgErr()
107
}
108
189
- // folder loader
190
- for d.NextBlock() {
191
- if d.Val() == "load" {
192
- folderLoader = append(folderLoader, d.RemainingArgs()...)
193
- }
194
- }
195
- }
196
-
197
- // put configured loaders into the map
198
- loaders := make(map[string]caddytls.CertificateLoader)
199
- if len(fileLoader) > 0 {
200
- loaders["load_files"] = fileLoader
201
- }
202
- if len(folderLoader) > 0 {
203
- loaders["load_folders"] = folderLoader
204
- }
205
-
206
- return loaders, nil
207
-}
109
+ var hasBlock bool
110
+ for h.NextBlock() {
111
+ hasBlock = true
112
209
-func (st *ServerType) parseTLSConnPolicy(d *caddyfile.Dispenser) (*caddytls.ConnectionPolicy, error) {
210
- cp := new(caddytls.ConnectionPolicy)
113
+ switch h.Val() {
114
212
- for d.Next() {
213
- for d.NextBlock() {
214
- switch d.Val() {
115
+ // connection policy
116
case "protocols":
216
- args := d.RemainingArgs()
117
+ args := h.RemainingArgs()
118
if len(args) == 0 {
218
- return nil, d.SyntaxErr("one or two protocols")
119
+ return nil, h.SyntaxErr("one or two protocols")
120
}
121
if len(args) > 0 {
122
if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
222
- return nil, d.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
123
+ return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
124
}
125
cp.ProtocolMin = args[0]
126
}
127
if len(args) > 1 {
128
if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
228
- return nil, d.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
129
+ return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
130
}
131
cp.ProtocolMax = args[1]
132
}
133
case "ciphers":
233
- for d.NextArg() {
234
- if _, ok := caddytls.SupportedCipherSuites[d.Val()]; !ok {
235
- return nil, d.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", d.Val())
134
+ for h.NextArg() {
135
+ if _, ok := caddytls.SupportedCipherSuites[h.Val()]; !ok {
136
+ return nil, h.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
137
}
237
- cp.CipherSuites = append(cp.CipherSuites, d.Val())
138
+ cp.CipherSuites = append(cp.CipherSuites, h.Val())
139
}
140
case "curves":
240
- for d.NextArg() {
241
- if _, ok := caddytls.SupportedCurves[d.Val()]; !ok {
242
- return nil, d.Errf("Wrong curve name or curve not supported: '%s'", d.Val())
141
+ for h.NextArg() {
142
+ if _, ok := caddytls.SupportedCurves[h.Val()]; !ok {
143
+ return nil, h.Errf("Wrong curve name or curve not supported: '%s'", h.Val())
144
}
244
- cp.Curves = append(cp.Curves, d.Val())
145
+ cp.Curves = append(cp.Curves, h.Val())
146
}
147
case "alpn":
247
- args := d.RemainingArgs()
148
+ args := h.RemainingArgs()
149
if len(args) == 0 {
249
- return nil, d.ArgErr()
150
+ return nil, h.ArgErr()
151
}
152
cp.ALPN = args
153
+
154
+ // certificate folder loader
155
+ case "load":
156
+ folderLoader = append(folderLoader, h.RemainingArgs()...)
157
+
158
+ // automation policy
159
+ case "ca":
160
+ arg := h.RemainingArgs()
161
+ if len(arg) != 1 {
162
+ return nil, h.ArgErr()
163
+ }
164
+ mgr.CA = arg[0]
165
+
166
+ // TODO: other properties for automation manager
167
}
168
}
169
+
170
+ // a naked tls directive is not allowed
171
+ if len(firstLine) == 0 && !hasBlock {
172
+ return nil, h.ArgErr()
173
+ }
174
+ }
175
+
176
+ // connection policy
177
+ configVals = append(configVals, ConfigValue{
178
+ Class: "tls.connection_policy",
179
+ Value: cp,
180
+ })
181
+
182
+ // certificate loaders
183
+ if len(fileLoader) > 0 {
184
+ configVals = append(configVals, ConfigValue{
185
+ Class: "tls.certificate_loader",
186
+ Value: fileLoader,
187
+ })
188
+ }
189
+ if len(folderLoader) > 0 {
190
+ configVals = append(configVals, ConfigValue{
191
+ Class: "tls.certificate_loader",
192
+ Value: folderLoader,
193
+ })
194
+ }
195
+
196
+ // automation policy
197
+ if off {
198
+ configVals = append(configVals, ConfigValue{
199
+ Class: "tls.off",
200
+ Value: true,
201
+ })
202
+ } else if !reflect.DeepEqual(mgr, caddytls.ACMEManagerMaker{}) {
203
+ configVals = append(configVals, ConfigValue{
204
+ Class: "tls.automation_manager",
205
+ Value: mgr,
206
+ })
207
+ }
208
+
209
+ return configVals, nil
210
+}
211
+
212
+func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
213
+ if !h.Next() {
214
+ return nil, h.ArgErr()
215
+ }
216
+
217
+ if !h.NextArg() {
218
+ return nil, h.ArgErr()
219
+ }
220
+ to := h.Val()
221
+
222
+ var code string
223
+ if h.NextArg() {
224
+ code = h.Val()
225
+ }
226
+ if code == "permanent" {
227
+ code = "301"
228
+ }
229
+ if code == "temporary" || code == "" {
230
+ code = "307"
231
+ }
232
+ var body string
233
+ if code == "meta" {
234
+ // Script tag comes first since that will better imitate a redirect in the browser's
235
+ // history, but the meta tag is a fallback for most non-JS clients.
236
+ const metaRedir = `<!DOCTYPE html>
237
+<html>
238
+ <head>
239
+ <title>Redirecting...</title>
240
+ <script>window.location.replace("%s");</script>
241
+ <meta http-equiv="refresh" content="0; URL='%s'">
242
+ </head>
243
+ <body>Redirecting to <a href="%s">%s</a>...</body>
244
+</html>
245
+`
246
+ safeTo := html.EscapeString(to)
247
+ body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
248
}
249
256
- return cp, nil
250
+ return caddyhttp.StaticResponse{
251
+ StatusCode: caddyhttp.WeakString(code),
252
+ Headers: http.Header{"Location": []string{to}},
253
+ Body: body,
254
+ }, nil
255
}
caddyconfig/httpcaddyfile/directives.go
new
+182
@@ -0,0 +1,182 @@
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 httpcaddyfile
16
+
17
+import (
18
+ "encoding/json"
19
+
20
+ "github.com/caddyserver/caddy/v2"
21
+ "github.com/caddyserver/caddy/v2/caddyconfig"
22
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
23
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
24
+)
25
+
26
+// defaultDirectiveOrder specifies the order
27
+// to apply directives in HTTP routes.
28
+// TODO: finish the ability to customize this
29
+var defaultDirectiveOrder = []string{
30
+ "rewrite",
31
+ "try_files",
32
+ "headers",
33
+ "encode",
34
+ "templates",
35
+ "redir",
36
+ "static_response", // TODO: "reply" or "respond"?
37
+ "reverse_proxy",
38
+ "file_server",
39
+}
40
+
41
+// RegisterDirective registers a unique directive dir with an
42
+// associated unmarshaling (setup) function. When directive dir
43
+// is encountered in a Caddyfile, setupFunc will be called to
44
+// unmarshal its tokens.
45
+func RegisterDirective(dir string, setupFunc UnmarshalFunc) {
46
+ if _, ok := registeredDirectives[dir]; ok {
47
+ panic("directive " + dir + " already registered")
48
+ }
49
+ registeredDirectives[dir] = setupFunc
50
+}
51
+
52
+// RegisterHandlerDirective is like RegisterDirective, but for
53
+// directives which specifically output only an HTTP handler.
54
+func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
55
+ RegisterDirective(dir, func(h Helper) ([]ConfigValue, error) {
56
+ if !h.Next() {
57
+ return nil, h.ArgErr()
58
+ }
59
+
60
+ matcherSet, ok, err := h.MatcherToken()
61
+ if err != nil {
62
+ return nil, err
63
+ }
64
+ if ok {
65
+ h.Dispenser.Delete() // strip matcher token
66
+ }
67
+
68
+ h.Dispenser.Reset() // pretend this lookahead never happened
69
+ val, err := setupFunc(h)
70
+ if err != nil {
71
+ return nil, err
72
+ }
73
+
74
+ return h.NewRoute(matcherSet, val), nil
75
+ })
76
+}
77
+
78
+// Helper is a type which helps setup a value from
79
+// Caddyfile tokens.
80
+type Helper struct {
81
+ *caddyfile.Dispenser
82
+ warnings *[]caddyconfig.Warning
83
+ matcherDefs map[string]map[string]json.RawMessage
84
+}
85
+
86
+// JSON converts val into JSON. Any errors are added to warnings.
87
+func (h Helper) JSON(val interface{}, warnings *[]caddyconfig.Warning) json.RawMessage {
88
+ return caddyconfig.JSON(val, h.warnings)
89
+}
90
+
91
+// MatcherToken assumes the current token is (possibly) a matcher, and
92
+// if so, returns the matcher set along with a true value. If the current
93
+// token is not a matcher, nil and false is returned. Note that a true
94
+// value may be returned with a nil matcher set if it is a catch-all.
95
+func (h Helper) MatcherToken() (map[string]json.RawMessage, bool, error) {
96
+ if !h.NextArg() {
97
+ return nil, false, nil
98
+ }
99
+ return matcherSetFromMatcherToken(h.Dispenser.Token(), h.matcherDefs, h.warnings)
100
+}
101
+
102
+// NewRoute returns config values relevant to creating a new HTTP route.
103
+func (h Helper) NewRoute(matcherSet map[string]json.RawMessage,
104
+ handler caddyhttp.MiddlewareHandler) []ConfigValue {
105
+ mod, err := caddy.GetModule(caddy.GetModuleName(handler))
106
+ if err != nil {
107
+ // TODO: append to warnings
108
+ }
109
+ var matcherSetsRaw []map[string]json.RawMessage
110
+ if matcherSet != nil {
111
+ matcherSetsRaw = append(matcherSetsRaw, matcherSet)
112
+ }
113
+ return []ConfigValue{
114
+ {
115
+ Class: "route",
116
+ Value: caddyhttp.Route{
117
+ MatcherSetsRaw: matcherSetsRaw,
118
+ HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", mod.ID(), h.warnings)},
119
+ },
120
+ },
121
+ }
122
+}
123
+
124
+// NewBindAddresses returns config values relevant to adding
125
+// listener bind addresses to the config.
126
+func (h Helper) NewBindAddresses(addrs []string) []ConfigValue {
127
+ return []ConfigValue{{Class: "bind", Value: addrs}}
128
+}
129
+
130
+// NewVarsRoute returns config values relevant to adding a
131
+// "vars" wrapper route to the config.
132
+func (h Helper) NewVarsRoute(route caddyhttp.Route) []ConfigValue {
133
+ return []ConfigValue{{Class: "var", Value: route}}
134
+}
135
+
136
+// ConfigValue represents a value to be added to the final
137
+// configuration, or a value to be consulted when building
138
+// the final configuration.
139
+type ConfigValue struct {
140
+ // The kind of value this is. As the config is
141
+ // being built, the adapter will look in the
142
+ // "pile" for values belonging to a certain
143
+ // class when it is setting up a certain part
144
+ // of the config. The associated value will be
145
+ // type-asserted and placed accordingly.
146
+ Class string
147
+
148
+ // The value to be used when building the config.
149
+ // Generally its type is associated with the
150
+ // name of the Class.
151
+ Value interface{}
152
+
153
+ directive string
154
+}
155
+
156
+// serverBlock pairs a Caddyfile server block
157
+// with a "pile" of config values, keyed by class
158
+// name.
159
+type serverBlock struct {
160
+ block caddyfile.ServerBlock
161
+ pile map[string][]ConfigValue // config values obtained from directives
162
+}
163
+
164
+type (
165
+ // UnmarshalFunc is a function which can unmarshal Caddyfile
166
+ // tokens into zero or more config values using a Helper type.
167
+ // These are passed in a call to RegisterDirective.
168
+ UnmarshalFunc func(h Helper) ([]ConfigValue, error)
169
+
170
+ // UnmarshalHandlerFunc is like UnmarshalFunc, except the
171
+ // output of the unmarshaling is an HTTP handler. This
172
+ // function does not need to deal with HTTP request matching
173
+ // which is abstracted away. Since writing HTTP handlers
174
+ // with Caddyfile support is very common, this is a more
175
+ // convenient way to add a handler to the chain since a lot
176
+ // of the details common to HTTP handlers are taken care of
177
+ // for you. These are passed to a call to
178
+ // RegisterHandlerDirective.
179
+ UnmarshalHandlerFunc func(h Helper) (caddyhttp.MiddlewareHandler, error)
180
+)
181
+
182
+var registeredDirectives = make(map[string]UnmarshalFunc)
caddyconfig/httpcaddyfile/handlers.go
-36
@@ -17,7 +17,6 @@ package httpcaddyfile
17
import (
18
"encoding/json"
19
"fmt"
20
- "log"
20
21
"github.com/caddyserver/caddy/v2"
22
"github.com/caddyserver/caddy/v2/caddyconfig"
@@ -55,38 +54,3 @@ func (st *ServerType) parseMatcherDefinitions(d *caddyfile.Dispenser) (map[strin
54
}
55
return matchers, nil
56
}
58
-
59
-// directiveBuckets returns a list of middleware/handler directives.
60
-// Buckets are ordered, and directives should be evaluated in their
61
-// bucket order. Within a bucket, directives are not ordered. Hence,
62
-// the return value has a slice of buckets, where each bucket is a
63
-// map, which is a strongly-typed reminder that directives within a
64
-// bucket are not ordered.
65
-func directiveBuckets() []map[string]struct{} {
66
- directiveBuckets := []map[string]struct{}{
67
- // prefer odd-numbered buckets; evens are there for contingencies
68
- {}, // 0
69
- {}, // 1 - keep empty unless necessary
70
- {}, // 2
71
- {}, // 3 - first handlers, last responders
72
- {}, // 4
73
- {}, // 5 - middle of chain
74
- {}, // 6
75
- {}, // 7 - last handlers, first responders
76
- {}, // 8
77
- {}, // 9 - keep empty unless necessary
78
- {}, // 10
79
- }
80
- for _, mod := range caddy.GetModules("http.handlers") {
81
- if hd, ok := mod.New().(HandlerDirective); ok {
82
- bucket := hd.Bucket()
83
- if bucket < 0 || bucket >= len(directiveBuckets) {
84
- log.Printf("[ERROR] directive %s: bucket out of range [0-%d): %d; skipping",
85
- mod.Name, len(directiveBuckets), bucket)
86
- continue
87
- }
88
- directiveBuckets[bucket][mod.ID()] = struct{}{}
89
- }
90
- }
91
- return directiveBuckets
92
-}
caddyconfig/httpcaddyfile/httptype.go
+172
-197
@@ -17,17 +17,18 @@ package httpcaddyfile
17
import (
18
"encoding/json"
19
"fmt"
20
+ "log"
21
"reflect"
22
+ "sort"
23
"strconv"
24
"strings"
25
24
- "github.com/mholt/certmagic"
25
-
26
"github.com/caddyserver/caddy/v2"
27
"github.com/caddyserver/caddy/v2/caddyconfig"
28
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
29
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
30
"github.com/caddyserver/caddy/v2/modules/caddytls"
31
+ "github.com/mholt/certmagic"
32
)
33
34
func init() {
@@ -38,24 +39,57 @@ func init() {
39
type ServerType struct {
40
}
41
41
-// ValidDirectives returns the list of known directives.
42
-func (ServerType) ValidDirectives() []string {
43
- dirs := []string{"matcher", "root", "tls", "redir"} // TODO: put special-case (hard-coded, or non-handler) directives here
44
- for _, mod := range caddy.GetModules("http.handlers") {
45
- if _, ok := mod.New().(HandlerDirective); ok {
46
- dirs = append(dirs, mod.ID())
47
- }
48
- }
49
- return dirs
50
-}
42
+// TODO: error on unrecognized directives
43
44
// Setup makes a config from the tokens.
45
func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
46
options map[string]string) (*caddy.Config, []caddyconfig.Warning, error) {
47
var warnings []caddyconfig.Warning
48
49
+ var serverBlocks []serverBlock
50
+ for _, sblock := range originalServerBlocks {
51
+ serverBlocks = append(serverBlocks, serverBlock{
52
+ block: sblock,
53
+ pile: make(map[string][]ConfigValue),
54
+ })
55
+ }
56
+
57
+ for _, sb := range serverBlocks {
58
+ // extract matcher definitions
59
+ d := sb.block.DispenseDirective("matcher")
60
+ matcherDefs, err := st.parseMatcherDefinitions(d)
61
+ if err != nil {
62
+ return nil, nil, err
63
+ }
64
+
65
+ for _, segment := range sb.block.Segments {
66
+ dir := segment.Directive()
67
+ if dir == "matcher" {
68
+ // TODO: This is a special case because we pre-processed it; handle this better
69
+ continue
70
+ }
71
+ if dirFunc, ok := registeredDirectives[dir]; ok {
72
+ results, err := dirFunc(Helper{
73
+ Dispenser: segment.NewDispenser(),
74
+ warnings: &warnings,
75
+ matcherDefs: matcherDefs,
76
+ })
77
+ if err != nil {
78
+ return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err)
79
+ }
80
+ for _, result := range results {
81
+ result.directive = dir
82
+ sb.pile[result.Class] = append(sb.pile[result.Class], result)
83
+ }
84
+ } else {
85
+ // TODO: this should be an error
86
+ log.Printf("%s not registered", dir)
87
+ }
88
+ }
89
+ }
90
+
91
// map
58
- sbmap, err := st.mapAddressToServerBlocks(originalServerBlocks)
92
+ sbmap, err := st.mapAddressToServerBlocks(serverBlocks)
93
if err != nil {
94
return nil, warnings, err
95
}
@@ -63,6 +97,22 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
97
// reduce
98
pairings := st.consolidateAddrMappings(sbmap)
99
100
+ // TODO: shorthand placeholders
101
+ // for _, p := range pairings {
102
+ // for _, sblock := range p.serverBlocks {
103
+ // for _, tokens := range sblock.Tokens {
104
+ // for i := 0; i < len(tokens); i++ {
105
+ // switch tokens[i].Text {
106
+ // case "{uri}":
107
+ // tokens[i].Text = "{http.request.uri}"
108
+ // case "{path}":
109
+ // tokens[i].Text = "{http.request.uri.path}"
110
+ // }
111
+ // }
112
+ // }
113
+ // }
114
+ // }
115
+
116
// each pairing of listener addresses to list of server
117
// blocks is basically a server definition
118
servers, err := st.serversFromPairings(pairings, &warnings)
@@ -81,45 +131,33 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
131
tlsApp := caddytls.TLS{Certificates: make(map[string]json.RawMessage)}
132
for _, p := range pairings {
133
for _, sblock := range p.serverBlocks {
84
- if tkns, ok := sblock.Tokens["tls"]; ok {
85
- // extract all unique hostnames from the server block
86
- // keys, then convert to a slice for use in the TLS app
87
- hostMap := make(map[string]struct{})
88
- for _, sblockKey := range sblock.Keys {
89
- addr, err := standardizeAddress(sblockKey)
134
+ // tls automation policies
135
+ if mmVals, ok := sblock.pile["tls.automation_manager"]; ok {
136
+ for _, mmVal := range mmVals {
137
+ mm := mmVal.Value.(caddytls.ManagerMaker)
138
+ sblockHosts, err := st.autoHTTPSHosts(sblock)
139
if err != nil {
91
- return nil, warnings, fmt.Errorf("parsing server block key: %v", err)
140
+ return nil, warnings, err
141
}
93
- hostMap[addr.Host] = struct{}{}
94
- }
95
- sblockHosts := make([]string, 0, len(hostMap))
96
- for host := range hostMap {
97
- sblockHosts = append(sblockHosts, host)
142
+ tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
143
+ Hosts: sblockHosts,
144
+ ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID(), &warnings),
145
+ })
146
}
147
+ }
148
100
- // parse tokens to get ACME manager config
101
- acmeMgr, err := st.parseTLSAutomationManager(caddyfile.NewDispenser("Caddyfile", tkns))
102
- if err != nil {
103
- return nil, warnings, err
104
- }
105
-
106
- tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
107
- Hosts: sblockHosts,
108
- ManagementRaw: caddyconfig.JSONModuleObject(acmeMgr, "module", "acme", &warnings),
109
- })
110
-
111
- // parse tokens to get certificates to be loaded manually
112
- certLoaders, err := st.parseTLSCerts(caddyfile.NewDispenser("Caddyfile", tkns))
113
- if err != nil {
114
- return nil, nil, err
115
- }
116
- for loaderName, loader := range certLoaders {
149
+ // tls certificate loaders
150
+ if clVals, ok := sblock.pile["tls.certificate_loader"]; ok {
151
+ for _, clVal := range clVals {
152
+ loader := clVal.Value.(caddytls.CertificateLoader)
153
+ loaderName := caddy.GetModuleName(loader)
154
tlsApp.Certificates[loaderName] = caddyconfig.JSON(loader, &warnings)
155
}
119
-
156
}
157
}
158
}
159
+ // consolidate automation policies that are the exact same
160
+ tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
161
162
// annnd the top-level config, then we're done!
163
cfg := &caddy.Config{AppsRaw: make(map[string]json.RawMessage)}
@@ -140,10 +178,11 @@ func (st *ServerType) hostsFromServerBlockKeys(sb caddyfile.ServerBlock) ([]stri
178
// first get each unique hostname
179
hostMap := make(map[string]struct{})
180
for _, sblockKey := range sb.Keys {
143
- addr, err := standardizeAddress(sblockKey)
181
+ addr, err := ParseAddress(sblockKey)
182
if err != nil {
183
return nil, fmt.Errorf("parsing server block key: %v", err)
184
}
185
+ addr = addr.Normalize()
186
hostMap[addr.Host] = struct{}{}
187
}
188
@@ -167,121 +206,75 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
206
}
207
208
for _, sblock := range p.serverBlocks {
170
- matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock)
209
+ matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock.block)
210
if err != nil {
172
- return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.Keys, err)
173
- }
174
-
175
- // extract matcher definitions
176
- d := caddyfile.NewDispenser("Caddyfile", sblock.Tokens["matcher"])
177
- matcherDefs, err := st.parseMatcherDefinitions(d)
178
- if err != nil {
179
- return nil, err
211
+ return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.block.Keys, err)
212
}
213
214
+ // if there are user-defined variables, then siteVarSubroute will
215
+ // wrap the handlerSubroute; otherwise handlerSubroute will be the
216
+ // site's primary subroute.
217
siteVarSubroute, handlerSubroute := new(caddyhttp.Subroute), new(caddyhttp.Subroute)
218
184
- // built-in directives
219
+ // tls: connection policies and toggle auto HTTPS
220
186
- // root: path to root of site
187
- if tkns, ok := sblock.Tokens["root"]; ok {
188
- routes, err := st.parseRoot(tkns, matcherDefs, warnings)
189
- if err != nil {
190
- return nil, err
191
- }
192
- siteVarSubroute.Routes = append(siteVarSubroute.Routes, routes...)
221
+ autoHTTPSQualifiedHosts, err := st.autoHTTPSHosts(sblock)
222
+ if err != nil {
223
+ return nil, err
224
}
194
-
195
- // tls: off and conn policies
196
- if tkns, ok := sblock.Tokens["tls"]; ok {
197
- // get the hosts for this server block...
198
- hosts, err := st.hostsFromServerBlockKeys(sblock)
199
- if err != nil {
200
- return nil, err
225
+ if _, ok := sblock.pile["tls.off"]; ok {
226
+ // tls off: disable TLS (and automatic HTTPS) for server block's names
227
+ if srv.AutoHTTPS == nil {
228
+ srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
229
}
202
-
203
- // ...and of those, which ones qualify for auto HTTPS
204
- var autoHTTPSQualifiedHosts []string
205
- for _, h := range hosts {
206
- if certmagic.HostQualifies(h) {
207
- autoHTTPSQualifiedHosts = append(autoHTTPSQualifiedHosts, h)
208
- }
209
- }
210
-
211
- if len(tkns) == 2 && tkns[1].Text == "off" {
212
- // tls off: disable TLS (and automatic HTTPS) for server block's names
213
- if srv.AutoHTTPS == nil {
214
- srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
215
- }
216
- srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, autoHTTPSQualifiedHosts...)
217
- } else {
218
- // tls connection policies
219
- cp, err := st.parseTLSConnPolicy(caddyfile.NewDispenser("Caddyfile", tkns))
220
- if err != nil {
221
- return nil, err
222
- }
223
- // TODO: are matchers needed if every hostname of the config is matched?
224
- cp.Matchers = map[string]json.RawMessage{
225
- "sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
226
- }
227
- srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
228
- }
229
- }
230
-
231
- // set up each handler directive
232
- for _, dirBucket := range directiveBuckets() {
233
- for dir := range dirBucket {
234
- // keep in mind that multiple occurrences of the directive may appear here
235
- tkns, ok := sblock.Tokens[dir]
236
- if !ok {
237
- continue
238
- }
239
-
240
- // extract matcher sets from matcher tokens, if any
241
- matcherSetsMap, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
242
-
243
- mod, err := caddy.GetModule("http.handlers." + dir)
244
- if err != nil {
245
- return nil, fmt.Errorf("getting handler module '%s': %v", mod.Name, err)
246
- }
247
-
248
- // the tokens have been divided by matcher set for us,
249
- // so iterate each one and set them up
250
- for _, mst := range matcherSetsMap {
251
- unm, ok := mod.New().(caddyfile.Unmarshaler)
252
- if !ok {
253
- return nil, fmt.Errorf("handler module '%s' is not a Caddyfile unmarshaler", mod.Name)
254
- }
255
- err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(d.File(), mst.tokens))
230
+ srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, autoHTTPSQualifiedHosts...)
231
+ } else if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
232
+ // tls connection policies
233
+ for _, cpVal := range cpVals {
234
+ cp := cpVal.Value.(*caddytls.ConnectionPolicy)
235
+ // only create if there is a non-empty policy
236
+ if !reflect.DeepEqual(cp, new(caddytls.ConnectionPolicy)) {
237
+ // make sure the policy covers all hostnames from the block
238
+ hosts, err := st.hostsFromServerBlockKeys(sblock.block)
239
if err != nil {
240
return nil, err
241
}
259
- handler, ok := unm.(caddyhttp.MiddlewareHandler)
260
- if !ok {
261
- return nil, fmt.Errorf("handler module '%s' does not implement caddyhttp.MiddlewareHandler interface", mod.Name)
262
- }
242
264
- route := caddyhttp.Route{
265
- Handle: []json.RawMessage{
266
- caddyconfig.JSONModuleObject(handler, "handler", dir, warnings),
267
- },
268
- }
269
- if mst.matcherSet != nil {
270
- route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
243
+ // TODO: are matchers needed if every hostname of the config is matched?
244
+ cp.Matchers = map[string]json.RawMessage{
245
+ "sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
246
}
272
- handlerSubroute.Routes = append(handlerSubroute.Routes, route)
247
+ srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
248
}
274
-
249
}
250
+ // TODO: consolidate equal conn policies
251
}
252
278
- // redir: static responses that redirect
279
- if tkns, ok := sblock.Tokens["redir"]; ok {
280
- routes, err := st.parseRedir(tkns, matcherDefs, warnings)
281
- if err != nil {
282
- return nil, err
253
+ // vars: special routes that will have to wrap the normal handlers
254
+ // so that these variables can be used across their matchers too
255
+ for _, cfgVal := range sblock.pile["var"] {
256
+ siteVarSubroute.Routes = append(siteVarSubroute.Routes, cfgVal.Value.(caddyhttp.Route))
257
+ }
258
+
259
+ // set up each handler directive
260
+ dirRoutes := sblock.pile["route"]
261
+ // TODO: The ordering here depends on... if there is a list of
262
+ // directives to use, then sort by that, otherwise just use in
263
+ // the order they appear in the slice (which is the order they
264
+ // appeared in the Caddyfile)
265
+ sortByList := true
266
+ if sortByList {
267
+ dirPositions := make(map[string]int)
268
+ for i, dir := range defaultDirectiveOrder {
269
+ dirPositions[dir] = i
270
}
284
- handlerSubroute.Routes = append(handlerSubroute.Routes, routes...)
271
+ sort.SliceStable(dirRoutes, func(i, j int) bool {
272
+ iDir, jDir := dirRoutes[i].directive, dirRoutes[j].directive
273
+ return dirPositions[iDir] < dirPositions[jDir]
274
+ })
275
+ }
276
+ for _, r := range dirRoutes {
277
+ handlerSubroute.Routes = append(handlerSubroute.Routes, r.Value.(caddyhttp.Route))
278
}
279
280
// the route that contains the site's handlers will
@@ -298,7 +291,7 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
291
siteSubroute.Routes = append(
292
siteVarSubroute.Routes,
293
caddyhttp.Route{
301
- Handle: []json.RawMessage{
294
+ HandlersRaw: []json.RawMessage{
295
caddyconfig.JSONModuleObject(subSubRoute, "handler", "subroute", warnings),
296
},
297
},
@@ -308,8 +301,8 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
301
siteSubroute.Routes = consolidateRoutes(siteSubroute.Routes)
302
303
srv.Routes = append(srv.Routes, caddyhttp.Route{
311
- MatcherSets: matcherSetsEnc,
312
- Handle: []json.RawMessage{
304
+ MatcherSetsRaw: matcherSetsEnc,
305
+ HandlersRaw: []json.RawMessage{
306
caddyconfig.JSONModuleObject(siteSubroute, "handler", "subroute", warnings),
307
},
308
})
@@ -323,16 +316,32 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
316
return servers, nil
317
}
318
319
+func (st ServerType) autoHTTPSHosts(sb serverBlock) ([]string, error) {
320
+ // get the hosts for this server block...
321
+ hosts, err := st.hostsFromServerBlockKeys(sb.block)
322
+ if err != nil {
323
+ return nil, err
324
+ }
325
+ // ...and of those, which ones qualify for auto HTTPS
326
+ var autoHTTPSQualifiedHosts []string
327
+ for _, h := range hosts {
328
+ if certmagic.HostQualifies(h) {
329
+ autoHTTPSQualifiedHosts = append(autoHTTPSQualifiedHosts, h)
330
+ }
331
+ }
332
+ return autoHTTPSQualifiedHosts, nil
333
+}
334
+
335
// consolidateRoutes combines routes with the same properties
336
// (same matchers, same Terminal and Group settings) for a
337
// cleaner overall output.
338
func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
339
for i := 0; i < len(routes)-1; i++ {
331
- if reflect.DeepEqual(routes[i].MatcherSets, routes[i+1].MatcherSets) &&
340
+ if reflect.DeepEqual(routes[i].MatcherSetsRaw, routes[i+1].MatcherSetsRaw) &&
341
routes[i].Terminal == routes[i+1].Terminal &&
342
routes[i].Group == routes[i+1].Group {
343
// keep the handlers in the same order, then splice out repetitive route
335
- routes[i].Handle = append(routes[i].Handle, routes[i+1].Handle...)
344
+ routes[i].HandlersRaw = append(routes[i].HandlersRaw, routes[i+1].HandlersRaw...)
345
routes = append(routes[:i+1], routes[i+2:]...)
346
i--
347
}
@@ -340,53 +349,26 @@ func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
349
return routes
350
}
351
343
-func (st *ServerType) tokensToMatcherSets(
344
- tkns []caddyfile.Token,
345
- matcherDefs map[string]map[string]json.RawMessage,
346
- warnings *[]caddyconfig.Warning,
347
-) (map[string]matcherSetAndTokens, error) {
348
- m := make(map[string]matcherSetAndTokens)
349
-
350
- for len(tkns) > 0 {
351
- d := caddyfile.NewDispenser("Caddyfile", tkns)
352
- d.Next() // consume directive token
353
-
354
- // look for matcher; it should be the next argument
355
- var matcherToken caddyfile.Token
356
- var matcherSet map[string]json.RawMessage
357
- if d.NextArg() {
358
- var ok bool
359
- var err error
360
- matcherSet, ok, err = st.matcherSetFromMatcherToken(d.Token(), matcherDefs, warnings)
361
- if err != nil {
362
- return nil, err
363
- }
364
- if ok {
365
- // found a matcher; save it, then splice it out
366
- // since we don't want to parse it again
367
- matcherToken = d.Token()
368
- tkns = d.Delete()
352
+// consolidateAutomationPolicies combines automation policies that are the same,
353
+// for a cleaner overall output.
354
+func consolidateAutomationPolicies(aps []caddytls.AutomationPolicy) []caddytls.AutomationPolicy {
355
+ for i := 0; i < len(aps); i++ {
356
+ for j := 0; j < len(aps); j++ {
357
+ if j == i {
358
+ continue
359
}
370
- d.RemainingArgs() // advance to end of line
371
- }
372
- for d.NextBlock() {
373
- // skip entire block including any nested blocks; all
374
- // we care about is accessing next directive occurrence
375
- for d.Nested() {
376
- d.NextBlock()
360
+ if reflect.DeepEqual(aps[i].ManagementRaw, aps[j].ManagementRaw) {
361
+ aps[i].Hosts = append(aps[i].Hosts, aps[j].Hosts...)
362
}
363
+ aps = append(aps[:j], aps[j+1:]...)
364
+ i--
365
+ break
366
}
379
- end := d.Cursor() + 1
380
- m[matcherToken.Text] = matcherSetAndTokens{
381
- matcherSet: matcherSet,
382
- tokens: append(m[matcherToken.Text].tokens, tkns[:end]...),
383
- }
384
- tkns = tkns[end:]
367
}
386
- return m, nil
368
+ return aps
369
}
370
389
-func (st *ServerType) matcherSetFromMatcherToken(
371
+func matcherSetFromMatcherToken(
372
tkn caddyfile.Token,
373
matcherDefs map[string]map[string]json.RawMessage,
374
warnings *[]caddyconfig.Warning,
@@ -424,10 +406,11 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
406
var matcherPairs []*hostPathPair
407
408
for _, key := range sblock.Keys {
427
- addr, err := standardizeAddress(key)
409
+ addr, err := ParseAddress(key)
410
if err != nil {
411
return nil, fmt.Errorf("server block %v: parsing and standardizing address '%s': %v", sblock.Keys, key, err)
412
}
413
+ addr = addr.Normalize()
414
415
// choose a matcher pair that should be shared by this
416
// server block; if none exists yet, create one
@@ -504,14 +487,6 @@ func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (map[string]
487
return msEncoded, nil
488
}
489
507
-// HandlerDirective implements a directive for an HTTP handler,
508
-// in that it can unmarshal its own configuration from Caddyfile
509
-// tokens and also specify which directive bucket it belongs in.
510
-type HandlerDirective interface {
511
- caddyfile.Unmarshaler
512
- Bucket() int
513
-}
514
-
490
// tryInt tries to convert str to an integer. If it fails, it downgrades
491
// the error to a warning and returns 0.
492
func tryInt(str string, warnings *[]caddyconfig.Warning) int {
@@ -535,7 +510,7 @@ type matcherSetAndTokens struct {
510
// served on those addresses.
511
type sbAddrAssociation struct {
512
addresses []string
538
- serverBlocks []caddyfile.ServerBlock
513
+ serverBlocks []serverBlock
514
}
515
516
// Interface guard
context.go
+8
-3
@@ -99,11 +99,16 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
99
return nil, fmt.Errorf("module '%s' has no constructor", mod.Name)
100
}
101
102
- val := mod.New()
102
+ val := mod.New().(interface{})
103
104
- // value must be a pointer for unmarshaling into concrete type
104
+ // value must be a pointer for unmarshaling into concrete type, even if
105
+ // the module's concrete type is a slice or map; New() *should* return
106
+ // a pointer, otherwise unmarshaling errors or panics will occur
107
if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr {
106
- val = reflect.New(rv.Type()).Elem().Addr().Interface()
108
+ log.Printf("[WARNING] ModuleInfo.New() for module '%s' did not return a pointer,"+
109
+ " so we are using reflection to make a pointer instead; please fix this by"+
110
+ " using new(Type) or &Type notation in your module's New() function.", name)
111
+ val = reflect.New(rv.Type()).Elem().Addr().Interface().(Module)
112
}
113
114
// fill in its config only if there is a config to fill in
modules.go
+72
-35
@@ -23,48 +23,76 @@ import (
23
"sync"
24
)
25
26
-// Module represents a Caddy module.
27
-type Module struct {
26
+// Module is a type that is used as a Caddy module.
27
+type Module interface {
28
+ // This method indicates the type is a Caddy
29
+ // module. The returned ModuleInfo must have
30
+ // both a name and a constructor function.
31
+ // This method must not have any side-effects.
32
+ CaddyModule() ModuleInfo
33
+}
34
+
35
+// ModuleInfo represents a registered Caddy module.
36
+type ModuleInfo struct {
37
// Name is the full name of the module. It
38
// must be unique and properly namespaced.
39
Name string
40
32
- // New returns a new, empty instance of
33
- // the module's type. The host module
34
- // which loads this module will likely
35
- // invoke methods on the returned value.
36
- // It must return a pointer; if not, it
37
- // is converted into one.
38
- New func() interface{}
41
+ // New returns a pointer to a new, empty
42
+ // instance of the module's type. The host
43
+ // module which instantiates this module will
44
+ // likely type-assert and invoke methods on
45
+ // the returned value. This function must not
46
+ // have any side-effects.
47
+ New func() Module
48
+}
49
+
50
+// Namespace returns the module's namespace (scope)
51
+// which is all but the last element of its name.
52
+// If there is no explicit namespace in the name,
53
+// the whole name is considered the namespace.
54
+func (mi ModuleInfo) Namespace() string {
55
+ lastDot := strings.LastIndex(mi.Name, ".")
56
+ if lastDot < 0 {
57
+ return mi.Name
58
+ }
59
+ return mi.Name[:lastDot]
60
}
61
62
// ID returns a module's ID, which is the
63
// last element of its name.
43
-func (m Module) ID() string {
44
- if m.Name == "" {
64
+func (mi ModuleInfo) ID() string {
65
+ if mi.Name == "" {
66
return ""
67
}
47
- parts := strings.Split(m.Name, ".")
68
+ parts := strings.Split(mi.Name, ".")
69
return parts[len(parts)-1]
70
}
71
51
-// Namespace returns the module's namespace (scope)
52
-// which is all but the last element of its name.
53
-func (m Module) Namespace() string {
54
- lastDot := strings.LastIndex(m.Name, ".")
55
- if lastDot < 0 {
56
- return ""
72
+func (mi ModuleInfo) String() string { return mi.Name }
73
+
74
+// RegisterModule registers a module by receiving a
75
+// plain/empty value of the module. For registration to
76
+// be properly recorded, this should be called in the
77
+// init phase of runtime. Typically, the module package
78
+// will do this as a side-effect of being imported.
79
+// This function returns an error if the module's info
80
+// is incomplete or invalid, or if the module is
81
+// already registered.
82
+func RegisterModule(instance Module) error {
83
+ mod := instance.CaddyModule()
84
+
85
+ if mod.Name == "" {
86
+ return fmt.Errorf("missing ModuleInfo.Name")
87
}
58
- return m.Name[:lastDot]
59
-}
60
-
61
-func (m Module) String() string { return m.Name }
62
-
63
-// RegisterModule registers a module. Modules must call
64
-// this function in the init phase of runtime.
65
-func RegisterModule(mod Module) error {
66
- if mod.Name == "caddy" {
67
- return fmt.Errorf("modules cannot be named 'caddy'")
88
+ if mod.Name == "caddy" || mod.Name == "admin" {
89
+ return fmt.Errorf("module name '%s' is reserved", mod.Name)
90
+ }
91
+ if mod.New == nil {
92
+ return fmt.Errorf("missing ModuleInfo.New")
93
+ }
94
+ if val := mod.New(); val == nil {
95
+ return fmt.Errorf("ModuleInfo.New must return a non-nil module instance")
96
}
97
98
modulesMu.Lock()
@@ -77,18 +105,27 @@ func RegisterModule(mod Module) error {
105
return nil
106
}
107
80
-// GetModule returns a module by its full name.
81
-func GetModule(name string) (Module, error) {
108
+// GetModule returns module information from its full name.
109
+func GetModule(name string) (ModuleInfo, error) {
110
modulesMu.Lock()
111
defer modulesMu.Unlock()
84
-
112
m, ok := modules[name]
113
if !ok {
87
- return Module{}, fmt.Errorf("module not registered: %s", name)
114
+ return ModuleInfo{}, fmt.Errorf("module not registered: %s", name)
115
}
116
return m, nil
117
}
118
119
+// GetModuleName returns a module's name from an instance of its value.
120
+// If the value is not a module, an empty name will be returned.
121
+func GetModuleName(instance interface{}) string {
122
+ var name string
123
+ if mod, ok := instance.(Module); ok {
124
+ name = mod.CaddyModule().Name
125
+ }
126
+ return name
127
+}
128
+
129
// GetModules returns all modules in the given scope/namespace.
130
// For example, a scope of "foo" returns modules named "foo.bar",
131
// "foo.loo", but not "bar", "foo.bar.loo", etc. An empty scope
@@ -98,7 +135,7 @@ func GetModule(name string) (Module, error) {
135
//
136
// Because modules are registered to a map, the returned slice
137
// will be sorted to keep it deterministic.
101
-func GetModules(scope string) []Module {
138
+func GetModules(scope string) []ModuleInfo {
139
modulesMu.Lock()
140
defer modulesMu.Unlock()
141
@@ -110,7 +147,7 @@ func GetModules(scope string) []Module {
147
scopeParts = []string{}
148
}
149
113
- var mods []Module
150
+ var mods []ModuleInfo
151
iterateModules:
152
for name, m := range modules {
153
modParts := strings.Split(name, ".")
@@ -223,6 +260,6 @@ func strictUnmarshalJSON(data []byte, v interface{}) error {
260
}
261
262
var (
226
- modules = make(map[string]Module)
263
+ modules = make(map[string]ModuleInfo)
264
modulesMu sync.Mutex
265
)
modules/caddyhttp/caddyhttp.go
+12
-7
@@ -37,10 +37,7 @@ import (
37
func init() {
38
weakrand.Seed(time.Now().UnixNano())
39
40
- err := caddy.RegisterModule(caddy.Module{
41
- Name: "http",
42
- New: func() interface{} { return new(App) },
43
- })
40
+ err := caddy.RegisterModule(App{})
41
if err != nil {
42
log.Fatal(err)
43
}
@@ -58,6 +55,14 @@ type App struct {
55
ctx caddy.Context
56
}
57
58
+// CaddyModule returns the Caddy module information.
59
+func (App) CaddyModule() caddy.ModuleInfo {
60
+ return caddy.ModuleInfo{
61
+ Name: "http",
62
+ New: func() caddy.Module { return new(App) },
63
+ }
64
+}
65
+
66
// Provision sets up the app.
67
func (app *App) Provision(ctx caddy.Context) error {
68
app.ctx = ctx
@@ -227,7 +232,7 @@ func (app *App) automaticHTTPS() error {
232
// find all qualifying domain names, de-duplicated
233
domainSet := make(map[string]struct{})
234
for _, route := range srv.Routes {
230
- for _, matcherSet := range route.matcherSets {
235
+ for _, matcherSet := range route.MatcherSets {
236
for _, m := range matcherSet {
237
if hm, ok := m.(*MatchHost); ok {
238
for _, d := range *hm {
@@ -331,13 +336,13 @@ func (app *App) automaticHTTPS() error {
336
redirTo += "{http.request.uri}"
337
338
redirRoutes = append(redirRoutes, Route{
334
- matcherSets: []MatcherSet{
339
+ MatcherSets: []MatcherSet{
340
{
341
MatchProtocol("http"),
342
MatchHost(domains),
343
},
344
},
340
- handlers: []MiddlewareHandler{
345
+ Handlers: []MiddlewareHandler{
346
StaticResponse{
347
StatusCode: WeakString(strconv.Itoa(http.StatusTemporaryRedirect)), // TODO: use permanent redirect instead
348
Headers: http.Header{
modules/caddyhttp/caddylog/log.go
+9
-4
@@ -24,10 +24,7 @@ import (
24
)
25
26
func init() {
27
- caddy.RegisterModule(caddy.Module{
28
- Name: "http.handlers.log",
29
- New: func() interface{} { return new(Log) },
30
- })
27
+ caddy.RegisterModule(Log{})
28
}
29
30
// Log implements a simple logging middleware.
@@ -36,6 +33,14 @@ type Log struct {
33
counter int
34
}
35
36
+// CaddyModule returns the Caddy module information.
37
+func (Log) CaddyModule() caddy.ModuleInfo {
38
+ return caddy.ModuleInfo{
39
+ Name: "http.handlers.log",
40
+ New: func() caddy.Module { return new(Log) },
41
+ }
42
+}
43
+
44
func (l *Log) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
45
start := time.Now()
46
modules/caddyhttp/encode/brotli/brotli.go
+10
-5
@@ -19,16 +19,13 @@ import (
19
"strconv"
20
21
"github.com/andybalholm/brotli"
22
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
22
"github.com/caddyserver/caddy/v2"
23
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
24
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
25
)
26
27
func init() {
28
- caddy.RegisterModule(caddy.Module{
29
- Name: "http.encoders.brotli",
30
- New: func() interface{} { return new(Brotli) },
31
- })
28
+ caddy.RegisterModule(Brotli{})
29
}
30
31
// Brotli can create brotli encoders. Note that brotli
@@ -37,6 +34,14 @@ type Brotli struct {
34
Quality *int `json:"quality,omitempty"`
35
}
36
37
+// CaddyModule returns the Caddy module information.
38
+func (Brotli) CaddyModule() caddy.ModuleInfo {
39
+ return caddy.ModuleInfo{
40
+ Name: "http.encoders.brotli",
41
+ New: func() caddy.Module { return new(Brotli) },
42
+ }
43
+}
44
+
45
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
46
func (b *Brotli) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
47
for d.Next() {
modules/caddyhttp/encode/caddyfile.go
+18
-4
@@ -22,8 +22,25 @@ import (
22
"github.com/caddyserver/caddy/v2/caddyconfig"
23
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
24
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
25
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
26
)
27
28
+func init() {
29
+ httpcaddyfile.RegisterHandlerDirective("encode", parseCaddyfile)
30
+}
31
+
32
+// TODO: This is a good example of why UnmarshalCaddyfile is still a good idea... hmm.
33
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
34
+ enc := new(Encode)
35
+ err := enc.UnmarshalCaddyfile(h.Dispenser)
36
+ if err != nil {
37
+ return nil, err
38
+ }
39
+ return enc, nil
40
+}
41
+
42
+// TODO: Keep UnmarshalCaddyfile pattern?
43
+
44
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
45
//
46
// encode [<matcher>] <formats...> {
@@ -78,8 +95,5 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
95
return nil
96
}
97
81
-// Bucket returns the HTTP Caddyfile handler bucket number.
82
-func (enc Encode) Bucket() int { return 3 }
83
-
98
// Interface guard
85
-var _ httpcaddyfile.HandlerDirective = (*Encode)(nil)
99
+var _ caddyfile.Unmarshaler = (*Encode)(nil)
modules/caddyhttp/encode/encode.go
+9
-4
@@ -35,10 +35,7 @@ import (
35
)
36
37
func init() {
38
- caddy.RegisterModule(caddy.Module{
39
- Name: "http.handlers.encode",
40
- New: func() interface{} { return new(Encode) },
41
- })
38
+ caddy.RegisterModule(Encode{})
39
}
40
41
// Encode is a middleware which can encode responses.
@@ -50,6 +47,14 @@ type Encode struct {
47
writerPools map[string]*sync.Pool // TODO: these pools do not get reused through config reloads...
48
}
49
50
+// CaddyModule returns the Caddy module information.
51
+func (Encode) CaddyModule() caddy.ModuleInfo {
52
+ return caddy.ModuleInfo{
53
+ Name: "http.handlers.encode",
54
+ New: func() caddy.Module { return new(Encode) },
55
+ }
56
+}
57
+
58
// Provision provisions enc.
59
func (enc *Encode) Provision(ctx caddy.Context) error {
60
for modName, rawMsg := range enc.EncodingsRaw {
modules/caddyhttp/encode/gzip/gzip.go
+10
-5
@@ -20,16 +20,13 @@ import (
20
"fmt"
21
"strconv"
22
23
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
23
"github.com/caddyserver/caddy/v2"
24
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
25
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
26
)
27
28
func init() {
29
- caddy.RegisterModule(caddy.Module{
30
- Name: "http.encoders.gzip",
31
- New: func() interface{} { return new(Gzip) },
32
- })
29
+ caddy.RegisterModule(Gzip{})
30
}
31
32
// Gzip can create gzip encoders.
@@ -37,6 +34,14 @@ type Gzip struct {
34
Level int `json:"level,omitempty"`
35
}
36
37
+// CaddyModule returns the Caddy module information.
38
+func (Gzip) CaddyModule() caddy.ModuleInfo {
39
+ return caddy.ModuleInfo{
40
+ Name: "http.encoders.gzip",
41
+ New: func() caddy.Module { return new(Gzip) },
42
+ }
43
+}
44
+
45
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
46
func (g *Gzip) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
47
for d.Next() {
modules/caddyhttp/encode/zstd/zstd.go
+10
-5
@@ -15,22 +15,27 @@
15
package caddyzstd
16
17
import (
18
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
18
"github.com/caddyserver/caddy/v2"
19
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
20
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
21
"github.com/klauspost/compress/zstd"
22
)
23
24
func init() {
25
- caddy.RegisterModule(caddy.Module{
26
- Name: "http.encoders.zstd",
27
- New: func() interface{} { return new(Zstd) },
28
- })
25
+ caddy.RegisterModule(Zstd{})
26
}
27
28
// Zstd can create Zstandard encoders.
29
type Zstd struct{}
30
31
+// CaddyModule returns the Caddy module information.
32
+func (Zstd) CaddyModule() caddy.ModuleInfo {
33
+ return caddy.ModuleInfo{
34
+ Name: "http.encoders.zstd",
35
+ New: func() caddy.Module { return new(Zstd) },
36
+ }
37
+}
38
+
39
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
40
func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
41
return nil
modules/caddyhttp/fileserver/caddyfile.go
+50
-33
@@ -15,59 +15,58 @@
15
package fileserver
16
17
import (
18
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
18
+ "encoding/json"
19
+
20
+ "github.com/caddyserver/caddy/modules/caddyhttp/rewrite"
21
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
22
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
23
)
24
22
-// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
23
-//
24
-// file_server [<matcher>] [browse] {
25
-// hide <files...>
26
-// index <files...>
27
-// browse [<template_file>]
28
-// root <path>
29
-// }
30
-//
31
-// If browse is given on the first line, it can't be used in the block also.
32
-// The default root is the one given by the root directive.
33
-func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
34
- for d.Next() {
35
- args := d.RemainingArgs()
25
+func init() {
26
+ httpcaddyfile.RegisterHandlerDirective("file_server", parseCaddyfile)
27
+ httpcaddyfile.RegisterDirective("try_files", parseTryFiles)
28
+}
29
+
30
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
31
+ var fsrv FileServer
32
+
33
+ for h.Next() {
34
+ args := h.RemainingArgs()
35
switch len(args) {
36
case 0:
37
case 1:
38
if args[0] != "browse" {
40
- return d.ArgErr()
39
+ return nil, h.ArgErr()
40
}
41
fsrv.Browse = new(Browse)
42
default:
44
- return d.ArgErr()
43
+ return nil, h.ArgErr()
44
}
45
47
- for d.NextBlock() {
48
- switch d.Val() {
46
+ for h.NextBlock() {
47
+ switch h.Val() {
48
case "hide":
50
- fsrv.Hide = d.RemainingArgs()
49
+ fsrv.Hide = h.RemainingArgs()
50
if len(fsrv.Hide) == 0 {
52
- return d.ArgErr()
51
+ return nil, h.ArgErr()
52
}
53
case "index":
55
- fsrv.IndexNames = d.RemainingArgs()
54
+ fsrv.IndexNames = h.RemainingArgs()
55
if len(fsrv.Hide) == 0 {
57
- return d.ArgErr()
56
+ return nil, h.ArgErr()
57
}
58
case "root":
60
- if !d.Args(&fsrv.Root) {
61
- return d.ArgErr()
59
+ if !h.Args(&fsrv.Root) {
60
+ return nil, h.ArgErr()
61
}
62
case "browse":
63
if fsrv.Browse != nil {
65
- return d.Err("browsing is already configured")
64
+ return nil, h.Err("browsing is already configured")
65
}
66
fsrv.Browse = new(Browse)
68
- d.Args(&fsrv.Browse.TemplateFile)
67
+ h.Args(&fsrv.Browse.TemplateFile)
68
default:
70
- return d.Errf("unknown subdirective '%s'", d.Val())
69
+ return nil, h.Errf("unknown subdirective '%s'", h.Val())
70
}
71
}
72
}
@@ -77,11 +76,29 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
76
fsrv.Root = "{http.var.root}"
77
}
78
80
- return nil
79
+ return &fsrv, nil
80
}
81
83
-// Bucket returns the HTTP Caddyfile handler bucket number.
84
-func (fsrv FileServer) Bucket() int { return 7 }
82
+func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
83
+ if !h.Next() {
84
+ return nil, h.ArgErr()
85
+ }
86
+
87
+ try := h.RemainingArgs()
88
+ if len(try) == 0 {
89
+ return nil, h.ArgErr()
90
+ }
91
+
92
+ handler := rewrite.Rewrite{
93
+ URI: "{http.matchers.file.relative}{http.request.uri.query}",
94
+ }
95
86
-// Interface guard
87
-var _ httpcaddyfile.HandlerDirective = (*FileServer)(nil)
96
+ matcherSet := map[string]json.RawMessage{
97
+ "file": h.JSON(MatchFile{
98
+ Root: "{http.var.root}",
99
+ TryFiles: try,
100
+ }, nil),
101
+ }
102
+
103
+ return h.NewRoute(matcherSet, handler), nil
104
+}
modules/caddyhttp/fileserver/matcher.go
+15
-7
@@ -20,16 +20,13 @@ import (
20
"os"
21
"time"
22
23
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
23
"github.com/caddyserver/caddy/v2"
24
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
25
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
26
)
27
28
func init() {
29
- caddy.RegisterModule(caddy.Module{
30
- Name: "http.matchers.file",
31
- New: func() interface{} { return new(MatchFile) },
32
- })
29
+ caddy.RegisterModule(MatchFile{})
30
}
31
32
// MatchFile is an HTTP request matcher that can match
@@ -52,12 +49,20 @@ type MatchFile struct {
49
TryPolicy string `json:"try_policy,omitempty"`
50
}
51
52
+// CaddyModule returns the Caddy module information.
53
+func (MatchFile) CaddyModule() caddy.ModuleInfo {
54
+ return caddy.ModuleInfo{
55
+ Name: "http.matchers.file",
56
+ New: func() caddy.Module { return new(MatchFile) },
57
+ }
58
+}
59
+
60
// UnmarshalCaddyfile sets up the matcher from Caddyfile tokens. Syntax:
61
//
62
// file {
63
// root <path>
64
// try_files <files...>
60
-// try_policy <first_exist|smallest_size|largest_size|most_recent_modified>
65
+// try_policy first_exist|smallest_size|largest_size|most_recent_modified
66
// }
67
//
68
func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
@@ -82,6 +87,9 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
87
}
88
}
89
}
90
+ if m.Root == "" {
91
+ m.Root = "{http.var.root}"
92
+ }
93
return nil
94
}
95
@@ -121,7 +129,7 @@ func (m MatchFile) Match(r *http.Request) bool {
129
func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
130
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
131
124
- root := repl.ReplaceAll(m.Root, "")
132
+ root := repl.ReplaceAll(m.Root, ".")
133
134
// if list of files to try was omitted entirely,
135
// assume URL path
modules/caddyhttp/fileserver/staticfiles.go
+9
-4
@@ -36,10 +36,7 @@ import (
36
func init() {
37
weakrand.Seed(time.Now().UnixNano())
38
39
- caddy.RegisterModule(caddy.Module{
40
- Name: "http.handlers.file_server",
41
- New: func() interface{} { return new(FileServer) },
42
- })
39
+ caddy.RegisterModule(FileServer{})
40
}
41
42
// FileServer implements a static file server responder for Caddy.
@@ -50,6 +47,14 @@ type FileServer struct {
47
Browse *Browse `json:"browse,omitempty"`
48
}
49
50
+// CaddyModule returns the Caddy module information.
51
+func (FileServer) CaddyModule() caddy.ModuleInfo {
52
+ return caddy.ModuleInfo{
53
+ Name: "http.handlers.file_server",
54
+ New: func() caddy.Module { return new(FileServer) },
55
+ }
56
+}
57
+
58
// Provision sets up the static files responder.
59
func (fsrv *FileServer) Provision(ctx caddy.Context) error {
60
if fsrv.IndexNames == nil {
modules/caddyhttp/headers/caddyfile.go
+36
-37
@@ -18,11 +18,15 @@ import (
18
"net/http"
19
"strings"
20
21
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
21
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
22
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
23
)
24
25
-// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
25
+func init() {
26
+ httpcaddyfile.RegisterHandlerDirective("headers", parseCaddyfile)
27
+}
28
+
29
+// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
30
//
31
// headers [<matcher>] [[+|-]<field> <value>] {
32
// [+][<field>] [<value>]
@@ -31,62 +35,57 @@ import (
35
//
36
// Either a block can be opened or a single header field can be configured
37
// in the first line, but not both in the same directive.
34
-func (h *Headers) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
35
- for d.Next() {
38
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
39
+ hdr := new(Headers)
40
+ for h.Next() {
41
// first see if headers are in the initial line
42
var hasArgs bool
38
- if d.NextArg() {
43
+ if h.NextArg() {
44
hasArgs = true
40
- field := d.Val()
41
- d.NextArg()
42
- value := d.Val()
43
- h.processCaddyfileLine(field, value)
45
+ field := h.Val()
46
+ h.NextArg()
47
+ value := h.Val()
48
+ processCaddyfileLine(hdr, field, value)
49
}
50
51
// if not, they should be in a block
47
- for d.NextBlock() {
52
+ for h.NextBlock() {
53
if hasArgs {
49
- return d.Err("cannot specify headers in both arguments and block")
54
+ return nil, h.Err("cannot specify headers in both arguments and block")
55
}
51
- field := d.Val()
56
+ field := h.Val()
57
var value string
53
- if d.NextArg() {
54
- value = d.Val()
58
+ if h.NextArg() {
59
+ value = h.Val()
60
}
56
- h.processCaddyfileLine(field, value)
61
+ processCaddyfileLine(hdr, field, value)
62
}
63
}
59
- return nil
64
+ return hdr, nil
65
}
66
62
-func (h *Headers) processCaddyfileLine(field, value string) {
67
+func processCaddyfileLine(hdr *Headers, field, value string) {
68
if strings.HasPrefix(field, "+") {
64
- if h.Response == nil {
65
- h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
69
+ if hdr.Response == nil {
70
+ hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
71
}
67
- if h.Response.Add == nil {
68
- h.Response.Add = make(http.Header)
72
+ if hdr.Response.Add == nil {
73
+ hdr.Response.Add = make(http.Header)
74
}
70
- h.Response.Add.Set(field[1:], value)
75
+ hdr.Response.Add.Set(field[1:], value)
76
} else if strings.HasPrefix(field, "-") {
72
- if h.Response == nil {
73
- h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
77
+ if hdr.Response == nil {
78
+ hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
79
}
75
- h.Response.Delete = append(h.Response.Delete, field[1:])
76
- h.Response.Deferred = true
80
+ hdr.Response.Delete = append(hdr.Response.Delete, field[1:])
81
+ hdr.Response.Deferred = true
82
} else {
78
- if h.Response == nil {
79
- h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
83
+ if hdr.Response == nil {
84
+ hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
85
}
81
- if h.Response.Set == nil {
82
- h.Response.Set = make(http.Header)
86
+ if hdr.Response.Set == nil {
87
+ hdr.Response.Set = make(http.Header)
88
}
84
- h.Response.Set.Set(field, value)
89
+ hdr.Response.Set.Set(field, value)
90
}
91
}
87
-
88
-// Bucket returns the HTTP Caddyfile handler bucket number.
89
-func (h Headers) Bucket() int { return 3 }
90
-
91
-// Interface guard
92
-var _ httpcaddyfile.HandlerDirective = (*Headers)(nil)
modules/caddyhttp/headers/headers.go
+9
-4
@@ -23,10 +23,7 @@ import (
23
)
24
25
func init() {
26
- caddy.RegisterModule(caddy.Module{
27
- Name: "http.handlers.headers",
28
- New: func() interface{} { return new(Headers) },
29
- })
26
+ caddy.RegisterModule(Headers{})
27
}
28
29
// Headers is a middleware which can mutate HTTP headers.
@@ -35,6 +32,14 @@ type Headers struct {
32
Response *RespHeaderOps `json:"response,omitempty"`
33
}
34
35
+// CaddyModule returns the Caddy module information.
36
+func (Headers) CaddyModule() caddy.ModuleInfo {
37
+ return caddy.ModuleInfo{
38
+ Name: "http.handlers.headers",
39
+ New: func() caddy.Module { return new(Headers) },
40
+ }
41
+}
42
+
43
// HeaderOps defines some operations to
44
// perform on HTTP headers.
45
type HeaderOps struct {
modules/caddyhttp/markdown/markdown.go
+9
-4
@@ -28,16 +28,21 @@ import (
28
)
29
30
func init() {
31
- caddy.RegisterModule(caddy.Module{
32
- Name: "http.handlers.markdown",
33
- New: func() interface{} { return new(Markdown) },
34
- })
31
+ caddy.RegisterModule(Markdown{})
32
}
33
34
// Markdown is a middleware for rendering a Markdown response body.
35
type Markdown struct {
36
}
37
38
+// CaddyModule returns the Caddy module information.
39
+func (Markdown) CaddyModule() caddy.ModuleInfo {
40
+ return caddy.ModuleInfo{
41
+ Name: "http.handlers.markdown",
42
+ New: func() caddy.Module { return new(Markdown) },
43
+ }
44
+}
45
+
46
func (m Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
47
buf := bufPool.Get().(*bytes.Buffer)
48
buf.Reset()
modules/caddyhttp/matchers.go
+124
-52
@@ -80,50 +80,25 @@ type (
80
)
81
82
func init() {
83
- caddy.RegisterModule(caddy.Module{
83
+ caddy.RegisterModule(MatchHost{})
84
+ caddy.RegisterModule(MatchPath{})
85
+ caddy.RegisterModule(MatchPathRE{})
86
+ caddy.RegisterModule(MatchMethod{})
87
+ caddy.RegisterModule(MatchQuery{})
88
+ caddy.RegisterModule(MatchHeader{})
89
+ caddy.RegisterModule(MatchHeaderRE{})
90
+ caddy.RegisterModule(new(MatchProtocol))
91
+ caddy.RegisterModule(MatchRemoteIP{})
92
+ caddy.RegisterModule(MatchNegate{})
93
+ caddy.RegisterModule(new(MatchStarlarkExpr))
94
+}
95
+
96
+// CaddyModule returns the Caddy module information.
97
+func (MatchHost) CaddyModule() caddy.ModuleInfo {
98
+ return caddy.ModuleInfo{
99
Name: "http.matchers.host",
85
- New: func() interface{} { return new(MatchHost) },
86
- })
87
- caddy.RegisterModule(caddy.Module{
88
- Name: "http.matchers.path",
89
- New: func() interface{} { return new(MatchPath) },
90
- })
91
- caddy.RegisterModule(caddy.Module{
92
- Name: "http.matchers.path_regexp",
93
- New: func() interface{} { return new(MatchPathRE) },
94
- })
95
- caddy.RegisterModule(caddy.Module{
96
- Name: "http.matchers.method",
97
- New: func() interface{} { return new(MatchMethod) },
98
- })
99
- caddy.RegisterModule(caddy.Module{
100
- Name: "http.matchers.query",
101
- New: func() interface{} { return new(MatchQuery) },
102
- })
103
- caddy.RegisterModule(caddy.Module{
104
- Name: "http.matchers.header",
105
- New: func() interface{} { return new(MatchHeader) },
106
- })
107
- caddy.RegisterModule(caddy.Module{
108
- Name: "http.matchers.header_regexp",
109
- New: func() interface{} { return new(MatchHeaderRE) },
110
- })
111
- caddy.RegisterModule(caddy.Module{
112
- Name: "http.matchers.protocol",
113
- New: func() interface{} { return new(MatchProtocol) },
114
- })
115
- caddy.RegisterModule(caddy.Module{
116
- Name: "http.matchers.remote_ip",
117
- New: func() interface{} { return new(MatchRemoteIP) },
118
- })
119
- caddy.RegisterModule(caddy.Module{
120
- Name: "http.matchers.not",
121
- New: func() interface{} { return new(MatchNegate) },
122
- })
123
- caddy.RegisterModule(caddy.Module{
124
- Name: "http.matchers.starlark_expr",
125
- New: func() interface{} { return new(MatchStarlarkExpr) },
126
- })
100
+ New: func() caddy.Module { return new(MatchHost) },
101
+ }
102
}
103
104
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
@@ -165,6 +140,14 @@ outer:
140
return false
141
}
142
143
+// CaddyModule returns the Caddy module information.
144
+func (MatchPath) CaddyModule() caddy.ModuleInfo {
145
+ return caddy.ModuleInfo{
146
+ Name: "http.matchers.path",
147
+ New: func() caddy.Module { return new(MatchPath) },
148
+ }
149
+}
150
+
151
// Match returns true if r matches m.
152
func (m MatchPath) Match(r *http.Request) bool {
153
for _, matchPath := range m {
@@ -186,19 +169,39 @@ func (m MatchPath) Match(r *http.Request) bool {
169
170
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
171
func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
189
- *m = d.RemainingArgs()
172
+ for d.Next() {
173
+ *m = d.RemainingArgs()
174
+ }
175
return nil
176
}
177
178
+// CaddyModule returns the Caddy module information.
179
+func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
180
+ return caddy.ModuleInfo{
181
+ Name: "http.matchers.path_regexp",
182
+ New: func() caddy.Module { return new(MatchPathRE) },
183
+ }
184
+}
185
+
186
// Match returns true if r matches m.
187
func (m MatchPathRE) Match(r *http.Request) bool {
188
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
189
return m.MatchRegexp.Match(r.URL.Path, repl, "path_regexp")
190
}
191
192
+// CaddyModule returns the Caddy module information.
193
+func (MatchMethod) CaddyModule() caddy.ModuleInfo {
194
+ return caddy.ModuleInfo{
195
+ Name: "http.matchers.method",
196
+ New: func() caddy.Module { return new(MatchMethod) },
197
+ }
198
+}
199
+
200
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
201
func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
201
- *m = d.RemainingArgs()
202
+ for d.Next() {
203
+ *m = d.RemainingArgs()
204
+ }
205
return nil
206
}
207
@@ -212,6 +215,14 @@ func (m MatchMethod) Match(r *http.Request) bool {
215
return false
216
}
217
218
+// CaddyModule returns the Caddy module information.
219
+func (MatchQuery) CaddyModule() caddy.ModuleInfo {
220
+ return caddy.ModuleInfo{
221
+ Name: "http.matchers.query",
222
+ New: func() caddy.Module { return new(MatchQuery) },
223
+ }
224
+}
225
+
226
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
227
func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
228
for d.Next() {
@@ -237,6 +248,14 @@ func (m MatchQuery) Match(r *http.Request) bool {
248
return false
249
}
250
251
+// CaddyModule returns the Caddy module information.
252
+func (MatchHeader) CaddyModule() caddy.ModuleInfo {
253
+ return caddy.ModuleInfo{
254
+ Name: "http.matchers.header",
255
+ New: func() caddy.Module { return new(MatchHeader) },
256
+ }
257
+}
258
+
259
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
260
func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
261
for d.Next() {
@@ -270,6 +289,14 @@ func (m MatchHeader) Match(r *http.Request) bool {
289
return true
290
}
291
292
+// CaddyModule returns the Caddy module information.
293
+func (MatchHeaderRE) CaddyModule() caddy.ModuleInfo {
294
+ return caddy.ModuleInfo{
295
+ Name: "http.matchers.header_regexp",
296
+ New: func() caddy.Module { return new(MatchHeaderRE) },
297
+ }
298
+}
299
+
300
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
301
func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
302
if *m == nil {
@@ -319,6 +346,14 @@ func (m MatchHeaderRE) Validate() error {
346
return nil
347
}
348
349
+// CaddyModule returns the Caddy module information.
350
+func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
351
+ return caddy.ModuleInfo{
352
+ Name: "http.matchers.protocol",
353
+ New: func() caddy.Module { return new(MatchProtocol) },
354
+ }
355
+}
356
+
357
// Match returns true if r matches m.
358
func (m MatchProtocol) Match(r *http.Request) bool {
359
switch string(m) {
@@ -334,14 +369,24 @@ func (m MatchProtocol) Match(r *http.Request) bool {
369
370
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
371
func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
337
- var proto string
338
- if !d.Args(&proto) {
339
- return d.Err("expected exactly one protocol")
372
+ for d.Next() {
373
+ var proto string
374
+ if !d.Args(&proto) {
375
+ return d.Err("expected exactly one protocol")
376
+ }
377
+ *m = MatchProtocol(proto)
378
}
341
- *m = MatchProtocol(proto)
379
return nil
380
}
381
382
+// CaddyModule returns the Caddy module information.
383
+func (MatchNegate) CaddyModule() caddy.ModuleInfo {
384
+ return caddy.ModuleInfo{
385
+ Name: "http.matchers.not",
386
+ New: func() caddy.Module { return new(MatchNegate) },
387
+ }
388
+}
389
+
390
// UnmarshalJSON unmarshals data into m's unexported map field.
391
// This is done because we cannot embed the map directly into
392
// the struct, but we need a struct because we need another
@@ -375,9 +420,19 @@ func (m MatchNegate) Match(r *http.Request) bool {
420
return !m.matchers.Match(r)
421
}
422
423
+// CaddyModule returns the Caddy module information.
424
+func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
425
+ return caddy.ModuleInfo{
426
+ Name: "http.matchers.remote_ip",
427
+ New: func() caddy.Module { return new(MatchRemoteIP) },
428
+ }
429
+}
430
+
431
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
432
func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
380
- m.Ranges = d.RemainingArgs()
433
+ for d.Next() {
434
+ m.Ranges = d.RemainingArgs()
435
+ }
436
return nil
437
}
438
@@ -442,6 +497,14 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
497
return false
498
}
499
500
+// CaddyModule returns the Caddy module information.
501
+func (MatchStarlarkExpr) CaddyModule() caddy.ModuleInfo {
502
+ return caddy.ModuleInfo{
503
+ Name: "http.matchers.starlark_expr", // TODO: Rename to 'starlark'?
504
+ New: func() caddy.Module { return new(MatchStarlarkExpr) },
505
+ }
506
+}
507
+
508
// Match returns true if r matches m.
509
func (m MatchStarlarkExpr) Match(r *http.Request) bool {
510
input := string(m)
@@ -513,8 +576,17 @@ func (mre *MatchRegexp) Match(input string, repl caddy.Replacer, scope string) b
576
577
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
578
func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
516
- if !d.Args(&mre.Name, &mre.Pattern) {
517
- return fmt.Errorf("missing arguments")
579
+ for d.Next() {
580
+ args := d.RemainingArgs()
581
+ switch len(args) {
582
+ case 1:
583
+ mre.Pattern = args[0]
584
+ case 2:
585
+ mre.Name = args[0]
586
+ mre.Pattern = args[1]
587
+ default:
588
+ return d.ArgErr()
589
+ }
590
}
591
return nil
592
}
modules/caddyhttp/requestbody/requestbody.go
+9
-4
@@ -22,10 +22,7 @@ import (
22
)
23
24
func init() {
25
- caddy.RegisterModule(caddy.Module{
26
- Name: "http.handlers.request_body",
27
- New: func() interface{} { return new(RequestBody) },
28
- })
25
+ caddy.RegisterModule(RequestBody{})
26
}
27
28
// RequestBody is a middleware for manipulating the request body.
@@ -33,6 +30,14 @@ type RequestBody struct {
30
MaxSize int64 `json:"max_size,omitempty"`
31
}
32
33
+// CaddyModule returns the Caddy module information.
34
+func (RequestBody) CaddyModule() caddy.ModuleInfo {
35
+ return caddy.ModuleInfo{
36
+ Name: "http.handlers.request_body", // TODO: better name for this?
37
+ New: func() caddy.Module { return new(RequestBody) },
38
+ }
39
+}
40
+
41
func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
42
if r.Body == nil {
43
return next.ServeHTTP(w, r)
modules/caddyhttp/reverseproxy/module.go
+18
-18
@@ -15,39 +15,39 @@
15
package reverseproxy
16
17
import (
18
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
19
- "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
18
"github.com/caddyserver/caddy/v2"
19
+ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
20
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
21
)
22
23
-// Register caddy module.
23
func init() {
25
- caddy.RegisterModule(caddy.Module{
24
+ caddy.RegisterModule(new(LoadBalanced))
25
+ httpcaddyfile.RegisterHandlerDirective("reverse_proxy", parseCaddyfile) // TODO: "proxy"?
26
+}
27
+
28
+// CaddyModule returns the Caddy module information.
29
+func (*LoadBalanced) CaddyModule() caddy.ModuleInfo {
30
+ return caddy.ModuleInfo{
31
Name: "http.handlers.reverse_proxy",
27
- New: func() interface{} { return new(LoadBalanced) },
28
- })
32
+ New: func() caddy.Module { return new(LoadBalanced) },
33
+ }
34
}
35
31
-// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
36
+// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
37
//
38
// proxy [<matcher>] <to>
39
//
40
// TODO: This needs to be finished. It definitely needs to be able to open a block...
36
-func (lb *LoadBalanced) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
37
- for d.Next() {
38
- allTo := d.RemainingArgs()
41
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
42
+ lb := new(LoadBalanced)
43
+ for h.Next() {
44
+ allTo := h.RemainingArgs()
45
if len(allTo) == 0 {
40
- return d.ArgErr()
46
+ return nil, h.ArgErr()
47
}
48
for _, to := range allTo {
49
lb.Upstreams = append(lb.Upstreams, &UpstreamConfig{Host: to})
50
}
51
}
46
- return nil
52
+ return lb, nil
53
}
48
-
49
-// Bucket returns the HTTP Caddyfile handler bucket number.
50
-func (*LoadBalanced) Bucket() int { return 7 }
51
-
52
-// Interface guard
53
-var _ httpcaddyfile.HandlerDirective = (*LoadBalanced)(nil)
modules/caddyhttp/rewrite/caddyfile.go
+11
-12
@@ -15,24 +15,23 @@
15
package rewrite
16
17
import (
18
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
18
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
19
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
20
)
21
22
-// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
22
+func init() {
23
+ httpcaddyfile.RegisterHandlerDirective("rewrite", parseCaddyfile)
24
+}
25
+
26
+// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
27
//
28
// rewrite [<matcher>] <to>
29
//
30
// The <to> parameter becomes the new URI.
27
-func (rewr *Rewrite) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
28
- for d.Next() {
29
- rewr.URI = d.Val()
31
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
32
+ var rewr Rewrite
33
+ for h.Next() {
34
+ rewr.URI = h.Val()
35
}
31
- return nil
36
+ return rewr, nil
37
}
33
-
34
-// Bucket returns the HTTP Caddyfile handler bucket number.
35
-func (rewr Rewrite) Bucket() int { return 1 }
36
-
37
-// Interface guard
38
-var _ httpcaddyfile.HandlerDirective = (*Rewrite)(nil)
modules/caddyhttp/rewrite/rewrite.go
+9
-4
@@ -24,10 +24,7 @@ import (
24
)
25
26
func init() {
27
- caddy.RegisterModule(caddy.Module{
28
- Name: "http.handlers.rewrite",
29
- New: func() interface{} { return new(Rewrite) },
30
- })
27
+ caddy.RegisterModule(Rewrite{})
28
}
29
30
// Rewrite is a middleware which can rewrite HTTP requests.
@@ -37,6 +34,14 @@ type Rewrite struct {
34
Rehandle bool `json:"rehandle,omitempty"`
35
}
36
37
+// CaddyModule returns the Caddy module information.
38
+func (Rewrite) CaddyModule() caddy.ModuleInfo {
39
+ return caddy.ModuleInfo{
40
+ Name: "http.handlers.rewrite",
41
+ New: func() caddy.Module { return new(Rewrite) },
42
+ }
43
+}
44
+
45
func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
46
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
47
var rehandleNeeded bool
modules/caddyhttp/routes.go
+19
-18
@@ -26,33 +26,34 @@ import (
26
// middlewares, and a responder for handling HTTP
27
// requests.
28
type Route struct {
29
- Group string `json:"group,omitempty"`
30
- MatcherSets []map[string]json.RawMessage `json:"match,omitempty"`
31
- Handle []json.RawMessage `json:"handle,omitempty"`
32
- Terminal bool `json:"terminal,omitempty"`
29
+ Group string `json:"group,omitempty"`
30
+ MatcherSetsRaw []map[string]json.RawMessage `json:"match,omitempty"`
31
+ HandlersRaw []json.RawMessage `json:"handle,omitempty"`
32
+ Terminal bool `json:"terminal,omitempty"`
33
34
// decoded values
35
- matcherSets []MatcherSet
36
- handlers []MiddlewareHandler
35
+ MatcherSets []MatcherSet `json:"-"`
36
+ Handlers []MiddlewareHandler `json:"-"`
37
}
38
39
// Empty returns true if the route has all zero/default values.
40
func (r Route) Empty() bool {
41
- return len(r.MatcherSets) == 0 &&
42
- len(r.Handle) == 0 &&
43
- len(r.handlers) == 0 &&
41
+ return len(r.MatcherSetsRaw) == 0 &&
42
+ len(r.MatcherSets) == 0 &&
43
+ len(r.HandlersRaw) == 0 &&
44
+ len(r.Handlers) == 0 &&
45
!r.Terminal &&
46
r.Group == ""
47
}
48
49
func (r Route) anyMatcherSetMatches(req *http.Request) bool {
49
- for _, ms := range r.matcherSets {
50
+ for _, ms := range r.MatcherSets {
51
if ms.Match(req) {
52
return true
53
}
54
}
55
// if no matchers, always match
55
- return len(r.matcherSets) == 0
56
+ return len(r.MatcherSets) == 0
57
}
58
59
// MatcherSet is a set of matchers which
@@ -79,7 +80,7 @@ type RouteList []Route
80
func (routes RouteList) Provision(ctx caddy.Context) error {
81
for i, route := range routes {
82
// matchers
82
- for _, matcherSet := range route.MatcherSets {
83
+ for _, matcherSet := range route.MatcherSetsRaw {
84
var matchers MatcherSet
85
for modName, rawMsg := range matcherSet {
86
val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
@@ -88,19 +89,19 @@ func (routes RouteList) Provision(ctx caddy.Context) error {
89
}
90
matchers = append(matchers, val.(RequestMatcher))
91
}
91
- routes[i].matcherSets = append(routes[i].matcherSets, matchers)
92
+ routes[i].MatcherSets = append(routes[i].MatcherSets, matchers)
93
}
93
- routes[i].MatcherSets = nil // allow GC to deallocate - TODO: Does this help?
94
+ routes[i].MatcherSetsRaw = nil // allow GC to deallocate - TODO: Does this help?
95
96
// handlers
96
- for j, rawMsg := range route.Handle {
97
+ for j, rawMsg := range route.HandlersRaw {
98
mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg)
99
if err != nil {
100
return fmt.Errorf("loading handler module in position %d: %v", j, err)
101
}
101
- routes[i].handlers = append(routes[i].handlers, mh.(MiddlewareHandler))
102
+ routes[i].Handlers = append(routes[i].Handlers, mh.(MiddlewareHandler))
103
}
103
- routes[i].Handle = nil // allow GC to deallocate - TODO: Does this help?
104
+ routes[i].HandlersRaw = nil // allow GC to deallocate - TODO: Does this help?
105
}
106
return nil
107
}
@@ -135,7 +136,7 @@ func (routes RouteList) BuildCompositeRoute(req *http.Request) Handler {
136
}
137
138
// apply the rest of the route
138
- for _, mh := range route.handlers {
139
+ for _, mh := range route.Handlers {
140
// we have to be sure to wrap mh outside
141
// of our current stack frame so that the
142
// reference to this mh isn't overwritten
modules/caddyhttp/staticerror.go
+9
-4
@@ -23,10 +23,7 @@ import (
23
)
24
25
func init() {
26
- caddy.RegisterModule(caddy.Module{
27
- Name: "http.handlers.error",
28
- New: func() interface{} { return new(StaticError) },
29
- })
26
+ caddy.RegisterModule(StaticError{})
27
}
28
29
// StaticError implements a simple handler that returns an error.
@@ -35,6 +32,14 @@ type StaticError struct {
32
StatusCode WeakString `json:"status_code,omitempty"`
33
}
34
35
+// CaddyModule returns the Caddy module information.
36
+func (StaticError) CaddyModule() caddy.ModuleInfo {
37
+ return caddy.ModuleInfo{
38
+ Name: "http.handlers.error",
39
+ New: func() caddy.Module { return new(StaticError) },
40
+ }
41
+}
42
+
43
func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
44
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
45
modules/caddyhttp/staticresp.go
+10
-7
@@ -24,10 +24,8 @@ import (
24
)
25
26
func init() {
27
- caddy.RegisterModule(caddy.Module{
28
- Name: "http.handlers.static_response",
29
- New: func() interface{} { return new(StaticResponse) },
30
- })
27
+ caddy.RegisterModule(StaticResponse{})
28
+ // TODO: Caddyfile directive
29
}
30
31
// StaticResponse implements a simple responder for static responses.
@@ -38,6 +36,14 @@ type StaticResponse struct {
36
Close bool `json:"close,omitempty"`
37
}
38
39
+// CaddyModule returns the Caddy module information.
40
+func (StaticResponse) CaddyModule() caddy.ModuleInfo {
41
+ return caddy.ModuleInfo{
42
+ Name: "http.handlers.static_response",
43
+ New: func() caddy.Module { return new(StaticResponse) },
44
+ }
45
+}
46
+
47
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
48
//
49
// static_response [<matcher>] <status> {
@@ -71,9 +77,6 @@ func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
77
return nil
78
}
79
74
-// Bucket returns the HTTP Caddyfile handler bucket number.
75
-func (StaticResponse) Bucket() int { return 7 }
76
-
80
func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
81
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
82
modules/caddyhttp/subroute.go
+9
-4
@@ -22,10 +22,7 @@ import (
22
)
23
24
func init() {
25
- caddy.RegisterModule(caddy.Module{
26
- Name: "http.handlers.subroute",
27
- New: func() interface{} { return new(Subroute) },
28
- })
25
+ caddy.RegisterModule(Subroute{})
26
}
27
28
// Subroute implements a handler that compiles and executes routes.
@@ -37,6 +34,14 @@ type Subroute struct {
34
Routes RouteList `json:"routes,omitempty"`
35
}
36
37
+// CaddyModule returns the Caddy module information.
38
+func (Subroute) CaddyModule() caddy.ModuleInfo {
39
+ return caddy.ModuleInfo{
40
+ Name: "http.handlers.subroute",
41
+ New: func() caddy.Module { return new(Subroute) },
42
+ }
43
+}
44
+
45
// Provision sets up subrouting.
46
func (sr *Subroute) Provision(ctx caddy.Context) error {
47
if sr.Routes != nil {
modules/caddyhttp/templates/caddyfile.go
+18
-19
@@ -15,11 +15,15 @@
15
package templates
16
17
import (
18
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
18
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
19
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
20
)
21
22
-// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
22
+func init() {
23
+ httpcaddyfile.RegisterHandlerDirective("templates", parseCaddyfile)
24
+}
25
+
26
+// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
27
//
28
// templates [<matcher>] {
29
// mime <types...>
@@ -27,23 +31,24 @@ import (
31
// root <path>
32
// }
33
//
30
-func (t *Templates) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
31
- for d.Next() {
32
- for d.NextBlock() {
33
- switch d.Val() {
34
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
35
+ t := new(Templates)
36
+ for h.Next() {
37
+ for h.NextBlock() {
38
+ switch h.Val() {
39
case "mime":
35
- t.MIMETypes = d.RemainingArgs()
40
+ t.MIMETypes = h.RemainingArgs()
41
if len(t.MIMETypes) == 0 {
37
- return d.ArgErr()
42
+ return nil, h.ArgErr()
43
}
44
case "between":
40
- t.Delimiters = d.RemainingArgs()
45
+ t.Delimiters = h.RemainingArgs()
46
if len(t.Delimiters) != 2 {
42
- return d.ArgErr()
47
+ return nil, h.ArgErr()
48
}
49
case "root":
45
- if !d.Args(&t.IncludeRoot) {
46
- return d.ArgErr()
50
+ if !h.Args(&t.IncludeRoot) {
51
+ return nil, h.ArgErr()
52
}
53
}
54
}
@@ -53,11 +58,5 @@ func (t *Templates) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
58
t.IncludeRoot = "{http.var.root}"
59
}
60
56
- return nil
61
+ return t, nil
62
}
58
-
59
-// Bucket returns the HTTP Caddyfile handler bucket number.
60
-func (t Templates) Bucket() int { return 5 }
61
-
62
-// Interface guard
63
-var _ httpcaddyfile.HandlerDirective = (*Templates)(nil)
modules/caddyhttp/templates/templates.go
+9
-4
@@ -27,10 +27,7 @@ import (
27
)
28
29
func init() {
30
- caddy.RegisterModule(caddy.Module{
31
- Name: "http.handlers.templates",
32
- New: func() interface{} { return new(Templates) },
33
- })
30
+ caddy.RegisterModule(Templates{})
31
}
32
33
// Templates is a middleware which execute response bodies as templates.
@@ -40,6 +37,14 @@ type Templates struct {
37
Delimiters []string `json:"delimiters,omitempty"`
38
}
39
40
+// CaddyModule returns the Caddy module information.
41
+func (Templates) CaddyModule() caddy.ModuleInfo {
42
+ return caddy.ModuleInfo{
43
+ Name: "http.handlers.templates",
44
+ New: func() caddy.Module { return new(Templates) },
45
+ }
46
+}
47
+
48
// Provision provisions t.
49
func (t *Templates) Provision(ctx caddy.Context) error {
50
if t.MIMETypes == nil {
modules/caddyhttp/vars.go
+18
-8
@@ -21,20 +21,22 @@ import (
21
)
22
23
func init() {
24
- caddy.RegisterModule(caddy.Module{
25
- Name: "http.handlers.vars",
26
- New: func() interface{} { return new(VarsMiddleware) },
27
- })
28
- caddy.RegisterModule(caddy.Module{
29
- Name: "http.matchers.vars",
30
- New: func() interface{} { return new(VarsMiddleware) },
31
- })
24
+ caddy.RegisterModule(VarsMiddleware{})
25
+ caddy.RegisterModule(VarsMatcher{})
26
}
27
28
// VarsMiddleware is an HTTP middleware which sets variables
29
// in the context, mainly for use by placeholders.
30
type VarsMiddleware map[string]string
31
32
+// CaddyModule returns the Caddy module information.
33
+func (VarsMiddleware) CaddyModule() caddy.ModuleInfo {
34
+ return caddy.ModuleInfo{
35
+ Name: "http.handlers.vars",
36
+ New: func() caddy.Module { return new(VarsMiddleware) },
37
+ }
38
+}
39
+
40
func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
41
vars := r.Context().Value(VarCtxKey).(map[string]interface{})
42
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
@@ -50,6 +52,14 @@ func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next H
52
// requests based on variables in the context.
53
type VarsMatcher map[string]string
54
55
+// CaddyModule returns the Caddy module information.
56
+func (VarsMatcher) CaddyModule() caddy.ModuleInfo {
57
+ return caddy.ModuleInfo{
58
+ Name: "http.matchers.vars",
59
+ New: func() caddy.Module { return new(VarsMatcher) },
60
+ }
61
+}
62
+
63
// Match matches a request based on variables in the context.
64
func (m VarsMatcher) Match(r *http.Request) bool {
65
vars := r.Context().Value(VarCtxKey).(map[string]string)
modules/caddytls/acmemanager.go
+12
-7
@@ -28,10 +28,7 @@ import (
28
)
29
30
func init() {
31
- caddy.RegisterModule(caddy.Module{
32
- Name: "tls.management.acme",
33
- New: func() interface{} { return new(ACMEManagerMaker) },
34
- })
31
+ caddy.RegisterModule(ACMEManagerMaker{})
32
}
33
34
// ACMEManagerMaker makes an ACME manager
@@ -57,9 +54,17 @@ type ACMEManagerMaker struct {
54
keyType certcrypto.KeyType
55
}
56
60
-// newManager is a no-op to satisfy the ManagerMaker interface,
57
+// CaddyModule returns the Caddy module information.
58
+func (ACMEManagerMaker) CaddyModule() caddy.ModuleInfo {
59
+ return caddy.ModuleInfo{
60
+ Name: "tls.management.acme",
61
+ New: func() caddy.Module { return new(ACMEManagerMaker) },
62
+ }
63
+}
64
+
65
+// NewManager is a no-op to satisfy the ManagerMaker interface,
66
// because this manager type is a special case.
62
-func (m *ACMEManagerMaker) newManager(interactive bool) (certmagic.Manager, error) {
67
+func (m ACMEManagerMaker) NewManager(interactive bool) (certmagic.Manager, error) {
68
return nil, nil
69
}
70
@@ -203,4 +208,4 @@ func onDemandAskRequest(ask string, name string) error {
208
}
209
210
// Interface guard
206
-var _ managerMaker = (*ACMEManagerMaker)(nil)
211
+var _ ManagerMaker = (*ACMEManagerMaker)(nil)
modules/caddytls/fileloader.go
+9
-4
@@ -23,15 +23,20 @@ import (
23
)
24
25
func init() {
26
- caddy.RegisterModule(caddy.Module{
27
- Name: "tls.certificates.load_files",
28
- New: func() interface{} { return FileLoader{} },
29
- })
26
+ caddy.RegisterModule(FileLoader{})
27
}
28
29
// FileLoader loads certificates and their associated keys from disk.
30
type FileLoader []CertKeyFilePair
31
32
+// CaddyModule returns the Caddy module information.
33
+func (FileLoader) CaddyModule() caddy.ModuleInfo {
34
+ return caddy.ModuleInfo{
35
+ Name: "tls.certificates.load_files",
36
+ New: func() caddy.Module { return new(FileLoader) },
37
+ }
38
+}
39
+
40
// CertKeyFilePair pairs certificate and key file names along with their
41
// encoding format so that they can be loaded from disk.
42
type CertKeyFilePair struct {
modules/caddytls/folderloader.go
+9
-4
@@ -28,10 +28,7 @@ import (
28
)
29
30
func init() {
31
- caddy.RegisterModule(caddy.Module{
32
- Name: "tls.certificates.load_folders",
33
- New: func() interface{} { return FolderLoader{} },
34
- })
31
+ caddy.RegisterModule(FolderLoader{})
32
}
33
34
// FolderLoader loads certificates and their associated keys from disk
@@ -39,6 +36,14 @@ func init() {
36
// files which contain both a certificate and a key.
37
type FolderLoader []string
38
39
+// CaddyModule returns the Caddy module information.
40
+func (FolderLoader) CaddyModule() caddy.ModuleInfo {
41
+ return caddy.ModuleInfo{
42
+ Name: "tls.certificates.load_folders",
43
+ New: func() caddy.Module { return new(FolderLoader) },
44
+ }
45
+}
46
+
47
// LoadCertificates loads all the certificates+keys in the directories
48
// listed in fl from all files ending with .pem. This method of loading
49
// certificates expects the certificate and key to be bundled into the
modules/caddytls/matchers.go
+9
-4
@@ -20,14 +20,19 @@ import (
20
"github.com/caddyserver/caddy/v2"
21
)
22
23
+func init() {
24
+ caddy.RegisterModule(MatchServerName{})
25
+}
26
+
27
// MatchServerName matches based on SNI.
28
type MatchServerName []string
29
26
-func init() {
27
- caddy.RegisterModule(caddy.Module{
30
+// CaddyModule returns the Caddy module information.
31
+func (MatchServerName) CaddyModule() caddy.ModuleInfo {
32
+ return caddy.ModuleInfo{
33
Name: "tls.handshake_match.sni",
29
- New: func() interface{} { return MatchServerName{} },
30
- })
34
+ New: func() caddy.Module { return new(MatchServerName) },
35
+ }
36
}
37
38
// Match matches hello based on SNI.
modules/caddytls/standardstek/stek.go
+9
-4
@@ -24,10 +24,7 @@ import (
24
)
25
26
func init() {
27
- caddy.RegisterModule(caddy.Module{
28
- Name: "tls.stek.standard",
29
- New: func() interface{} { return new(standardSTEKProvider) },
30
- })
27
+ caddy.RegisterModule(standardSTEKProvider{})
28
}
29
30
type standardSTEKProvider struct {
@@ -35,6 +32,14 @@ type standardSTEKProvider struct {
32
timer *time.Timer
33
}
34
35
+// CaddyModule returns the Caddy module information.
36
+func (standardSTEKProvider) CaddyModule() caddy.ModuleInfo {
37
+ return caddy.ModuleInfo{
38
+ Name: "tls.stek.standard",
39
+ New: func() caddy.Module { return new(standardSTEKProvider) },
40
+ }
41
+}
42
+
43
// Initialize sets the configuration for s and returns the starting keys.
44
func (s *standardSTEKProvider) Initialize(config *caddytls.SessionTicketService) ([][32]byte, error) {
45
// keep a reference to the config; we'll need it when rotating keys
modules/caddytls/tls.go
+15
-10
@@ -30,10 +30,7 @@ import (
30
)
31
32
func init() {
33
- caddy.RegisterModule(caddy.Module{
34
- Name: "tls",
35
- New: func() interface{} { return new(TLS) },
36
- })
33
+ caddy.RegisterModule(TLS{})
34
35
// opt-in TLS 1.3 for Go1.12
36
// TODO: remove this line when Go1.13 is released.
@@ -53,6 +50,14 @@ type TLS struct {
50
ctx caddy.Context
51
}
52
53
+// CaddyModule returns the Caddy module information.
54
+func (TLS) CaddyModule() caddy.ModuleInfo {
55
+ return caddy.ModuleInfo{
56
+ Name: "tls",
57
+ New: func() caddy.Module { return new(TLS) },
58
+ }
59
+}
60
+
61
// Provision sets up the configuration for the TLS app.
62
func (t *TLS) Provision(ctx caddy.Context) error {
63
t.ctx = ctx
@@ -73,7 +78,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
78
if err != nil {
79
return fmt.Errorf("loading TLS automation management module: %s", err)
80
}
76
- t.Automation.Policies[i].Management = val.(managerMaker)
81
+ t.Automation.Policies[i].Management = val.(ManagerMaker)
82
t.Automation.Policies[i].ManagementRaw = nil // allow GC to deallocate - TODO: Does this help?
83
}
84
@@ -237,7 +242,7 @@ type AutomationPolicy struct {
242
Hosts []string `json:"hosts,omitempty"`
243
ManagementRaw json.RawMessage `json:"management,omitempty"`
244
240
- Management managerMaker `json:"-"`
245
+ Management ManagerMaker `json:"-"`
246
}
247
248
// makeCertMagicConfig converts ap into a CertMagic config. Passing onDemand
@@ -252,7 +257,7 @@ func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy.Context) certmagic.Conf
257
}
258
259
return certmagic.Config{
255
- NewManager: ap.Management.newManager,
260
+ NewManager: ap.Management.NewManager,
261
}
262
}
263
@@ -290,9 +295,9 @@ type RateLimit struct {
295
Burst int `json:"burst,omitempty"`
296
}
297
293
-// managerMaker makes a certificate manager.
294
-type managerMaker interface {
295
- newManager(interactive bool) (certmagic.Manager, error)
298
+// ManagerMaker makes a certificate manager.
299
+type ManagerMaker interface {
300
+ NewManager(interactive bool) (certmagic.Manager, error)
301
}
302
303
// These perpetual values are used for on-demand TLS.
modules_test.go
+6
-6
@@ -21,7 +21,7 @@ import (
21
22
func TestGetModules(t *testing.T) {
23
modulesMu.Lock()
24
- modules = map[string]Module{
24
+ modules = map[string]ModuleInfo{
25
"a": {Name: "a"},
26
"a.b": {Name: "a.b"},
27
"a.b.c": {Name: "a.b.c"},
@@ -38,11 +38,11 @@ func TestGetModules(t *testing.T) {
38
39
for i, tc := range []struct {
40
input string
41
- expect []Module
41
+ expect []ModuleInfo
42
}{
43
{
44
input: "",
45
- expect: []Module{
45
+ expect: []ModuleInfo{
46
{Name: "a"},
47
{Name: "b"},
48
{Name: "c"},
@@ -50,7 +50,7 @@ func TestGetModules(t *testing.T) {
50
},
51
{
52
input: "a",
53
- expect: []Module{
53
+ expect: []ModuleInfo{
54
{Name: "a.b"},
55
{Name: "a.c"},
56
{Name: "a.d"},
@@ -58,7 +58,7 @@ func TestGetModules(t *testing.T) {
58
},
59
{
60
input: "a.b",
61
- expect: []Module{
61
+ expect: []ModuleInfo{
62
{Name: "a.b.c"},
63
{Name: "a.b.cd"},
64
},
@@ -68,7 +68,7 @@ func TestGetModules(t *testing.T) {
68
},
69
{
70
input: "b",
71
- expect: []Module{
71
+ expect: []ModuleInfo{
72
{Name: "b.a"},
73
{Name: "b.b"},
74
},
storage.go
+9
-4
@@ -23,10 +23,7 @@ import (
23
)
24
25
func init() {
26
- RegisterModule(Module{
27
- Name: "caddy.storage.file_system",
28
- New: func() interface{} { return new(fileStorage) },
29
- })
26
+ RegisterModule(fileStorage{})
27
}
28
29
// StorageConverter is a type that can convert itself
@@ -43,6 +40,14 @@ type fileStorage struct {
40
Root string `json:"root"`
41
}
42
43
+// CaddyModule returns the Caddy module information.
44
+func (fileStorage) CaddyModule() ModuleInfo {
45
+ return ModuleInfo{
46
+ Name: "caddy.storage.file_system",
47
+ New: func() Module { return new(fileStorage) },
48
+ }
49
+}
50
+
51
func (s fileStorage) CertMagicStorage() (certmagic.Storage, error) {
52
return &certmagic.FileStorage{Path: s.Root}, nil
53
}