Search in sources :

Example 1 with StreamingChannel

use of com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingChannel in project riposte by Nike-Inc.

the class ProxyRouterEndpointExecutionHandler method doChannelRead.

@Override
public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
    Endpoint<?> endpoint = state.getEndpointForExecution();
    if (shouldHandleDoChannelReadMessage(msg, endpoint)) {
        ProxyRouterProcessingState proxyRouterState = getOrCreateProxyRouterProcessingState(ctx);
        ProxyRouterEndpoint endpointProxyRouter = ((ProxyRouterEndpoint) endpoint);
        RequestInfo<?> requestInfo = state.getRequestInfo();
        if (msg instanceof HttpRequest) {
            if (requestInfo instanceof RiposteInternalRequestInfo) {
                // Tell this RequestInfo that we'll be managing the release of content chunks, so that when
                //      RequestInfo.releaseAllResources() is called we don't have extra reference count removals.
                ((RiposteInternalRequestInfo) requestInfo).contentChunksWillBeReleasedExternally();
            }
            // We're supposed to start streaming. 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.
            CompletableFuture<DownstreamRequestFirstChunkInfo> firstChunkFuture = state.getPreEndpointExecutionWorkChain().thenCompose(functionWithTracingAndMdc(aVoid -> endpointProxyRouter.getDownstreamRequestFirstChunkInfo(requestInfo, longRunningTaskExecutor, ctx), ctx));
            Long endpointTimeoutOverride = endpointProxyRouter.completableFutureTimeoutOverrideMillis();
            long callTimeoutValueToUse = (endpointTimeoutOverride == null) ? defaultCompletableFutureTimeoutMillis : endpointTimeoutOverride;
            // When the first chunk is ready, stream it downstream and set up what happens afterward.
            firstChunkFuture.whenComplete((downstreamRequestFirstChunkInfo, throwable) -> {
                Optional<ManualModeTask<HttpResponse>> circuitBreakerManualTask = getCircuitBreaker(downstreamRequestFirstChunkInfo, ctx).map(CircuitBreaker::newManualModeTask);
                StreamingCallback callback = new StreamingCallbackForCtx(ctx, circuitBreakerManualTask, endpointProxyRouter, requestInfo, proxyRouterState);
                if (throwable != null) {
                    // Something blew up trying to determine the first chunk info.
                    callback.unrecoverableErrorOccurred(throwable);
                } else if (!ctx.channel().isOpen()) {
                    // The channel was closed for some reason before we were able to start streaming.
                    String errorMsg = "The channel from the original caller was closed before we could begin the " + "downstream call.";
                    Exception channelClosedException = new RuntimeException(errorMsg);
                    runnableWithTracingAndMdc(() -> logger.warn(errorMsg), ctx).run();
                    callback.unrecoverableErrorOccurred(channelClosedException);
                } else {
                    try {
                        // Ok we have the first chunk info. Start by setting the downstream call info in the request
                        //      info (i.e. for access logs if desired)
                        requestInfo.addRequestAttribute(DOWNSTREAM_CALL_PATH_REQUEST_ATTR_KEY, HttpUtils.extractPath(downstreamRequestFirstChunkInfo.firstChunk.getUri()));
                        // Try our circuit breaker (if we have one).
                        Throwable circuitBreakerException = null;
                        try {
                            circuitBreakerManualTask.ifPresent(ManualModeTask::throwExceptionIfCircuitBreakerIsOpen);
                        } catch (Throwable t) {
                            circuitBreakerException = t;
                        }
                        if (circuitBreakerException == null) {
                            // No circuit breaker, or the breaker is closed. We can now stream the first chunk info.
                            String downstreamHost = downstreamRequestFirstChunkInfo.host;
                            int downstreamPort = downstreamRequestFirstChunkInfo.port;
                            HttpRequest downstreamRequestFirstChunk = downstreamRequestFirstChunkInfo.firstChunk;
                            boolean isSecureHttpsCall = downstreamRequestFirstChunkInfo.isHttps;
                            boolean relaxedHttpsValidation = downstreamRequestFirstChunkInfo.relaxedHttpsValidation;
                            // Tell the proxyRouterState about the streaming callback so that
                            //      callback.unrecoverableErrorOccurred(...) can be called in the case of an error
                            //      on subsequent chunks.
                            proxyRouterState.setStreamingCallback(callback);
                            // Setup the streaming channel future with everything it needs to kick off the
                            //      downstream request.
                            proxyRouterState.setStreamingStartTimeNanos(System.nanoTime());
                            CompletableFuture<StreamingChannel> streamingChannel = streamingAsyncHttpClient.streamDownstreamCall(downstreamHost, downstreamPort, downstreamRequestFirstChunk, isSecureHttpsCall, relaxedHttpsValidation, callback, callTimeoutValueToUse, ctx);
                            // Tell the streaming channel future what to do when it completes.
                            streamingChannel = streamingChannel.whenComplete((sc, cause) -> {
                                if (cause == null) {
                                    // Successfully connected and sent the first chunk. We can now safely let
                                    //      the remaining content chunks through for streaming.
                                    proxyRouterState.triggerChunkProcessing(sc);
                                } else {
                                    // Something blew up while connecting to the downstream server.
                                    callback.unrecoverableErrorOccurred(cause);
                                }
                            });
                            // Set the streaming channel future on the state so it can be connected to.
                            proxyRouterState.setStreamingChannelCompletableFuture(streamingChannel);
                        } else {
                            // Circuit breaker is tripped (or otherwise threw an unexpected exception). Immediately
                            //      short circuit the error back to the client.
                            callback.unrecoverableErrorOccurred(circuitBreakerException);
                        }
                    } catch (Throwable t) {
                        callback.unrecoverableErrorOccurred(t);
                    }
                }
            });
        } else if (msg instanceof HttpContent) {
            HttpContent msgContent = (HttpContent) msg;
            //      chunk-streaming behavior and subsequent cleanup for the given HttpContent.
            if (!releaseContentChunkIfStreamAlreadyFailed(msgContent, proxyRouterState)) {
                registerChunkStreamingAction(proxyRouterState, msgContent, ctx);
            }
        }
        return PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT;
    }
    return PipelineContinuationBehavior.CONTINUE;
}
Also used : HttpRequest(io.netty.handler.codec.http.HttpRequest) Span(com.nike.wingtips.Span) LoggerFactory(org.slf4j.LoggerFactory) ResponseInfo(com.nike.riposte.server.http.ResponseInfo) HttpObject(io.netty.handler.codec.http.HttpObject) ProxyRouterEndpoint(com.nike.riposte.server.http.ProxyRouterEndpoint) Map(java.util.Map) HttpRequest(io.netty.handler.codec.http.HttpRequest) CompletionException(java.util.concurrent.CompletionException) EventLoop(io.netty.channel.EventLoop) DownstreamRequestFirstChunkInfo(com.nike.riposte.server.http.ProxyRouterEndpoint.DownstreamRequestFirstChunkInfo) BaseInboundHandlerWithTracingAndMdcSupport(com.nike.riposte.server.handler.base.BaseInboundHandlerWithTracingAndMdcSupport) Endpoint(com.nike.riposte.server.http.Endpoint) HttpUtils(com.nike.riposte.util.HttpUtils) StreamingChannel(com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingChannel) ChannelAttributes(com.nike.riposte.server.channelpipeline.ChannelAttributes) CircuitBreaker(com.nike.fastbreak.CircuitBreaker) RiposteInternalRequestInfo(com.nike.riposte.server.http.impl.RiposteInternalRequestInfo) Optional(java.util.Optional) HttpResponse(io.netty.handler.codec.http.HttpResponse) StreamingCallback(com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingCallback) HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) EventExecutor(io.netty.util.concurrent.EventExecutor) RequestInfo(com.nike.riposte.server.http.RequestInfo) CircuitBreakerDelegate(com.nike.fastbreak.CircuitBreakerDelegate) ManualModeTask(com.nike.fastbreak.CircuitBreaker.ManualModeTask) CompletableFuture(java.util.concurrent.CompletableFuture) StreamingAsyncHttpClient(com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient) PipelineContinuationBehavior(com.nike.riposte.server.handler.base.PipelineContinuationBehavior) Deque(java.util.Deque) LastHttpContent(io.netty.handler.codec.http.LastHttpContent) AsyncNettyHelper(com.nike.riposte.util.AsyncNettyHelper) ChannelHandlerContext(io.netty.channel.ChannelHandlerContext) HttpContent(io.netty.handler.codec.http.HttpContent) Attribute(io.netty.util.Attribute) OutboundMessageSendHeadersChunkFromResponseInfo(com.nike.riposte.server.channelpipeline.message.OutboundMessageSendHeadersChunkFromResponseInfo) Logger(org.slf4j.Logger) Executor(java.util.concurrent.Executor) AsyncNettyHelper.executeOnlyIfChannelIsActive(com.nike.riposte.util.AsyncNettyHelper.executeOnlyIfChannelIsActive) CircuitBreakerForHttpStatusCode.getDefaultHttpStatusCodeCircuitBreakerForKey(com.nike.fastbreak.CircuitBreakerForHttpStatusCode.getDefaultHttpStatusCodeCircuitBreakerForKey) AsyncNettyHelper.functionWithTracingAndMdc(com.nike.riposte.util.AsyncNettyHelper.functionWithTracingAndMdc) ChannelFuture(io.netty.channel.ChannelFuture) ExecutionException(java.util.concurrent.ExecutionException) FullHttpResponse(io.netty.handler.codec.http.FullHttpResponse) WrapperException(com.nike.backstopper.exception.WrapperException) OutboundMessageSendContentChunk(com.nike.riposte.server.channelpipeline.message.OutboundMessageSendContentChunk) AsyncNettyHelper.runnableWithTracingAndMdc(com.nike.riposte.util.AsyncNettyHelper.runnableWithTracingAndMdc) LastOutboundMessageSendLastContentChunk(com.nike.riposte.server.channelpipeline.message.LastOutboundMessageSendLastContentChunk) Pair(com.nike.internal.util.Pair) ProxyRouterProcessingState(com.nike.riposte.server.http.ProxyRouterProcessingState) CircuitBreaker(com.nike.fastbreak.CircuitBreaker) StreamingCallback(com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingCallback) HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) ProxyRouterProcessingState(com.nike.riposte.server.http.ProxyRouterProcessingState) DownstreamRequestFirstChunkInfo(com.nike.riposte.server.http.ProxyRouterEndpoint.DownstreamRequestFirstChunkInfo) CompletionException(java.util.concurrent.CompletionException) ExecutionException(java.util.concurrent.ExecutionException) WrapperException(com.nike.backstopper.exception.WrapperException) CompletableFuture(java.util.concurrent.CompletableFuture) ManualModeTask(com.nike.fastbreak.CircuitBreaker.ManualModeTask) ProxyRouterEndpoint(com.nike.riposte.server.http.ProxyRouterEndpoint) RiposteInternalRequestInfo(com.nike.riposte.server.http.impl.RiposteInternalRequestInfo) LastHttpContent(io.netty.handler.codec.http.LastHttpContent) HttpContent(io.netty.handler.codec.http.HttpContent)

Example 2 with StreamingChannel

use of com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingChannel in project riposte by Nike-Inc.

the class ProxyRouterEndpointExecutionHandler method registerChunkStreamingAction.

protected void registerChunkStreamingAction(ProxyRouterProcessingState proxyRouterState, HttpContent msgContent, ChannelHandlerContext ctx) {
    // We have a content chunk to stream downstream. Attach the chunk processing to the proxyRouterState and
    //      tell it to stream itself when that future says everything is ready.
    proxyRouterState.registerStreamingChannelChunkProcessingAction((sc, cause) -> {
        if (releaseContentChunkIfStreamAlreadyFailed(msgContent, proxyRouterState)) {
            //      content has been released. Nothing left for us to do.
            return;
        }
        if (cause == null) {
            // Nothing has blown up yet, so stream this next chunk downstream. Calling streamChunk() will decrement
            //      the chunk's reference count (at some point in the future), allowing it to be destroyed since
            //      this should be the last handle on the chunk's memory.
            ChannelFuture writeFuture = sc.streamChunk(msgContent);
            writeFuture.addListener(future -> {
                if (!future.isSuccess()) {
                    try {
                        String errorMsg = "Chunk streaming ChannelFuture came back as being unsuccessful. " + "downstream_channel_id=" + sc.getChannel().toString();
                        Throwable errorToFire = new WrapperException(errorMsg, future.cause());
                        StreamingCallback callback = proxyRouterState.getStreamingCallback();
                        if (callback != null)
                            callback.unrecoverableErrorOccurred(errorToFire);
                        else {
                            proxyRouterState.setStreamingFailed();
                            runnableWithTracingAndMdc(() -> logger.error("Unrecoverable error occurred and somehow the StreamingCallback was " + "not available. This should not be possible. Firing the following " + "error down the pipeline manually: " + errorMsg, errorToFire), ctx).run();
                            executeOnlyIfChannelIsActive(ctx, "ProxyRouterEndpointExecutionHandler-streamchunk-writefuture-unsuccessful", () -> ctx.fireExceptionCaught(errorToFire));
                        }
                    } finally {
                        sc.closeChannelDueToUnrecoverableError(future.cause());
                    }
                }
            });
        } else {
            StreamingChannel scToNotify = sc;
            try {
                // Something blew up while attempting to send a chunk to the downstream server.
                if (scToNotify == null) {
                    // No StreamingChannel from the registration future. Try to extract it from the
                    //      proxyRouterState directly if possible.
                    CompletableFuture<StreamingChannel> scFuture = proxyRouterState.getStreamingChannelCompletableFuture();
                    if (scFuture.isDone() && !scFuture.isCompletedExceptionally()) {
                        try {
                            scToNotify = scFuture.join();
                        } catch (Throwable t) {
                            runnableWithTracingAndMdc(() -> logger.error("What? This should never happen. Swallowing.", t), ctx).run();
                        }
                    }
                }
                String downstreamChannelId = (scToNotify == null) ? "UNKNOWN" : scToNotify.getChannel().toString();
                String errorMsg = "Chunk streaming future came back as being unsuccessful. " + "downstream_channel_id=" + downstreamChannelId;
                Throwable errorToFire = new WrapperException(errorMsg, cause);
                StreamingCallback callback = proxyRouterState.getStreamingCallback();
                if (callback != null)
                    callback.unrecoverableErrorOccurred(errorToFire);
                else {
                    runnableWithTracingAndMdc(() -> logger.error("Unrecoverable error occurred and somehow the StreamingCallback was not " + "available. This should not be possible. Firing the following error down the " + "pipeline manually: " + errorMsg, errorToFire), ctx).run();
                    executeOnlyIfChannelIsActive(ctx, "ProxyRouterEndpointExecutionHandler-streamchunk-unsuccessful", () -> ctx.fireExceptionCaught(errorToFire));
                }
            } finally {
                // We were never able to call StreamingChannel.streamChunk() on this chunk, so it still has a
                //      dangling reference count handle that needs cleaning up. Since there's nothing left to
                //      do with this chunk, we can release it now.
                msgContent.release();
                // Close down the StreamingChannel so its Channel can be released back to the pool.
                if (scToNotify != null) {
                    scToNotify.closeChannelDueToUnrecoverableError(cause);
                } else {
                    @SuppressWarnings("ThrowableResultOfMethodCallIgnored") Throwable actualCause = unwrapAsyncExceptions(cause);
                    if (!(actualCause instanceof WrapperException)) {
                        runnableWithTracingAndMdc(() -> logger.error("Unable to extract StreamingChannel during error handling and the error that " + "caused it was not a WrapperException, meaning " + "StreamingAsyncHttpClient.streamDownstreamCall(...) did not properly handle it. " + "This should likely never happen and might leave things in a bad state - it " + "should be investigated and fixed! The error that caused this is: ", cause), ctx).run();
                    }
                }
            }
        }
    });
}
Also used : ChannelFuture(io.netty.channel.ChannelFuture) WrapperException(com.nike.backstopper.exception.WrapperException) StreamingChannel(com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingChannel) StreamingCallback(com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingCallback)

Example 3 with StreamingChannel

use of com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingChannel in project riposte by Nike-Inc.

the class StreamingAsyncHttpClientTest method constructor_sets_fields_as_expected.

@Test
public void constructor_sets_fields_as_expected() {
    // given
    Deque<Span> spanStackMock = mock(Deque.class);
    Map<String, String> mdcInfoMock = mock(Map.class);
    // when
    StreamingChannel sc = new StreamingChannel(channelMock, channelPoolMock, callActiveHolder, downstreamLastChunkSentHolder, spanStackMock, mdcInfoMock);
    // then
    assertThat(sc.channel).isSameAs(channelMock);
    assertThat(sc.getChannel()).isSameAs(sc.channel);
    assertThat(sc.pool).isSameAs(channelPoolMock);
    assertThat(sc.callActiveHolder).isSameAs(callActiveHolder);
    assertThat(sc.downstreamLastChunkSentHolder).isSameAs(downstreamLastChunkSentHolder);
    assertThat(sc.distributedTracingSpanStack).isSameAs(spanStackMock);
    assertThat(sc.distributedTracingMdcInfo).isSameAs(mdcInfoMock);
}
Also used : StreamingChannel(com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingChannel) Span(com.nike.wingtips.Span) Test(org.junit.Test)

Aggregations

StreamingChannel (com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingChannel)3 WrapperException (com.nike.backstopper.exception.WrapperException)2 StreamingCallback (com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingCallback)2 Span (com.nike.wingtips.Span)2 ChannelFuture (io.netty.channel.ChannelFuture)2 CircuitBreaker (com.nike.fastbreak.CircuitBreaker)1 ManualModeTask (com.nike.fastbreak.CircuitBreaker.ManualModeTask)1 CircuitBreakerDelegate (com.nike.fastbreak.CircuitBreakerDelegate)1 CircuitBreakerForHttpStatusCode.getDefaultHttpStatusCodeCircuitBreakerForKey (com.nike.fastbreak.CircuitBreakerForHttpStatusCode.getDefaultHttpStatusCodeCircuitBreakerForKey)1 Pair (com.nike.internal.util.Pair)1 StreamingAsyncHttpClient (com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient)1 ChannelAttributes (com.nike.riposte.server.channelpipeline.ChannelAttributes)1 LastOutboundMessageSendLastContentChunk (com.nike.riposte.server.channelpipeline.message.LastOutboundMessageSendLastContentChunk)1 OutboundMessageSendContentChunk (com.nike.riposte.server.channelpipeline.message.OutboundMessageSendContentChunk)1 OutboundMessageSendHeadersChunkFromResponseInfo (com.nike.riposte.server.channelpipeline.message.OutboundMessageSendHeadersChunkFromResponseInfo)1 BaseInboundHandlerWithTracingAndMdcSupport (com.nike.riposte.server.handler.base.BaseInboundHandlerWithTracingAndMdcSupport)1 PipelineContinuationBehavior (com.nike.riposte.server.handler.base.PipelineContinuationBehavior)1 Endpoint (com.nike.riposte.server.http.Endpoint)1 HttpProcessingState (com.nike.riposte.server.http.HttpProcessingState)1 ProxyRouterEndpoint (com.nike.riposte.server.http.ProxyRouterEndpoint)1