Skip to content

Commit

Permalink
Merge pull request #25 from ultradns/throttling_too_many_requests
Browse files Browse the repository at this point in the history
Throttling too many requests
  • Loading branch information
stevedejong authored Oct 10, 2024
2 parents 970ca9a + c8736cf commit 243eeec
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .sdk-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.7.0
v1.8.0
2 changes: 2 additions & 0 deletions internal/testing/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ func TestRecordResources(t *testing.T) {
})
t.Run("TestRDPoolRecordResources",
func(st *testing.T) {
integration.TestClient.EnableDefaultWarnLogger()
it.Test = st
it.TestRDPoolResources(zoneName)
})
t.Run("TestSFPoolRecordResources",
func(st *testing.T) {
integration.TestClient.DisableLogger()
it.Test = st
it.TestSFPoolResources(zoneName)
})
Expand Down
51 changes: 34 additions & 17 deletions pkg/client/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"

"github.com/ultradns/ultradns-go-sdk/internal/version"
"github.com/ultradns/ultradns-go-sdk/pkg/errors"
)

const contentType = "application/json"
const throttleSleep = 1 * time.Second
const maxThrottleRetry = 3

var (
defaultUserAgent = version.GetSDKVersion()
)

func (c *Client) Do(method, path string, payload, target interface{}) (*http.Response, error) {
func (c *Client) Do(method, path string, payload interface{}, target *Response) (*http.Response, error) {
url := fmt.Sprintf("%s/%s", c.baseURL, path)
body := new(bytes.Buffer)

Expand Down Expand Up @@ -52,13 +56,24 @@ func (c *Client) Do(method, path string, payload, target interface{}) (*http.Res
resp.Header = res.Header
}

if target == nil {
return resp, errors.ResponseTargetError("<nil>")
}

if resp.StatusCode == http.StatusTooManyRequests && target.retry < maxThrottleRetry {
c.Warn("Throttling the request: '%s %s': attempt=%v ", method, url, (target.retry + 1))
target.retry += 1
time.Sleep(throttleSleep)
return c.Do(method, path, payload, target)
}

if err != nil {
return resp, err
}

defer res.Body.Close()

er := validateResponse(res, target)
er := c.validateResponse(res, target)

if er != nil {
return resp, er
Expand All @@ -67,19 +82,7 @@ func (c *Client) Do(method, path string, payload, target interface{}) (*http.Res
return resp, nil
}

func validateResponse(res *http.Response, t interface{}) error {
if t == nil {
return errors.ResponseTargetError("<nil>")
}

// Api Response should be always Response{} struct
// or else throw ResponseTargetError
// with current Response struct type
target, ok := t.(*Response)

if !ok {
return errors.ResponseTargetError(fmt.Sprintf("%T", t))
}
func (c *Client) validateResponse(res *http.Response, target *Response) error {

if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusMultipleChoices {
if res.StatusCode == http.StatusNoContent {
Expand All @@ -92,13 +95,27 @@ func validateResponse(res *http.Response, t interface{}) error {
return err
}
} else {
err := json.NewDecoder(res.Body).Decode(&target.Error)
bodyBytes, err := io.ReadAll(res.Body)

if err != nil {
return err
}

return errors.APIResponseError(target.Error[0].String())
err = json.NewDecoder(bytes.NewReader(bodyBytes)).Decode(&target.ErrorList)

if err == nil {
return errors.APIResponseError(target.ErrorList[0].String())
}

c.Warn("Unable to parse API error message: %s", err.Error())

err = json.NewDecoder(bytes.NewReader(bodyBytes)).Decode(&target.Error)

if err == nil {
return errors.APIResponseError(target.Error.String())
}

return err
}

return nil
Expand Down
10 changes: 1 addition & 9 deletions pkg/client/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestDoSuccess(t *testing.T) {
}

if res.StatusCode != http.StatusOK {
t.Error(target.Error[0].String())
t.Error(target.ErrorList[0].String())
}
}

Expand All @@ -30,14 +30,6 @@ func TestDoNilTarget(t *testing.T) {
}
}

func TestDoWrongTarget(t *testing.T) {
_, err := integration.TestClient.Do(http.MethodGet, "zones", nil, &zone.Zone{})

if err.Error() != "Unexpected response type received: '*zone.Zone'" {
t.Fatal(err)
}
}

func TestDoNonExistingZone(t *testing.T) {
target := client.Target(&zone.Response{})
_, err := integration.TestClient.Do(http.MethodGet, "zones/unit-test-non-existing-zone.com", nil, target)
Expand Down
17 changes: 15 additions & 2 deletions pkg/client/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ type logLevelType int
const (
LogOff logLevelType = iota
LogError
LogWarn
LogDebug
LogTrace
)

const defaultLogFlags = log.Ldate | log.Lmicroseconds | log.Lmsgprefix

type logger struct {
logLevel logLevelType
logger *log.Logger
Expand All @@ -28,6 +31,8 @@ func (l logger) getLogPrefix(logLevel logLevelType) string {
switch logLevel {
case LogError:
return "[ERROR] "
case LogWarn:
return "[WARN]"
case LogDebug:
return "[DEBUG] "
case LogTrace:
Expand Down Expand Up @@ -68,6 +73,10 @@ func (c *Client) Error(format string, v ...any) {
c.logger.logf(LogError, format, v...)
}

func (c *Client) Warn(format string, v ...any) {
c.logger.logf(LogWarn, format, v...)
}

func (c *Client) Debug(format string, v ...any) {
c.logger.logf(LogDebug, format, v...)
}
Expand All @@ -76,12 +85,16 @@ func (c *Client) Trace(format string, v ...any) {
c.logger.logf(LogTrace, format, v...)
}

func (c *Client) EnableDefaultWarnLogger() {
c.EnableLogger(LogWarn, defaultLogFlags)
}

func (c *Client) EnableDefaultDebugLogger() {
c.EnableLogger(LogDebug, log.Ldate|log.Lmicroseconds|log.Lmsgprefix)
c.EnableLogger(LogDebug, defaultLogFlags)
}

func (c *Client) EnableDefaultTraceLogger() {
c.EnableLogger(LogTrace, log.Ldate|log.Lmicroseconds|log.Lmsgprefix)
c.EnableLogger(LogTrace, defaultLogFlags)
}

func (c *Client) EnableLogger(logLevel logLevelType, flags int) {
Expand Down
6 changes: 4 additions & 2 deletions pkg/client/structures.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ type Client struct {

// Response wraps the success and error response data.
type Response struct {
Data interface{}
Error []*ErrorResponse
Data interface{}
ErrorList []*ErrorResponse
Error *ErrorResponse
retry int
}

// ErrorResponse wraps the structure ultradns error response.
Expand Down

0 comments on commit 243eeec

Please sign in to comment.