@Test public void doChannelRead_should_return_CONTINUE_and_do_nothing_else_if_request_has_been_handled() throws Exception { // given ExceptionHandlingHandler handlerSpy = spy(handler); ResponseInfo<?> responseInfoMock = mock(ResponseInfo.class); Object msg = new Object(); state.setResponseInfo(responseInfoMock, null); // when PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, msg); // then verify(handlerSpy, times(0)).processUnhandledError(any(HttpProcessingState.class), any(Object.class), any(Throwable.class)); assertThat(state.getResponseInfo(), is(responseInfoMock)); assertThat(result, is(PipelineContinuationBehavior.CONTINUE)); }
@Test public void processError_returns_value_of_processUnhandledError_if_riposteErrorHandler_explodes() throws UnexpectedMajorErrorHandlingError, JsonProcessingException { // given HttpProcessingState stateMock = mock(HttpProcessingState.class); Object msg = new Object(); Throwable cause = new Exception(); ExceptionHandlingHandler handlerSpy = spy(handler); ResponseInfo<ErrorResponseBody> responseInfoMockFromCatchallMethod = mock(ResponseInfo.class); doThrow(new RuntimeException()).when(riposteErrorHandlerMock).maybeHandleError(any(), any()); doReturn(responseInfoMockFromCatchallMethod).when(handlerSpy).processUnhandledError(stateMock, msg, cause); // when ResponseInfo<ErrorResponseBody> response = handlerSpy.processError(stateMock, msg, cause); // then verify(riposteErrorHandlerMock).maybeHandleError(any(), any()); assertThat(response, is(responseInfoMockFromCatchallMethod)); }
@Test public void processError_returns_value_of_processUnhandledError_if_riposteErrorHandler_returns_null() throws UnexpectedMajorErrorHandlingError, JsonProcessingException { // given HttpProcessingState stateMock = mock(HttpProcessingState.class); Object msg = new Object(); Throwable cause = new Exception(); ExceptionHandlingHandler handlerSpy = spy(handler); ResponseInfo<ErrorResponseBody> responseInfoMockFromCatchallMethod = mock(ResponseInfo.class); doReturn(null).when(riposteErrorHandlerMock).maybeHandleError(any(), any()); doReturn(responseInfoMockFromCatchallMethod).when(handlerSpy).processUnhandledError(stateMock, msg, cause); // when ResponseInfo<ErrorResponseBody> response = handlerSpy.processError(stateMock, msg, cause); // then verify(riposteErrorHandlerMock).maybeHandleError(any(), any()); assertThat(response, is(responseInfoMockFromCatchallMethod)); }
@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; }
/** * Attempts to process the given error using the "normal" error handler {@link #riposteErrorHandler} to produce the * most specific error response possible for the given error. If that fails for any reason then the unhandled error * handler will take over to guarantee the user gets a generic error response that still follows our error contract. * If you already know your error is a non-normal unhandled error of the "how did we get here, this should never * happen" variety you can (and should) directly call {@link #processUnhandledError(HttpProcessingState, Object, * Throwable)} instead. */ protected ResponseInfo<ErrorResponseBody> processError(HttpProcessingState state, Object msg, Throwable cause) { RequestInfo<?> requestInfo = getRequestInfo(state, msg); try { ErrorResponseInfo contentFromErrorHandler = riposteErrorHandler.maybeHandleError(cause, requestInfo); if (contentFromErrorHandler != null) { // The regular error handler did handle the error. Setup our ResponseInfo. ResponseInfo<ErrorResponseBody> responseInfo = new FullResponseInfo<>(); setupResponseInfoBasedOnErrorResponseInfo(responseInfo, contentFromErrorHandler); return responseInfo; } } catch (Throwable errorHandlerFailed) { logger.error("An unexpected problem occurred while trying to handle an error.", errorHandlerFailed); } // If we reach here then it means the regular handler didn't handle the error (or blew up trying to handle it), // so the riposteUnhandledErrorHandler should take care of it. return processUnhandledError(state, msg, cause); }
/** * Attempts to process the given error using the "normal" error handler {@link #riposteErrorHandler} to produce the * most specific error response possible for the given error. If that fails for any reason then the unhandled error * handler will take over to guarantee the user gets a generic error response that still follows our error contract. * If you already know your error is a non-normal unhandled error of the "how did we get here, this should never * happen" variety you can (and should) directly call {@link #processUnhandledError(HttpProcessingState, Object, * Throwable)} instead. */ protected ResponseInfo<ErrorResponseBody> processError(HttpProcessingState state, Object msg, Throwable cause) { RequestInfo<?> requestInfo = getRequestInfo(state, msg); try { ErrorResponseInfo contentFromErrorHandler = riposteErrorHandler.maybeHandleError(cause, requestInfo); if (contentFromErrorHandler != null) { // The regular error handler did handle the error. Setup our ResponseInfo. ResponseInfo<ErrorResponseBody> responseInfo = new FullResponseInfo<>(); setupResponseInfoBasedOnErrorResponseInfo(responseInfo, contentFromErrorHandler); return responseInfo; } } catch (Throwable errorHandlerFailed) { logger.error("An unexpected problem occurred while trying to handle an error.", errorHandlerFailed); } // If we reach here then it means the regular handler didn't handle the error (or blew up trying to handle it), // so the riposteUnhandledErrorHandler should take care of it. return processUnhandledError(state, msg, cause); }
@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 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)); }
@Test public void processUnhandledError_uses_getRequestInfo_and_calls_riposteUnhandledErrorHandler_and_returns_value_of_setupResponseInfoBasedOnErrorResponseInfo() throws JsonProcessingException, UnexpectedMajorErrorHandlingError { // given HttpProcessingState stateMock = mock(HttpProcessingState.class); Object msg = new Object(); Throwable cause = new Exception(); ExceptionHandlingHandler handlerSpy = spy(handler); RequestInfo<?> requestInfoMock = mock(RequestInfo.class); ErrorResponseInfo errorResponseInfoMock = mock(ErrorResponseInfo.class); doReturn(requestInfoMock).when(handlerSpy).getRequestInfo(stateMock, msg); doReturn(errorResponseInfoMock).when(riposteUnhandledErrorHandlerMock).handleError(cause, requestInfoMock); // when ResponseInfo<ErrorResponseBody> response = handlerSpy.processUnhandledError(stateMock, msg, cause); // then verify(handlerSpy).getRequestInfo(stateMock, msg); verify(riposteUnhandledErrorHandlerMock).handleError(cause, requestInfoMock); ArgumentCaptor<ResponseInfo> responseInfoArgumentCaptor = ArgumentCaptor.forClass(ResponseInfo.class); verify(handlerSpy).setupResponseInfoBasedOnErrorResponseInfo(responseInfoArgumentCaptor.capture(), eq(errorResponseInfoMock)); ResponseInfo<ErrorResponseBody> responseInfoPassedIntoSetupMethod = responseInfoArgumentCaptor.getValue(); assertThat(response, is(responseInfoPassedIntoSetupMethod)); }
@Test public void finalizeChannelPipeline_should_send_error_response_if_state_indicates_no_response_already_sent() throws JsonProcessingException { // given state.setResponseWriterFinalChunkChannelFuture(null); HttpProcessingState stateSpy = spy(state); doReturn(stateSpy).when(stateAttributeMock).get(); doReturn(false).when(responseInfoMock).isResponseSendingStarted(); Object msg = new Object(); Throwable cause = new Exception("intentional test exception"); RequestInfo<?> requestInfoMock = mock(RequestInfo.class); ResponseInfo<ErrorResponseBody> errorResponseMock = mock(ResponseInfo.class); doReturn(requestInfoMock).when(exceptionHandlingHandlerMock).getRequestInfo(stateSpy, msg); doReturn(errorResponseMock).when(exceptionHandlingHandlerMock).processUnhandledError(eq(stateSpy), eq(msg), any(Throwable.class)); // when handler.finalizeChannelPipeline(ctxMock, msg, stateSpy, cause); // then verify(responseSenderMock).sendErrorResponse(ctxMock, requestInfoMock, errorResponseMock); verify(metricsListenerMock).onEvent(ServerMetricsEvent.RESPONSE_WRITE_FAILED, stateSpy); verify(ctxMock).flush(); }
exceptionHandlingHandler.processUnhandledError(state, msg, exceptionToUse); responseSender.sendErrorResponse(ctx, requestInfo, responseInfo);
exceptionHandlingHandler.processUnhandledError(state, msg, exceptionToUse); responseSender.sendErrorResponse(ctx, requestInfo, responseInfo);