@Override public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) { // We expect to be here for normal message processing, but only as a pass-through. If the state indicates that // the request was not handled then that's a pipeline misconfiguration and we need to throw an error. HttpProcessingState state = getStateAndCreateIfNeeded(ctx, null); if (!state.isRequestHandled()) { runnableWithTracingAndMdc(() -> { String errorMsg = "In ExceptionHandlingHandler's channelRead method, but the request has not yet been " + "handled. This should not be possible and indicates the pipeline is not set up " + "properly or some unknown and unexpected error state was triggered. Sending " + "unhandled error response"; logger.error(errorMsg); Exception ex = new InvalidRipostePipelineException(errorMsg); ResponseInfo<ErrorResponseBody> responseInfo = processUnhandledError(state, msg, ex); state.setResponseInfo(responseInfo, ex); addErrorAnnotationToOverallRequestSpan(state, responseInfo, ex); }, ctx).run(); } return PipelineContinuationBehavior.CONTINUE; }
@Override public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) { // We expect to be here for normal message processing, but only as a pass-through. If the state indicates that // the request was not handled then that's a pipeline misconfiguration and we need to throw an error. HttpProcessingState state = getStateAndCreateIfNeeded(ctx, null); if (!state.isRequestHandled()) { runnableWithTracingAndMdc(() -> { String errorMsg = "In ExceptionHandlingHandler's channelRead method, but the request has not yet been " + "handled. This should not be possible and indicates the pipeline is not set up " + "properly or some unknown and unexpected error state was triggered. Sending " + "unhandled error response"; logger.error(errorMsg); Exception ex = new InvalidRipostePipelineException(errorMsg); ResponseInfo<ErrorResponseBody> responseInfo = processUnhandledError(state, msg, ex); state.setResponseInfo(responseInfo, ex); addErrorAnnotationToOverallRequestSpan(state, responseInfo, ex); }, ctx).run(); } return PipelineContinuationBehavior.CONTINUE; }
@Test public void addErrorAnnotationToOverallRequestSpan_does_not_propagate_exceptions() { // given HttpProcessingState stateSpy = spy(state); doThrow(new RuntimeException("intentional exception")).when(stateSpy).getOverallRequestSpan(); // when Throwable ex = catchThrowable( () -> handler.addErrorAnnotationToOverallRequestSpan( stateSpy, mock(ResponseInfo.class), mock(Throwable.class) ) ); // then Assertions.assertThat(ex).isNull(); } }
@Test public void doChannelRead_should_call_processUnhandledError_and_set_response_on_state_and_return_CONTINUE_if_request_has_not_been_handled() throws Exception { // given ExceptionHandlingHandler handlerSpy = spy(handler); ResponseInfo<ErrorResponseBody> errorResponseMock = mock(ResponseInfo.class); Object msg = new Object(); doReturn(errorResponseMock).when(handlerSpy).processUnhandledError(eq(state), eq(msg), any(Throwable.class)); assertThat(state.isRequestHandled(), is(false)); // when PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, msg); // then verify(handlerSpy).processUnhandledError(eq(state), eq(msg), any(Throwable.class)); assertThat(state.getResponseInfo(), is(errorResponseMock)); Assertions.assertThat(state.getErrorThatTriggeredThisResponse()) .isInstanceOf(InvalidRipostePipelineException.class); verify(handlerSpy).addErrorAnnotationToOverallRequestSpan( state, errorResponseMock, state.getErrorThatTriggeredThisResponse() ); assertThat(result, is(PipelineContinuationBehavior.CONTINUE)); }
addErrorAnnotationToOverallRequestSpan(state, responseInfo, cause);
@Test public void doExceptionCaught_should_cancel_proxy_router_processing_if_endpoint_is_ProxyRouterEndpoint() { // given ExceptionHandlingHandler handlerSpy = spy(handler); Throwable cause = new Exception("intentional test exception"); ResponseInfo<ErrorResponseBody> errorResponseMock = mock(ResponseInfo.class); doReturn(errorResponseMock).when(handlerSpy).processError(state, null, cause); assertThat(state.getResponseInfo(), nullValue()); // when PipelineContinuationBehavior result = handlerSpy.doExceptionCaught(ctxMock, cause); // then verify(handlerSpy).getStateAndCreateIfNeeded(ctxMock, cause); verify(handlerSpy).processError(state, null, cause); assertThat(state.getResponseInfo(), is(errorResponseMock)); Assertions.assertThat(state.getErrorThatTriggeredThisResponse()).isSameAs(cause); verify(handlerSpy).addErrorAnnotationToOverallRequestSpan(state, errorResponseMock, cause); assertThat(result, is(PipelineContinuationBehavior.CONTINUE)); }
addErrorAnnotationToOverallRequestSpan(state, responseInfo, cause);
@UseDataProvider("errorAnnotationScenarioDataProvider") @Test public void addErrorAnnotationToOverallRequestSpan_works_as_expected( ErrorAnnotationScenario scenario ) { // given HttpProcessingState stateSpy = spy(state); doReturn(scenario.overallRequestSpanMock).when(stateSpy).getOverallRequestSpan(); ResponseInfo<ErrorResponseBody> responseInfoMock = mock(ResponseInfo.class); Throwable errorMock = mock(Throwable.class); doReturn(scenario.addErrorAnnotationConfigValue) .when(serverSpanNamingAndTaggingStrategyMock).shouldAddErrorAnnotationForCaughtException(any(), any()); String annotationValueFromStrategy = UUID.randomUUID().toString(); doReturn(annotationValueFromStrategy) .when(serverSpanNamingAndTaggingStrategyMock).errorAnnotationName(any(), any()); // when handler.addErrorAnnotationToOverallRequestSpan(stateSpy, responseInfoMock, errorMock); // then if (scenario.expectAnnotation) { verify(scenario.overallRequestSpanMock).addTimestampedAnnotationForCurrentTime(annotationValueFromStrategy); verify(serverSpanNamingAndTaggingStrategyMock).errorAnnotationName(responseInfoMock, errorMock); } if (scenario.overallRequestSpanMock != null) { verify(serverSpanNamingAndTaggingStrategyMock) .shouldAddErrorAnnotationForCaughtException(responseInfoMock, errorMock); } }
assertThat(state.getResponseInfo(), is(errorResponseMock)); Assertions.assertThat(state.getErrorThatTriggeredThisResponse()).isSameAs(cause); verify(handlerSpy).addErrorAnnotationToOverallRequestSpan(state, errorResponseMock, cause);