Spaces:
Sleeping
Sleeping
| package utils | |
| import ( | |
| "context" | |
| "net" | |
| "net/http" | |
| "net/url" | |
| "os" | |
| "strings" | |
| "time" | |
| ) | |
| // NewHTTPClientWithGoogleDNS creates an HTTP client that forces DNS resolution via 8.8.8.8 | |
| func NewHTTPClientWithGoogleDNS() *http.Client { | |
| dialer := &net.Dialer{ | |
| Resolver: &net.Resolver{ | |
| PreferGo: true, | |
| Dial: func(ctx context.Context, network, address string) (net.Conn, error) { | |
| d := net.Dialer{ | |
| Timeout: 10 * time.Second, | |
| } | |
| return d.DialContext(ctx, "udp", "8.8.8.8:53") | |
| }, | |
| }, | |
| } | |
| transport := &http.Transport{ | |
| DialContext: dialer.DialContext, | |
| ForceAttemptHTTP2: true, | |
| MaxIdleConns: 100, | |
| IdleConnTimeout: 90 * time.Second, | |
| TLSHandshakeTimeout: 10 * time.Second, | |
| ExpectContinueTimeout: 1 * time.Second, | |
| } | |
| return &http.Client{ | |
| Transport: transport, | |
| } | |
| } | |
| // NewIPv4OnlyHTTPClient creates an HTTP client that forces IPv4 only (disables IPv6) | |
| func NewIPv4OnlyHTTPClient() *http.Client { | |
| dialer := &net.Dialer{ | |
| Resolver: &net.Resolver{ | |
| PreferGo: true, | |
| Dial: func(ctx context.Context, network, address string) (net.Conn, error) { | |
| // Force IPv4 by dialing 8.8.8.8:53 (Google DNS) | |
| d := net.Dialer{ | |
| Timeout: 10 * time.Second, | |
| } | |
| return d.DialContext(ctx, "udp", "8.8.8.8:53") | |
| }, | |
| }, | |
| } | |
| transport := &http.Transport{ | |
| DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { | |
| // Force IPv4 by replacing any IPv6 addresses with IPv4 lookup | |
| // If address contains colon (IPv6), resolve hostname separately and pick IPv4 | |
| if strings.Contains(address, ":") { | |
| // Extract host from "host:port" or "[ipv6]:port" | |
| host, port, err := net.SplitHostPort(address) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Resolve host to IPv4 addresses only | |
| addrs, err := net.DefaultResolver.LookupIPAddr(ctx, host) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Find first IPv4 address | |
| for _, addr := range addrs { | |
| if addr.IP.To4() != nil { | |
| return dialer.DialContext(ctx, "tcp", net.JoinHostPort(addr.IP.String(), port)) | |
| } | |
| } | |
| return nil, &net.DNSError{Err: "no IPv4 address found for " + host} | |
| } | |
| // Already IPv4 or hostname, use default dialer | |
| return dialer.DialContext(ctx, "tcp", address) | |
| }, | |
| ForceAttemptHTTP2: true, | |
| MaxIdleConns: 100, | |
| IdleConnTimeout: 90 * time.Second, | |
| TLSHandshakeTimeout: 10 * time.Second, | |
| ExpectContinueTimeout: 1 * time.Second, | |
| } | |
| return &http.Client{ | |
| Transport: transport, | |
| } | |
| } | |
| // NewProxiedHTTPClient creates an HTTP client that uses a proxy if configured | |
| // It respects standard proxy environment variables and adds proxy support for HTTPS | |
| func NewProxiedHTTPClient() *http.Client { | |
| // Check for proxy configuration | |
| proxyURL := os.Getenv("TELEGRAM_PROXY_URL") | |
| if proxyURL == "" { | |
| proxyURL = os.Getenv("HTTPS_PROXY") | |
| if proxyURL == "" { | |
| proxyURL = os.Getenv("HTTP_PROXY") | |
| } | |
| } | |
| transport := &http.Transport{ | |
| ForceAttemptHTTP2: true, | |
| MaxIdleConns: 100, | |
| IdleConnTimeout: 90 * time.Second, | |
| TLSHandshakeTimeout: 10 * time.Second, | |
| ExpectContinueTimeout: 1 * time.Second, | |
| } | |
| // If proxy is configured, set it up | |
| if proxyURL != "" { | |
| u, err := url.Parse(proxyURL) | |
| if err != nil { | |
| // If proxy URL is invalid, log and continue without proxy | |
| // In production, we'd use a logger but this is a low-level utils function | |
| // So we'll just proceed without proxy | |
| } else { | |
| transport.Proxy = http.ProxyURL(u) | |
| } | |
| } | |
| return &http.Client{ | |
| Transport: transport, | |
| } | |
| } | |
| // IsProxyOrNetworkError checks if an error indicates proxy or network policy block | |
| func IsProxyOrNetworkError(err error) bool { | |
| if err == nil { | |
| return false | |
| } | |
| errStr := err.Error() | |
| // Common network/block indicators | |
| indicators := []string{ | |
| "operation not permitted", | |
| "permission denied", | |
| "network is unreachable", | |
| "connection refused", | |
| "timeout", | |
| "no route to host", | |
| "i/o timeout", | |
| "dial tcp", | |
| "connect:", | |
| "proxy", | |
| } | |
| for _, indicator := range indicators { | |
| if strings.Contains(strings.ToLower(errStr), indicator) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |