use of com.nike.riposte.server.http.ResponseInfo in project riposte by Nike-Inc.
the class ResponseSenderHandlerTest method sendResponse_should_setup_a_last_ditch_error_response_if_an_exception_occurs_in_doSendResponse.
@DataProvider(value = { "true", "false" })
@Test
public void sendResponse_should_setup_a_last_ditch_error_response_if_an_exception_occurs_in_doSendResponse(boolean sendLastDitchResponseInline) throws JsonProcessingException {
// given
Object msg = new Object();
AtomicInteger numTimesDoSendResponseCalled = new AtomicInteger(0);
RuntimeException expectedExceptionFromDoSendResponse = new RuntimeException("intentional test exception");
List<ResponseInfo<?>> responseInfosPassedToDoSendResponse = new ArrayList<>();
doAnswer(invocation -> {
int numTimesCalled = numTimesDoSendResponseCalled.incrementAndGet();
// Capture the response data for this call so it can be asserted on later.
ChannelHandlerContext ctx = invocation.getArgument(0);
HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
responseInfosPassedToDoSendResponse.add(state.getResponseInfo());
// Only throw the exception the first time.
if (numTimesCalled == 1) {
throw expectedExceptionFromDoSendResponse;
}
return null;
}).when(handlerSpy).doSendResponse(any(), any());
// Use a real HttpProcessingState so that we can verify the last-ditch response gets set on it properly.
HttpProcessingState state = new HttpProcessingState();
state.setResponseInfo(responseInfo, null);
doReturn(state).when(stateAttrMock).get();
// when
Throwable propagatedEx = catchThrowable(() -> handlerSpy.sendResponse(ctxMock, msg, sendLastDitchResponseInline));
// then
// The initial call should have been made to doSendResponse, using the original responseInfo.
assertThat(responseInfosPassedToDoSendResponse.get(0)).isSameAs(responseInfo);
// But now, after the exception handling, the state should contain a new ResponseInfo
// representing the last ditch error response.
ResponseInfo<?> lastDitchResponseInfo = state.getResponseInfo();
assertThat(lastDitchResponseInfo).isNotEqualTo(responseInfo);
assertThat(lastDitchResponseInfo.getContentForFullResponse()).isInstanceOf(ErrorResponseBodyImpl.class);
ErrorResponseBodyImpl lastDitchContent = (ErrorResponseBodyImpl) lastDitchResponseInfo.getContentForFullResponse();
assertThat(lastDitchContent).isNotNull();
assertThat(lastDitchContent.errors).hasSize(1);
DefaultErrorDTO errorDto = lastDitchContent.errors.get(0);
assertThat(errorDto.code).isEqualTo(SampleCoreApiError.GENERIC_SERVICE_ERROR.getErrorCode());
assertThat(errorDto.message).isEqualTo(SampleCoreApiError.GENERIC_SERVICE_ERROR.getMessage());
assertThat(errorDto.metadata).isEqualTo(SampleCoreApiError.GENERIC_SERVICE_ERROR.getMetadata());
String errorId = lastDitchContent.errorId();
assertThat(errorId).isNotBlank();
assertThat(lastDitchResponseInfo.getHeaders().get("error_uid")).isEqualTo(errorId);
assertThat(lastDitchResponseInfo.getHttpStatusCode()).isEqualTo(500);
// The request should have been marked as setting the last ditch response, so we only ever try this once.
verify(handlerSpy).markTriedSendingLastDitchResponse(state);
if (sendLastDitchResponseInline) {
// Verify that the last ditch response was sent inline, and matches expected properties.
assertThat(numTimesDoSendResponseCalled.get()).isEqualTo(2);
assertThat(responseInfosPassedToDoSendResponse.get(1)).isSameAs(lastDitchResponseInfo);
// Since the last ditch response was sent inline, and we made sure it would complete successfully,
// then no exception should have been propagated.
assertThat(propagatedEx).isNull();
} else {
// Verify that the last ditch response was not sent inline.
assertThat(numTimesDoSendResponseCalled.get()).isEqualTo(1);
// Since the last ditch response was not sent inline, the doSendResponse exception should have been
// propagated.
assertThat(propagatedEx).isSameAs(expectedExceptionFromDoSendResponse);
}
// Proxy streaming should be canceled.
verify(proxyStateMock).cancelRequestStreaming(expectedExceptionFromDoSendResponse, ctxMock);
verify(proxyStateMock).cancelDownstreamRequest(expectedExceptionFromDoSendResponse);
// But since the last-ditch response was sent successfully, the channel shouldn't have been closed.
verify(channelMock, never()).close();
}
use of com.nike.riposte.server.http.ResponseInfo in project riposte by Nike-Inc.
the class ExceptionHandlingHandler method doChannelRead.
@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);
// Ensure that a RequestInfo is set on the state, no matter what.
getRequestInfo(state, msg);
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;
}
Aggregations