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

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 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 }