Search in sources :

Example 1 with ErrorResponseBodyImpl

use of com.nike.backstopper.model.riposte.ErrorResponseBodyImpl in project riposte by Nike-Inc.

the class ResponseSenderHandler method sendResponse.

protected void sendResponse(ChannelHandlerContext ctx, Object msg, boolean sendLastDitchResponseInline) throws JsonProcessingException {
    try {
        // Try to send the response.
        doSendResponse(ctx, msg);
    } catch (Exception origSendEx) {
        boolean shouldRethrowOriginalSendEx = true;
        // Something went wrong while trying to send the response. We want to create a generic service error and
        // send that back to the caller if possible.
        // The HttpProcessingState will never be null thanks to ExceptionHandlingHandler
        HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
        boolean alreadyTriedSendingLastDitchResponse = alreadyTriedSendingLastDitchResponse(state);
        // Cancel any proxy router streaming that may be happening.
        ProxyRouterProcessingState proxyRouterProcessingState = ChannelAttributes.getProxyRouterProcessingStateForChannel(ctx).get();
        if (proxyRouterProcessingState != null) {
            proxyRouterProcessingState.cancelRequestStreaming(origSendEx, ctx);
            proxyRouterProcessingState.cancelDownstreamRequest(origSendEx);
        }
        // we can create a generic service error and try sending that.
        if (state.isResponseSendingStarted()) {
            runnableWithTracingAndMdc(() -> {
                logger.error("An unexpected error occurred while sending the response. At least part of the " + "response was sent, so there's nothing we can do at this point but close the connection.", origSendEx);
                // Add this error to the current span if possible, but only if no error tag already exists.
                Span currentSpan = Tracer.getInstance().getCurrentSpan();
                if (currentSpan != null && currentSpan.getTags().get(KnownZipkinTags.ERROR) == null) {
                    String errorTagValue = (origSendEx.getMessage() == null) ? origSendEx.getClass().getSimpleName() : origSendEx.getMessage();
                    currentSpan.putTag(KnownZipkinTags.ERROR, errorTagValue);
                }
            }, ctx).run();
            ctx.channel().close();
        } else if (!alreadyTriedSendingLastDitchResponse) {
            // Mark that we've tried doing the last-ditch response so that we only ever attempt it once.
            markTriedSendingLastDitchResponse(state);
            // We haven't already started response sending, so we can try sending a last ditch error response
            // instead that represents the response-sending exception.
            String errorId = UUID.randomUUID().toString();
            ResponseInfo<?> lastDitchErrorResponseInfo = ResponseInfo.newBuilder(new ErrorResponseBodyImpl(errorId, Collections.singleton(SampleCoreApiError.GENERIC_SERVICE_ERROR))).withHeaders(new DefaultHttpHeaders().set("error_uid", errorId)).withHttpStatusCode(500).build();
            state.setResponseInfo(lastDitchErrorResponseInfo, origSendEx);
            runnableWithTracingAndMdc(() -> logger.error("An unexpected error occurred while attempting to send a response. We'll attempt to send a " + "last-ditch error message. error_uid={}", errorId, origSendEx), ctx).run();
            if (sendLastDitchResponseInline) {
                doSendResponse(ctx, msg);
                // The last ditch response was successfully sent - we don't need to rethrow the original exception.
                shouldRethrowOriginalSendEx = false;
            }
        }
        if (shouldRethrowOriginalSendEx) {
            // will be allowed to propagate out of this class.
            throw origSendEx;
        }
    }
}
Also used : ErrorResponseBodyImpl(com.nike.backstopper.model.riposte.ErrorResponseBodyImpl) DefaultHttpHeaders(io.netty.handler.codec.http.DefaultHttpHeaders) HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) ProxyRouterProcessingState(com.nike.riposte.server.http.ProxyRouterProcessingState) Span(com.nike.wingtips.Span) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException)

Example 2 with ErrorResponseBodyImpl

use of com.nike.backstopper.model.riposte.ErrorResponseBodyImpl in project riposte by Nike-Inc.

the class RiposteUnhandledExceptionHandlerTest method prepareFrameworkResponseUsesErrorResponseBodyNettyAdapterWrapper.

@Test
public void prepareFrameworkResponseUsesErrorResponseBodyNettyAdapterWrapper() {
    RiposteUnhandledExceptionHandler myAdapter = new RiposteUnhandledExceptionHandler(projectApiErrors, utils);
    DefaultErrorContractDTO errorContract = new DefaultErrorContractDTO(UUID.randomUUID().toString(), Arrays.asList(projectApiErrors.getUnauthorizedApiError(), projectApiErrors.getMalformedRequestApiError()));
    ErrorResponseBody result = myAdapter.prepareFrameworkRepresentation(errorContract, 42, null, null, null);
    assertThat(result, instanceOf(ErrorResponseBodyImpl.class));
    ErrorResponseBodyImpl adapterResult = (ErrorResponseBodyImpl) result;
    assertThat(adapterResult.error_id, is(errorContract.error_id));
    assertThat(adapterResult.errors, is(errorContract.errors));
}
Also used : ErrorResponseBodyImpl(com.nike.backstopper.model.riposte.ErrorResponseBodyImpl) DefaultErrorContractDTO(com.nike.backstopper.model.DefaultErrorContractDTO) ErrorResponseBody(com.nike.riposte.server.error.handler.ErrorResponseBody) Test(org.junit.Test)

Example 3 with ErrorResponseBodyImpl

use of com.nike.backstopper.model.riposte.ErrorResponseBodyImpl in project riposte by Nike-Inc.

the class RiposteApiExceptionHandlerTest method prepareFrameworkResponseUsesErrorResponseBodyNettyAdapterWrapper.

@Test
public void prepareFrameworkResponseUsesErrorResponseBodyNettyAdapterWrapper() {
    RiposteApiExceptionHandler myAdapter = new RiposteApiExceptionHandler(projectApiErrors, validListenerList, utils);
    DefaultErrorContractDTO errorContract = new DefaultErrorContractDTO(UUID.randomUUID().toString(), Arrays.asList(projectApiErrors.getUnauthorizedApiError(), projectApiErrors.getMalformedRequestApiError()));
    ErrorResponseBody result = myAdapter.prepareFrameworkRepresentation(errorContract, 42, null, null, null);
    assertThat(result, instanceOf(ErrorResponseBodyImpl.class));
    ErrorResponseBodyImpl adapterResult = (ErrorResponseBodyImpl) result;
    assertThat(adapterResult.error_id, is(errorContract.error_id));
    assertThat(adapterResult.errors, is(errorContract.errors));
}
Also used : ErrorResponseBodyImpl(com.nike.backstopper.model.riposte.ErrorResponseBodyImpl) DefaultErrorContractDTO(com.nike.backstopper.model.DefaultErrorContractDTO) ErrorResponseBody(com.nike.riposte.server.error.handler.ErrorResponseBody) Test(org.junit.Test)

Example 4 with ErrorResponseBodyImpl

use of com.nike.backstopper.model.riposte.ErrorResponseBodyImpl 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)

Aggregations

ErrorResponseBodyImpl (com.nike.backstopper.model.riposte.ErrorResponseBodyImpl)4 Test (org.junit.Test)3 DefaultErrorContractDTO (com.nike.backstopper.model.DefaultErrorContractDTO)2 ErrorResponseBody (com.nike.riposte.server.error.handler.ErrorResponseBody)2 HttpProcessingState (com.nike.riposte.server.http.HttpProcessingState)2 JsonProcessingException (com.fasterxml.jackson.core.JsonProcessingException)1 DefaultErrorDTO (com.nike.backstopper.model.DefaultErrorDTO)1 Endpoint (com.nike.riposte.server.http.Endpoint)1 ProxyRouterProcessingState (com.nike.riposte.server.http.ProxyRouterProcessingState)1 ResponseInfo (com.nike.riposte.server.http.ResponseInfo)1 Span (com.nike.wingtips.Span)1 DataProvider (com.tngtech.java.junit.dataprovider.DataProvider)1 UseDataProvider (com.tngtech.java.junit.dataprovider.UseDataProvider)1 ChannelHandlerContext (io.netty.channel.ChannelHandlerContext)1 DefaultHttpHeaders (io.netty.handler.codec.http.DefaultHttpHeaders)1 ArrayList (java.util.ArrayList)1 AtomicInteger (java.util.concurrent.atomic.AtomicInteger)1 Assertions.catchThrowable (org.assertj.core.api.Assertions.catchThrowable)1