@@ -7,26 +7,128 @@ import (
77 "ipmap/config"
88 "net"
99 "net/http"
10+ "net/url"
1011 "strconv"
1112 "strings"
13+ "sync"
1214 "time"
1315
1416 "github.com/corpix/uarand"
1517)
1618
17- // Reusable HTTP client with connection pooling
18- var httpClient * http.Client
19+ // HTTP client with lazy initialization
20+ var (
21+ httpClient * http.Client
22+ httpClientOnce sync.Once
23+ lastProxyURL string
24+ lastDNSServers string
25+ clientMu sync.RWMutex
26+ )
27+
28+ // GetHTTPClient returns the HTTP client, creating or recreating if config changed
29+ func GetHTTPClient () * http.Client {
30+ clientMu .RLock ()
31+ currentProxy := config .ProxyURL
32+ currentDNS := strings .Join (config .DNSServers , "," )
33+ needsRecreate := httpClient != nil && (lastProxyURL != currentProxy || lastDNSServers != currentDNS )
34+ clientMu .RUnlock ()
35+
36+ if needsRecreate {
37+ clientMu .Lock ()
38+ // Double-check after acquiring write lock
39+ if lastProxyURL != currentProxy || lastDNSServers != currentDNS {
40+ httpClient = createHTTPClientWithConfig ()
41+ lastProxyURL = currentProxy
42+ lastDNSServers = currentDNS
43+ config .VerboseLog ("HTTP client recreated with new config (Proxy: %s, DNS: %s)" , currentProxy , currentDNS )
44+ }
45+ clientMu .Unlock ()
46+ return httpClient
47+ }
48+
49+ httpClientOnce .Do (func () {
50+ clientMu .Lock ()
51+ defer clientMu .Unlock ()
52+ httpClient = createHTTPClientWithConfig ()
53+ lastProxyURL = config .ProxyURL
54+ lastDNSServers = strings .Join (config .DNSServers , "," )
55+ })
56+
57+ return httpClient
58+ }
59+
60+ // createCustomDialer creates a dialer with optional custom DNS servers
61+ func createCustomDialer () * net.Dialer {
62+ dialer := & net.Dialer {
63+ Timeout : 10 * time .Second ,
64+ KeepAlive : 30 * time .Second ,
65+ }
66+ return dialer
67+ }
68+
69+ // createDialContext creates a DialContext function with optional custom DNS
70+ func createDialContext () func (ctx context.Context , network , addr string ) (net.Conn , error ) {
71+ dialer := createCustomDialer ()
72+
73+ if len (config .DNSServers ) == 0 {
74+ return dialer .DialContext
75+ }
76+
77+ // Custom DNS resolver
78+ resolver := & net.Resolver {
79+ PreferGo : true ,
80+ Dial : func (ctx context.Context , network , address string ) (net.Conn , error ) {
81+ d := net.Dialer {Timeout : 5 * time .Second }
82+ // Use first available custom DNS server
83+ for _ , dns := range config .DNSServers {
84+ dnsAddr := strings .TrimSpace (dns )
85+ if ! strings .Contains (dnsAddr , ":" ) {
86+ dnsAddr = dnsAddr + ":53"
87+ }
88+ conn , err := d .DialContext (ctx , "udp" , dnsAddr )
89+ if err == nil {
90+ return conn , nil
91+ }
92+ }
93+ // Fallback to default
94+ return d .DialContext (ctx , network , address )
95+ },
96+ }
97+
98+ config .VerboseLog ("Using custom DNS servers: %v" , config .DNSServers )
99+
100+ return func (ctx context.Context , network , addr string ) (net.Conn , error ) {
101+ // Split host and port
102+ host , port , err := net .SplitHostPort (addr )
103+ if err != nil {
104+ return dialer .DialContext (ctx , network , addr )
105+ }
106+
107+ // Resolve using custom DNS
108+ ips , err := resolver .LookupIPAddr (ctx , host )
109+ if err != nil || len (ips ) == 0 {
110+ // Fallback to normal resolution
111+ return dialer .DialContext (ctx , network , addr )
112+ }
19113
20- func init () {
21- httpClient = createHTTPClient ()
114+ // Try each resolved IP
115+ for _ , ip := range ips {
116+ conn , err := dialer .DialContext (ctx , network , net .JoinHostPort (ip .String (), port ))
117+ if err == nil {
118+ return conn , nil
119+ }
120+ }
121+
122+ // Fallback
123+ return dialer .DialContext (ctx , network , addr )
124+ }
22125}
23126
24- func createHTTPClient () * http.Client {
127+ func createHTTPClientWithConfig () * http.Client {
25128 transport := & http.Transport {
26129 TLSClientConfig : & tls.Config {
27130 InsecureSkipVerify : true ,
28131 MinVersion : tls .VersionTLS12 ,
29- // Allow more cipher suites for compatibility
30132 CipherSuites : []uint16 {
31133 tls .TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,
32134 tls .TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ,
@@ -36,21 +138,25 @@ func createHTTPClient() *http.Client {
36138 tls .TLS_RSA_WITH_AES_128_GCM_SHA256 ,
37139 },
38140 },
39- // Connection pooling
40- MaxIdleConns : 100 ,
41- MaxIdleConnsPerHost : 10 ,
42- IdleConnTimeout : 90 * time .Second ,
43- // Timeouts
141+ MaxIdleConns : 100 ,
142+ MaxIdleConnsPerHost : 10 ,
143+ IdleConnTimeout : 90 * time .Second ,
44144 TLSHandshakeTimeout : 10 * time .Second ,
45145 ResponseHeaderTimeout : 10 * time .Second ,
46146 ExpectContinueTimeout : 1 * time .Second ,
47- // Custom dialer with timeout
48- DialContext : (& net.Dialer {
49- Timeout : 10 * time .Second ,
50- KeepAlive : 30 * time .Second ,
51- }).DialContext ,
52- // Enable HTTP/2
53- ForceAttemptHTTP2 : true ,
147+ DialContext : createDialContext (),
148+ ForceAttemptHTTP2 : true ,
149+ }
150+
151+ // Configure proxy if specified
152+ if config .ProxyURL != "" {
153+ proxyURL , err := url .Parse (config .ProxyURL )
154+ if err != nil {
155+ config .ErrorLog ("Invalid proxy URL '%s': %v" , config .ProxyURL , err )
156+ } else {
157+ transport .Proxy = http .ProxyURL (proxyURL )
158+ config .VerboseLog ("Using proxy: %s" , config .ProxyURL )
159+ }
54160 }
55161
56162 return & http.Client {
@@ -59,7 +165,6 @@ func createHTTPClient() *http.Client {
59165 if len (via ) >= 10 {
60166 return http .ErrUseLastResponse
61167 }
62- // Preserve headers on redirect
63168 for key , val := range via [0 ].Header {
64169 if _ , ok := req .Header [key ]; ! ok {
65170 req .Header [key ] = val
@@ -118,7 +223,7 @@ func RequestFuncWithRetry(ip string, url string, timeout int, maxRetries int) []
118223 req .Header .Set ("Sec-Ch-Ua-Mobile" , "?0" )
119224 req .Header .Set ("Sec-Ch-Ua-Platform" , `"Windows"` )
120225
121- resp , err := httpClient .Do (req )
226+ resp , err := GetHTTPClient () .Do (req )
122227
123228 if err != nil {
124229 cancel () // Cancel on error
0 commit comments