Skip to content

Commit c8571ce

Browse files
committed
Make use of "x-ratelimit-reset" HTTP response header when rate limit is exceeded in tests
1 parent ce87b8e commit c8571ce

3 files changed

Lines changed: 79 additions & 11 deletions

File tree

Assets/Plugins/StreamChat/Core/LowLevelClient/API/Internal/InternalApiClientBase.cs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Linq;
23
using System.Text;
34
using System.Threading.Tasks;
45
using StreamChat.Core.Exceptions;
@@ -161,11 +162,8 @@ private async Task<TResponse> HttpRequest<TResponse>(HttpMethodType httpMethod,
161162
#if STREAM_TESTS_ENABLED
162163
if (apiError.StatusCode == StreamApiException.RateLimitErrorHttpStatusCode && attempt < 50)
163164
{
164-
var delaySeconds = 61 + attempt * 10;
165-
_logs.Warning($"API CLIENT, TESTS MODE, Rate Limit API Error - Wait for {delaySeconds} seconds");
166-
await Task.Delay(delaySeconds * 1000);
167-
return await HttpRequest<TResponse>(httpMethod, endpoint,
168-
requestBody, queryParameters, ++attempt);
165+
return await HandleRateLimit<TResponse>(httpMethod, endpoint, requestBody, queryParameters, attempt,
166+
httpResponse);
169167
}
170168
#endif
171169

@@ -296,5 +294,42 @@ private void LogRestCall(Uri uri, string endpoint, HttpMethodType httpMethod, st
296294

297295
_logs.Info(_sb.ToString());
298296
}
297+
298+
private async Task<TResponse> HandleRateLimit<TResponse>(HttpMethodType httpMethod, string endpoint,
299+
object requestBody, QueryParameters queryParameters, int attempt, HttpResponse httpResponse)
300+
{
301+
if (attempt >= 50)
302+
{
303+
throw new StreamApiException(new APIErrorInternalDTO
304+
{ Code = StreamApiException.RateLimitErrorHttpStatusCode });
305+
}
306+
307+
var delaySeconds = GetBackoffDelay(attempt, httpResponse, out var resetHeaderTimestamp);
308+
_logs.Warning($"API CLIENT, TESTS MODE, Rate Limit API Error - Wait for {delaySeconds} seconds. Timestamp reset header: {resetHeaderTimestamp}");
309+
await Task.Delay(delaySeconds * 1000);
310+
return await HttpRequest<TResponse>(httpMethod, endpoint, requestBody, queryParameters, ++attempt);
311+
}
312+
313+
private int GetBackoffDelay(int attempt, HttpResponse httpResponse, out int resetHeaderTimestamp)
314+
{
315+
resetHeaderTimestamp = -1;
316+
if (httpResponse.TryGetHeader("x-ratelimit-reset", out var values))
317+
{
318+
var resetTimestamp = values.FirstOrDefault();
319+
320+
if (int.TryParse(resetTimestamp, out var rateLimitTimestamp))
321+
{
322+
resetHeaderTimestamp = rateLimitTimestamp;
323+
var now = (int)new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds();
324+
var secondsLeft = rateLimitTimestamp - now;
325+
if (secondsLeft > 0)
326+
{
327+
return secondsLeft;
328+
}
329+
}
330+
}
331+
332+
return 61 + attempt * 10;
333+
}
299334
}
300335
}

Assets/Plugins/StreamChat/Libs/Http/HttpResponse.cs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Net.Http;
1+
using System.Collections.Generic;
2+
using System.Net.Http;
23
using System.Threading.Tasks;
34
using StreamChat.Libs.Utils;
45
using UnityEngine.Networking;
@@ -14,24 +15,56 @@ public readonly struct HttpResponse
1415
public int StatusCode { get; }
1516
public string Result { get; }
1617

18+
public bool TryGetHeader(string key, out IEnumerable<string> values)
19+
{
20+
values = null;
21+
22+
if (_httpResponseMessage != null)
23+
{
24+
return _httpResponseMessage.Headers.TryGetValues(key, out values);
25+
}
26+
27+
if (_unityWebRequest != null)
28+
{
29+
var value = _unityWebRequest.GetResponseHeader(key);
30+
if (value != null)
31+
{
32+
values = new[] { value };
33+
}
34+
35+
return value != null;
36+
}
37+
38+
return false;
39+
}
40+
1741
public static async Task<HttpResponse> CreateFromHttpResponseMessageAsync(
1842
HttpResponseMessage httpResponseMessage)
1943
{
2044
var result = await httpResponseMessage.Content.ReadAsStringAsync();
21-
return new HttpResponse(httpResponseMessage.IsSuccessStatusCode, (int)httpResponseMessage.StatusCode, result);
45+
return new HttpResponse(httpResponseMessage.IsSuccessStatusCode, (int)httpResponseMessage.StatusCode,
46+
result, httpResponseMessage, null);
2247
}
2348

2449
public static HttpResponse CreateFromUnityWebRequest(UnityWebRequest unityWebRequest)
2550
{
2651
var result = unityWebRequest.downloadHandler?.text ?? string.Empty;
27-
return new HttpResponse(unityWebRequest.IsRequestSuccessful(), (int)unityWebRequest.responseCode, result);
52+
return new HttpResponse(unityWebRequest.IsRequestSuccessful(), (int)unityWebRequest.responseCode, result,
53+
null,
54+
unityWebRequest);
2855
}
2956

30-
public HttpResponse(bool isSuccessStatusCode, int statusCode, string result)
57+
public HttpResponse(bool isSuccessStatusCode, int statusCode, string result,
58+
HttpResponseMessage httpResponseMessage, UnityWebRequest unityRequest)
3159
{
3260
IsSuccessStatusCode = isSuccessStatusCode;
3361
StatusCode = statusCode;
3462
Result = result;
63+
_httpResponseMessage = httpResponseMessage;
64+
_unityWebRequest = unityRequest;
3565
}
66+
67+
private readonly HttpResponseMessage _httpResponseMessage;
68+
private readonly UnityWebRequest _unityWebRequest;
3669
}
3770
}

Assets/Plugins/StreamChat/Tests/LowLevelClient/Api/MessageApi.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public void when_client_post_request_expect_valid_uri_and_request_body_in_http_c
5959
{
6060
_lowLevelClient.Connect();
6161

62-
var response = new HttpResponse(true, 200, "{\"reaction\": {\"type\": \"like\"}}");
62+
var response = new HttpResponse(true, 200, "{\"reaction\": {\"type\": \"like\"}}", null, null);
6363

6464
_mockHttpClient.SendHttpRequestAsync(Arg.Is(HttpMethodType.Post),Arg.Any<Uri>(), Arg.Any<object>())
6565
.Returns(response);
@@ -77,7 +77,7 @@ public void when_client_delete_request_expect_valid_uri_in_http_client(EndpointT
7777
{
7878
_lowLevelClient.Connect();
7979

80-
var response = new HttpResponse(true, 200, "{\"reaction\": {\"type\": \"like\"}}");
80+
var response = new HttpResponse(true, 200, "{\"reaction\": {\"type\": \"like\"}}", null, null);
8181

8282
_mockHttpClient.SendHttpRequestAsync(Arg.Is(HttpMethodType.Post),Arg.Any<Uri>(), Arg.Any<HttpContent>())
8383
.Returns(response);

0 commit comments

Comments
 (0)