Handling limits and exceedings
This commit is contained in:
parent
191d0ae1fb
commit
f1ce041219
16
go.mod
16
go.mod
@ -1,10 +1,20 @@
|
|||||||
module github.com/retailcrm/mg-transport-api-client-go
|
module github.com/retailcrm/mg-transport-api-client-go
|
||||||
|
|
||||||
go 1.13
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/go-querystring v1.0.0
|
github.com/google/go-querystring v1.0.0
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/maypok86/otter v1.0.0
|
||||||
|
github.com/stretchr/testify v1.8.1
|
||||||
gopkg.in/h2non/gock.v1 v1.1.2
|
gopkg.in/h2non/gock.v1 v1.1.2
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dolthub/maphash v0.1.0 // indirect
|
||||||
|
github.com/dolthub/swiss v0.2.1 // indirect
|
||||||
|
github.com/gammazero/deque v0.2.1 // indirect
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
25
go.sum
25
go.sum
@ -1,20 +1,33 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||||
|
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||||
|
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||||
|
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||||
|
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
|
||||||
|
github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
|
||||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||||
|
github.com/maypok86/otter v1.0.0 h1:nP13eaFQrfRQHD1vxEgdlqR9gLHvfW2VcS0hFitglIY=
|
||||||
|
github.com/maypok86/otter v1.0.0/go.mod h1:koSPT30yWtqMNrFohaywMlgSHCuUg6IVqeDerwIM/Mg=
|
||||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||||
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -6,8 +6,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const MaxRPS = 100
|
||||||
|
|
||||||
var prefix = "/api/transport/v1"
|
var prefix = "/api/transport/v1"
|
||||||
|
|
||||||
// GetRequest performs GET request to the provided route.
|
// GetRequest performs GET request to the provided route.
|
||||||
@ -68,11 +71,32 @@ func makeRequest(reqType, url string, buf io.Reader, c *MgClient) ([]byte, int,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.mux.Lock()
|
||||||
|
defer c.mux.Unlock()
|
||||||
|
|
||||||
|
attempt := 0
|
||||||
|
tryAgain:
|
||||||
|
sleepTime := time.Second - time.Since(c.lastTime)
|
||||||
|
if sleepTime < 0 {
|
||||||
|
c.lastTime = time.Now()
|
||||||
|
c.rps = 0
|
||||||
|
} else if c.rps == MaxRPS {
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
c.lastTime = time.Now()
|
||||||
|
c.rps = 0
|
||||||
|
}
|
||||||
|
c.rps++
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, 0, NewCriticalHTTPError(err)
|
return res, 0, NewCriticalHTTPError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
|
||||||
|
attempt++
|
||||||
|
goto tryAgain
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode >= http.StatusInternalServerError {
|
if resp.StatusCode >= http.StatusInternalServerError {
|
||||||
err = NewServerError(resp)
|
err = NewServerError(resp)
|
||||||
return res, resp.StatusCode, err
|
return res, resp.StatusCode, err
|
||||||
|
45
v1/storage.go
Normal file
45
v1/storage.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/maypok86/otter"
|
||||||
|
)
|
||||||
|
|
||||||
|
const mgClientCacheTTL = time.Hour * 1
|
||||||
|
|
||||||
|
var NegativeCapacity = errors.New("capacity cannot be less than 1")
|
||||||
|
|
||||||
|
type MGClientPool struct {
|
||||||
|
cache *otter.CacheWithVariableTTL[string, *MgClient]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMGClientPool initializes the client cache
|
||||||
|
func NewMGClientPool(capacity int) (*MGClientPool, error) {
|
||||||
|
if capacity <= 0 {
|
||||||
|
return nil, NegativeCapacity
|
||||||
|
}
|
||||||
|
|
||||||
|
cache, _ := otter.MustBuilder[string, *MgClient](capacity).WithVariableTTL().Build()
|
||||||
|
return &MGClientPool{cache: &cache}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MGClientPool) Get(token string, url string) *MgClient {
|
||||||
|
if client, ok := m.cache.Get(token); ok {
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
client := New(url, token)
|
||||||
|
m.cache.Set(token, client, mgClientCacheTTL)
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MGClientPool) Remove(token string) {
|
||||||
|
m.cache.Delete(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MGClientPool) Close() {
|
||||||
|
m.cache.Close()
|
||||||
|
}
|
30
v1/storage_test.go
Normal file
30
v1/storage_test.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StorageTest struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStorage(t *testing.T) {
|
||||||
|
suite.Run(t, new(StorageTest))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StorageTest) Test_MGClientPool() {
|
||||||
|
clientPool, err := NewMGClientPool(1)
|
||||||
|
t.Assert().NoError(err)
|
||||||
|
|
||||||
|
client := clientPool.Get("test_token", "test_url")
|
||||||
|
t.Assert().Equal("test_url", client.URL)
|
||||||
|
|
||||||
|
clientPool.Remove("test_token")
|
||||||
|
clientPool.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *StorageTest) Test_NegativeCapacity() {
|
||||||
|
_, err := NewMGClientPool(-1)
|
||||||
|
t.Assert().Equal(NegativeCapacity.Error(), err.Error())
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -83,6 +84,9 @@ type MgClient struct {
|
|||||||
Debug bool `json:"debug"`
|
Debug bool `json:"debug"`
|
||||||
httpClient *http.Client `json:"-"`
|
httpClient *http.Client `json:"-"`
|
||||||
logger BasicLogger `json:"-"`
|
logger BasicLogger `json:"-"`
|
||||||
|
mux sync.Mutex `json:"-"`
|
||||||
|
lastTime time.Time `json:"-"`
|
||||||
|
rps int `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Channel type.
|
// Channel type.
|
||||||
|
Loading…
Reference in New Issue
Block a user