From db407d497f2932387abd5ca6ce0b7136b37591b8 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Tue, 18 Nov 2025 22:51:55 +0800 Subject: [PATCH] Add DisableAutoFallback option for proxy endpoints #896 - Added new behavior in upstream random selector (ignore offline check for upstream if there is only one upstream for this proxy rule) - Added config file only DisableAutoFallback option --- src/mod/dynamicproxy/default.go | 39 ++++++++++--------- src/mod/dynamicproxy/dynamicproxy.go | 2 +- .../dynamicproxy/loadbalance/onlineStatus.go | 15 ++++++- .../dynamicproxy/loadbalance/originPicker.go | 17 ++++---- src/mod/dynamicproxy/proxyRequestHandler.go | 2 +- src/mod/dynamicproxy/typedef.go | 1 + src/reverseproxy.go | 13 +++++-- src/web/components/httprp.html | 21 ++++++++++ 8 files changed, 77 insertions(+), 33 deletions(-) diff --git a/src/mod/dynamicproxy/default.go b/src/mod/dynamicproxy/default.go index 0e26ae9..3a74a3b 100644 --- a/src/mod/dynamicproxy/default.go +++ b/src/mod/dynamicproxy/default.go @@ -47,23 +47,24 @@ func GetDefaultProxyEndpoint() ProxyEndpoint { randomPrefix := uuid.New().String() return ProxyEndpoint{ ProxyType: ProxyTypeHost, - RootOrMatchingDomain: randomPrefix + ".internal", - MatchingDomainAlias: []string{}, - ActiveOrigins: []*loadbalance.Upstream{}, - InactiveOrigins: []*loadbalance.Upstream{}, - UseStickySession: false, - UseActiveLoadBalance: false, - Disabled: false, - BypassGlobalTLS: false, - VirtualDirectories: []*VirtualDirectoryEndpoint{}, - HeaderRewriteRules: GetDefaultHeaderRewriteRules(), - EnableWebsocketCustomHeaders: false, - AuthenticationProvider: GetDefaultAuthenticationProvider(), - RequireRateLimit: false, - RateLimit: 0, - DisableUptimeMonitor: false, - AccessFilterUUID: "default", - DefaultSiteOption: DefaultSite_InternalStaticWebServer, - DefaultSiteValue: "", - } + RootOrMatchingDomain: randomPrefix + ".internal", + MatchingDomainAlias: []string{}, + ActiveOrigins: []*loadbalance.Upstream{}, + InactiveOrigins: []*loadbalance.Upstream{}, + UseStickySession: false, + UseActiveLoadBalance: false, + Disabled: false, + BypassGlobalTLS: false, + VirtualDirectories: []*VirtualDirectoryEndpoint{}, + HeaderRewriteRules: GetDefaultHeaderRewriteRules(), + EnableWebsocketCustomHeaders: false, + AuthenticationProvider: GetDefaultAuthenticationProvider(), + RequireRateLimit: false, + RateLimit: 0, + DisableUptimeMonitor: false, + DisableAutoFallback: false, + AccessFilterUUID: "default", + DefaultSiteOption: DefaultSite_InternalStaticWebServer, + DefaultSiteValue: "", +} } diff --git a/src/mod/dynamicproxy/dynamicproxy.go b/src/mod/dynamicproxy/dynamicproxy.go index 4945336..ea9c262 100644 --- a/src/mod/dynamicproxy/dynamicproxy.go +++ b/src/mod/dynamicproxy/dynamicproxy.go @@ -152,7 +152,7 @@ func (router *Router) StartProxyService() error { } } - selectedUpstream, err := router.loadBalancer.GetRequestUpstreamTarget(w, r, sep.ActiveOrigins, sep.UseStickySession) + selectedUpstream, err := router.loadBalancer.GetRequestUpstreamTarget(w, r, sep.ActiveOrigins, sep.UseStickySession, sep.DisableAutoFallback) if err != nil { http.ServeFile(w, r, "./web/hosterror.html") router.Option.Logger.PrintAndLog("dprouter", "failed to get upstream for hostname", err) diff --git a/src/mod/dynamicproxy/loadbalance/onlineStatus.go b/src/mod/dynamicproxy/loadbalance/onlineStatus.go index a30217a..7b1099c 100644 --- a/src/mod/dynamicproxy/loadbalance/onlineStatus.go +++ b/src/mod/dynamicproxy/loadbalance/onlineStatus.go @@ -61,7 +61,20 @@ func (m *RouteManager) NotifyHostUnreachableWithTimeout(upstreamIp string, timeo */ // FilterOfflineOrigins return only online origins from a list of origins -func (m *RouteManager) FilterOfflineOrigins(origins []*Upstream) []*Upstream { +// If originalUpstreamCount is 1 (only one upstream), or disableAutoFallback is true, return all origins unchanged +func (m *RouteManager) FilterOfflineOrigins(origins []*Upstream, originalUpstreamCount int, disableAutoFallback bool) []*Upstream { + // If there's only one upstream, don't filter it out even if it's offline + // This prevents the 521 error when upgrading/restarting the only upstream server + if originalUpstreamCount == 1 { + return origins + } + + // If auto-fallback is disabled, don't filter offline origins + // This allows uptime monitoring to continue without automatically disabling upstreams + if disableAutoFallback { + return origins + } + var onlineOrigins []*Upstream for _, origin := range origins { if m.IsTargetOnline(origin.OriginIpOrDomain) { diff --git a/src/mod/dynamicproxy/loadbalance/originPicker.go b/src/mod/dynamicproxy/loadbalance/originPicker.go index d5229a8..e19403a 100644 --- a/src/mod/dynamicproxy/loadbalance/originPicker.go +++ b/src/mod/dynamicproxy/loadbalance/originPicker.go @@ -19,7 +19,7 @@ const ( // GetRequestUpstreamTarget return the upstream target where this // request should be routed -func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.Request, origins []*Upstream, useStickySession bool) (*Upstream, error) { +func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.Request, origins []*Upstream, useStickySession bool, disableAutoFallback bool) (*Upstream, error) { if len(origins) == 0 { return nil, errors.New("no upstream is defined for this host") } @@ -30,8 +30,9 @@ func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.R targetOriginId, err := m.getSessionHandler(r, origins) if err != nil { // No valid session found or origin is offline - // Filter the offline origins - origins = m.FilterOfflineOrigins(origins) + // Filter the offline origins (but only if there's more than 1 upstream and auto-fallback is not disabled) + originalUpstreamCount := len(origins) + origins = m.FilterOfflineOrigins(origins, originalUpstreamCount, disableAutoFallback) if len(origins) == 0 { return nil, errors.New("no online upstream is available for origin: " + r.Host) } @@ -55,8 +56,9 @@ func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.R } //No sticky session, get a random origin - //Filter the offline origins - origins = m.FilterOfflineOrigins(origins) + //Filter the offline origins (but only if there's more than 1 upstream and auto-fallback is not disabled) + originalUpstreamCount := len(origins) + origins = m.FilterOfflineOrigins(origins, originalUpstreamCount, disableAutoFallback) if len(origins) == 0 { return nil, errors.New("no online upstream is available for origin: " + r.Host) } @@ -73,8 +75,9 @@ func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.R } // GetUsableUpstreamCounts return the number of usable upstreams -func (m *RouteManager) GetUsableUpstreamCounts(origins []*Upstream) int { - origins = m.FilterOfflineOrigins(origins) +func (m *RouteManager) GetUsableUpstreamCounts(origins []*Upstream, disableAutoFallback bool) int { + originalUpstreamCount := len(origins) + origins = m.FilterOfflineOrigins(origins, originalUpstreamCount, disableAutoFallback) return len(origins) } diff --git a/src/mod/dynamicproxy/proxyRequestHandler.go b/src/mod/dynamicproxy/proxyRequestHandler.go index ced8377..10d28af 100644 --- a/src/mod/dynamicproxy/proxyRequestHandler.go +++ b/src/mod/dynamicproxy/proxyRequestHandler.go @@ -155,7 +155,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe reqHostname := r.Host /* Load balancing */ - selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession) + selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession, target.DisableAutoFallback) if err != nil { http.ServeFile(w, r, "./web/rperror.html") h.Parent.Option.Logger.PrintAndLog("proxy", "Failed to assign an upstream for this request", err) diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go index 44b36b4..75b0ea5 100644 --- a/src/mod/dynamicproxy/typedef.go +++ b/src/mod/dynamicproxy/typedef.go @@ -210,6 +210,7 @@ type ProxyEndpoint struct { //Uptime Monitor DisableUptimeMonitor bool //Disable uptime monitor for this endpoint + DisableAutoFallback bool //Disable automatic fallback when uptime monitor detects an upstream is down (continue monitoring but don't auto-disable upstream) DisableLogging bool //Disable logging of reverse proxy requests DisableStatisticCollection bool //Disable statistic collection for this endpoint diff --git a/src/reverseproxy.go b/src/reverseproxy.go index 43e8dd6..499d9cf 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -428,6 +428,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { Tags: tags, DisableUptimeMonitor: !enableUtm, + DisableAutoFallback: false, // Default to false for new endpoints DisableLogging: disableLog, BlockCommonExploits: blockCommonExploits, BlockAICrawlers: blockAICrawlers, @@ -627,20 +628,24 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{}, } } - if authProviderType == 1 { + switch authProviderType { + case 1: newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodBasic - } else if authProviderType == 2 { + case 2: newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodForward - } else if authProviderType == 3 { + case 3: newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodOauth2 - } else { + default: newProxyEndpoint.AuthenticationProvider.AuthMethod = dynamicproxy.AuthMethodNone } + disableAutoFallback, _ := utils.PostBool(r, "dAutoFallback") + newProxyEndpoint.RequireRateLimit = requireRateLimit newProxyEndpoint.RateLimit = proxyRateLimit newProxyEndpoint.UseStickySession = useStickySession newProxyEndpoint.DisableUptimeMonitor = disbleUtm + newProxyEndpoint.DisableAutoFallback = disableAutoFallback newProxyEndpoint.DisableChunkedTransferEncoding = disableChunkedEncoding newProxyEndpoint.DisableLogging = disableLogging newProxyEndpoint.DisableStatisticCollection = disableStatisticCollection diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index 137e0bc..e107479 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -316,6 +316,12 @@ Enable active uptime monitor and auto disable upstreams that are offline
+