Skip to content

Commit 7e361b8

Browse files
author
Karl Rieb
committed
Parse Retry-After header in API v1 503 responses.
Fixes T84239.
1 parent 371a922 commit 7e361b8

File tree

3 files changed

+57
-9
lines changed

3 files changed

+57
-9
lines changed

ChangeLog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
- Parse Retry-After header for 503 retry exceptions in API v1.
12
- Add example from online tutorial.
23

34
---------------------------------------------

src/main/java/com/dropbox/core/DbxRequestUtil.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,18 @@ public static DbxException unexpectedStatus(HttpRequestor.Response response)
332332
case 500:
333333
return new ServerException(requestId, message);
334334
case 503:
335-
return new RetryException(requestId, message);
335+
// API v1 may include Retry-After in 503 responses, v2 does not
336+
String retryAfter = getFirstHeaderMaybe(response, "Retry-After");
337+
try {
338+
if (retryAfter != null && !retryAfter.trim().isEmpty()) {
339+
int backoffSecs = Integer.parseInt(retryAfter);
340+
return new RetryException(requestId, message, backoffSecs, TimeUnit.SECONDS);
341+
} else {
342+
return new RetryException(requestId, message);
343+
}
344+
} catch (NumberFormatException ex) {
345+
return new BadResponseException(requestId, "Invalid value for HTTP header: \"Retry-After\"");
346+
}
336347
default:
337348
return new BadResponseCodeException(
338349
requestId,

src/test/java/com/dropbox/core/v1/DbxClientV1Test.java

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.InputStream;
2222
import java.io.IOException;
2323
import java.io.OutputStream;
24+
import java.util.ArrayList;
2425
import java.util.Collections;
2526
import java.util.List;
2627
import java.util.Map;
@@ -65,20 +66,28 @@ public void testRetrySuccess() throws DbxException, IOException {
6566
DbxClientV1 client = new DbxClientV1(config, "fakeAccessToken");
6667
String json = "{\"reset\":true,\"entries\":[],\"cursor\":\"fakeCursor\",\"has_more\":true}";
6768

69+
6870
// 503 twice, then return result
6971
HttpRequestor.Uploader mockUploader = mockUploader();
7072
when(mockUploader.finish())
71-
.thenReturn(createEmptyResponse(503))
72-
.thenReturn(createEmptyResponse(503))
73+
.thenReturn(createEmptyResponse(503)) // no backoff
74+
.thenReturn(createRateLimitResponse(1)) // backoff 1 sec
75+
.thenReturn(createRateLimitResponse(2)) // backoff 2 sec
7376
.thenReturn(createSuccessResponse(json));
7477

7578
when(mockRequestor.startPost(anyString(), anyHeaders()))
7679
.thenReturn(mockUploader);
7780

81+
long start = System.currentTimeMillis();
7882
DbxDelta<DbxEntry> actual = client.getDelta(null);
83+
long end = System.currentTimeMillis();
7984

80-
// should have only been called 3 times: initial call + 2 retries
81-
verify(mockRequestor, times(3)).startPost(anyString(), anyHeaders());
85+
// no way easy way to properly test this, but request should
86+
// have taken AT LEAST 3 seconds due to backoff.
87+
assertTrue((end - start) >= 3000L, "duration: " + (end - start) + " millis");
88+
89+
// should have been called 4 times: initial call + 3 retries
90+
verify(mockRequestor, times(4)).startPost(anyString(), anyHeaders());
8291

8392
assertEquals(actual.reset, true);
8493
assertEquals(actual.cursor, "fakeCursor");
@@ -178,14 +187,20 @@ private static HttpRequestor.Response createEmptyResponse(int statusCode) {
178187
);
179188
}
180189

181-
private static HttpRequestor.Response createDownloaderResponse(byte [] body, String header, String json) {
182-
Map<String, List<String>> headers = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER);
183-
headers.put(header, Collections.<String>singletonList(json));
190+
private static HttpRequestor.Response createRateLimitResponse(long backoffSeconds) {
191+
byte [] body = new byte[0];
192+
return new HttpRequestor.Response(
193+
503, // API v1 uses 503 for rate limits
194+
new ByteArrayInputStream(body),
195+
headers("Retry-After", Long.toString(backoffSeconds))
196+
);
197+
}
184198

199+
private static HttpRequestor.Response createDownloaderResponse(byte [] body, String header, String json) {
185200
return new HttpRequestor.Response(
186201
200,
187202
new ByteArrayInputStream(body),
188-
headers
203+
headers(header, json)
189204
);
190205
}
191206

@@ -210,6 +225,27 @@ public OutputStream answer(InvocationOnMock invocation) {
210225
return uploader;
211226
}
212227

228+
private static Map<String, List<String>> headers(String name, String value, String ... rest) {
229+
assertTrue(rest.length % 2 == 0);
230+
231+
Map<String, List<String>> headers = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER);
232+
List<String> values = new ArrayList<String>();
233+
headers.put(name, values);
234+
values.add(value);
235+
for (int i = 0; i < rest.length; i += 2) {
236+
name = rest[i];
237+
value = rest[i+1];
238+
values = headers.get(name);
239+
if (values == null) {
240+
values = new ArrayList<String>();
241+
headers.put(name, values);
242+
}
243+
values.add(value);
244+
}
245+
246+
return headers;
247+
}
248+
213249
private static Iterable<HttpRequestor.Header> anyHeaders() {
214250
return Matchers.<Iterable<HttpRequestor.Header>>any();
215251
}

0 commit comments

Comments
 (0)