Search in sources :

Example 1 with ResponseInfo

use of com.nike.riposte.server.http.ResponseInfo in project riposte by Nike-Inc.

the class NonblockingEndpointExecutionHandler method doChannelRead.

@Override
public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) {
    HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
    Endpoint<?> endpoint = state.getEndpointForExecution();
    if (shouldHandleDoChannelReadMessage(msg, endpoint)) {
        // We only do something when the last chunk of content has arrived.
        if (msg instanceof LastHttpContent) {
            NonblockingEndpoint nonblockingEndpoint = ((NonblockingEndpoint) endpoint);
            // We're supposed to execute the endpoint. There may be pre-endpoint-execution validation logic or
            // other work that needs to happen before the endpoint is executed, so set up the
            // CompletableFuture for the endpoint call to only execute if the pre-endpoint-execution
            // validation/work chain is successful.
            RequestInfo<?> requestInfo = state.getRequestInfo();
            Span endpointExecutionSpan = findEndpointExecutionSpan(state);
            CompletableFuture<ResponseInfo<?>> responseFuture = state.getPreEndpointExecutionWorkChain().thenCompose(doExecuteEndpointFunction(requestInfo, nonblockingEndpoint, endpointExecutionSpan, ctx));
            // Register an on-completion callback so we can be notified when the CompletableFuture finishes.
            responseFuture.whenComplete((responseInfo, throwable) -> {
                // full details on why this needs to be done here.
                if (endpointExecutionSpan != null && spanTaggingStrategy.shouldAddEndpointFinishAnnotation()) {
                    addEndpointFinishAnnotation(endpointExecutionSpan, spanTaggingStrategy);
                }
                // Kick off the response processing, depending on whether the result is an error or not.
                if (throwable != null)
                    asyncErrorCallback(ctx, throwable);
                else
                    asyncCallback(ctx, responseInfo);
            });
            // TODO: We might be able to put the timeout future in an if block in the case that the endpoint
            // returned an already-completed future (i.e. if responseFuture.isDone() returns true at this
            // point).
            // Also schedule a timeout check with our Netty event loop to make sure we kill the
            // CompletableFuture if it goes on too long.
            Long endpointTimeoutOverride = nonblockingEndpoint.completableFutureTimeoutOverrideMillis();
            long timeoutValueToUse = (endpointTimeoutOverride == null) ? defaultCompletableFutureTimeoutMillis : endpointTimeoutOverride;
            ScheduledFuture<?> responseTimeoutScheduledFuture = ctx.channel().eventLoop().schedule(() -> {
                if (!responseFuture.isDone()) {
                    runnableWithTracingAndMdc(() -> logger.error("A non-blocking endpoint's CompletableFuture did not finish within " + "the allotted timeout ({} milliseconds). Forcibly cancelling it.", timeoutValueToUse), ctx).run();
                    @SuppressWarnings("unchecked") Throwable errorToUse = nonblockingEndpoint.getCustomTimeoutExceptionCause(requestInfo, ctx);
                    if (errorToUse == null)
                        errorToUse = new NonblockingEndpointCompletableFutureTimedOut(timeoutValueToUse);
                    responseFuture.completeExceptionally(errorToUse);
                }
            }, timeoutValueToUse, TimeUnit.MILLISECONDS);
            /*
                    The problem with the scheduled timeout check is that it holds on to the RequestInfo,
                    ChannelHandlerContext, and a bunch of other stuff that *should* become garbage the instant the
                    request finishes, but because of the timeout check it has to wait until the check executes
                    before the garbage is collectible. In high volume servers the default 60 second timeout is way
                    too long and acts like a memory leak and results in garbage collection thrashing if the
                    available memory can be filled within the 60 second timeout. To combat this we cancel the
                    timeout future when the endpoint future finishes. Netty will remove the cancelled timeout future
                    from its scheduled list within a short time, thus letting the garbage be collected.
                */
            responseFuture.whenComplete((responseInfo, throwable) -> {
                if (!responseTimeoutScheduledFuture.isDone())
                    responseTimeoutScheduledFuture.cancel(false);
            });
        }
        // completes (see asyncCallback() and asyncErrorCallback()).
        return PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT;
    }
    // error to be returned to the client.
    return PipelineContinuationBehavior.CONTINUE;
}
Also used : ResponseInfo(com.nike.riposte.server.http.ResponseInfo) LastOutboundMessageSendFullResponseInfo(com.nike.riposte.server.channelpipeline.message.LastOutboundMessageSendFullResponseInfo) HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) LastHttpContent(io.netty.handler.codec.http.LastHttpContent) Span(com.nike.wingtips.Span) NonblockingEndpointCompletableFutureTimedOut(com.nike.riposte.server.error.exception.NonblockingEndpointCompletableFutureTimedOut) NonblockingEndpoint(com.nike.riposte.server.http.NonblockingEndpoint)

Example 2 with ResponseInfo

use of com.nike.riposte.server.http.ResponseInfo in project riposte by Nike-Inc.

the class RequestFilterHandler method doChannelRead.

@Override
public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) {
    if (msg instanceof HttpRequest) {
        HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
        handlerUtils.createRequestInfoFromNettyHttpRequestAndHandleStateSetupIfNecessary((HttpRequest) msg, state);
        // If the Netty HttpRequest is invalid, we shouldn't process any of the filters.
        handlerUtils.throwExceptionIfNotSuccessfullyDecoded((HttpRequest) msg);
        // The HttpRequest is valid, so process the filters.
        BiFunction<RequestAndResponseFilter, RequestInfo, RequestInfo> normalFilterCall = (filter, request) -> filter.filterRequestFirstChunkNoPayload(request, ctx);
        BiFunction<RequestAndResponseFilter, RequestInfo, Pair<RequestInfo, Optional<ResponseInfo<?>>>> shortCircuitFilterCall = (filter, request) -> filter.filterRequestFirstChunkWithOptionalShortCircuitResponse(request, ctx);
        return handleFilterLogic(ctx, msg, state, normalFilterCall, shortCircuitFilterCall);
    }
    if (msg instanceof LastHttpContent) {
        HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
        BiFunction<RequestAndResponseFilter, RequestInfo, RequestInfo> normalFilterCall = (filter, request) -> filter.filterRequestLastChunkWithFullPayload(request, ctx);
        BiFunction<RequestAndResponseFilter, RequestInfo, Pair<RequestInfo, Optional<ResponseInfo<?>>>> shortCircuitFilterCall = (filter, request) -> filter.filterRequestLastChunkWithOptionalShortCircuitResponse(request, ctx);
        return handleFilterLogic(ctx, msg, state, normalFilterCall, shortCircuitFilterCall);
    }
    // Not the first or last chunk. No filters were executed, so continue normally.
    return PipelineContinuationBehavior.CONTINUE;
}
Also used : HttpRequest(io.netty.handler.codec.http.HttpRequest) HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) RequestInfo(com.nike.riposte.server.http.RequestInfo) Logger(org.slf4j.Logger) HttpRequest(io.netty.handler.codec.http.HttpRequest) RequestAndResponseFilter(com.nike.riposte.server.http.filter.RequestAndResponseFilter) BiFunction(java.util.function.BiFunction) LoggerFactory(org.slf4j.LoggerFactory) ResponseInfo(com.nike.riposte.server.http.ResponseInfo) PipelineContinuationBehavior(com.nike.riposte.server.handler.base.PipelineContinuationBehavior) LastHttpContent(io.netty.handler.codec.http.LastHttpContent) BaseInboundHandlerWithTracingAndMdcSupport(com.nike.riposte.server.handler.base.BaseInboundHandlerWithTracingAndMdcSupport) ChannelHandlerContext(io.netty.channel.ChannelHandlerContext) List(java.util.List) ChannelAttributes(com.nike.riposte.server.channelpipeline.ChannelAttributes) LastOutboundMessageSendFullResponseInfo(com.nike.riposte.server.channelpipeline.message.LastOutboundMessageSendFullResponseInfo) Optional(java.util.Optional) ChannelHandler(io.netty.channel.ChannelHandler) Pair(com.nike.internal.util.Pair) Collections(java.util.Collections) ResponseInfo(com.nike.riposte.server.http.ResponseInfo) LastOutboundMessageSendFullResponseInfo(com.nike.riposte.server.channelpipeline.message.LastOutboundMessageSendFullResponseInfo) RequestAndResponseFilter(com.nike.riposte.server.http.filter.RequestAndResponseFilter) HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) RequestInfo(com.nike.riposte.server.http.RequestInfo) LastHttpContent(io.netty.handler.codec.http.LastHttpContent) Pair(com.nike.internal.util.Pair)

Example 3 with ResponseInfo

use of com.nike.riposte.server.http.ResponseInfo in project riposte by Nike-Inc.

the class RequestAndResponseFilterTest method default_method_implementations_behave_as_expected.

@Test
public void default_method_implementations_behave_as_expected() {
    // given
    RequestAndResponseFilter defaultImpl = new RequestAndResponseFilter() {

        @Override
        @Nullable
        public <T> RequestInfo<T> filterRequestFirstChunkNoPayload(@NotNull RequestInfo<T> currentRequestInfo, @NotNull ChannelHandlerContext ctx) {
            return null;
        }

        @Override
        @Nullable
        public <T> RequestInfo<T> filterRequestLastChunkWithFullPayload(@NotNull RequestInfo<T> currentRequestInfo, @NotNull ChannelHandlerContext ctx) {
            return null;
        }

        @Override
        @Nullable
        public <T> ResponseInfo<T> filterResponse(@NotNull ResponseInfo<T> currentResponseInfo, @NotNull RequestInfo<?> requestInfo, @NotNull ChannelHandlerContext ctx) {
            return null;
        }
    };
    // expect
    assertThat(defaultImpl.isShortCircuitRequestFilter()).isFalse();
    Throwable firstChunkShortCircuitEx = catchThrowable(() -> defaultImpl.filterRequestFirstChunkWithOptionalShortCircuitResponse(mock(RequestInfo.class), mock(ChannelHandlerContext.class)));
    assertThat(firstChunkShortCircuitEx).isNotNull().isInstanceOf(UnsupportedOperationException.class);
    Throwable lastChunkShortCircuitEx = catchThrowable(() -> defaultImpl.filterRequestLastChunkWithOptionalShortCircuitResponse(mock(RequestInfo.class), mock(ChannelHandlerContext.class)));
    assertThat(lastChunkShortCircuitEx).isNotNull().isInstanceOf(UnsupportedOperationException.class);
    assertThat(defaultImpl.shouldExecuteBeforeSecurityValidation()).isTrue();
}
Also used : ResponseInfo(com.nike.riposte.server.http.ResponseInfo) Assertions.catchThrowable(org.assertj.core.api.Assertions.catchThrowable) ChannelHandlerContext(io.netty.channel.ChannelHandlerContext) RequestInfo(com.nike.riposte.server.http.RequestInfo) NotNull(org.jetbrains.annotations.NotNull) Test(org.junit.Test)

Example 4 with ResponseInfo

use of com.nike.riposte.server.http.ResponseInfo in project riposte by Nike-Inc.

the class ShortCircuitingRequestAndResponseFilterTest method default_method_implementations_behave_as_expected.

@Test
public void default_method_implementations_behave_as_expected() {
    // given
    ShortCircuitingRequestAndResponseFilter defaultImpl = new ShortCircuitingRequestAndResponseFilter() {

        @Override
        @Nullable
        public <T> ResponseInfo<T> filterResponse(@NotNull ResponseInfo<T> currentResponseInfo, @NotNull RequestInfo<?> requestInfo, @NotNull ChannelHandlerContext ctx) {
            return null;
        }

        @Override
        @Nullable
        public <T> Pair<RequestInfo<T>, Optional<ResponseInfo<?>>> filterRequestFirstChunkWithOptionalShortCircuitResponse(@NotNull RequestInfo<T> currentRequestInfo, @NotNull ChannelHandlerContext ctx) {
            return null;
        }

        @Override
        @Nullable
        public <T> Pair<RequestInfo<T>, Optional<ResponseInfo<?>>> filterRequestLastChunkWithOptionalShortCircuitResponse(@NotNull RequestInfo<T> currentRequestInfo, @NotNull ChannelHandlerContext ctx) {
            return null;
        }
    };
    // expect
    assertThat(defaultImpl.isShortCircuitRequestFilter()).isTrue();
    Throwable firstChunkEx = catchThrowable(() -> defaultImpl.filterRequestFirstChunkNoPayload(mock(RequestInfo.class), mock(ChannelHandlerContext.class)));
    assertThat(firstChunkEx).isNotNull().isInstanceOf(UnsupportedOperationException.class);
    Throwable lastChunkEx = catchThrowable(() -> defaultImpl.filterRequestLastChunkWithFullPayload(mock(RequestInfo.class), mock(ChannelHandlerContext.class)));
    assertThat(lastChunkEx).isNotNull().isInstanceOf(UnsupportedOperationException.class);
}
Also used : ResponseInfo(com.nike.riposte.server.http.ResponseInfo) Optional(java.util.Optional) Assertions.catchThrowable(org.assertj.core.api.Assertions.catchThrowable) ChannelHandlerContext(io.netty.channel.ChannelHandlerContext) RequestInfo(com.nike.riposte.server.http.RequestInfo) NotNull(org.jetbrains.annotations.NotNull) Test(org.junit.Test)

Example 5 with ResponseInfo

use of com.nike.riposte.server.http.ResponseInfo in project riposte by Nike-Inc.

the class ExceptionHandlingHandlerTest method processUnhandledError_uses_getRequestInfo_and_calls_riposteUnhandledErrorHandler_and_returns_value_of_setupResponseInfoBasedOnErrorResponseInfo.

@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));
}
Also used : ResponseInfo(com.nike.riposte.server.http.ResponseInfo) ErrorResponseInfo(com.nike.riposte.server.error.handler.ErrorResponseInfo) FullResponseInfo(com.nike.riposte.server.http.impl.FullResponseInfo) ErrorResponseInfo(com.nike.riposte.server.error.handler.ErrorResponseInfo) HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) Assertions.catchThrowable(org.assertj.core.api.Assertions.catchThrowable) ErrorResponseBody(com.nike.riposte.server.error.handler.ErrorResponseBody) IncompleteHttpCallTimeoutException(com.nike.riposte.server.error.exception.IncompleteHttpCallTimeoutException) TooManyOpenChannelsException(com.nike.riposte.server.error.exception.TooManyOpenChannelsException) InvalidRipostePipelineException(com.nike.riposte.server.error.exception.InvalidRipostePipelineException) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException) Test(org.junit.Test)

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