1

I readed this post: C# (429) Too Many Requests and i understod the responde code but... why only return this status code when the call is done from server side (backend) and production mode (hosted)? the service never return this code when call (the same service) from chrome's navigate url or when i do the call server side (backend) but my localhost.

CASE 1 (works fine in localhost - the service url is not localhost, is hosted)

App A (localhost) call App B (hosted) --> works fine

        for (int i = 0; i < 1000; i++)
    {
        HttpClient client = new HttpClient();
        client.BaseAddress = new Uri(url);

        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        String response = client.GetStringAsync(urlParameters).Result;  
        client.Dispose();
    }

CASE 2 (work fine)

Chrome navigator call App B (hosted) --> works fine

enter image description here

CASE 3 (similar to case 1 but too less requests - NOT WORK)

App A (hosted) call App B (hosted) --> 429

Why? What is the problem? How can solve it?

Dan Def
  • 1,836
  • 2
  • 21
  • 39
Vandersan
  • 21
  • 3
  • 4
    for (int i = 0; i < 1000; i++) HttpClient client = new HttpClient(); <-- oh god! Please read [You're using HttpClient wrong and it's destabilizing your software](https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/) and the follow-up [You're (probably still) using HttpClient wrong and it is destabilizing your software](https://josef.codes/you-are-probably-still-using-httpclient-wrong-and-it-is-destabilizing-your-software/) – ProgrammingLlama Oct 22 '22 at 15:07
  • Additionally to what the 1st comment says, i have to admit that i find it quite puzzling that you would forgo the simple and reliable `using` statement in favor of manually disposing an IDisposable... –  Oct 22 '22 at 15:27
  • You say it works fine but your screenshot shows (only) 610 requests instead of 1000 – Rafalon Oct 22 '22 at 15:37
  • @Rafalon: That's a separate test (I guess he's refreshing the browser to cause many requests). – Eric J. Oct 22 '22 at 15:41
  • @ProgrammingLlama Thanks for the information, i will do the change. Only one HttpClient for all app requests. But the code is only for test and show us the problem. I don't use this loop code, only one call. My question is why this sample code works at localhost (or chrome) and not works deployed/hosted with less calls. – Vandersan Oct 22 '22 at 15:50
  • @Rafalon yes, the screenshot is "only" for 610 requests, but in production mode (hosted) with 100/200 request the call is blocked... is only for evidence the problem. My question is why this sample code works at localhost (or chrome) and not works deployed/hosted with less calls – Vandersan Oct 22 '22 at 15:50

1 Answers1

1

What's Happening

The HTTP 429 response code indicates you have been rate limited. The idea is to prevent one caller from overwhelming a service, making it less availabe to other callers.

Most Common

That limiting can be based on many things. Most common are

  • Number of calls per unit time (usually per second)
  • Number of concurrent calls

The General Case

A rate limiter may also forgive a short burst of calls that happens occasionally, may allow more calls before hitting the brakes based on who you are (using your IP or an API key for example), dynamically adjust its limits based on total system load, or do other things.

Probably Happening Here

Based on your description, I would guess the number of concurrent calls could be causing production rate limiting. Rather than hitting the external API hard trying to guess what the rules are, try reaching out to them to ask. If that is not an option, running multiple requests in parallel could validate this theory.

Handling

A great way to deal with this is to back off your requests when you receive an HTTP 429.

The service should return a Retry-After header indicating how many seconds you should wait before trying again. If it does, wait that long before resubmitting your request.

If the service does not provide that header (I work with a major one that does not), use exponential backoff instead.

Depending on your needs, you may want to tell your own caller to try again later (return an HTTP 429 yourself) or you may want to queue up pending requests and work off the queue to submit them all.

Preventing

If you know the rate limits, you can pre-emptively limit your outbound call rate so you get into this situation less often.

For call-per-second limits, you can use a counter variable that you reset (in a thread-safe way) every second. If the known call limit would be exceeded, calculate when the counter will reset (store a timestamp when it does) and delay processing that long.

For a concurrent-call limit, a SemaphoreSlim works nicely. Set the maximum count to whatever your concurrent rate limit is. Acquire the semaphore before making a request and release it (in a finally block) after your call completes.

If you have multiple servers subject to the same rate limit (e.g. if rate limiting is based on an API key rather than IP address), it gets harder to self-limit, but you can set self-limiting parameters (calls per second and concurrent calls) in a configuration file, and tune them over time to maximize your throughput without hitting excessive HTTP 429's.

Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • Thanks for the info @Eric J. I think that i understand the 429 response code and your good solution, but my question is why this sample code works at localhost (or chrome) and not works deployed/hosted with less calls... There are not API keys, there are not authentication, and the ip... could be a pattern? i tryed this code hosted in two diferents hostings (differents ip) and the result is the same. – Vandersan Oct 22 '22 at 15:57
  • I mention in my answer that the most likely reason you see this only in production is that your local tests doesn't create concurrent requests, and some services rate limit based on that. – Eric J. Oct 22 '22 at 17:13