Skip to content

Commit 81079a5

Browse files
authored
Merge pull request #163 from microsoftgraph/bugfix/retry-handler-stream-request
- fixes a bug where the retry handler would use the response streaming information instead of the requst to evaluate
2 parents 09ad2c1 + fd1e3c0 commit 81079a5

File tree

2 files changed

+59
-30
lines changed

2 files changed

+59
-30
lines changed

src/main/java/com/microsoft/graph/httpcore/RetryHandler.java

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@ public class RetryHandler implements Interceptor{
2222
*/
2323
private final String RETRY_ATTEMPT_HEADER = "Retry-Attempt";
2424
private final String RETRY_AFTER = "Retry-After";
25-
private final String TRANSFER_ENCODING = "Transfer-Encoding";
26-
private final String TRANSFER_ENCODING_CHUNKED = "chunked";
27-
private final String APPLICATION_OCTET_STREAM = "application/octet-stream";
28-
private final String CONTENT_TYPE = "Content-Type";
25+
/** Content length request header value */
26+
private final String CONTENT_LENGTH = "Content-Length";
2927

3028
public static final int MSClientErrorCodeTooManyRequests = 429;
3129
public static final int MSClientErrorCodeServiceUnavailable = 503;
@@ -66,7 +64,7 @@ boolean retryRequest(Response response, int executionCount, Request request, Ret
6664
// without any retry attempt.
6765
shouldRetry =
6866
(executionCount <= retryOptions.maxRetries())
69-
&& checkStatus(statusCode) && isBuffered(response, request)
67+
&& checkStatus(statusCode) && isBuffered(request)
7068
&& shouldRetryCallback != null
7169
&& shouldRetryCallback.shouldRetry(retryOptions.delay(), executionCount, request, response);
7270

@@ -106,27 +104,22 @@ boolean checkStatus(int statusCode) {
106104
|| statusCode == MSClientErrorCodeGatewayTimeout);
107105
}
108106

109-
boolean isBuffered(Response response, Request request) {
110-
String methodName = request.method();
111-
if(methodName.equalsIgnoreCase("GET") || methodName.equalsIgnoreCase("DELETE") || methodName.equalsIgnoreCase("HEAD") || methodName.equalsIgnoreCase("OPTIONS"))
112-
return true;
107+
boolean isBuffered(Request request) {
108+
final String methodName = request.method();
113109

114-
boolean isHTTPMethodPutPatchOrPost = methodName.equalsIgnoreCase("POST") ||
110+
final boolean isHTTPMethodPutPatchOrPost = methodName.equalsIgnoreCase("POST") ||
115111
methodName.equalsIgnoreCase("PUT") ||
116112
methodName.equalsIgnoreCase("PATCH");
117113

118-
if(isHTTPMethodPutPatchOrPost) {
119-
boolean isStream = response.header(CONTENT_TYPE)!=null && response.header(CONTENT_TYPE).equalsIgnoreCase(APPLICATION_OCTET_STREAM);
120-
if(!isStream) {
121-
String transferEncoding = response.header(TRANSFER_ENCODING);
122-
boolean isTransferEncodingChunked = (transferEncoding != null) &&
123-
transferEncoding.equalsIgnoreCase(TRANSFER_ENCODING_CHUNKED);
124-
125-
if(request.body() != null && isTransferEncodingChunked)
126-
return true;
127-
}
114+
if(isHTTPMethodPutPatchOrPost && request.body() != null) {
115+
try {
116+
return request.body().contentLength() != -1L;
117+
} catch (IOException ex) {
118+
// expected
119+
return false;
120+
}
128121
}
129-
return false;
122+
return true;
130123
}
131124

132125
@Override

src/test/java/com/microsoft/graph/httpcore/RetryHandlerTest.java

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.junit.Assert.assertNotNull;
55
import static org.junit.Assert.assertTrue;
66

7+
import java.io.IOException;
78
import java.net.HttpURLConnection;
89

910
import org.junit.Test;
@@ -16,6 +17,7 @@
1617
import okhttp3.Request;
1718
import okhttp3.RequestBody;
1819
import okhttp3.Response;
20+
import okio.BufferedSink;
1921

2022
public class RetryHandlerTest {
2123

@@ -76,7 +78,7 @@ public void testRetryRequestWithMaxRetryAttempts() {
7678
// Default retry options with Number of maxretries default to 3
7779
RetryOptions retryOptions = new RetryOptions();
7880
// Try to execute one more than allowed default max retries
79-
int executionCount = RetryOptions.DEFAULT_MAX_RETRIES + 1;
81+
int executionCount = RetryOptions.DEFAULT_MAX_RETRIES + 1;
8082
assertFalse(retryhandler.retryRequest(response, executionCount, httpget, retryOptions));
8183
}
8284

@@ -87,7 +89,7 @@ public void testRetryRequestForStatusCode() {
8789
Response response = new Response.Builder()
8890
.protocol(Protocol.HTTP_1_1)
8991
// For status code 500 which is not in (429 503 504), So NO retry
90-
.code(HTTP_SERVER_ERROR)
92+
.code(HTTP_SERVER_ERROR)
9193
.message( "Internal Server Error")
9294
.request(httpget)
9395
.build();
@@ -100,7 +102,7 @@ public void testRetryRequestWithTransferEncoding() {
100102
Request httppost = new Request.Builder().url(testmeurl).post(RequestBody.create(MediaType.parse("application/json"), "TEST")).build();
101103
Response response = new Response.Builder()
102104
.protocol(Protocol.HTTP_1_1)
103-
.code(HttpURLConnection.HTTP_GATEWAY_TIMEOUT)
105+
.code(HttpURLConnection.HTTP_GATEWAY_TIMEOUT)
104106
.message( "gateway timeout")
105107
.request(httppost)
106108
.addHeader("Transfer-Encoding", "chunked")
@@ -114,15 +116,15 @@ public void testRetryRequestWithExponentialBackOff() {
114116
Request httppost = new Request.Builder().url(testmeurl).post(RequestBody.create(MediaType.parse("application/json"), "TEST")).build();
115117
Response response = new Response.Builder()
116118
.protocol(Protocol.HTTP_1_1)
117-
.code(HttpURLConnection.HTTP_GATEWAY_TIMEOUT)
119+
.code(HttpURLConnection.HTTP_GATEWAY_TIMEOUT)
118120
.message( "gateway timeout")
119121
.request(httppost)
120122
.addHeader("Transfer-Encoding", "chunked")
121123
.build();
122-
124+
123125
assertTrue(retryhandler.retryRequest(response, 1, httppost, new RetryOptions()));
124126
}
125-
127+
126128
@Test
127129
public void testGetRetryAfterWithHeader() {
128130
RetryHandler retryHandler = new RetryHandler();
@@ -131,7 +133,7 @@ public void testGetRetryAfterWithHeader() {
131133
delay = retryHandler.getRetryAfter(TestResponse().newBuilder().addHeader("Retry-After", "1").build(), 2, 3);
132134
assertTrue(delay == 1000);
133135
}
134-
136+
135137
@Test
136138
public void testGetRetryAfterOnFirstExecution() {
137139
RetryHandler retryHandler = new RetryHandler();
@@ -140,14 +142,48 @@ public void testGetRetryAfterOnFirstExecution() {
140142
delay = retryHandler.getRetryAfter(TestResponse(), 3, 2);
141143
assertTrue(delay > 3100);
142144
}
143-
145+
144146
@Test
145147
public void testGetRetryAfterMaxExceed() {
146148
RetryHandler retryHandler = new RetryHandler();
147149
long delay = retryHandler.getRetryAfter(TestResponse(), 190, 1);
148150
assertTrue(delay == 180000);
149151
}
150-
152+
@Test
153+
public void testIsBuffered() {
154+
final RetryHandler retryHandler = new RetryHandler();
155+
Request request = new Request.Builder().url("https://localhost").method("GET", null).build();
156+
assertTrue("Get Request is buffered", retryHandler.isBuffered(request));
157+
158+
request = new Request.Builder().url("https://localhost").method("DELETE", null).build();
159+
assertTrue("Delete Request is buffered", retryHandler.isBuffered(request));
160+
161+
request = new Request.Builder().url("https://localhost")
162+
.method("POST",
163+
RequestBody.create(MediaType.parse("application/json"),
164+
"{\"key\": 42 }"))
165+
.build();
166+
assertTrue("Post Request is buffered", retryHandler.isBuffered(request));
167+
168+
request = new Request.Builder().url("https://localhost")
169+
.method("POST",
170+
new RequestBody() {
171+
172+
@Override
173+
public MediaType contentType() {
174+
return MediaType.parse("application/octet-stream");
175+
}
176+
177+
@Override
178+
public void writeTo(BufferedSink sink) throws IOException {
179+
// TODO Auto-generated method stub
180+
181+
}
182+
})
183+
.build();
184+
assertFalse("Post Stream Request is not buffered", retryHandler.isBuffered(request));
185+
}
186+
151187
Response TestResponse() {
152188
return new Response.Builder()
153189
.code(429)

0 commit comments

Comments
 (0)