/** * @param asyncHttpClientHelper The async HTTP client you want this method to use to make the AWS metadata call. * * @return A {@link CompletableFuture} that will contain the AWS instance ID this app is running on (assuming it * completes successfully). If an error occurs retrieving the instance ID from AWS then the error will be logged and * {@link AppInfo#UNKNOWN_VALUE} returned as the value. */ public static CompletableFuture<String> getAwsInstanceId(AsyncHttpClientHelper asyncHttpClientHelper) { return asyncHttpClientHelper.executeAsyncHttpRequest( asyncHttpClientHelper.getRequestBuilder(AMAZON_METADATA_INSTANCE_ID_URL, HttpMethod.GET), Response::getResponseBody ).handle((instanceId, error) -> { if (error != null) { logger.error("Unable to get instance ID info from AWS metadata service.", error); return AppInfo.UNKNOWN_VALUE; } if (instanceId == null) { logger.error("AWS metadata service returned null for instance ID. Using 'unknown' as fallback."); return AppInfo.UNKNOWN_VALUE; } return instanceId; }); }
return asyncHttpClientHelper.executeAsyncHttpRequest( asyncHttpClientHelper.getRequestBuilder(AMAZON_METADATA_DOCUMENT_URL, HttpMethod.GET), response -> {
@Test public void executeAsyncHttpRequest_with_ctx_throws_IllegalStateException_if_state_is_null() { // given RequestBuilderWrapper rbwMock = mock(RequestBuilderWrapper.class); AsyncResponseHandler responseHandlerMock = mock(AsyncResponseHandler.class); CompletableFuture cfMock = mock(CompletableFuture.class); doReturn(cfMock).when(helperSpy).executeAsyncHttpRequest( any(RequestBuilderWrapper.class), any(AsyncResponseHandler.class), any(Deque.class), any(Map.class) ); doReturn(null).when(stateAttributeMock).get(); // when Throwable ex = catchThrowable(() -> helperSpy.executeAsyncHttpRequest(rbwMock, responseHandlerMock, ctxMock)); // then assertThat(ex).isInstanceOf(IllegalStateException.class); }
/** * Executes the given request asynchronously, handling the response with the given responseHandlerFunction, and * returns a {@link CompletableFuture} that represents the result of executing the * responseHandlerFunction on the downstream response. Any error anywhere along the way will cause the returned * future to be completed with {@link CompletableFuture#completeExceptionally(Throwable)}. * <p/> * NOTE: This is a helper method for calling {@link #executeAsyncHttpRequest(RequestBuilderWrapper, * AsyncResponseHandler, java.util.Deque, java.util.Map)} that uses {@link * ChannelAttributes#getHttpProcessingStateForChannel(ChannelHandlerContext)} to extract the {@link * HttpProcessingState} from the given ctx argument, and then grabs {@link * HttpProcessingState#getDistributedTraceStack()} and {@link HttpProcessingState#getLoggerMdcContextMap()} to use * as the distributed trace stack and MDC info for the downstream call. */ public <O> CompletableFuture<O> executeAsyncHttpRequest(RequestBuilderWrapper requestBuilderWrapper, AsyncResponseHandler<O> responseHandlerFunction, ChannelHandlerContext ctx) { HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get(); if (state == null) throw new IllegalStateException("state cannot be null"); Map<String, String> mdcContextMap = state.getLoggerMdcContextMap(); Deque<Span> distributedTraceStack = state.getDistributedTraceStack(); requestBuilderWrapper.setCtx(ctx); return executeAsyncHttpRequest(requestBuilderWrapper, responseHandlerFunction, distributedTraceStack, mdcContextMap); }
/** * Executes the given request asynchronously, handling the response with the given responseHandlerFunction, and * returns a {@link CompletableFuture} that represents the result of executing the * responseHandlerFunction on the downstream response. Any error anywhere along the way will cause the returned * future to be completed with {@link CompletableFuture#completeExceptionally(Throwable)}. * <p/> * NOTE: This is a helper method for calling {@link #executeAsyncHttpRequest(RequestBuilderWrapper, * AsyncResponseHandler, java.util.Deque, java.util.Map)} that uses the current thread's {@link * Tracer#getCurrentSpanStackCopy()} and {@link org.slf4j.MDC#getCopyOfContextMap()} for the for the distributed * trace stack and MDC info for the downstream call. */ public <O> CompletableFuture<O> executeAsyncHttpRequest(RequestBuilderWrapper requestBuilderWrapper, AsyncResponseHandler<O> responseHandlerFunction) { Map<String, String> mdcContextMap = MDC.getCopyOfContextMap(); Deque<Span> distributedTraceStack = Tracer.getInstance().getCurrentSpanStackCopy(); return executeAsyncHttpRequest(requestBuilderWrapper, responseHandlerFunction, distributedTraceStack, mdcContextMap); }
private Pair<CompletableFuture<String>, AsyncResponseHandler<String>> executeGetAwsRegionAndExtractHandler() { CompletableFuture<String> cf = AwsUtil.getAwsRegion(asyncClientMock); verify(asyncClientMock).getRequestBuilder(AMAZON_METADATA_DOCUMENT_URL, HttpMethod.GET); ArgumentCaptor<AsyncResponseHandler> handlerArgCaptor = ArgumentCaptor.forClass(AsyncResponseHandler.class); verify(asyncClientMock).executeAsyncHttpRequest(eq(regionRequestBuilderWrapperMock), handlerArgCaptor.capture()); AsyncResponseHandler<String> handler = handlerArgCaptor.getValue(); return Pair.of(cf, handler); }
private Pair<CompletableFuture<String>, AsyncResponseHandler<String>> executeGetAwsInstanceIdAndExtractHandler() { CompletableFuture<String> cf = AwsUtil.getAwsInstanceId(asyncClientMock); verify(asyncClientMock).getRequestBuilder(AMAZON_METADATA_INSTANCE_ID_URL, HttpMethod.GET); ArgumentCaptor<AsyncResponseHandler> handlerArgCaptor = ArgumentCaptor.forClass(AsyncResponseHandler.class); verify(asyncClientMock).executeAsyncHttpRequest(eq(awsInstanceIdRequestBuilderWrapperMock), handlerArgCaptor.capture()); AsyncResponseHandler<String> handler = handlerArgCaptor.getValue(); return Pair.of(cf, handler); }
@Test public void executeAsyncHttpRequest_with_ctx_extracts_mdc_and_tracing_info_from_ctx_and_delegates_to_kitchen_sink_execute_method() { // given RequestBuilderWrapper rbwMock = mock(RequestBuilderWrapper.class); AsyncResponseHandler responseHandlerMock = mock(AsyncResponseHandler.class); CompletableFuture cfMock = mock(CompletableFuture.class); doReturn(cfMock).when(helperSpy).executeAsyncHttpRequest( any(RequestBuilderWrapper.class), any(AsyncResponseHandler.class), any(Deque.class), any(Map.class) ); Map<String, String> mdcMock = mock(Map.class); Deque<Span> spanStackMock = mock(Deque.class); state.setLoggerMdcContextMap(mdcMock); state.setDistributedTraceStack(spanStackMock); // when CompletableFuture result = helperSpy.executeAsyncHttpRequest(rbwMock, responseHandlerMock, ctxMock); // then verify(helperSpy).executeAsyncHttpRequest(rbwMock, responseHandlerMock, spanStackMock, mdcMock); assertThat(result).isSameAs(cfMock); verify(rbwMock).setCtx(ctxMock); }
@Test public void basic_executeAsyncHttpRequest_extracts_mdc_and_tracing_info_from_current_thread_and_delegates_to_kitchen_sink_execute_method() { // given RequestBuilderWrapper rbw = mock(RequestBuilderWrapper.class); AsyncResponseHandler responseHandler = mock(AsyncResponseHandler.class); CompletableFuture cfMock = mock(CompletableFuture.class); doReturn(cfMock).when(helperSpy).executeAsyncHttpRequest( any(RequestBuilderWrapper.class), any(AsyncResponseHandler.class), any(Deque.class), any(Map.class) ); Tracer.getInstance().startRequestWithRootSpan("foo"); Deque<Span> expectedSpanStack = Tracer.getInstance().getCurrentSpanStackCopy(); Map<String, String> expectedMdc = MDC.getCopyOfContextMap(); // when CompletableFuture result = helperSpy.executeAsyncHttpRequest(rbw, responseHandler); // then verify(helperSpy).executeAsyncHttpRequest(rbw, responseHandler, expectedSpanStack, expectedMdc); assertThat(result).isSameAs(cfMock); }
.executeAsyncHttpRequest(eq(regionRequestBuilderWrapperMock), any(AsyncResponseHandler.class)); .executeAsyncHttpRequest(eq(awsInstanceIdRequestBuilderWrapperMock), any(AsyncResponseHandler.class));
@DataProvider(value = { "true", "false" }, splitBy = "\\|") @Test public void executeAsyncHttpRequest_completes_future_if_exception_happens_during_setup( boolean throwCircuitBreakerOpenException) { // given RuntimeException exToThrow = (throwCircuitBreakerOpenException) ? new CircuitBreakerOpenException("foo", "kaboom") : new RuntimeException("kaboom"); doThrow(exToThrow).when(helperSpy).getCircuitBreaker(any(RequestBuilderWrapper.class)); Logger loggerMock = mock(Logger.class); Whitebox.setInternalState(helperSpy, "logger", loggerMock); // when CompletableFuture result = helperSpy .executeAsyncHttpRequest(mock(RequestBuilderWrapper.class), mock(AsyncResponseHandler.class), null, null); // then assertThat(result).isCompletedExceptionally(); Throwable ex = catchThrowable(result::get); assertThat(ex) .isInstanceOf(ExecutionException.class) .hasCause(exToThrow); if (throwCircuitBreakerOpenException) verifyZeroInteractions(loggerMock); else verify(loggerMock).error(anyString(), anyString(), anyString(), eq(exToThrow)); }
@DataProvider(value = { "true", "false" }) @Test public void getAwsRegion_returns_completable_future_with_fallback_handling_logic(boolean completeExceptionally) { // given // Don't complete the core future, just return it, so we can complete it how we want. doReturn(regionCoreFuture).when(asyncClientMock) .executeAsyncHttpRequest(eq(regionRequestBuilderWrapperMock), any(AsyncResponseHandler.class)); Pair<CompletableFuture<String>, AsyncResponseHandler<String>> resultAndHandler = executeGetAwsRegionAndExtractHandler(); assertThat(regionCoreFuture).isNotDone(); assertThat(resultAndHandler.getLeft()).isNotDone(); // when if (completeExceptionally) regionCoreFuture.completeExceptionally(new RuntimeException("kaboom")); else regionCoreFuture.complete(null); // then // In either case, we should get AppInfo.UNKNOWN_VALUE as the final result from the future with fallback logic. assertThat(resultAndHandler.getLeft()).isCompleted(); assertThat(resultAndHandler.getLeft().join()).isEqualTo(AppInfo.UNKNOWN_VALUE); }
@DataProvider(value = { "true", "false" }) @Test public void getAwsInstanceId_returns_completable_future_with_fallback_handling_logic( boolean completeExceptionally) { // given // Don't complete the core future, just return it, so we can complete it how we want. doReturn(awsInstanceIdCoreFuture) .when(asyncClientMock) .executeAsyncHttpRequest(eq(awsInstanceIdRequestBuilderWrapperMock), any(AsyncResponseHandler.class)); Pair<CompletableFuture<String>, AsyncResponseHandler<String>> resultAndHandler = executeGetAwsInstanceIdAndExtractHandler(); assertThat(awsInstanceIdCoreFuture).isNotDone(); assertThat(resultAndHandler.getLeft()).isNotDone(); // when if (completeExceptionally) awsInstanceIdCoreFuture.completeExceptionally(new RuntimeException("kaboom")); else awsInstanceIdCoreFuture.complete(null); // then // In either case, we should get AppInfo.UNKNOWN_VALUE as the final result from the future with fallback logic. assertThat(resultAndHandler.getLeft()).isCompleted(); assertThat(resultAndHandler.getLeft().join()).isEqualTo(AppInfo.UNKNOWN_VALUE); }
CompletableFuture resultFuture = helperSpy.executeAsyncHttpRequest( rbw, responseHandlerMock, initialSpanStack, initialMdc );
Response result = asyncClient.executeAsyncHttpRequest( rbw, response -> response, distributedTraceStackForCall, mdcContextForCall ).join();
() -> asyncClient.executeAsyncHttpRequest( rbw, response -> response, finalDistributedTraceStackForCall, finalMdcContextForCall ).join()