package http import ( "context" "net" "net/http" "strings" "time" "github.com/debridmediamanager.com/zurg/internal/config" "github.com/debridmediamanager.com/zurg/pkg/logutil" cmap "github.com/orcaman/concurrent-map/v2" "go.uber.org/zap" ) type HTTPClient struct { Client *http.Client MaxRetries int Backoff func(attempt int) time.Duration CheckRespStatus func(resp *http.Response, err error) bool BearerToken string log *zap.SugaredLogger config config.ConfigInterface IPv6 cmap.ConcurrentMap[string, net.IP] } func (r *HTTPClient) Do(req *http.Request) (*http.Response, error) { if r.config != nil && strings.Contains(req.Host, "download.real-debrid.") { prefHost := r.config.GetRandomPreferredHost() if prefHost != "" { req.Host = prefHost } } if r.BearerToken != "" { req.Header.Set("Authorization", "Bearer "+r.BearerToken) } if r.config.ShouldForceIPv6() { dialer := &net.Dialer{} transport := &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { host, port, err := net.SplitHostPort(addr) if err != nil { return nil, err } if ipv6, ok := r.IPv6.Get(host); !ok { // Lookup IP address if not found in map ips, err := net.LookupIP(host) if err != nil { return nil, err } for _, ip := range ips { if ip.To4() == nil { // IPv6 ipv6 = ip r.IPv6.Set(host, ipv6) break } } if ipv6 == nil { // No IPv6 address found, fallback to default addr = net.JoinHostPort(host, port) } else { addr = net.JoinHostPort(ipv6.String(), port) } } else if ipv6 != nil && host == req.URL.Hostname() { addr = net.JoinHostPort(ipv6.String(), port) } return dialer.DialContext(ctx, network, addr) }, } r.Client.Transport = transport } var resp *http.Response var err error for attempt := 0; attempt < r.MaxRetries; attempt++ { resp, err = r.Client.Do(req) if !r.CheckRespStatus(resp, err) { return resp, err } time.Sleep(r.Backoff(attempt)) } return resp, err } func NewHTTPClient(token string, maxRetries int, cfg config.ConfigInterface) *HTTPClient { return &HTTPClient{ BearerToken: token, Client: &http.Client{}, MaxRetries: maxRetries, Backoff: func(attempt int) time.Duration { return time.Duration(attempt) * time.Second }, CheckRespStatus: func(resp *http.Response, err error) bool { if err != nil { return true } if resp.StatusCode == 429 { return true } // no need to retry) return false }, log: logutil.NewLogger().Named("client"), config: cfg, IPv6: cmap.New[net.IP](), } }