Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 12:15 UTC
reverseproxy: Add ability to clear dynamic upstreams cache during retries (#7662)
* reverseproxy: Add ability to clear dynamic upstreams cache during retries This is an optional interface for dynamic upstream modules to implement if they cache results. TODO: More documentation; this is an experiment. * Add some godoc * Export interface; update godoc
Matt Holt committed
Apr 28, 2026 at 09:16 UTC
4d6945769d205f2a60acf13eefb5af0a2eb428fc
2 files changed
+46
-4
modules/caddyhttp/reverseproxy/reverseproxy.go
+29
@@ -574,6 +574,17 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
574
// get the updated list of upstreams
575
upstreams := h.Upstreams
576
if h.DynamicUpstreams != nil {
577
+ if retries > 0 {
578
+ // after a failure (and thus during a retry), give dynamic upstream modules an opportunity
579
+ // to purge their relevant cache entries so we don't keep retrying bad upstreams
580
+ if cachingDynamicUpstreams, ok := h.DynamicUpstreams.(CachingUpstreamSource); ok {
581
+ if err := cachingDynamicUpstreams.ResetCache(r); err != nil {
582
+ if c := h.logger.Check(zapcore.ErrorLevel, "failed clearing dynamic upstream source's cache"); c != nil {
583
+ c.Write(zap.Error(err))
584
+ }
585
+ }
586
+ }
587
+ }
588
dUpstreams, err := h.DynamicUpstreams.GetUpstreams(r)
589
if err != nil {
590
if c := h.logger.Check(zapcore.ErrorLevel, "failed getting dynamic upstreams; falling back to static upstreams"); c != nil {
@@ -1640,10 +1651,28 @@ type Selector interface {
1651
// may be called during each retry, multiple times per request, and as
1652
// such, needs to be instantaneous. The returned slice will not be
1653
// modified.
1654
+//
1655
+// For upstream sources that cache results, implement the
1656
+// [CachingUpstreamSource] interface for optimal performance.
1657
type UpstreamSource interface {
1658
GetUpstreams(*http.Request) ([]*Upstream, error)
1659
}
1660
1661
+// CachingUpstreamSource is an upstream source that caches its upstreams.
1662
+// The relevant cache entry can be cleared/reset for a given request during
1663
+// retries if a request fails. This can help ensure that failing backends
1664
+// are not retried.
1665
+//
1666
+// EXPERIMENTAL: Subject to change.
1667
+type CachingUpstreamSource interface {
1668
+ UpstreamSource
1669
+
1670
+ // ResetCache clears any cache entry related to the given request.
1671
+ // The next time GetUpstreams is called, it should have new upstream
1672
+ // information for the given request.
1673
+ ResetCache(*http.Request) error
1674
+}
1675
+
1676
// Hop-by-hop headers. These are removed when sent to the backend.
1677
// As of RFC 7230, hop-by-hop headers are required to appear in the
1678
// Connection header field. These are the headers defined by the
modules/caddyhttp/reverseproxy/upstreams.go
+17
-4
@@ -119,6 +119,18 @@ func (su *SRVUpstreams) Provision(ctx caddy.Context) error {
119
return nil
120
}
121
122
+func (su *SRVUpstreams) ResetCache(r *http.Request) error {
123
+ srvsMu.Lock()
124
+ if r == nil {
125
+ srvs = make(map[string]srvLookup)
126
+ } else {
127
+ suAddr, _, _, _ := su.expandedAddr(r)
128
+ delete(srvs, suAddr)
129
+ }
130
+ srvsMu.Unlock()
131
+ return nil
132
+}
133
+
134
func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
135
suAddr, service, proto, name := su.expandedAddr(r)
136
@@ -554,8 +566,9 @@ var (
566
567
// Interface guards
568
var (
557
- _ caddy.Provisioner = (*SRVUpstreams)(nil)
558
- _ UpstreamSource = (*SRVUpstreams)(nil)
559
- _ caddy.Provisioner = (*AUpstreams)(nil)
560
- _ UpstreamSource = (*AUpstreams)(nil)
569
+ _ caddy.Provisioner = (*SRVUpstreams)(nil)
570
+ _ UpstreamSource = (*SRVUpstreams)(nil)
571
+ _ CachingUpstreamSource = (*SRVUpstreams)(nil)
572
+ _ caddy.Provisioner = (*AUpstreams)(nil)
573
+ _ UpstreamSource = (*AUpstreams)(nil)
574
)