Skip to content

Commit 4e31ada

Browse files
committed
reverse_proxy: status field in lb_retry_match
1 parent b35c1aa commit 4e31ada

4 files changed

Lines changed: 215 additions & 318 deletions

File tree

modules/caddyhttp/matchers.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,6 +1554,15 @@ func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
15541554
// ParseCaddyfileNestedMatcherSet parses the Caddyfile tokens for a nested
15551555
// matcher set, and returns its raw module map value.
15561556
func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
1557+
return ParseCaddyfileNestedMatcherSetWithFilter(d, nil)
1558+
}
1559+
1560+
// ParseCaddyfileNestedMatcherSetWithFilter is like ParseCaddyfileNestedMatcherSet
1561+
// but accepts an optional filter function. For each directive name encountered in
1562+
// the block, the filter is called first. If it returns true, the directive was
1563+
// handled by the filter and is not treated as a matcher. If it returns false,
1564+
// the directive is treated as a request matcher as usual.
1565+
func ParseCaddyfileNestedMatcherSetWithFilter(d *caddyfile.Dispenser, filter func(name string, d *caddyfile.Dispenser) (bool, error)) (caddy.ModuleMap, error) {
15571566
matcherMap := make(map[string]any)
15581567

15591568
// in case there are multiple instances of the same matcher, concatenate
@@ -1562,8 +1571,17 @@ func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, er
15621571
// instances of the matcher in this set
15631572
tokensByMatcherName := make(map[string][]caddyfile.Token)
15641573
for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
1565-
matcherName := d.Val()
1566-
tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
1574+
name := d.Val()
1575+
if filter != nil {
1576+
handled, err := filter(name, d)
1577+
if err != nil {
1578+
return nil, err
1579+
}
1580+
if handled {
1581+
continue
1582+
}
1583+
}
1584+
tokensByMatcherName[name] = append(tokensByMatcherName[name], d.NextSegment()...)
15671585
}
15681586

15691587
for matcherName, tokens := range tokensByMatcherName {

modules/caddyhttp/reverseproxy/caddyfile.go

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
6767
// lb_retries <retries>
6868
// lb_try_duration <duration>
6969
// lb_try_interval <interval>
70-
// lb_retry_on <conditions...>
71-
// lb_retry_match <request-matcher>
70+
// lb_retry_match {
71+
// <request-matcher>
72+
// status <codes...>
73+
// }
7274
//
7375
// # active health checking
7476
// health_uri <uri>
@@ -324,36 +326,20 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
324326
h.LoadBalancing.TryInterval = caddy.Duration(dur)
325327

326328
case "lb_retry_match":
327-
matcherSet, err := caddyhttp.ParseCaddyfileNestedMatcherSet(d)
328-
if err != nil {
329-
return d.Errf("failed to parse lb_retry_match: %v", err)
330-
}
331329
if h.LoadBalancing == nil {
332330
h.LoadBalancing = new(LoadBalancing)
333331
}
334-
h.LoadBalancing.RetryMatchRaw = append(h.LoadBalancing.RetryMatchRaw, matcherSet)
335-
336-
case "lb_retry_on":
337-
args := d.RemainingArgs()
338-
if len(args) == 0 {
339-
return d.ArgErr()
340-
}
341-
if h.LoadBalancing == nil {
342-
h.LoadBalancing = new(LoadBalancing)
332+
condSet, matcherSet, err := parseRetryMatchBlock(d)
333+
if err != nil {
334+
return err
343335
}
344-
for _, arg := range args {
345-
switch arg {
346-
case "empty_response", "tls_handshake_error", "response_timeout":
347-
h.LoadBalancing.RetryOn = append(h.LoadBalancing.RetryOn, arg)
348-
default:
349-
if len(arg) == 3 && strings.HasSuffix(arg, "xx") {
350-
arg = arg[:1]
351-
}
352-
if _, err := strconv.Atoi(arg); err != nil {
353-
return d.Errf("bad retry_on condition '%s': must be a keyword or status code", arg)
354-
}
355-
h.LoadBalancing.RetryOn = append(h.LoadBalancing.RetryOn, arg)
336+
if condSet != nil {
337+
if matcherSet != nil {
338+
condSet.MatchRaw = matcherSet
356339
}
340+
h.LoadBalancing.RetryConditionsRaw = append(h.LoadBalancing.RetryConditionsRaw, condSet)
341+
} else if matcherSet != nil {
342+
h.LoadBalancing.RetryMatchRaw = append(h.LoadBalancing.RetryMatchRaw, matcherSet)
357343
}
358344

359345
case "health_uri":
@@ -1710,6 +1696,46 @@ func (u *MultiUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
17101696
return nil
17111697
}
17121698

1699+
// parseRetryMatchBlock parses the lb_retry_match block, which may contain
1700+
// standard request matchers alongside "status" directives.
1701+
// It returns a RetryConditionSet (if status is present) and/or
1702+
// a matcher set (from request matchers). If only matchers are present, the
1703+
// condition set is nil (backward compatible with retry_match).
1704+
func parseRetryMatchBlock(d *caddyfile.Dispenser) (*RetryConditionSet, caddy.ModuleMap, error) {
1705+
var condSet RetryConditionSet
1706+
var hasConditions bool
1707+
1708+
matcherSet, err := caddyhttp.ParseCaddyfileNestedMatcherSetWithFilter(d, func(name string, d *caddyfile.Dispenser) (bool, error) {
1709+
if name != "status" {
1710+
return false, nil
1711+
}
1712+
args := d.RemainingArgs()
1713+
if len(args) == 0 {
1714+
return false, d.ArgErr()
1715+
}
1716+
for _, arg := range args {
1717+
if len(arg) == 3 && strings.HasSuffix(arg, "xx") {
1718+
arg = arg[:1]
1719+
}
1720+
code, err := strconv.Atoi(arg)
1721+
if err != nil {
1722+
return false, d.Errf("bad status value '%s': %v", arg, err)
1723+
}
1724+
condSet.Status = append(condSet.Status, code)
1725+
}
1726+
hasConditions = true
1727+
return true, nil
1728+
})
1729+
if err != nil {
1730+
return nil, nil, err
1731+
}
1732+
1733+
if hasConditions {
1734+
return &condSet, matcherSet, nil
1735+
}
1736+
return nil, matcherSet, nil
1737+
}
1738+
17131739
const matcherPrefix = "@"
17141740

17151741
// Interface guards

0 commit comments

Comments
 (0)