Skip to content

Commit b153fe3

Browse files
committed
Add RateLimitException with RetryAfter field.
1 parent ec15763 commit b153fe3

File tree

2 files changed

+65
-21
lines changed

2 files changed

+65
-21
lines changed

Dropbox.Api/DropboxClient.common.cs

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public class HttpException : Exception
120120
/// <param name="message">The message.</param>
121121
/// <param name="uri">The request uri.</param>
122122
/// <param name="inner">The inner.</param>
123-
public HttpException(int statusCode, string message = null, Uri uri = null, Exception inner = null)
123+
internal HttpException(int statusCode, string message = null, Uri uri = null, Exception inner = null)
124124
: base(message, inner)
125125
{
126126
this.StatusCode = statusCode;
@@ -154,7 +154,7 @@ public class BadInputException : HttpException
154154
/// </summary>
155155
/// <param name="message">The message that describes the error.</param>
156156
/// <param name="uri">The request URI.</param>
157-
public BadInputException(string message, Uri uri = null)
157+
internal BadInputException(string message, Uri uri = null)
158158
: base(400, message, uri)
159159
{
160160
}
@@ -170,7 +170,7 @@ public partial class AuthException
170170
/// </summary>
171171
/// <param name="message">The message that describes the error.</param>
172172
/// <param name="uri">The request URI</param>
173-
[Obsolete("This constructor will be removed soon.")]
173+
[Obsolete("This constructor will be removed soon.")]
174174
public AuthException(string message, Uri uri = null) : base(null, message)
175175
{
176176
this.StatusCode = 401;
@@ -197,23 +197,22 @@ public AuthException(string message, Uri uri = null) : base(null, message)
197197
}
198198

199199
/// <summary>
200-
/// An HTTP Exception that will cause a retry
200+
/// An HTTP Exception that will cause a retry due to transient failure. The SDK will perform
201+
/// a certain number of retries which is configurable in <see cref="DropboxClient"/>. If the client
202+
/// still gets this exception, it's up to the client to decide whether to continue retrying or not.
201203
/// </summary>
202204
public class RetryException : HttpException
203205
{
204206
/// <summary>
205207
/// Initializes a new instance of the <see cref="RetryException"/> class.
206208
/// </summary>
207209
/// <param name="statusCode">The status code.</param>
208-
/// <param name="isRateLimit">if set to <c>true</c> the server responded with
209-
/// an error indicating rate limiting.</param>
210210
/// <param name="message">The message.</param>
211211
/// <param name="uri">The request URI.</param>
212212
/// <param name="inner">The inner.</param>
213-
public RetryException(int statusCode, bool isRateLimit = false, string message = null, Uri uri = null, Exception inner = null)
213+
internal RetryException(int statusCode, string message = null, Uri uri = null, Exception inner = null)
214214
: base(statusCode, message, uri, inner)
215215
{
216-
this.IsRateLimit = isRateLimit;
217216
}
218217

219218
/// <summary>
@@ -222,6 +221,48 @@ public RetryException(int statusCode, bool isRateLimit = false, string message =
222221
/// <value>
223222
/// <c>true</c> if this response is a rate limit; otherwise, <c>false</c>.
224223
/// </value>
225-
public bool IsRateLimit { get; private set; }
224+
[Obsolete("This field will be removed soon. Please catch RateLimitException separately.")]
225+
public virtual bool IsRateLimit
226+
{
227+
get { return false; }
228+
}
229+
}
230+
231+
/// <summary>
232+
/// An HTTP Exception that will cause a retry due to rate limiting. The SDK will not do auto-retry for
233+
/// this type of exception. The client should do proper backoff based on the value of
234+
/// <see cref="RateLimitException.RetryAfter"/> field.
235+
/// </summary>
236+
public class RateLimitException : RetryException
237+
{
238+
/// <summary>
239+
/// Initializes a new instance of the <see cref="RateLimitException"/> class.
240+
/// </summary>
241+
/// <param name="retryAfter">The time in second which the client should retry after.</param>
242+
/// <param name="message">The message.</param>
243+
/// <param name="uri">The request URI.</param>
244+
/// <param name="inner">The inner.</param>
245+
internal RateLimitException(int retryAfter, string message = null, Uri uri = null, Exception inner = null)
246+
: base(429, message, uri, inner)
247+
{
248+
this.RetryAfter = retryAfter;
249+
}
250+
251+
/// <summary>
252+
/// Gets the value in second which the client should backoff and retry after.
253+
/// </summary>
254+
public int RetryAfter { get; private set; }
255+
256+
/// <summary>
257+
/// Gets a value indicating whether this error represents a rate limiting response from the server.
258+
/// </summary>
259+
/// <value>
260+
/// <c>true</c> if this response is a rate limit; otherwise, <c>false</c>.
261+
/// </value>
262+
[Obsolete("This field will be removed soon.")]
263+
public override bool IsRateLimit
264+
{
265+
get { return true; }
266+
}
226267
}
227268
}

Dropbox.Api/DropboxRequestHandler.cs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ async Task<TResponse> ITransport.SendUploadRequestAsync<TRequest, TResponse, TEr
160160
var serializedArg = JsonWriter.Write(request, requestEncoder, true);
161161
var res = await this.RequestJsonStringWithRetry(host, route, RouteStyle.Upload, serializedArg, body)
162162
.ConfigureAwait(false);
163-
163+
164164
if (res.IsError)
165165
{
166166
throw StructuredException<TError>.Decode<ApiException<TError>>(res.ObjectResult, errorDecoder);
@@ -273,16 +273,15 @@ private async Task<Result> RequestJsonStringWithRetry(
273273
}
274274
}
275275
}
276-
catch (RetryException re)
276+
catch (RateLimitException)
277277
{
278-
if (!re.IsRateLimit)
279-
{
280-
// dropbox maps 503 - ServiceUnavailable to be a rate limiting error.
281-
// do not count a rate limiting error as an attempt
282-
attempt++;
283-
}
284-
285-
if (attempt > maxRetries)
278+
throw;
279+
}
280+
catch (RetryException)
281+
{
282+
// dropbox maps 503 - ServiceUnavailable to be a rate limiting error.
283+
// do not count a rate limiting error as an attempt
284+
if (++attempt > maxRetries)
286285
{
287286
throw;
288287
}
@@ -306,7 +305,7 @@ private async Task<Result> RequestJsonStringWithRetry(
306305
/// <returns>The contents of the error field if present, otherwise <paramref name="text" />.</returns>
307306
private string CheckForError(string text)
308307
{
309-
try
308+
try
310309
{
311310
var obj = JObject.Parse(text);
312311
JToken error;
@@ -411,7 +410,11 @@ private async Task<Result> RequestJsonString(
411410
}
412411
else if ((int)response.StatusCode == 429)
413412
{
414-
throw new RetryException(429, uri: uri, isRateLimit: true);
413+
var text = await response.Content.ReadAsStringAsync();
414+
text = this.CheckForError(text);
415+
var retryAfter = response.Headers.GetValues("Retry-After");
416+
417+
throw new RateLimitException(int.Parse(retryAfter.First()), message: text, uri: uri);
415418
}
416419
else if (response.StatusCode == HttpStatusCode.Conflict ||
417420
response.StatusCode == HttpStatusCode.Forbidden ||

0 commit comments

Comments
 (0)