Search in sources :

Example 1 with DefaultErrorDTO

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

the class ErrorResponseBodyImplTest method verifyAdapter.

private void verifyAdapter(ErrorResponseBodyImpl adapter, String expectedErrorId, List<ApiError> expectedErrors) {
    assertThat(adapter.error_id, is(expectedErrorId));
    assertThat(adapter.errorId(), is(expectedErrorId));
    assertThat(adapter.errors.size(), is(expectedErrors.size()));
    for (int i = 0; i < expectedErrors.size(); i++) {
        ApiError apiError = expectedErrors.get(i);
        DefaultErrorDTO errorView = adapter.errors.get(i);
        assertThat(errorView.code, is(apiError.getErrorCode()));
        assertThat(errorView.message, is(apiError.getMessage()));
    }
}
Also used : DefaultErrorDTO(com.nike.backstopper.model.DefaultErrorDTO) ApiError(com.nike.backstopper.apierror.ApiError)

Example 2 with DefaultErrorDTO

use of com.nike.backstopper.model.DefaultErrorDTO 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 3 with DefaultErrorDTO

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

the class VerifyTimeoutsAndProxyConnectionPoolingWorksComponentTest method verify_incomplete_call_is_timed_out.

@Test
public void verify_incomplete_call_is_timed_out() throws InterruptedException, TimeoutException, ExecutionException, IOException {
    Bootstrap bootstrap = new Bootstrap();
    EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
    try {
        CompletableFuture<Pair<String, String>> responseFromServer = new CompletableFuture<>();
        // Create a raw netty HTTP client so we can fiddle with headers and intentionally create a bad request
        // that should trigger the bad call timeout.
        bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline p = ch.pipeline();
                p.addLast(new HttpClientCodec());
                p.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
                p.addLast(new SimpleChannelInboundHandler<HttpObject>() {

                    @Override
                    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
                        if (msg instanceof FullHttpResponse) {
                            // Store the server response for asserting on later.
                            FullHttpResponse responseMsg = (FullHttpResponse) msg;
                            responseFromServer.complete(Pair.of(responseMsg.content().toString(CharsetUtil.UTF_8), responseMsg.headers().get(HttpHeaders.Names.CONNECTION)));
                        } else {
                            // Should never happen.
                            throw new RuntimeException("Received unexpected message type: " + msg.getClass());
                        }
                    }
                });
            }
        });
        // Connect to the server.
        Channel ch = bootstrap.connect("localhost", downstreamServerConfig.endpointsPort()).sync().channel();
        // Create a bad HTTP request. This one will be bad because it has a non-zero content-length header,
        // but we're sending no payload. The server should (correctly) sit and wait for payload bytes to
        // arrive until it hits the timeout, at which point it should return the correct error response.
        HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, LongDelayTestEndpoint.MATCHING_PATH);
        request.headers().set(HttpHeaders.Names.HOST, "localhost");
        request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, "100");
        long beforeCallTimeNanos = System.nanoTime();
        // Send the bad request.
        ch.writeAndFlush(request);
        // Wait for the response to be received and the connection to be closed.
        try {
            ch.closeFuture().get(incompleteCallTimeoutMillis * 10, TimeUnit.MILLISECONDS);
            responseFromServer.get(incompleteCallTimeoutMillis * 10, TimeUnit.MILLISECONDS);
        } catch (TimeoutException ex) {
            fail("The call took much longer than expected without receiving a response. " + "Cancelling this test - it's not working properly", ex);
        }
        // If we reach here then the call should be complete.
        long totalCallTimeMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - beforeCallTimeNanos);
        // Verify that we got back the correct error response.
        // It should be a MALFORMED_REQUEST with extra metadata explaining that the call was bad.
        Pair<String, String> responseInfo = responseFromServer.get();
        DefaultErrorContractDTO errorContract = objectMapper.readValue(responseInfo.getLeft(), DefaultErrorContractDTO.class);
        assertThat(errorContract).isNotNull();
        assertThat(errorContract.errors.size()).isEqualTo(1);
        DefaultErrorDTO error = errorContract.errors.get(0);
        ApiError expectedApiError = SampleCoreApiError.MALFORMED_REQUEST;
        Map<String, Object> expectedMetadata = MapBuilder.builder("cause", (Object) "Unfinished/invalid HTTP request").build();
        assertThat(error.code).isEqualTo(expectedApiError.getErrorCode());
        assertThat(error.message).isEqualTo(expectedApiError.getMessage());
        assertThat(error.metadata).isEqualTo(expectedMetadata);
        // The server should have closed the connection even though we asked for keep-alive.
        assertThat(responseInfo.getRight()).isEqualTo(HttpHeaders.Values.CLOSE);
        // Total call time should be pretty close to incompleteCallTimeoutMillis give or take a few
        // milliseconds, but due to the inability to account for slow machines running the unit tests,
        // a server that isn't warmed up, etc, we can't put a ceiling on the wiggle room we'd need, so
        // we'll just verify it took at least the minimum necessary amount of time.
        assertThat(totalCallTimeMillis).isGreaterThanOrEqualTo(incompleteCallTimeoutMillis);
    } finally {
        eventLoopGroup.shutdownGracefully();
    }
}
Also used : SocketChannel(io.netty.channel.socket.SocketChannel) NioSocketChannel(io.netty.channel.socket.nio.NioSocketChannel) ChannelHandlerContext(io.netty.channel.ChannelHandlerContext) HttpClientCodec(io.netty.handler.codec.http.HttpClientCodec) CompletableFuture(java.util.concurrent.CompletableFuture) HttpObject(io.netty.handler.codec.http.HttpObject) Bootstrap(io.netty.bootstrap.Bootstrap) DefaultErrorDTO(com.nike.backstopper.model.DefaultErrorDTO) FullHttpResponse(io.netty.handler.codec.http.FullHttpResponse) NioEventLoopGroup(io.netty.channel.nio.NioEventLoopGroup) Pair(com.nike.internal.util.Pair) TimeoutException(java.util.concurrent.TimeoutException) SimpleChannelInboundHandler(io.netty.channel.SimpleChannelInboundHandler) DefaultFullHttpRequest(io.netty.handler.codec.http.DefaultFullHttpRequest) HttpRequest(io.netty.handler.codec.http.HttpRequest) DefaultFullHttpRequest(io.netty.handler.codec.http.DefaultFullHttpRequest) DefaultErrorContractDTO(com.nike.backstopper.model.DefaultErrorContractDTO) SocketChannel(io.netty.channel.socket.SocketChannel) NioSocketChannel(io.netty.channel.socket.nio.NioSocketChannel) Channel(io.netty.channel.Channel) TimeoutException(java.util.concurrent.TimeoutException) IOException(java.io.IOException) ExecutionException(java.util.concurrent.ExecutionException) ChannelPipeline(io.netty.channel.ChannelPipeline) NioSocketChannel(io.netty.channel.socket.nio.NioSocketChannel) HttpObjectAggregator(io.netty.handler.codec.http.HttpObjectAggregator) NioEventLoopGroup(io.netty.channel.nio.NioEventLoopGroup) EventLoopGroup(io.netty.channel.EventLoopGroup) HttpObject(io.netty.handler.codec.http.HttpObject) ApiError(com.nike.backstopper.apierror.ApiError) SampleCoreApiError(com.nike.backstopper.apierror.sample.SampleCoreApiError) Test(org.junit.Test)

Aggregations

DefaultErrorDTO (com.nike.backstopper.model.DefaultErrorDTO)3 ApiError (com.nike.backstopper.apierror.ApiError)2 ChannelHandlerContext (io.netty.channel.ChannelHandlerContext)2 Test (org.junit.Test)2 SampleCoreApiError (com.nike.backstopper.apierror.sample.SampleCoreApiError)1 DefaultErrorContractDTO (com.nike.backstopper.model.DefaultErrorContractDTO)1 ErrorResponseBodyImpl (com.nike.backstopper.model.riposte.ErrorResponseBodyImpl)1 Pair (com.nike.internal.util.Pair)1 Endpoint (com.nike.riposte.server.http.Endpoint)1 HttpProcessingState (com.nike.riposte.server.http.HttpProcessingState)1 ResponseInfo (com.nike.riposte.server.http.ResponseInfo)1 DataProvider (com.tngtech.java.junit.dataprovider.DataProvider)1 UseDataProvider (com.tngtech.java.junit.dataprovider.UseDataProvider)1 Bootstrap (io.netty.bootstrap.Bootstrap)1 Channel (io.netty.channel.Channel)1 ChannelPipeline (io.netty.channel.ChannelPipeline)1 EventLoopGroup (io.netty.channel.EventLoopGroup)1 SimpleChannelInboundHandler (io.netty.channel.SimpleChannelInboundHandler)1 NioEventLoopGroup (io.netty.channel.nio.NioEventLoopGroup)1 SocketChannel (io.netty.channel.socket.SocketChannel)1