http_upstream_dial_timeout_test.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. package repository
  2. import (
  3. "context"
  4. "net"
  5. "net/url"
  6. "testing"
  7. "time"
  8. "github.com/stretchr/testify/require"
  9. )
  10. // 回归:上游 Transport 必须显式配置建连超时。
  11. //
  12. // http.Transport.DialContext 为 nil 时 Go 使用零值 net.Dialer(Timeout=0),
  13. // DNS 解析与 TCP 握手没有任何上限,只能等内核重传耗尽(Linux 约 130 秒)。
  14. // ResponseHeaderTimeout 只覆盖连接建立之后的阶段,管不到建连。
  15. // 上游域名被解析到不可达 IP 时,串行的多账号故障转移会把一次请求拖到数分钟。
  16. func TestBuildUpstreamTransportSetsDialTimeout(t *testing.T) {
  17. settings := defaultPoolSettings(nil)
  18. transport, err := buildUpstreamTransport(settings, nil, upstreamProtocolModeDefault)
  19. require.NoError(t, err)
  20. require.NotNil(t, transport.DialContext, "DialContext 缺失会退化为无超时的零值 dialer")
  21. require.Equal(t, defaultUpstreamTLSHandshakeTimeout, transport.TLSHandshakeTimeout)
  22. }
  23. func TestNewUpstreamDialerHasBoundedTimeout(t *testing.T) {
  24. dialer := newUpstreamDialer()
  25. require.Greater(t, dialer.Timeout, time.Duration(0), "建连超时必须有上限")
  26. require.Equal(t, defaultUpstreamDialTimeout, dialer.Timeout)
  27. require.Equal(t, defaultUpstreamDialKeepAlive, dialer.KeepAlive)
  28. }
  29. // 建连超时对 HTTP 代理同样生效:Transport.Proxy 走的仍是 DialContext,
  30. // 代理地址不可达时必须快速失败而不是挂满内核超时。
  31. func TestBuildUpstreamTransportKeepsDialTimeoutWithHTTPProxy(t *testing.T) {
  32. proxyURL, err := url.Parse("http://127.0.0.1:1080")
  33. require.NoError(t, err)
  34. transport, err := buildUpstreamTransport(defaultPoolSettings(nil), proxyURL, upstreamProtocolModeDefault)
  35. require.NoError(t, err)
  36. require.NotNil(t, transport.Proxy)
  37. require.NotNil(t, transport.DialContext)
  38. }
  39. // SOCKS5 分支会覆盖 Transport.DialContext,覆盖后仍必须是有超时的拨号器。
  40. func TestBuildUpstreamTransportKeepsDialContextWithSOCKS5Proxy(t *testing.T) {
  41. proxyURL, err := url.Parse("socks5h://127.0.0.1:1080")
  42. require.NoError(t, err)
  43. transport, err := buildUpstreamTransport(defaultPoolSettings(nil), proxyURL, upstreamProtocolModeDefault)
  44. require.NoError(t, err)
  45. require.NotNil(t, transport.DialContext)
  46. }
  47. // Timeout 字段确实被 net.Dialer 用于建连:拨一个已被 close 的本地监听端口,
  48. // 断言 Dialer 走的是自己的超时路径而不是无限等待。
  49. // (不依赖外网可达性,CI 中确定性执行。)
  50. func TestUpstreamDialerRespectsContextCancellation(t *testing.T) {
  51. listener, err := net.Listen("tcp", "127.0.0.1:0")
  52. require.NoError(t, err)
  53. addr := listener.Addr().String()
  54. require.NoError(t, listener.Close())
  55. ctx, cancel := context.WithCancel(context.Background())
  56. cancel()
  57. conn, err := newUpstreamDialer().DialContext(ctx, "tcp", addr)
  58. if conn != nil {
  59. _ = conn.Close()
  60. }
  61. require.Error(t, err, "已取消的 context 必须立即中止拨号")
  62. }