@Override public Response intercept(Chain chain) throws IOException { ConcurrencyLimiterListener limiterListenerTag = chain.request().tag(ConcurrencyLimiterListener.class); if (limiterListenerTag == null) { return chain.proceed(chain.request()); } ListenableFuture<Limiter.Listener> limiterFuture = limiterListenerTag.limiterListener(); Preconditions.checkState(limiterFuture.isDone(), "Limit listener future should have been fulfilled."); Limiter.Listener listener = Futures.getUnchecked(limiterFuture); try { Response response = chain.proceed(chain.request()); if (DROPPED_CODES.contains(response.code())) { listener.onDropped(); return response; } else if (!response.isSuccessful() || response.isRedirect()) { listener.onIgnore(); return response; } else { return wrapResponse(listener, response); } } catch (IOException e) { listener.onIgnore(); throw e; } }
private static Response wrapResponse(Limiter.Listener listener, Response response) throws IOException { // OkHttp guarantees not-null to execute() and callbacks, but not at this level. if (response.body() == null) { listener.onIgnore(); return response; } else if (response.body().source().exhausted()) { // this case exists for Feign, which does not properly close empty responses listener.onSuccess(); return response; } ResponseBody currentBody = response.body(); ResponseBody newResponseBody = ResponseBody.create( currentBody.contentType(), currentBody.contentLength(), wrapSource(currentBody.source(), listener)); return response.newBuilder() .body(newResponseBody) .build(); }
@Test public void dropsIfRateLimited() throws IOException { Response rateLimited = response.newBuilder().code(429).build(); when(chain.proceed(request)).thenReturn(rateLimited); assertThat(interceptor.intercept(chain)).isEqualTo(rateLimited); verify(listener).onDropped(); }
client.addInterceptor(new ConcurrencyLimitingInterceptor()); client.addInterceptor(InstrumentedInterceptor.create(registry, hostEventsSink, serviceClass)); client.addInterceptor(OkhttpTraceInterceptor.INSTANCE);
client.addInterceptor(new ConcurrencyLimitingInterceptor()); client.addInterceptor(InstrumentedInterceptor.create(registry, hostEventsSink, serviceClass)); client.addInterceptor(OkhttpTraceInterceptor.INSTANCE);
@Test public void dropsIfUnavailable() throws IOException { Response unavailable = response.newBuilder().code(503).build(); when(chain.proceed(request)).thenReturn(unavailable); assertThat(interceptor.intercept(chain)).isEqualTo(unavailable); verify(listener).onDropped(); }
private static Response wrapResponse(Limiter.Listener listener, Response response) throws IOException { // OkHttp guarantees not-null to execute() and callbacks, but not at this level. if (response.body() == null) { listener.onIgnore(); return response; } else if (response.body().source().exhausted()) { // this case exists for Feign, which does not properly close empty responses listener.onSuccess(); return response; } ResponseBody currentBody = response.body(); ResponseBody newResponseBody = ResponseBody.create( currentBody.contentType(), currentBody.contentLength(), wrapSource(currentBody.source(), listener)); return response.newBuilder() .body(newResponseBody) .build(); }
@Override public Response intercept(Chain chain) throws IOException { ConcurrencyLimiterListener limiterListenerTag = chain.request().tag(ConcurrencyLimiterListener.class); if (limiterListenerTag == null) { return chain.proceed(chain.request()); } ListenableFuture<Limiter.Listener> limiterFuture = limiterListenerTag.limiterListener(); Preconditions.checkState(limiterFuture.isDone(), "Limit listener future should have been fulfilled."); Limiter.Listener listener = Futures.getUnchecked(limiterFuture); try { Response response = chain.proceed(chain.request()); if (DROPPED_CODES.contains(response.code())) { listener.onDropped(); return response; } else if (!response.isSuccessful() || response.isRedirect()) { listener.onIgnore(); return response; } else { return wrapResponse(listener, response); } } catch (IOException e) { listener.onIgnore(); throw e; } }
@Test public void ignoresIfRedirect() throws IOException { Response redirect = response.newBuilder().code(308).build(); when(chain.proceed(request)).thenReturn(redirect); assertThat(interceptor.intercept(chain)).isEqualTo(redirect); verify(listener).onIgnore(); }
@Test public void ignoresIfUnsuccessful() throws IOException { Response unsuccessful = response.newBuilder().code(500).build(); when(chain.proceed(request)).thenReturn(unsuccessful); assertThat(interceptor.intercept(chain)).isEqualTo(unsuccessful); verify(listener).onIgnore(); }
@Test public void ignoresIfNoContent() throws IOException { Response noContent = response.newBuilder().code(204).build(); when(chain.proceed(request)).thenReturn(noContent); assertThat(interceptor.intercept(chain)).isEqualTo(noContent); verify(listener).onIgnore(); }
@Test public void ignoresIfIoException() throws IOException { IOException exception = new IOException(); when(chain.proceed(request)).thenThrow(exception); assertThatThrownBy(() -> interceptor.intercept(chain)).isEqualTo(exception); verify(listener).onIgnore(); }
@Test public void wrapsResponseBody() throws IOException { String data = "data"; ResponseBody body = ResponseBody.create(MediaType.parse("application/json"), data); when(chain.proceed(request)).thenReturn(response.newBuilder().body(body).build()); Response wrappedResponse = interceptor.intercept(chain); verifyZeroInteractions(listener); assertThat(wrappedResponse.body().string()).isEqualTo(data); verify(listener).onSuccess(); }
@Test public void proxyHandlesExceptions() throws IOException { ResponseBody body = ResponseBody.create(MediaType.parse("application/json"), -1, mockSource); when(chain.proceed(request)).thenReturn(response.newBuilder().body(body).build()); IOException exception = new IOException(); when(mockSource.readByteArray()).thenThrow(exception); Response erroneousResponse = interceptor.intercept(chain); assertThatThrownBy(() -> erroneousResponse.body().source().readByteArray()).isEqualTo(exception); }
@Test public void marksSuccessIfContentEmpty() throws IOException { Response empty = response.newBuilder().code(204) .body(ResponseBody.create(MediaType.parse("application/json"), new byte[0])) .build(); when(chain.proceed(request)).thenReturn(empty); assertThat(interceptor.intercept(chain)).isEqualTo(empty); verify(listener).onSuccess(); } }