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;
}
}
}
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));
}
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));
}
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();
}
Aggregations