Search in sources :

Example 16 with ResponseInfo

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();
}
Also used : ResponseInfo(com.nike.riposte.server.http.ResponseInfo) ErrorResponseBodyImpl(com.nike.backstopper.model.riposte.ErrorResponseBodyImpl) HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) ArrayList(java.util.ArrayList) ChannelHandlerContext(io.netty.channel.ChannelHandlerContext) Endpoint(com.nike.riposte.server.http.Endpoint) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Assertions.catchThrowable(org.assertj.core.api.Assertions.catchThrowable) DefaultErrorDTO(com.nike.backstopper.model.DefaultErrorDTO) DataProvider(com.tngtech.java.junit.dataprovider.DataProvider) UseDataProvider(com.tngtech.java.junit.dataprovider.UseDataProvider) Test(org.junit.Test)

Example 17 with ResponseInfo

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;
}
Also used : FullResponseInfo(com.nike.riposte.server.http.impl.FullResponseInfo) ResponseInfo(com.nike.riposte.server.http.ResponseInfo) ErrorResponseInfo(com.nike.riposte.server.error.handler.ErrorResponseInfo) HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) InvalidRipostePipelineException(com.nike.riposte.server.error.exception.InvalidRipostePipelineException) IncompleteHttpCallTimeoutException(com.nike.riposte.server.error.exception.IncompleteHttpCallTimeoutException) InvalidRipostePipelineException(com.nike.riposte.server.error.exception.InvalidRipostePipelineException) TooManyOpenChannelsException(com.nike.riposte.server.error.exception.TooManyOpenChannelsException)

Aggregations

ResponseInfo (com.nike.riposte.server.http.ResponseInfo)17 Test (org.junit.Test)13 Assertions.catchThrowable (org.assertj.core.api.Assertions.catchThrowable)11 HttpProcessingState (com.nike.riposte.server.http.HttpProcessingState)10 LastOutboundMessageSendFullResponseInfo (com.nike.riposte.server.channelpipeline.message.LastOutboundMessageSendFullResponseInfo)8 RequestInfo (com.nike.riposte.server.http.RequestInfo)8 ChannelHandlerContext (io.netty.channel.ChannelHandlerContext)6 DataProvider (com.tngtech.java.junit.dataprovider.DataProvider)5 Pair (com.nike.internal.util.Pair)4 IncompleteHttpCallTimeoutException (com.nike.riposte.server.error.exception.IncompleteHttpCallTimeoutException)4 InvalidRipostePipelineException (com.nike.riposte.server.error.exception.InvalidRipostePipelineException)4 TooManyOpenChannelsException (com.nike.riposte.server.error.exception.TooManyOpenChannelsException)4 ErrorResponseInfo (com.nike.riposte.server.error.handler.ErrorResponseInfo)4 PipelineContinuationBehavior (com.nike.riposte.server.handler.base.PipelineContinuationBehavior)4 FullResponseInfo (com.nike.riposte.server.http.impl.FullResponseInfo)4 JsonProcessingException (com.fasterxml.jackson.core.JsonProcessingException)3 ErrorResponseBody (com.nike.riposte.server.error.handler.ErrorResponseBody)3 RequestAndResponseFilter (com.nike.riposte.server.http.filter.RequestAndResponseFilter)3 BiFunction (java.util.function.BiFunction)3 ChannelAttributes (com.nike.riposte.server.channelpipeline.ChannelAttributes)2