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