@marcel91200 / caddy Mirror · read-only / commits / e51e56a4
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

httpcaddyfile: Fix nested blocks; add handle directive; refactor

The fix that was initially put forth in #2971 was good, but only for up to one layer of nesting. The real problem was that we forgot to increment nesting when already inside a block if we saw another open curly brace that opens another block (dispenser.go L157-158). The new 'handle' directive allows HTTP Caddyfiles to be designed more like nginx location blocks if the user prefers. Inside a handle block, directives are still ordered just like they are outside of them, but handler blocks at a given level of nesting are mutually exclusive. This work benefitted from some refactoring and cleanup.

Matthew Holt committed Jan 16, 2020 at 17:08 UTC e51e56a4944622c0a2c7d19da4bb6b9bb07c1973
10 files changed +269 -100
caddyconfig/caddyfile/dispenser.go
+11 -9
@@ -152,8 +152,10 @@ func (d *Dispenser) NextBlock(initialNestingLevel int) bool {
152 if !d.Next() {
153 return false // should be EOF error
154 }
155 - if d.Val() == "}" {
155 + if d.Val() == "}" && !d.nextOnSameLine() {
156 d.nesting--
157 + } else if d.Val() == "{" && !d.nextOnSameLine() {
158 + d.nesting++
159 }
160 return d.nesting > initialNestingLevel
161 }
@@ -262,9 +264,9 @@ func (d *Dispenser) NewFromNextTokens() *Dispenser {
264 if !openedBlock {
265 // because NextBlock() consumes the initial open
266 // curly brace, we rewind here to append it, since
265 - // our case is special in that we want to include
266 - // all the tokens including surrounding curly braces
267 - // for a new dispenser to have
267 + // our case is special in that we want the new
268 + // dispenser to have all the tokens including
269 + // surrounding curly braces
270 d.Prev()
271 tkns = append(tkns, d.Token())
272 d.Next()
@@ -273,12 +275,12 @@ func (d *Dispenser) NewFromNextTokens() *Dispenser {
275 tkns = append(tkns, d.Token())
276 }
277 if openedBlock {
276 - // include closing brace accordingly
278 + // include closing brace
279 tkns = append(tkns, d.Token())
278 - // since NewFromNextTokens is intended to consume the entire
279 - // directive, we must call Next() here and consume the closing
280 - // curly brace
281 - d.Next()
280 +
281 + // do not consume the closing curly brace; the
282 + // next iteration of the enclosing loop will
283 + // call Next() and consume it
284 }
285 return NewDispenser(tkns)
286 }
caddyconfig/httpcaddyfile/builtins.go
+33 -1
@@ -34,6 +34,7 @@ func init() {
34 RegisterHandlerDirective("redir", parseRedir)
35 RegisterHandlerDirective("respond", parseRespond)
36 RegisterHandlerDirective("route", parseRoute)
37 + RegisterHandlerDirective("handle", parseHandle)
38 }
39
40 func parseBind(h Helper) ([]ConfigValue, error) {
@@ -76,7 +77,7 @@ func parseRoot(h Helper) ([]ConfigValue, error) {
77 route.MatcherSetsRaw = []caddy.ModuleMap{matcherSet}
78 }
79
79 - return h.NewVarsRoute(route), nil
80 + return []ConfigValue{{Class: "route", Value: route}}, nil
81 }
82
83 func parseTLS(h Helper) ([]ConfigValue, error) {
@@ -330,3 +331,34 @@ func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
331
332 return sr, nil
333 }
334 +
335 +func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) {
336 + var allResults []ConfigValue
337 +
338 + for h.Next() {
339 + for nesting := h.Nesting(); h.NextBlock(nesting); {
340 + dir := h.Val()
341 +
342 + dirFunc, ok := registeredDirectives[dir]
343 + if !ok {
344 + return nil, h.Errf("unrecognized directive: %s", dir)
345 + }
346 +
347 + subHelper := h
348 + subHelper.Dispenser = h.NewFromNextTokens()
349 +
350 + results, err := dirFunc(subHelper)
351 + if err != nil {
352 + return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
353 + }
354 + for _, result := range results {
355 + result.directive = dir
356 + allResults = append(allResults, result)
357 + }
358 + }
359 +
360 + return buildSubroute(allResults, h.groupCounter)
361 + }
362 +
363 + return nil, nil
364 +}
caddyconfig/httpcaddyfile/directives.go
+34 -19
@@ -16,7 +16,6 @@ package httpcaddyfile
16
17 import (
18 "encoding/json"
19 - "fmt"
19 "sort"
20
21 "github.com/caddyserver/caddy/v2"
@@ -28,7 +27,10 @@ import (
27 // directiveOrder specifies the order
28 // to apply directives in HTTP routes.
29 var directiveOrder = []string{
30 + "root",
31 +
32 "rewrite",
33 +
34 "strip_prefix",
35 "strip_suffix",
36 "uri_replace",
@@ -38,7 +40,10 @@ var directiveOrder = []string{
40 "request_header",
41 "encode",
42 "templates",
43 +
44 + "handle",
45 "route",
46 +
47 "redir",
48 "respond",
49 "reverse_proxy",
@@ -46,6 +51,17 @@ var directiveOrder = []string{
51 "file_server",
52 }
53
54 +// directiveIsOrdered returns true if dir is
55 +// a known, ordered (sorted) directive.
56 +func directiveIsOrdered(dir string) bool {
57 + for _, d := range directiveOrder {
58 + if d == dir {
59 + return true
60 + }
61 + }
62 + return false
63 +}
64 +
65 // RegisterDirective registers a unique directive dir with an
66 // associated unmarshaling (setup) function. When directive dir
67 // is encountered in a Caddyfile, setupFunc will be called to
@@ -98,7 +114,7 @@ type Helper struct {
114 warnings *[]caddyconfig.Warning
115 matcherDefs map[string]caddy.ModuleMap
116 parentBlock caddyfile.ServerBlock
101 - groupCounter *int
117 + groupCounter counter
118 }
119
120 // Option gets the option keyed by name.
@@ -130,8 +146,8 @@ func (h Helper) JSON(val interface{}) json.RawMessage {
146 return caddyconfig.JSON(val, h.warnings)
147 }
148
133 -// MatcherToken assumes the current token is (possibly) a matcher, and
134 -// if so, returns the matcher set along with a true value. If the current
149 +// MatcherToken assumes the next argument token is (possibly) a matcher,
150 +// and if so, returns the matcher set along with a true value. If the next
151 // token is not a matcher, nil and false is returned. Note that a true
152 // value may be returned with a nil matcher set if it is a catch-all.
153 func (h Helper) MatcherToken() (caddy.ModuleMap, bool, error) {
@@ -186,14 +202,13 @@ func (h Helper) GroupRoutes(vals []ConfigValue) {
202 }
203
204 // now that we know the group will have some effect, do it
189 - groupNum := *h.groupCounter
205 + groupName := h.groupCounter.nextGroup()
206 for i := range vals {
207 if route, ok := vals[i].Value.(caddyhttp.Route); ok {
192 - route.Group = fmt.Sprintf("group%d", groupNum)
208 + route.Group = groupName
209 vals[i].Value = route
210 }
211 }
196 - *h.groupCounter++
212 }
213
214 // NewBindAddresses returns config values relevant to adding
@@ -202,12 +217,6 @@ func (h Helper) NewBindAddresses(addrs []string) []ConfigValue {
217 return []ConfigValue{{Class: "bind", Value: addrs}}
218 }
219
205 -// NewVarsRoute returns config values relevant to adding a
206 -// "vars" wrapper route to the config.
207 -func (h Helper) NewVarsRoute(route caddyhttp.Route) []ConfigValue {
208 - return []ConfigValue{{Class: "var", Value: route}}
209 -}
210 -
220 // ConfigValue represents a value to be added to the final
221 // configuration, or a value to be consulted when building
222 // the final configuration.
@@ -228,7 +237,7 @@ type ConfigValue struct {
237 directive string
238 }
239
231 -func sortRoutes(handlers []ConfigValue) {
240 +func sortRoutes(routes []ConfigValue) {
241 dirPositions := make(map[string]int)
242 for i, dir := range directiveOrder {
243 dirPositions[dir] = i
@@ -237,15 +246,21 @@ func sortRoutes(handlers []ConfigValue) {
246 // while we are sorting, we will need to decode a route's path matcher
247 // in order to sub-sort by path length; we can amortize this operation
248 // for efficiency by storing the decoded matchers in a slice
240 - decodedMatchers := make([]caddyhttp.MatchPath, len(handlers))
249 + decodedMatchers := make([]caddyhttp.MatchPath, len(routes))
250
242 - sort.SliceStable(handlers, func(i, j int) bool {
243 - iDir, jDir := handlers[i].directive, handlers[j].directive
251 + sort.SliceStable(routes, func(i, j int) bool {
252 + iDir, jDir := routes[i].directive, routes[j].directive
253 if iDir == jDir {
254 // directives are the same; sub-sort by path matcher length
255 // if there's only one matcher set and one path (common case)
247 - iRoute := handlers[i].Value.(caddyhttp.Route)
248 - jRoute := handlers[j].Value.(caddyhttp.Route)
256 + iRoute, ok := routes[i].Value.(caddyhttp.Route)
257 + if !ok {
258 + return false
259 + }
260 + jRoute, ok := routes[j].Value.(caddyhttp.Route)
261 + if !ok {
262 + return false
263 + }
264
265 if len(iRoute.MatcherSetsRaw) == 1 && len(jRoute.MatcherSetsRaw) == 1 {
266 // use already-decoded matcher, or decode if it's the first time seeing it
caddyconfig/httpcaddyfile/httptype.go
+140 -50
@@ -41,7 +41,7 @@ type ServerType struct {
41 func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
42 options map[string]interface{}) (*caddy.Config, []caddyconfig.Warning, error) {
43 var warnings []caddyconfig.Warning
44 - groupCounter := new(int)
44 + gc := counter{new(int)}
45
46 var serverBlocks []serverBlock
47 for _, sblock := range originalServerBlocks {
@@ -146,7 +146,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
146 warnings: &warnings,
147 matcherDefs: matcherDefs,
148 parentBlock: sb.block,
149 - groupCounter: groupCounter,
149 + groupCounter: gc,
150 })
151 if err != nil {
152 return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err)
@@ -169,7 +169,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
169
170 // each pairing of listener addresses to list of server
171 // blocks is basically a server definition
172 - servers, err := st.serversFromPairings(pairings, options, &warnings)
172 + servers, err := st.serversFromPairings(pairings, options, &warnings, gc)
173 if err != nil {
174 return nil, warnings, err
175 }
@@ -306,6 +306,7 @@ func (st *ServerType) serversFromPairings(
306 pairings []sbAddrAssociation,
307 options map[string]interface{},
308 warnings *[]caddyconfig.Warning,
309 + groupCounter counter,
310 ) (map[string]*caddyhttp.Server, error) {
311 servers := make(map[string]*caddyhttp.Server)
312
@@ -320,32 +321,32 @@ func (st *ServerType) serversFromPairings(
321 // case their matchers overlap; we do this somewhat naively by
322 // descending sort by length of host then path
323 sort.SliceStable(p.serverBlocks, func(i, j int) bool {
323 - // TODO: we could pre-process the lengths for efficiency,
324 + // TODO: we could pre-process the specificities for efficiency,
325 // but I don't expect many blocks will have SO many keys...
326 var iLongestPath, jLongestPath string
327 var iLongestHost, jLongestHost string
328 for _, key := range p.serverBlocks[i].block.Keys {
329 addr, _ := ParseAddress(key)
329 - if length(addr.Host) > length(iLongestHost) {
330 + if specificity(addr.Host) > specificity(iLongestHost) {
331 iLongestHost = addr.Host
332 }
332 - if len(addr.Path) > len(iLongestPath) {
333 + if specificity(addr.Path) > specificity(iLongestPath) {
334 iLongestPath = addr.Path
335 }
336 }
337 for _, key := range p.serverBlocks[j].block.Keys {
338 addr, _ := ParseAddress(key)
338 - if length(addr.Host) > length(jLongestHost) {
339 + if specificity(addr.Host) > specificity(jLongestHost) {
340 jLongestHost = addr.Host
341 }
341 - if len(addr.Path) > len(jLongestPath) {
342 + if specificity(addr.Path) > specificity(jLongestPath) {
343 jLongestPath = addr.Path
344 }
345 }
345 - if length(iLongestHost) == length(jLongestHost) {
346 + if specificity(iLongestHost) == specificity(jLongestHost) {
347 return len(iLongestPath) > len(jLongestPath)
348 }
348 - return length(iLongestHost) > length(jLongestHost)
349 + return specificity(iLongestHost) > specificity(jLongestHost)
350 })
351
352 // create a subroute for each site in the server block
@@ -355,10 +356,7 @@ func (st *ServerType) serversFromPairings(
356 return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.block.Keys, err)
357 }
358
358 - siteSubroute := new(caddyhttp.Subroute)
359 -
359 // tls: connection policies and toggle auto HTTPS
361 -
360 autoHTTPSQualifiedHosts, err := st.autoHTTPSHosts(sblock)
361 if err != nil {
362 return nil, err
@@ -391,38 +389,13 @@ func (st *ServerType) serversFromPairings(
389 // TODO: consolidate equal conn policies
390 }
391
394 - // vars: make sure these are linked in first so future
395 - // routes can use the variables they define
396 - for _, cfgVal := range sblock.pile["var"] {
397 - siteSubroute.Routes = append(siteSubroute.Routes, cfgVal.Value.(caddyhttp.Route))
398 - }
399 -
392 // set up each handler directive, making sure to honor directive order
393 dirRoutes := sblock.pile["route"]
402 - sortRoutes(dirRoutes)
403 -
404 - // add all the routes piled in from directives
405 - for _, r := range dirRoutes {
406 - // as a special case, group rewrite directives so that they are mutually exclusive;
407 - // this means that only the first matching rewrite will be evaluated, and that's
408 - // probably a good thing, since there should never be a need to do more than one
409 - // rewrite (I think?), and cascading rewrites smell bad... imagine these rewrites:
410 - // rewrite /docs/json/* /docs/json/index.html
411 - // rewrite /docs/* /docs/index.html
412 - // (We use this on the Caddy website, or at least we did once.) The first rewrite's
413 - // result is also matched by the second rewrite, making the first rewrite pointless.
414 - // See issue #2959.
415 - if r.directive == "rewrite" {
416 - route := r.Value.(caddyhttp.Route)
417 - route.Group = "rewriting"
418 - r.Value = route
419 - }
420 -
421 - siteSubroute.Routes = append(siteSubroute.Routes, r.Value.(caddyhttp.Route))
394 + siteSubroute, err := buildSubroute(dirRoutes, groupCounter)
395 + if err != nil {
396 + return nil, err
397 }
398
424 - siteSubroute.Routes = consolidateRoutes(siteSubroute.Routes)
425 -
399 if len(matcherSetsEnc) == 0 && len(p.serverBlocks) == 1 {
400 // no need to wrap the handlers in a subroute if this is
401 // the only server block and there is no matcher for it
@@ -446,6 +419,98 @@ func (st *ServerType) serversFromPairings(
419 return servers, nil
420 }
421
422 +func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subroute, error) {
423 + for _, val := range routes {
424 + if !directiveIsOrdered(val.directive) {
425 + return nil, fmt.Errorf("directive '%s' is not ordered, so it cannot be used here", val.directive)
426 + }
427 + }
428 +
429 + sortRoutes(routes)
430 +
431 + subroute := new(caddyhttp.Subroute)
432 +
433 + // get a group name for rewrite directives, if needed
434 + var rewriteGroupName string
435 + var rewriteCount int
436 + for _, r := range routes {
437 + if r.directive == "rewrite" {
438 + rewriteCount++
439 + if rewriteCount > 1 {
440 + break
441 + }
442 + }
443 + }
444 + if rewriteCount > 1 {
445 + rewriteGroupName = groupCounter.nextGroup()
446 + }
447 +
448 + // get a group name for handle blocks, if needed
449 + var handleGroupName string
450 + var handleCount int
451 + for _, r := range routes {
452 + if r.directive == "handle" {
453 + handleCount++
454 + if handleCount > 1 {
455 + break
456 + }
457 + }
458 + }
459 + if handleCount > 1 {
460 + handleGroupName = groupCounter.nextGroup()
461 + }
462 +
463 + // add all the routes piled in from directives
464 + for _, r := range routes {
465 + // as a special case, group rewrite directives so that they are mutually exclusive;
466 + // this means that only the first matching rewrite will be evaluated, and that's
467 + // probably a good thing, since there should never be a need to do more than one
468 + // rewrite (I think?), and cascading rewrites smell bad... imagine these rewrites:
469 + // rewrite /docs/json/* /docs/json/index.html
470 + // rewrite /docs/* /docs/index.html
471 + // (We use this on the Caddy website, or at least we did once.) The first rewrite's
472 + // result is also matched by the second rewrite, making the first rewrite pointless.
473 + // See issue #2959.
474 + if r.directive == "rewrite" {
475 + route := r.Value.(caddyhttp.Route)
476 + route.Group = rewriteGroupName
477 + r.Value = route
478 + }
479 +
480 + // handle blocks are also mutually exclusive by definition
481 + if r.directive == "handle" {
482 + route := r.Value.(caddyhttp.Route)
483 + route.Group = handleGroupName
484 + r.Value = route
485 + }
486 +
487 + switch route := r.Value.(type) {
488 + case caddyhttp.Subroute:
489 + // if a route-class config value is actually a Subroute handler
490 + // with nothing but a list of routes, then it is the intention
491 + // of the directive to keep these handlers together and in this
492 + // same order, but not necessarily in a subroute (if it wanted
493 + // to keep them in a subroute, the directive would have returned
494 + // a route with a Subroute as its handler); this is useful to
495 + // keep multiple handlers/routes together and in the same order
496 + // so that the sorting procedure we did above doesn't reorder them
497 + if route.Errors != nil {
498 + // if error handlers are also set, this is confusing; it's
499 + // probably supposed to be wrapped in a Route and encoded
500 + // as a regular handler route... programmer error.
501 + panic("found subroute with more than just routes; perhaps it should have been wrapped in a route?")
502 + }
503 + subroute.Routes = append(subroute.Routes, route.Routes...)
504 + case caddyhttp.Route:
505 + subroute.Routes = append(subroute.Routes, route)
506 + }
507 + }
508 +
509 + subroute.Routes = consolidateRoutes(subroute.Routes)
510 +
511 + return subroute, nil
512 +}
513 +
514 func (st ServerType) autoHTTPSHosts(sb serverBlock) ([]string, error) {
515 // get the hosts for this server block...
516 hosts, err := st.hostsFromServerBlockKeys(sb.block)
@@ -521,7 +586,6 @@ func matcherSetFromMatcherToken(
586 }
587 return m, true, nil
588 }
524 -
589 return nil, false, nil
590 }
591
@@ -659,14 +723,40 @@ func tryInt(val interface{}, warnings *[]caddyconfig.Warning) int {
723 return intVal
724 }
725
662 -// length returns len(s) minus any wildcards (*). Basically,
663 -// it's a length count that penalizes the use of wildcards.
664 -// This is useful for comparing hostnames, but probably not
665 -// paths so much (for example, '*.example.com' is clearly
666 -// less specific than 'a.example.com', but is '/a' more or
667 -// less specific than '/a*'?).
668 -func length(s string) int {
669 - return len(s) - strings.Count(s, "*")
726 +// specifity returns len(s) minus any wildcards (*) and
727 +// placeholders ({...}). Basically, it's a length count
728 +// that penalizes the use of wildcards and placeholders.
729 +// This is useful for comparing hostnames and paths.
730 +// However, wildcards in paths are not a sure answer to
731 +// the question of specificity. For exmaple,
732 +// '*.example.com' is clearly less specific than
733 +// 'a.example.com', but is '/a' more or less specific
734 +// than '/a*'?
735 +func specificity(s string) int {
736 + l := len(s) - strings.Count(s, "*")
737 + for len(s) > 0 {
738 + start := strings.Index(s, "{")
739 + if start < 0 {
740 + return l
741 + }
742 + end := strings.Index(s[start:], "}") + start + 1
743 + if end <= start {
744 + return l
745 + }
746 + l -= end - start
747 + s = s[end:]
748 + }
749 + return l
750 +}
751 +
752 +type counter struct {
753 + n *int
754 +}
755 +
756 +func (c counter) nextGroup() string {
757 + name := fmt.Sprintf("group%d", *c.n)
758 + *c.n++
759 + return name
760 }
761
762 type matcherSetAndTokens struct {
caddyconfig/httpcaddyfile/httptype_test.go new
+33
@@ -0,0 +1,33 @@
1 +package httpcaddyfile
2 +
3 +import "testing"
4 +
5 +func TestSpecificity(t *testing.T) {
6 + for i, tc := range []struct {
7 + input string
8 + expect int
9 + }{
10 + {"", 0},
11 + {"*", 0},
12 + {"*.*", 1},
13 + {"{placeholder}", 0},
14 + {"/{placeholder}", 1},
15 + {"foo", 3},
16 + {"example.com", 11},
17 + {"a.example.com", 13},
18 + {"*.example.com", 12},
19 + {"/foo", 4},
20 + {"/foo*", 4},
21 + {"{placeholder}.example.com", 12},
22 + {"{placeholder.example.com", 24},
23 + {"}.", 2},
24 + {"}{", 2},
25 + {"{}", 0},
26 + {"{{{}}", 1},
27 + } {
28 + actual := specificity(tc.input)
29 + if actual != tc.expect {
30 + t.Errorf("Test %d (%s): Expected %d but got %d", i, tc.input, tc.expect, actual)
31 + }
32 + }
33 +}
modules/caddyhttp/autohttps.go
+3
@@ -338,6 +338,9 @@ func (app *App) automaticHTTPSPhase2() error {
338 if err != nil {
339 return fmt.Errorf("%s: managing certificate for %s: %s", srvName, domains, err)
340 }
341 +
342 + // no longer needed; allow GC to deallocate
343 + srv.AutoHTTPS.domainSet = nil
344 }
345
346 return nil
modules/caddyhttp/matchers_test.go
+1 -1
@@ -199,7 +199,7 @@ func TestPathMatcher(t *testing.T) {
199 },
200 {
201 match: MatchPath{"*.ext"},
202 - input: "/foo.ext",
202 + input: "/foo/bar.ext",
203 expect: true,
204 },
205 {
modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+13 -18
@@ -81,7 +81,7 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
81 //
82 // php_fastcgi localhost:7777
83 //
84 -// is equivalent to:
84 +// is equivalent to a route consisting of:
85 //
86 // @canonicalPath {
87 // file {
@@ -104,8 +104,8 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
104 // }
105 // }
106 //
107 -// Thus, this directive produces multiple routes, each with a different
108 -// matcher because multiple consecutive routes are necessary to support
107 +// Thus, this directive produces multiple handlers, each with a different
108 +// matcher because multiple consecutive hgandlers are necessary to support
109 // the common PHP use case. If this "common" config is not compatible
110 // with a user's PHP requirements, they can use a manual approach based
111 // on the example above to configure it precisely as they need.
@@ -114,7 +114,7 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
114 //
115 // php_fastcgi /subpath localhost:7777
116 //
117 -// then the resulting routes are wrapped in a subroute that uses the
117 +// then the resulting handlers are wrapped in a subroute that uses the
118 // user's matcher as a prerequisite to enter the subroute. In other
119 // words, the directive's matcher is necessary, but not sufficient.
120 func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
@@ -198,12 +198,13 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
198 HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rpHandler, "handler", "reverse_proxy", nil)},
199 }
200
201 + subroute := caddyhttp.Subroute{
202 + Routes: caddyhttp.RouteList{redirRoute, rewriteRoute, rpRoute},
203 + }
204 +
205 // the user's matcher is a prerequisite for ours, so
206 // wrap ours in a subroute and return that
207 if hasUserMatcher {
204 - subroute := caddyhttp.Subroute{
205 - Routes: caddyhttp.RouteList{redirRoute, rewriteRoute, rpRoute},
206 - }
208 return []httpcaddyfile.ConfigValue{
209 {
210 Class: "route",
@@ -215,20 +216,14 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
216 }, nil
217 }
218
218 - // if the user did not specify a matcher, then
219 - // we can just use our own matchers
219 + // otherwise, return the literal subroute instead of
220 + // individual routes, to ensure they stay together and
221 + // are treated as a single unit, without necessarily
222 + // creating an actual subroute in the output
223 return []httpcaddyfile.ConfigValue{
224 {
225 Class: "route",
223 - Value: redirRoute,
224 - },
225 - {
226 - Class: "route",
227 - Value: rewriteRoute,
228 - },
229 - {
230 - Class: "route",
231 - Value: rpRoute,
226 + Value: subroute,
227 },
228 }, nil
229 }
modules/caddyhttp/rewrite/rewrite.go
+1 -1
@@ -50,7 +50,7 @@ type Rewrite struct {
50 // You can also use placeholders. For example, to preserve the existing
51 // query string, you might use: "?{http.request.uri.query}&a=b". Any
52 // key-value pairs you add to the query string will not overwrite
53 - // existing values.
53 + // existing values (individual pairs are append-only).
54 //
55 // To clear the query string, explicitly set an empty one: "?"
56 URI string `json:"uri,omitempty"`
modules/caddyhttp/routes.go
-1
@@ -252,7 +252,6 @@ func wrapMiddleware(mh MiddlewareHandler) Middleware {
252
253 return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
254 // TODO: This is where request tracing could be implemented
255 - // TODO: Trace a diff of the request, would be cool too... see what changed since the last middleware (host, headers, URI...)
255 // TODO: see what the std lib gives us in terms of stack tracing too
256 return mh.ServeHTTP(w, r, nextCopy)
257 })