Files
zurg/pkg/http/client.go
2023-11-25 16:14:32 +01:00

108 lines
2.7 KiB
Go

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](),
}
}