http.Transport 未设置 DialContext 时使用零值 net.Dialer(Timeout=0), DNS 解析与 TCP 握手没有任何上限,只能等内核 TCP 重传耗尽(Linux 约 130 秒)。 ResponseHeaderTimeout 只约束连接建立之后等待响应头的阶段,覆盖不到建连。 上游域名被解析到 443 不可达的 IP 时(DNS 污染 / 路由异常),单个账号就要卡满 内核超时;而多账号故障转移是串行的,一次请求会阻塞数分钟且不写中间错误, 客户端表现为"长时间无首字后直接结束"。 - buildUpstreamTransport 显式设置 DialContext(10s)与 TLSHandshakeTimeout(10s) - proxyutil SOCKS5 分支的 forward dialer 由 proxy.Direct(零值 dialer,同样无超时) 换成带 10s 超时的 net.Dialer;该分支会覆盖 Transport.DialContext, 调用方设置的建连超时对它无效,必须在此补上 Fixes #5151
59 lines
1.9 KiB
Go
59 lines
1.9 KiB
Go
package proxyutil
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"net"
|
||
"net/http"
|
||
"net/url"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
var errStub = errors.New("stub dial")
|
||
|
||
// 回归:SOCKS5 分支覆盖了调用方在 Transport 上设置的 DialContext,
|
||
// 底层 forward dialer 必须自带建连超时。proxy.Direct 是零值 net.Dialer,
|
||
// 代理不可达时会一直卡到内核 TCP 重传耗尽(Linux 约 130 秒)。
|
||
func TestSOCKS5ForwardDialerHasBoundedTimeout(t *testing.T) {
|
||
require.Greater(t, socks5ForwardDialer.Timeout, time.Duration(0))
|
||
require.Equal(t, socks5DialTimeout, socks5ForwardDialer.Timeout)
|
||
require.Equal(t, socks5DialKeepAlive, socks5ForwardDialer.KeepAlive)
|
||
}
|
||
|
||
func TestConfigureTransportProxySOCKS5SetsDialContext(t *testing.T) {
|
||
for _, scheme := range []string{"socks5", "socks5h"} {
|
||
t.Run(scheme, func(t *testing.T) {
|
||
proxyURL, err := url.Parse(scheme + "://127.0.0.1:1080")
|
||
require.NoError(t, err)
|
||
|
||
transport := &http.Transport{}
|
||
require.NoError(t, ConfigureTransportProxy(transport, proxyURL))
|
||
require.NotNil(t, transport.DialContext)
|
||
require.Nil(t, transport.Proxy, "SOCKS5 不应设置 Transport.Proxy")
|
||
})
|
||
}
|
||
}
|
||
|
||
// HTTP 代理走 Transport.Proxy,不得覆盖调用方设置的 DialContext。
|
||
func TestConfigureTransportProxyHTTPPreservesDialContext(t *testing.T) {
|
||
proxyURL, err := url.Parse("http://127.0.0.1:8080")
|
||
require.NoError(t, err)
|
||
|
||
called := false
|
||
transport := &http.Transport{}
|
||
transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
|
||
called = true
|
||
return nil, errStub
|
||
}
|
||
|
||
require.NoError(t, ConfigureTransportProxy(transport, proxyURL))
|
||
require.NotNil(t, transport.Proxy)
|
||
require.NotNil(t, transport.DialContext)
|
||
|
||
_, _ = transport.DialContext(context.Background(), "tcp", "127.0.0.1:1")
|
||
require.True(t, called, "HTTP 代理分支不应替换调用方的 DialContext")
|
||
}
|