use of com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingCallback in project riposte by Nike-Inc.
the class ProxyRouterEndpointExecutionHandler method doChannelRead.
@Override
public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) {
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, true);
} 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, true);
} 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.uri()));
// 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;
boolean performSubSpanAroundDownstreamCall = downstreamRequestFirstChunkInfo.performSubSpanAroundDownstreamCall;
boolean addTracingHeadersToDownstreamCall = downstreamRequestFirstChunkInfo.addTracingHeadersToDownstreamCall;
// 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, performSubSpanAroundDownstreamCall, addTracingHeadersToDownstreamCall, proxyRouterState, requestInfo, 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, true);
}
});
// 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, true);
}
} catch (Throwable t) {
callback.unrecoverableErrorOccurred(t, true);
}
}
});
} 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;
}
use of com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingCallback 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 -> {
// a problem.
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) {
// This doesn't necessarily guarantee a broken downstream response in the case where
// the downstream system returned a response before receiving all request chunks
// (e.g. short circuit error response), so we'll call unrecoverableErrorOccurred()
// with false for the guaranteesBrokenDownstreamResponse argument. This will give
// the downstream system a chance to fully send its response if it had started
// but not yet completed by the time we hit this code on the request chunk.
callback.unrecoverableErrorOccurred(errorToFire, false);
} else {
// We have to call proxyRouterState.cancelRequestStreaming() here since we couldn't
// call callback.unrecoverableErrorOccurred(...);
proxyRouterState.cancelRequestStreaming(errorToFire, ctx);
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 {
// Close down the StreamingChannel so its Channel can be released back to the pool.
sc.closeChannelDueToUnrecoverableError(future.cause());
}
} else if (msgContent instanceof LastHttpContent) {
// This msgContent was the last chunk and it was streamed successfully, so mark the proxy router
// state as having completed successfully.
proxyRouterState.setRequestStreamingCompletedSuccessfully();
}
});
} 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) {
// This doesn't necessarily guarantee a broken downstream response in the case where
// the downstream system returned a response before receiving all request chunks
// (e.g. short circuit error response), so we'll call unrecoverableErrorOccurred()
// with false for the guaranteesBrokenDownstreamResponse argument. This will give
// the downstream system a chance to fully send its response if it had started
// but not yet completed by the time we hit this code on the request chunk.
callback.unrecoverableErrorOccurred(errorToFire, false);
} 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();
}
}
}
}
});
}
use of com.nike.riposte.client.asynchttp.netty.StreamingAsyncHttpClient.StreamingCallback in project riposte by Nike-Inc.
the class StreamingAsyncHttpClientTest method streamDownstreamCall_setsHostHeaderCorrectly.
@DataProvider(value = { "80 | false | localhost | localhost", "80 | true | localhost | localhost:80", "8080 | false | localhost | localhost:8080", "443 | true | localhost | localhost", "443 | false | localhost | localhost:443", "8080 | true | localhost | localhost:8080" }, splitBy = "\\|")
@Test
public void streamDownstreamCall_setsHostHeaderCorrectly(int downstreamPort, boolean isSecure, String downstreamHost, String expectedHostHeader) {
// given
DefaultHttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "");
ChannelHandlerContext ctx = mockChannelHandlerContext();
StreamingCallback streamingCallback = mock(StreamingCallback.class);
ProxyRouterProcessingState proxyState = ChannelAttributes.getProxyRouterProcessingStateForChannel(ctx).get();
RequestInfo<?> requestInfoMock = mock(RequestInfo.class);
// when
new StreamingAsyncHttpClient(200, 200, true, mock(DistributedTracingConfig.class)).streamDownstreamCall(downstreamHost, downstreamPort, request, isSecure, false, streamingCallback, 200, true, true, proxyState, requestInfoMock, ctx);
// then
assertThat(request.headers().get(HOST)).isEqualTo(expectedHostHeader);
}
Aggregations