Best way to handle HTTP 429 errors

Clouse24 picture Clouse24 · Dec 26, 2017 · Viewed 11.7k times · Source

I'm hitting an api that will return a 429, too many requests, response if you hit it more than 250 times in a five minute span. The count resets every five minutes so I've been handling like this:

try
{
    return request.GetResponse();  
}
catch (Exception e)
{
    if (e.Message.Contains("429"))
    {
        System.Threading.Thread.Sleep(5 * 60 * 1000);
        return request.GetResponse();
    }
    else
    {
        throw new Exception(e.Message);
    }
}

Is this the correct way to handle this situation?

Answer

Matt Spinks picture Matt Spinks · Dec 26, 2017

One thing to consider here is that the api you are consuming might "penalize" you if you start going over the limit. I think it's best to be proactive and throttle your api requests, so you never receive the 429 errors.

We started experiencing the same thing on our app (429 errors) from an api we are consuming. The limit on this particular api was 10 every 60 seconds. We implemented a throttle() function that utilizes a memory cache with the date/time embedded. The api we used also tracked usage on an account basis. You may not need that. But this is a snippet we used to throttle our requests to ensure we were always under the limit:

    private void throttle()
    {
        var maxPerPeriod = 250;
        //If you utilize multiple accounts, you can throttle per account. If not, don't use this:
        var keyPrefix = "a_unique_id_for_the_basis_of_throttling";
        var intervalPeriod = 300000;//5 minutes
        var sleepInterval = 5000;//period to "sleep" before trying again (if the limits have been reached)
        var recentTransactions = MemoryCache.Default.Count(x => x.Key.StartsWith(keyPrefix));
        while (recentTransactions >= maxPerPeriod)
        {
            System.Threading.Thread.Sleep(sleepInterval);
            recentTransactions = MemoryCache.Default.Count(x => x.Key.StartsWith(keyPrefix));
        }
        var key = keyPrefix + "_" + DateTime.Now.ToUniversalTime().ToString("yyyyMMddHHmm");
        var existing = MemoryCache.Default.Where(x => x.Key.StartsWith(key));
        if (existing != null && existing.Any())
        {
            var counter = 2;
            var last = existing.OrderBy(x => x.Key).Last();
            var pieces = last.Key.Split('_');
            if (pieces.Count() > 2)
            {
                var lastCount = 0;
                if (int.TryParse(pieces[2], out lastCount))
                {
                    counter = lastCount + 1;
                }
            }
            key = key + "_" + counter;
        }
        var policy = new CacheItemPolicy
        {
            AbsoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(intervalPeriod)
        };
        MemoryCache.Default.Set(key, 1, policy);
    }

We used this throttle() function in our code like this:

    public override void DoAction()
    {
        throttle();
        var url = ContentUri;
        var request = (HttpWebRequest)WebRequest.Create(url);
        request.Method = "GET";
        request.Headers.Add("Authorization", "Bearer " + AccessToken);
        request.Accept = "application/json";
        WebResponse response = request.GetResponse();
        var dataStream = new MemoryStream();
        using (Stream responseStream = request.GetResponse().GetResponseStream())
        {
            //DO STUFF WITH THE DOWNLOADED DATA HERE...
        }
        dataStream.Close();
        response.Close();
    }

It essentially keeps track of your requests in the cache. If the limit has been reached, it pauses until enough time has passed so that you are still under the limit.