mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-10 00:33:28 +08:00
🐛 fix(proxy): 避免全局代理影响数据库连接
移除数据库和 Redis 连接对全局代理的隐式继承 保留连接自身代理与 HTTP 隧道配置并修正缓存释放链路 同步六种语言的代理作用域说明并增加回归测试 Fixes #347
This commit is contained in:
@@ -564,7 +564,7 @@ func (a *App) invalidateCachedDatabase(config connection.ConnectionConfig, reaso
|
||||
if resolvedConfig, err := a.resolveConnectionSecrets(config); err == nil {
|
||||
config = resolvedConfig
|
||||
}
|
||||
effectiveConfig := applyGlobalProxyToConnection(config)
|
||||
effectiveConfig := config
|
||||
key := getCacheKey(effectiveConfig)
|
||||
shortKey := shortCacheKey(key)
|
||||
|
||||
@@ -876,7 +876,7 @@ func (a *App) resolveEffectiveConnectionConfig(config connection.ConnectionConfi
|
||||
if err != nil {
|
||||
return config, wrapConnectError(config, err)
|
||||
}
|
||||
return applyGlobalProxyToConnection(resolvedConfig), nil
|
||||
return resolvedConfig, nil
|
||||
}
|
||||
|
||||
func (a *App) getDatabaseWithPing(config connection.ConnectionConfig, forcePing bool) (db.Database, error) {
|
||||
@@ -884,7 +884,7 @@ func (a *App) getDatabaseWithPing(config connection.ConnectionConfig, forcePing
|
||||
if err != nil {
|
||||
return nil, wrapConnectError(config, err)
|
||||
}
|
||||
effectiveConfig := applyGlobalProxyToConnection(resolvedConfig)
|
||||
effectiveConfig := resolvedConfig
|
||||
isFileDB := isFileDatabaseType(effectiveConfig.Type)
|
||||
|
||||
key := getCacheKey(effectiveConfig)
|
||||
@@ -1164,7 +1164,7 @@ func (a *App) connectDatabaseWithStartupRetry(rawConfig connection.ConnectionCon
|
||||
var lastEffectiveConfig connection.ConnectionConfig
|
||||
|
||||
for attempt := 1; attempt <= startupConnectRetryAttempts; attempt++ {
|
||||
effectiveConfig := applyGlobalProxyToConnection(rawConfig)
|
||||
effectiveConfig := rawConfig
|
||||
lastEffectiveConfig = effectiveConfig
|
||||
cacheKey := shortenCacheKey(getCacheKey(effectiveConfig))
|
||||
|
||||
@@ -1219,12 +1219,7 @@ func (a *App) startupPhaseLabel() string {
|
||||
age = 0
|
||||
}
|
||||
if age <= startupConnectRetryWindow {
|
||||
snapshot := currentGlobalProxyConfig()
|
||||
state := "关闭"
|
||||
if snapshot.Enabled {
|
||||
state = fmt.Sprintf("启用(%s://%s:%d)", strings.ToLower(strings.TrimSpace(snapshot.Proxy.Type)), strings.TrimSpace(snapshot.Proxy.Host), snapshot.Proxy.Port)
|
||||
}
|
||||
return fmt.Sprintf("启动期(age=%s,全局代理=%s)", age, state)
|
||||
return fmt.Sprintf("启动期(age=%s)", age)
|
||||
}
|
||||
return fmt.Sprintf("稳定期(age=%s)", age)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (f *fakeStartupRetryDB) GetTriggers(dbName, tableName string) ([]connection
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestConnectDatabaseWithStartupRetry_RetriesTransientFailureAndReappliesGlobalProxy(t *testing.T) {
|
||||
func TestConnectDatabaseWithStartupRetry_RetriesTransientFailureWithoutApplyingGlobalProxy(t *testing.T) {
|
||||
originalNewDatabaseFunc := newDatabaseFunc
|
||||
originalResolveDialConfigWithProxyFunc := resolveDialConfigWithProxyFunc
|
||||
snapshot := currentGlobalProxyConfig()
|
||||
@@ -101,11 +101,11 @@ func TestConnectDatabaseWithStartupRetry_RetriesTransientFailureAndReappliesGlob
|
||||
if seenConfigs[0].UseProxy {
|
||||
t.Fatalf("expected first attempt without proxy, got %+v", seenConfigs[0])
|
||||
}
|
||||
if !seenConfigs[1].UseProxy {
|
||||
t.Fatalf("expected second attempt with proxy after startup retry, got %+v", seenConfigs[1])
|
||||
if seenConfigs[1].UseProxy {
|
||||
t.Fatalf("expected global proxy change not to affect second database attempt, got %+v", seenConfigs[1])
|
||||
}
|
||||
if !effectiveConfig.UseProxy {
|
||||
t.Fatalf("expected returned effective config to include proxy, got %+v", effectiveConfig)
|
||||
if effectiveConfig.UseProxy {
|
||||
t.Fatalf("expected returned effective config to exclude global proxy, got %+v", effectiveConfig)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,10 +300,10 @@ func TestGetDatabaseWithPing_CoolsDownRepeatedFailures(t *testing.T) {
|
||||
}
|
||||
|
||||
a := &App{
|
||||
startedAt: time.Now().Add(-startupConnectRetryWindow - time.Second),
|
||||
dbCache: make(map[string]cachedDatabase),
|
||||
startedAt: time.Now().Add(-startupConnectRetryWindow - time.Second),
|
||||
dbCache: make(map[string]cachedDatabase),
|
||||
connectFailures: make(map[string]cachedConnectFailure),
|
||||
runningQueries: make(map[string]queryContext),
|
||||
runningQueries: make(map[string]queryContext),
|
||||
}
|
||||
config := connection.ConnectionConfig{Type: "postgres", Host: "10.1.131.86", Port: 5432, User: "postgres"}
|
||||
|
||||
@@ -349,10 +349,10 @@ func TestGetDatabaseWithPing_AllowsRetryAfterFailureCooldown(t *testing.T) {
|
||||
}
|
||||
|
||||
a := &App{
|
||||
startedAt: time.Now().Add(-startupConnectRetryWindow - time.Second),
|
||||
dbCache: make(map[string]cachedDatabase),
|
||||
startedAt: time.Now().Add(-startupConnectRetryWindow - time.Second),
|
||||
dbCache: make(map[string]cachedDatabase),
|
||||
connectFailures: make(map[string]cachedConnectFailure),
|
||||
runningQueries: make(map[string]queryContext),
|
||||
runningQueries: make(map[string]queryContext),
|
||||
}
|
||||
config := connection.ConnectionConfig{Type: "postgres", Host: "10.1.131.86", Port: 5432, User: "postgres"}
|
||||
|
||||
@@ -409,10 +409,10 @@ func TestGetDatabaseWithPing_ClearsFailureCooldownAfterSuccess(t *testing.T) {
|
||||
}
|
||||
|
||||
a := &App{
|
||||
startedAt: time.Now().Add(-startupConnectRetryWindow - time.Second),
|
||||
dbCache: make(map[string]cachedDatabase),
|
||||
startedAt: time.Now().Add(-startupConnectRetryWindow - time.Second),
|
||||
dbCache: make(map[string]cachedDatabase),
|
||||
connectFailures: make(map[string]cachedConnectFailure),
|
||||
runningQueries: make(map[string]queryContext),
|
||||
runningQueries: make(map[string]queryContext),
|
||||
}
|
||||
config := connection.ConnectionConfig{Type: "postgres", Host: "10.1.131.86", Port: 5432, User: "postgres"}
|
||||
|
||||
|
||||
@@ -10,6 +10,15 @@ import (
|
||||
proxytunnel "GoNavi-Wails/internal/proxy"
|
||||
)
|
||||
|
||||
func isFileDatabaseType(driverType string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(driverType)) {
|
||||
case "sqlite", "duckdb":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func resolveDialConfigWithProxy(raw connection.ConnectionConfig) (connection.ConnectionConfig, error) {
|
||||
config := raw
|
||||
if config.UseHTTPTunnel {
|
||||
|
||||
@@ -160,36 +160,6 @@ func (a *App) GetGlobalProxyConfig() connection.QueryResult {
|
||||
}
|
||||
}
|
||||
|
||||
func applyGlobalProxyToConnection(config connection.ConnectionConfig) connection.ConnectionConfig {
|
||||
effective := config
|
||||
if effective.UseProxy || effective.UseHTTPTunnel {
|
||||
return effective
|
||||
}
|
||||
if isFileDatabaseType(effective.Type) {
|
||||
effective.Proxy = connection.ProxyConfig{}
|
||||
return effective
|
||||
}
|
||||
|
||||
snapshot := currentGlobalProxyConfig()
|
||||
if !snapshot.Enabled {
|
||||
effective.Proxy = connection.ProxyConfig{}
|
||||
return effective
|
||||
}
|
||||
|
||||
effective.UseProxy = true
|
||||
effective.Proxy = snapshot.Proxy
|
||||
return effective
|
||||
}
|
||||
|
||||
func isFileDatabaseType(driverType string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(driverType)) {
|
||||
case "sqlite", "duckdb":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func newHTTPClientWithGlobalProxy(timeout time.Duration) *http.Client {
|
||||
client := &http.Client{
|
||||
Timeout: timeout,
|
||||
|
||||
52
internal/app/global_proxy_database_scope_test.go
Normal file
52
internal/app/global_proxy_database_scope_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"GoNavi-Wails/internal/connection"
|
||||
)
|
||||
|
||||
func TestResolveEffectiveConnectionConfigUsesOnlyConnectionProxy(t *testing.T) {
|
||||
snapshot := currentGlobalProxyConfig()
|
||||
if _, err := setGlobalProxyConfig(true, connection.ProxyConfig{
|
||||
Type: "socks5",
|
||||
Host: "127.0.0.1",
|
||||
Port: 1080,
|
||||
}); err != nil {
|
||||
t.Fatalf("enable global proxy failed: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_, _ = setGlobalProxyConfig(snapshot.Enabled, snapshot.Proxy)
|
||||
})
|
||||
|
||||
app := NewApp()
|
||||
directConfig := connection.ConnectionConfig{
|
||||
Type: "mysql",
|
||||
Host: "db.internal",
|
||||
Port: 3306,
|
||||
}
|
||||
effectiveDirect, err := app.resolveEffectiveConnectionConfig(directConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve direct config failed: %v", err)
|
||||
}
|
||||
if effectiveDirect.UseProxy || effectiveDirect.Proxy != (connection.ProxyConfig{}) {
|
||||
t.Fatalf("global proxy must not be injected into a direct database connection, got %+v", effectiveDirect)
|
||||
}
|
||||
|
||||
connectionProxy := connection.ProxyConfig{
|
||||
Type: "http",
|
||||
Host: "db-proxy.internal",
|
||||
Port: 8080,
|
||||
User: "proxy-user",
|
||||
}
|
||||
proxiedConfig := directConfig
|
||||
proxiedConfig.UseProxy = true
|
||||
proxiedConfig.Proxy = connectionProxy
|
||||
effectiveProxied, err := app.resolveEffectiveConnectionConfig(proxiedConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve proxied config failed: %v", err)
|
||||
}
|
||||
if !effectiveProxied.UseProxy || !proxyConfigEqual(effectiveProxied.Proxy, connectionProxy) {
|
||||
t.Fatalf("connection-specific proxy must be preserved, got %+v", effectiveProxied)
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ func (a *App) DBReleaseConnection(config connection.ConnectionConfig) connection
|
||||
logger.Error(wrapped, "DBReleaseConnection 解析连接密文失败:%s", formatConnSummary(config))
|
||||
return connection.QueryResult{Success: false, Message: wrapped.Error()}
|
||||
}
|
||||
closed := a.releaseCachedDatabaseConnectionsForConfig(applyGlobalProxyToConnection(resolvedConfig))
|
||||
closed := a.releaseCachedDatabaseConnectionsForConfig(resolvedConfig)
|
||||
|
||||
logger.Infof("DBReleaseConnection 已释放数据库连接:%s 数量=%d", formatConnSummary(resolvedConfig), closed)
|
||||
return connection.QueryResult{Success: true, Message: a.appText("db.backend.message.release_success", nil), Data: map[string]int{"closed": closed}}
|
||||
|
||||
@@ -175,10 +175,10 @@ func TestFormatConnSummary_DefaultTimeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBReleaseConnectionClosesAllDatabaseCacheEntriesForSameInstance(t *testing.T) {
|
||||
func TestDBReleaseConnectionClosesAllDatabaseCacheEntriesForSameInstanceDespiteGlobalProxy(t *testing.T) {
|
||||
proxySnapshot := currentGlobalProxyConfig()
|
||||
if _, err := setGlobalProxyConfig(false, proxySnapshot.Proxy); err != nil {
|
||||
t.Fatalf("disable global proxy failed: %v", err)
|
||||
if _, err := setGlobalProxyConfig(true, connection.ProxyConfig{Type: "socks5", Host: "127.0.0.1", Port: 1080}); err != nil {
|
||||
t.Fatalf("enable global proxy failed: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_, _ = setGlobalProxyConfig(proxySnapshot.Enabled, proxySnapshot.Proxy)
|
||||
|
||||
@@ -682,7 +682,7 @@ func (a *App) getRedisClient(config connection.ConnectionConfig) (redis.RedisCli
|
||||
return nil, wrapped
|
||||
}
|
||||
|
||||
effectiveConfig := applyGlobalProxyToConnection(resolvedConfig)
|
||||
effectiveConfig := resolvedConfig
|
||||
connectConfig, proxyErr := resolveDialConfigWithProxyFunc(effectiveConfig)
|
||||
if proxyErr != nil {
|
||||
wrapped := wrapConnectError(effectiveConfig, proxyErr)
|
||||
@@ -735,7 +735,7 @@ func (a *App) openRedisClientIsolated(config connection.ConnectionConfig) (redis
|
||||
return nil, wrapped
|
||||
}
|
||||
|
||||
effectiveConfig := applyGlobalProxyToConnection(resolvedConfig)
|
||||
effectiveConfig := resolvedConfig
|
||||
connectConfig, proxyErr := resolveDialConfigWithProxyFunc(effectiveConfig)
|
||||
if proxyErr != nil {
|
||||
wrapped := wrapConnectError(effectiveConfig, proxyErr)
|
||||
@@ -824,7 +824,7 @@ func (a *App) releaseRedisClientsForConfig(config connection.ConnectionConfig) (
|
||||
if err != nil {
|
||||
return 0, wrapConnectError(config, err)
|
||||
}
|
||||
targetKey := getConnectionReleaseMatchKey(applyGlobalProxyToConnection(resolvedConfig))
|
||||
targetKey := getConnectionReleaseMatchKey(resolvedConfig)
|
||||
closed := 0
|
||||
|
||||
redisCacheMu.Lock()
|
||||
|
||||
@@ -385,15 +385,17 @@ func TestRedisTestConnectionUsesIsolatedClientAndClosesIt(t *testing.T) {
|
||||
CloseAllRedisClients()
|
||||
}()
|
||||
CloseAllRedisClients()
|
||||
if _, err := setGlobalProxyConfig(false, proxySnapshot.Proxy); err != nil {
|
||||
t.Fatalf("disable global proxy failed: %v", err)
|
||||
if _, err := setGlobalProxyConfig(true, connection.ProxyConfig{Type: "socks5", Host: "127.0.0.1", Port: 1080}); err != nil {
|
||||
t.Fatalf("enable global proxy failed: %v", err)
|
||||
}
|
||||
|
||||
client := &capturingRedisClient{}
|
||||
var dialConfig connection.ConnectionConfig
|
||||
newRedisClientFunc = func() redislib.RedisClient {
|
||||
return client
|
||||
}
|
||||
resolveDialConfigWithProxyFunc = func(raw connection.ConnectionConfig) (connection.ConnectionConfig, error) {
|
||||
dialConfig = raw
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
@@ -413,6 +415,9 @@ func TestRedisTestConnectionUsesIsolatedClientAndClosesIt(t *testing.T) {
|
||||
if len(redisCache) != 0 {
|
||||
t.Fatalf("redis test connection must not write global redis cache, got %d entries", len(redisCache))
|
||||
}
|
||||
if dialConfig.UseProxy {
|
||||
t.Fatalf("global proxy must not be applied to Redis connections, got %+v", dialConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedisTestConnectionReturnsLocalizedCloseFailure(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user