From bb2662b33c702bf141dcb4d02733d976b7bfcd4d Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 26 Nov 2018 04:26:27 +0100 Subject: [PATCH] Handle HTTP 429 responses with a request backoff approach Since it's difficult to wrap all possible go-mastodon API calls in a backoff algorithm outside of the package itself, I decided to implement a simple version of it in go-mastodon's doAPI itself. This works nicely, but could be improved in two ways still: - Abort sleeping when context gets cancelled - Make backoff optional / configurable Personally, I still think this is a good start and probably fits most of go-mastodon's use-cases. It certainly beats string-grepping for status code "429" in clients. --- mastodon.go | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/mastodon.go b/mastodon.go index 30a0516..a91da4d 100644 --- a/mastodon.go +++ b/mastodon.go @@ -16,6 +16,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/tomnomnom/linkheader" ) @@ -118,11 +119,32 @@ func (c *Client) doAPI(ctx context.Context, method string, uri string, params in req.Header.Set("Content-Type", ct) } - resp, err := c.Do(req) - if err != nil { - return err + var resp *http.Response + backoff := 1000 * time.Millisecond + for { + resp, err = c.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + // handle status code 429, which indicates the server is throttling + // our requests. Do an exponential backoff and retry the request. + if resp.StatusCode == 429 { + if backoff > time.Hour { + break + } + backoff *= 2 + + select { + case <-time.After(backoff): + case <-ctx.Done(): + return ctx.Err() + } + continue + } + break } - defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return parseAPIError("bad request", resp)