github.com/status-im/status-go@v1.1.0/services/wallet/thirdparty/opensea/http_client.go (about) 1 package opensea 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "sync" 9 "time" 10 11 "github.com/ethereum/go-ethereum/log" 12 ) 13 14 const requestTimeout = 5 * time.Second 15 const getRequestRetryMaxCount = 15 16 const getRequestWaitTime = 300 * time.Millisecond 17 18 type HTTPClient struct { 19 client *http.Client 20 getRequestLock sync.RWMutex 21 } 22 23 func NewHTTPClient() *HTTPClient { 24 return &HTTPClient{ 25 client: &http.Client{ 26 Timeout: requestTimeout, 27 }, 28 } 29 } 30 31 func (o *HTTPClient) doGetRequest(ctx context.Context, url string, apiKey string) ([]byte, error) { 32 // Ensure only one thread makes a request at a time 33 o.getRequestLock.Lock() 34 defer o.getRequestLock.Unlock() 35 36 retryCount := 0 37 statusCode := http.StatusOK 38 39 // Try to do the request without an apiKey first 40 tmpAPIKey := "" 41 42 for { 43 req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 44 if err != nil { 45 return nil, err 46 } 47 48 req.Header.Set("Content-Type", "application/json") 49 req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0") 50 if len(tmpAPIKey) > 0 { 51 req.Header.Set("X-API-KEY", tmpAPIKey) 52 } 53 54 resp, err := o.client.Do(req) 55 if err != nil { 56 return nil, err 57 } 58 defer func() { 59 if err := resp.Body.Close(); err != nil { 60 log.Error("failed to close opensea request body", "err", err) 61 } 62 }() 63 64 statusCode = resp.StatusCode 65 switch resp.StatusCode { 66 case http.StatusOK: 67 body, err := ioutil.ReadAll(resp.Body) 68 return body, err 69 case http.StatusBadRequest: 70 // The OpenSea v2 API will return error 400 if the account holds no collectibles on 71 // the requested chain. This shouldn't be treated as an error, return an empty body. 72 return nil, nil 73 case http.StatusTooManyRequests: 74 if retryCount < getRequestRetryMaxCount { 75 // sleep and retry 76 time.Sleep(getRequestWaitTime) 77 retryCount++ 78 continue 79 } 80 // break and error 81 case http.StatusForbidden: 82 // Request requires an apiKey, set it and retry 83 if tmpAPIKey == "" && apiKey != "" { 84 tmpAPIKey = apiKey 85 // sleep and retry 86 time.Sleep(getRequestWaitTime) 87 continue 88 } 89 // break and error 90 default: 91 // break and error 92 } 93 break 94 } 95 return nil, fmt.Errorf("unsuccessful request: %d %s", statusCode, http.StatusText(statusCode)) 96 }