From 7d1f7771c9961dc6607a6ac5894b9c3195c25fcf Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Wed, 13 Jul 2022 16:15:00 -0400 Subject: reverseproxy: Implement retry count, alternative to try_duration (#4756) * reverseproxy: Implement retry count, alternative to try_duration * Add Caddyfile support for `retry_match` * Refactor to deduplicate matcher parsing logic * Fix lint --- modules/caddyhttp/matchers.go | 103 ++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 49 deletions(-) (limited to 'modules/caddyhttp/matchers.go') diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index 4c35802..430318a 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -1003,57 +1003,12 @@ func (MatchNot) CaddyModule() caddy.ModuleInfo { // UnmarshalCaddyfile implements caddyfile.Unmarshaler. func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - // first, unmarshal each matcher in the set from its tokens - type matcherPair struct { - raw caddy.ModuleMap - decoded MatcherSet - } for d.Next() { - var mp matcherPair - matcherMap := make(map[string]RequestMatcher) - - // in case there are multiple instances of the same matcher, concatenate - // their tokens (we expect that UnmarshalCaddyfile should be able to - // handle more than one segment); otherwise, we'd overwrite other - // instances of the matcher in this set - tokensByMatcherName := make(map[string][]caddyfile.Token) - for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { - matcherName := d.Val() - tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...) - } - for matcherName, tokens := range tokensByMatcherName { - mod, err := caddy.GetModule("http.matchers." + matcherName) - if err != nil { - return d.Errf("getting matcher module '%s': %v", matcherName, err) - } - unm, ok := mod.New().(caddyfile.Unmarshaler) - if !ok { - return d.Errf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName) - } - err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(tokens)) - if err != nil { - return err - } - rm, ok := unm.(RequestMatcher) - if !ok { - return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName) - } - matcherMap[matcherName] = rm - mp.decoded = append(mp.decoded, rm) - } - - // we should now have a functional 'not' matcher, but we also - // need to be able to marshal as JSON, otherwise config - // adaptation will be missing the matchers! - mp.raw = make(caddy.ModuleMap) - for name, matcher := range matcherMap { - jsonBytes, err := json.Marshal(matcher) - if err != nil { - return fmt.Errorf("marshaling %T matcher: %v", matcher, err) - } - mp.raw[name] = jsonBytes + matcherSet, err := ParseCaddyfileNestedMatcherSet(d) + if err != nil { + return err } - m.MatcherSetsRaw = append(m.MatcherSetsRaw, mp.raw) + m.MatcherSetsRaw = append(m.MatcherSetsRaw, matcherSet) } return nil } @@ -1352,6 +1307,56 @@ func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return nil } +// ParseCaddyfileNestedMatcher parses the Caddyfile tokens for a nested +// matcher set, and returns its raw module map value. +func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) { + matcherMap := make(map[string]RequestMatcher) + + // in case there are multiple instances of the same matcher, concatenate + // their tokens (we expect that UnmarshalCaddyfile should be able to + // handle more than one segment); otherwise, we'd overwrite other + // instances of the matcher in this set + tokensByMatcherName := make(map[string][]caddyfile.Token) + for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { + matcherName := d.Val() + tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...) + } + + for matcherName, tokens := range tokensByMatcherName { + mod, err := caddy.GetModule("http.matchers." + matcherName) + if err != nil { + return nil, d.Errf("getting matcher module '%s': %v", matcherName, err) + } + unm, ok := mod.New().(caddyfile.Unmarshaler) + if !ok { + return nil, d.Errf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName) + } + err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(tokens)) + if err != nil { + return nil, err + } + rm, ok := unm.(RequestMatcher) + if !ok { + return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName) + } + matcherMap[matcherName] = rm + } + + // we should now have a functional matcher, but we also + // need to be able to marshal as JSON, otherwise config + // adaptation will be missing the matchers! + matcherSet := make(caddy.ModuleMap) + for name, matcher := range matcherMap { + jsonBytes, err := json.Marshal(matcher) + if err != nil { + return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err) + } + matcherSet[name] = jsonBytes + } + + return matcherSet, nil +} + var ( wordRE = regexp.MustCompile(`\w+`) ) -- cgit v1.2.3