private OkHttpClient createRetryingClient(int maxNumRetries, Duration backoffSlotSize) { return createRetryingClient(maxNumRetries, backoffSlotSize, url); }
private OkHttpClient createRetryingClient(int maxNumRetries) { return createRetryingClient(maxNumRetries, Duration.ofMillis(500)); }
private OkHttpClient createRetryingClient(int maxNumRetries, String... urls) { return createRetryingClient(maxNumRetries, Duration.ofMillis(10), urls); }
@Test public void doesNotHangIfManyCallsResultInExceptions() throws Exception { int maxRetries = OkHttpClients.NUM_SCHEDULING_THREADS * 2; for (int i = 0; i <= maxRetries; i++) { server.enqueue(new MockResponse().setResponseCode(503)); } Call call = createRetryingClient(maxRetries, Duration.ofMillis(2) /* backoff slot size */) .newCall(new Request.Builder().url(url).build()); assertThatThrownBy(call::execute).isInstanceOf(IOException.class); }
@Test(timeout = 10_000) public void handlesInterruptedThreads() throws Exception { server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE)); OkHttpClient client = createRetryingClient(0); Thread thread = new Thread(() -> { try { client.newCall(new Request.Builder().url(url).build()).execute(); } catch (IOException e) { // nothing } }); thread.start(); Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); thread.interrupt(); thread.join(); }
@Test public void handlesRetryOther_doesNotRedirectInfinitelyOften() throws Exception { // Note that RemotingOkHttpClient.MAX_NUM_RELOCATIONS = 20 for (int i = 0; i < 21; ++i) { server.enqueue(new MockResponse().setResponseCode(308).addHeader(HttpHeaders.LOCATION, url)); } Call call = createRetryingClient(1).newCall(new Request.Builder().url(url).build()); assertThatThrownBy(call::execute) .isInstanceOf(IOException.class) .hasMessage("Exceeded the maximum number of allowed redirects for initial URL: %s/", url); assertThat(server.getRequestCount()).isEqualTo(21); }
@Test public void verifyIoExceptionMetricsAreRegistered() { Call call = createRetryingClient(0, "http://bogus").newCall(new Request.Builder().url("http://bogus").build()); assertThatExceptionOfType(IOException.class) .isThrownBy(call::execute); List<HostMetrics> hostMetrics = hostEventsSink.getMetrics().stream() .filter(metrics -> metrics.hostname().equals("bogus")) .filter(metrics -> metrics.serviceName().equals("OkHttpClientsTest")) .collect(Collectors.toList()); HostMetrics actualMetrics = Iterables.getOnlyElement(hostMetrics); assertThat(actualMetrics.getIoExceptions().getCount()).isEqualTo(1); }
@Test public void handlesQos_503FailsOverToAnotherUrl() throws Exception { server.enqueue(new MockResponse().setResponseCode(503)); server2.enqueue(new MockResponse().setBody("foo")); server2.enqueue(new MockResponse().setBody("bar")); OkHttpClient client = createRetryingClient(1, url, url2); Call call = client.newCall(new Request.Builder().url(url).build()); assertThat(call.execute().body().string()).isEqualTo("foo"); Call call2 = client.newCall(new Request.Builder().url(url).build()); assertThat(call2.execute().body().string()).isEqualTo("bar"); assertThat(server.getRequestCount()).isEqualTo(1); assertThat(server2.getRequestCount()).isEqualTo(2); }
@Test public void verifyResponseMetricsAreRegistered() throws IOException { server.enqueue(new MockResponse().setBody("pong")); createRetryingClient(1).newCall(new Request.Builder().url(url).build()).execute(); List<HostMetrics> hostMetrics = hostEventsSink.getMetrics().stream() .filter(metrics -> metrics.hostname().equals("localhost")) .filter(metrics -> metrics.serviceName().equals("OkHttpClientsTest")) .filter(metrics -> metrics.port() == server.getPort()) .collect(Collectors.toList()); HostMetrics actualMetrics = Iterables.getOnlyElement(hostMetrics); assertThat(actualMetrics.get2xx().getCount()).isGreaterThanOrEqualTo(1); }
@Test public void handlesIoExceptions_obeysMaxNumRetries() throws Exception { server.shutdown(); server2.shutdown(); server3.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); OkHttpClient client = createRetryingClient(1, url, url2, url3); Call call = client.newCall(new Request.Builder().url(url + "/foo?bar").build()); assertThatThrownBy(call::execute) .isInstanceOf(IOException.class) .hasMessage("Failed to complete the request due to an IOException"); assertThat(server3.getRequestCount()).isEqualTo(0); }
@Test public void handlesIoExceptions_retriesOtherServers() throws Exception { server.shutdown(); server2.shutdown(); server3.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); OkHttpClient client = createRetryingClient(2, url, url2, url3); Call call = client.newCall(new Request.Builder().url(url + "/foo?bar").build()); assertThat(call.execute().body().string()).isEqualTo("foo"); assertThat(server3.takeRequest().getPath()).isEqualTo("/foo?bar"); }
@Test public void doesNotShareBackoffStateBetweenDifferentCalls() throws Exception { OkHttpClient client = createRetryingClient(1); server.enqueue(new MockResponse().setResponseCode(503)); server.enqueue(new MockResponse().setBody("pong")); Call call = client.newCall(new Request.Builder().url(url).build()); assertThat(call.execute().body().string()).isEqualTo("pong"); // The following call would fail if OkHttpClients.create() constructed clients that share backoff state. server.enqueue(new MockResponse().setResponseCode(503)); server.enqueue(new MockResponse().setBody("pong")); call = client.newCall(new Request.Builder().url(url).build()); assertThat(call.execute().body().string()).isEqualTo("pong"); assertThat(server.getRequestCount()).isEqualTo(4 /* two from each call */); }
@Test public void throwsIoExceptionWithCorrectBodyAfterFailingToDeserializeSerializableError() throws Exception { String responseJson = "{\"attribute\": \"foo\"}"; MockResponse mockResponse = new MockResponse() .setBody(responseJson) .addHeader("Content-Type", "application/json") .setResponseCode(400); server.enqueue(mockResponse); OkHttpClient client = createRetryingClient(1); Call call = client.newCall(new Request.Builder().url(url).build()); assertThatThrownBy(call::execute).isInstanceOf(SafeIoException.class) .hasMessageContaining("Error 400. (Failed to parse response body as SerializableError.)"); }
@Test public void handlesUnavailable_succeedsWhenClientRetriesSufficientlyOften() throws Exception { server.enqueue(new MockResponse().setResponseCode(503)); server.enqueue(new MockResponse().setResponseCode(503)); server.enqueue(new MockResponse().setBody("pong")); Call call = createRetryingClient(2).newCall(new Request.Builder().url(url).build()); assertThat(call.execute().body().string()).isEqualTo("pong"); assertThat(server.getRequestCount()).isEqualTo(3 /* original plus two retries */); }
@Test public void handlesThrottle_succeedsWhenClientRetriesSufficientlyOften() throws Exception { server.enqueue(new MockResponse().setResponseCode(429)); server.enqueue(new MockResponse().setResponseCode(429)); server.enqueue(new MockResponse().setBody("pong")); Call call = createRetryingClient(2).newCall(new Request.Builder().url(url).build()); assertThat(call.execute().body().string()).isEqualTo("pong"); assertThat(server.getRequestCount()).isEqualTo(3 /* original plus two retries */); }
@Test public void handlesQos_redirectsToOtherUrlThenRetriesAnotherUrl() throws Exception { // First hits server, then 308 redirects to server2, then 503 redirects back to server. server.enqueue(new MockResponse().setResponseCode(308).addHeader(HttpHeaders.LOCATION, url2)); server2.enqueue(new MockResponse().setResponseCode(503)); server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); OkHttpClient client = createRetryingClient(1, url, url2); Call call = client.newCall(new Request.Builder().url(url).build()); assertThat(call.execute().body().string()).isEqualTo("foo"); assertThat(server.getRequestCount()).isEqualTo(2); assertThat(server2.getRequestCount()).isEqualTo(1); }
@Test public void handlesUnavailable_obeysMaxNumRetriesAndEventuallyPropagatesQosException() throws Exception { Call call; server.enqueue(new MockResponse().setResponseCode(503)); call = createRetryingClient(0).newCall(new Request.Builder().url(url).build()); assertThatThrownBy(call::execute) .isInstanceOf(IOException.class) .hasMessage("Failed to complete the request due to a server-side QoS condition: 503"); server.enqueue(new MockResponse().setResponseCode(503)); server.enqueue(new MockResponse().setResponseCode(503)); server.enqueue(new MockResponse().setResponseCode(503)); call = createRetryingClient(2).newCall(new Request.Builder().url(url).build()); assertThatThrownBy(call::execute) .isInstanceOf(IOException.class) .hasMessage("Failed to complete the request due to a server-side QoS condition: 503"); assertThat(server.getRequestCount()).isEqualTo(4 /* original plus two retries */); }
@Test public void handlesThrottle_obeysMaxNumRetriesAndEventuallyPropagatesQosException() throws Exception { Call call; server.enqueue(new MockResponse().setResponseCode(429)); call = createRetryingClient(0).newCall(new Request.Builder().url(url).build()); assertThatThrownBy(call::execute) .isInstanceOf(IOException.class) .hasMessage("Failed to reschedule call since the number of configured backoffs are exhausted"); server.enqueue(new MockResponse().setResponseCode(429)); server.enqueue(new MockResponse().setResponseCode(429)); server.enqueue(new MockResponse().setResponseCode(429)); call = createRetryingClient(2).newCall(new Request.Builder().url(url).build()); assertThatThrownBy(call::execute) .isInstanceOf(IOException.class) .hasMessage("Failed to reschedule call since the number of configured backoffs are exhausted"); assertThat(server.getRequestCount()).isEqualTo(4 /* original plus two retries */); }
@Test public void handlesThrottle_obeysMaxNumRetriesEvenWhenRetryAfterHeaderIsGiven() throws Exception { server.enqueue(new MockResponse().setResponseCode(429).addHeader(HttpHeaders.RETRY_AFTER, "0")); server.enqueue(new MockResponse().setResponseCode(429).addHeader(HttpHeaders.RETRY_AFTER, "0")); server.enqueue(new MockResponse().setResponseCode(429).addHeader(HttpHeaders.RETRY_AFTER, "0")); Call call = createRetryingClient(2).newCall(new Request.Builder().url(url).build()); assertThatThrownBy(call::execute) .isInstanceOf(IOException.class) .hasMessage("Failed to reschedule call since the number of configured backoffs are exhausted"); assertThat(server.getRequestCount()).isEqualTo(3 /* original plus two retries */); }
@Test public void throwsRemoteExceptionAfterRetry() throws Exception { // first we get a 503 server.enqueue(new MockResponse().setResponseCode(503)); // then we get a RemoteException SerializableError error = SerializableError.builder().errorCode("error code").errorName("error name").build(); MockResponse mockResponse = new MockResponse() .setBody(new ObjectMapper().writeValueAsString(error)) .addHeader("Content-Type", "application/json") .setResponseCode(400); server.enqueue(mockResponse); OkHttpClient client = createRetryingClient(1); Call call = client.newCall(new Request.Builder().url(url).build()); assertThatThrownBy(call::execute).isInstanceOf(RemoteException.class); }