Skip to content

Commit 7795383

Browse files
committed
Improved retrofit timeout handling.
Retrofit `Call` now uses http client's configured timeout to compute value for `timeout()` and blocking `execute()` exception.
1 parent bbaa4c3 commit 7795383

File tree

2 files changed

+71
-28
lines changed

2 files changed

+71
-28
lines changed

extras/retrofit2/src/main/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCall.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ class AsyncHttpClientCall implements Cloneable, okhttp3.Call {
4444
* Default {@link #execute()} timeout in milliseconds (value: <b>{@value}</b>)
4545
*
4646
* @see #execute()
47-
* @see #executeTimeoutMillis
4847
*/
49-
public static final long DEFAULT_EXECUTE_TIMEOUT_MILLIS = 30_000;
48+
static final long DEFAULT_EXECUTE_TIMEOUT_MILLIS = 30_000;
5049

5150
private static final ResponseBody EMPTY_BODY = ResponseBody.create(null, "");
5251

@@ -64,12 +63,6 @@ class AsyncHttpClientCall implements Cloneable, okhttp3.Call {
6463
@NonNull
6564
Supplier<AsyncHttpClient> httpClientSupplier;
6665

67-
/**
68-
* {@link #execute()} response timeout in milliseconds.
69-
*/
70-
@Builder.Default
71-
long executeTimeoutMillis = DEFAULT_EXECUTE_TIMEOUT_MILLIS;
72-
7366
/**
7467
* Retrofit request.
7568
*/
@@ -140,7 +133,7 @@ public Request request() {
140133
@Override
141134
public Response execute() throws IOException {
142135
try {
143-
return executeHttpRequest().get(getExecuteTimeoutMillis(), TimeUnit.MILLISECONDS);
136+
return executeHttpRequest().get(getRequestTimeoutMillis(), TimeUnit.MILLISECONDS);
144137
} catch (ExecutionException e) {
145138
throw toIOException(e.getCause());
146139
} catch (Exception e) {
@@ -179,7 +172,19 @@ public boolean isCanceled() {
179172

180173
@Override
181174
public Timeout timeout() {
182-
return new Timeout().timeout(executeTimeoutMillis, TimeUnit.MILLISECONDS);
175+
return new Timeout().timeout(getRequestTimeoutMillis(), TimeUnit.MILLISECONDS);
176+
}
177+
178+
/**
179+
* Returns HTTP request timeout in milliseconds, retrieved from http client configuration.
180+
*
181+
* @return request timeout in milliseconds.
182+
* @see #DEFAULT_EXECUTE_TIMEOUT_MILLIS
183+
*/
184+
protected long getRequestTimeoutMillis() {
185+
val config = getHttpClient().getConfig();
186+
val timeout = Math.max(config.getReadTimeout(), config.getRequestTimeout());
187+
return (timeout < 1) ? DEFAULT_EXECUTE_TIMEOUT_MILLIS : timeout;
183188
}
184189

185190
@Override

extras/retrofit2/src/test/java/org/asynchttpclient/extras/retrofit/AsyncHttpClientCallTest.java

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@
1414

1515
import io.netty.handler.codec.http.DefaultHttpHeaders;
1616
import io.netty.handler.codec.http.EmptyHttpHeaders;
17+
import lombok.extern.slf4j.Slf4j;
1718
import lombok.val;
1819
import okhttp3.MediaType;
1920
import okhttp3.Request;
2021
import okhttp3.RequestBody;
2122
import org.asynchttpclient.AsyncCompletionHandler;
2223
import org.asynchttpclient.AsyncHttpClient;
24+
import org.asynchttpclient.AsyncHttpClientConfig;
2325
import org.asynchttpclient.BoundRequestBuilder;
26+
import org.asynchttpclient.DefaultAsyncHttpClient;
27+
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
2428
import org.asynchttpclient.Response;
2529
import org.mockito.ArgumentCaptor;
2630
import org.testng.Assert;
@@ -33,11 +37,14 @@
3337
import java.util.Arrays;
3438
import java.util.Collection;
3539
import java.util.concurrent.ExecutionException;
40+
import java.util.concurrent.TimeUnit;
3641
import java.util.concurrent.TimeoutException;
3742
import java.util.concurrent.atomic.AtomicInteger;
43+
import java.util.concurrent.atomic.AtomicReference;
3844
import java.util.function.Consumer;
3945
import java.util.function.Supplier;
4046

47+
import static java.util.concurrent.TimeUnit.SECONDS;
4148
import static org.asynchttpclient.extras.retrofit.AsyncHttpClientCall.runConsumer;
4249
import static org.asynchttpclient.extras.retrofit.AsyncHttpClientCall.runConsumers;
4350
import static org.mockito.ArgumentMatchers.any;
@@ -46,15 +53,20 @@
4653
import static org.testng.Assert.assertNotEquals;
4754
import static org.testng.Assert.assertTrue;
4855

56+
@Slf4j
4957
public class AsyncHttpClientCallTest {
5058
static final Request REQUEST = new Request.Builder().url("http://www.google.com/").build();
59+
static final DefaultAsyncHttpClientConfig DEFAULT_AHC_CONFIG = new DefaultAsyncHttpClientConfig.Builder()
60+
.setRequestTimeout(1_000)
61+
.build();
5162

5263
private AsyncHttpClient httpClient;
5364
private Supplier<AsyncHttpClient> httpClientSupplier = () -> httpClient;
5465

5566
@BeforeMethod
5667
void setup() {
57-
this.httpClient = mock(AsyncHttpClient.class);
68+
httpClient = mock(AsyncHttpClient.class);
69+
when(httpClient.getConfig()).thenReturn(DEFAULT_AHC_CONFIG);
5870
}
5971

6072
@Test(expectedExceptions = NullPointerException.class, dataProvider = "first")
@@ -108,7 +120,6 @@ void shouldInvokeConsumersOnEachExecution(Consumer<AsyncCompletionHandler<?>> ha
108120
.onRequestFailure(t -> numFailed.incrementAndGet())
109121
.onRequestSuccess(r -> numOk.incrementAndGet())
110122
.requestCustomizer(rb -> numRequestCustomizer.incrementAndGet())
111-
.executeTimeoutMillis(1000)
112123
.build();
113124

114125
// when
@@ -245,13 +256,12 @@ public void contentTypeHeaderIsPassedInRequest() throws Exception {
245256
Request request = requestWithBody();
246257

247258
ArgumentCaptor<org.asynchttpclient.Request> capture = ArgumentCaptor.forClass(org.asynchttpclient.Request.class);
248-
AsyncHttpClient client = mock(AsyncHttpClient.class);
249259

250-
givenResponseIsProduced(client, aResponse());
260+
givenResponseIsProduced(httpClient, aResponse());
251261

252-
whenRequestIsMade(client, request);
262+
whenRequestIsMade(httpClient, request);
253263

254-
verify(client).executeRequest(capture.capture(), any());
264+
verify(httpClient).executeRequest(capture.capture(), any());
255265

256266
org.asynchttpclient.Request ahcRequest = capture.getValue();
257267

@@ -263,11 +273,9 @@ public void contentTypeHeaderIsPassedInRequest() throws Exception {
263273

264274
@Test
265275
public void contenTypeIsOptionalInResponse() throws Exception {
266-
AsyncHttpClient client = mock(AsyncHttpClient.class);
276+
givenResponseIsProduced(httpClient, responseWithBody(null, "test"));
267277

268-
givenResponseIsProduced(client, responseWithBody(null, "test"));
269-
270-
okhttp3.Response response = whenRequestIsMade(client, REQUEST);
278+
okhttp3.Response response = whenRequestIsMade(httpClient, REQUEST);
271279

272280
assertEquals(response.code(), 200);
273281
assertEquals(response.header("Server"), "nginx");
@@ -277,11 +285,9 @@ public void contenTypeIsOptionalInResponse() throws Exception {
277285

278286
@Test
279287
public void contentTypeIsProperlyParsedIfPresent() throws Exception {
280-
AsyncHttpClient client = mock(AsyncHttpClient.class);
281-
282-
givenResponseIsProduced(client, responseWithBody("text/plain", "test"));
288+
givenResponseIsProduced(httpClient, responseWithBody("text/plain", "test"));
283289

284-
okhttp3.Response response = whenRequestIsMade(client, REQUEST);
290+
okhttp3.Response response = whenRequestIsMade(httpClient, REQUEST);
285291

286292
assertEquals(response.code(), 200);
287293
assertEquals(response.header("Server"), "nginx");
@@ -292,11 +298,9 @@ public void contentTypeIsProperlyParsedIfPresent() throws Exception {
292298

293299
@Test
294300
public void bodyIsNotNullInResponse() throws Exception {
295-
AsyncHttpClient client = mock(AsyncHttpClient.class);
296-
297-
givenResponseIsProduced(client, responseWithNoBody());
301+
givenResponseIsProduced(httpClient, responseWithNoBody());
298302

299-
okhttp3.Response response = whenRequestIsMade(client, REQUEST);
303+
okhttp3.Response response = whenRequestIsMade(httpClient, REQUEST);
300304

301305
assertEquals(response.code(), 200);
302306
assertEquals(response.header("Server"), "nginx");
@@ -315,6 +319,40 @@ void getHttpClientShouldThrowISEIfSupplierReturnsNull() {
315319
call.getHttpClient();
316320
}
317321

322+
@Test
323+
void shouldReturnTimeoutSpecifiedInAHCInstanceConfig() {
324+
// given:
325+
val cfgBuilder = new DefaultAsyncHttpClientConfig.Builder();
326+
//val clientRef = new AtomicReference<AsyncHttpClient>();
327+
AsyncHttpClientConfig config = null;
328+
329+
// and: setup call
330+
val call = AsyncHttpClientCall.builder()
331+
.httpClientSupplier(httpClientSupplier)
332+
.request(requestWithBody())
333+
.build();
334+
335+
// when: set read timeout to 5s, req timeout to 6s
336+
config = cfgBuilder.setReadTimeout((int) SECONDS.toMillis(5))
337+
.setRequestTimeout((int) SECONDS.toMillis(6))
338+
.build();
339+
when(httpClient.getConfig()).thenReturn(config);
340+
341+
// then
342+
assertEquals(call.getRequestTimeoutMillis(), SECONDS.toMillis(6));
343+
assertEquals(call.timeout().timeoutNanos(), SECONDS.toNanos(6));
344+
345+
// when: set read timeout to 10 seconds, req timeout to 7s
346+
config = cfgBuilder.setReadTimeout((int) SECONDS.toMillis(10))
347+
.setRequestTimeout((int) SECONDS.toMillis(7))
348+
.build();
349+
when(httpClient.getConfig()).thenReturn(config);
350+
351+
// then:
352+
assertEquals(call.getRequestTimeoutMillis(), SECONDS.toMillis(10));
353+
assertEquals(call.timeout().timeoutNanos(), SECONDS.toNanos(10));
354+
}
355+
318356
private void givenResponseIsProduced(AsyncHttpClient client, Response response) {
319357
when(client.executeRequest(any(org.asynchttpclient.Request.class), any())).thenAnswer(invocation -> {
320358
AsyncCompletionHandler<Response> handler = invocation.getArgument(1);

0 commit comments

Comments
 (0)