status-go/services/wallet/thirdparty/opensea/http_client.go

99 lines
2.3 KiB
Go
Raw Normal View History

package opensea
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
"go.uber.org/zap"
"github.com/status-im/status-go/logutils"
)
2023-08-17 18:10:13 +00:00
const requestTimeout = 5 * time.Second
const getRequestRetryMaxCount = 15
const getRequestWaitTime = 300 * time.Millisecond
type HTTPClient struct {
client *http.Client
getRequestLock sync.RWMutex
}
2023-08-17 18:10:13 +00:00
func NewHTTPClient() *HTTPClient {
return &HTTPClient{
client: &http.Client{
2023-08-17 18:10:13 +00:00
Timeout: requestTimeout,
},
}
}
func (o *HTTPClient) doGetRequest(ctx context.Context, url string, apiKey string) ([]byte, error) {
// Ensure only one thread makes a request at a time
o.getRequestLock.Lock()
defer o.getRequestLock.Unlock()
retryCount := 0
statusCode := http.StatusOK
// Try to do the request without an apiKey first
tmpAPIKey := ""
for {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0")
if len(tmpAPIKey) > 0 {
req.Header.Set("X-API-KEY", tmpAPIKey)
}
resp, err := o.client.Do(req)
if err != nil {
return nil, err
}
defer func() {
if err := resp.Body.Close(); err != nil {
logutils.ZapLogger().Error("failed to close opensea request body", zap.Error(err))
}
}()
statusCode = resp.StatusCode
switch resp.StatusCode {
case http.StatusOK:
body, err := ioutil.ReadAll(resp.Body)
return body, err
case http.StatusBadRequest:
// The OpenSea v2 API will return error 400 if the account holds no collectibles on
// the requested chain. This shouldn't be treated as an error, return an empty body.
return nil, nil
case http.StatusTooManyRequests:
2023-08-17 18:10:13 +00:00
if retryCount < getRequestRetryMaxCount {
// sleep and retry
2023-08-17 18:10:13 +00:00
time.Sleep(getRequestWaitTime)
retryCount++
continue
}
// break and error
case http.StatusForbidden:
// Request requires an apiKey, set it and retry
if tmpAPIKey == "" && apiKey != "" {
tmpAPIKey = apiKey
// sleep and retry
2023-08-17 18:10:13 +00:00
time.Sleep(getRequestWaitTime)
continue
}
// break and error
default:
// break and error
}
break
}
return nil, fmt.Errorf("unsuccessful request: %d %s", statusCode, http.StatusText(statusCode))
}