use of com.nike.riposte.server.http.HttpProcessingState in project riposte by Nike-Inc.
the class NonblockingEndpointExecutionHandler 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)) {
// 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();
@SuppressWarnings("unchecked") CompletableFuture<ResponseInfo<?>> responseFuture = state.getPreEndpointExecutionWorkChain().thenCompose(functionWithTracingAndMdc(aVoid -> (CompletableFuture<ResponseInfo<?>>) nonblockingEndpoint.execute(requestInfo, longRunningTaskExecutor, ctx), ctx));
// Register an on-completion callback so we can be notified when the CompletableFuture finishes.
responseFuture.whenComplete((responseInfo, throwable) -> {
if (throwable != null)
asyncErrorCallback(ctx, throwable);
else
asyncCallback(ctx, responseInfo);
});
// Also schedule a timeout check with our Netty event loop to make sure we kill the
// CompletableFuture if it goes on too long.
long timeoutValueToUse = (nonblockingEndpoint.completableFutureTimeoutOverrideMillis() == null) ? defaultCompletableFutureTimeoutMillis : nonblockingEndpoint.completableFutureTimeoutOverrideMillis();
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 collectable. 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;
}
use of com.nike.riposte.server.http.HttpProcessingState in project riposte by Nike-Inc.
the class ProcessFinalResponseOutputHandler method write.
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
// Deal with the final outbound HttpResponse
if (msg instanceof HttpResponse) {
HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
if (state != null)
state.setActualResponseObject((HttpResponse) msg);
}
// Deal with the final outbound body content
if (msg instanceof HttpContent) {
HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
if (state != null && state.getResponseInfo() != null) {
ResponseInfo<?> responseInfo = state.getResponseInfo();
long contentBytes = ((HttpContent) msg).content().readableBytes();
if (responseInfo.getFinalContentLength() == null)
responseInfo.setFinalContentLength(contentBytes);
else
responseInfo.setFinalContentLength(responseInfo.getFinalContentLength() + contentBytes);
}
}
super.write(ctx, msg, promise);
}
use of com.nike.riposte.server.http.HttpProcessingState in project riposte by Nike-Inc.
the class ResponseFilterHandler method executeResponseFilters.
protected void executeResponseFilters(ChannelHandlerContext ctx) {
try {
HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
// (for example).
if (state.isResponseSendingStarted())
return;
// RequestHasBeenHandledVerificationHandler should have made sure that state.getResponseInfo() is not null.
ResponseInfo<?> currentResponseInfo = state.getResponseInfo();
for (RequestAndResponseFilter filter : filtersInResponseProcessingOrder) {
try {
currentResponseInfo = responseInfoUpdateNoNulls(filter, currentResponseInfo, filter.filterResponse(currentResponseInfo, state.getRequestInfo(), ctx));
} catch (Throwable ex) {
logger.error("An error occurred while processing a request filter. This error will be ignored and the " + "filtering/processing will continue normally, however this error should be fixed (filters " + "should never throw errors). filter_class={}", filter.getClass().getName(), ex);
}
}
state.setResponseInfo(currentResponseInfo);
} catch (Throwable ex) {
logger.error("An error occurred while setting up to process response filters. This error will be ignored and the " + "pipeline will continue normally without any filtering having occurred, however this error should be " + "fixed (it should be impossible to reach here).", ex);
}
}
use of com.nike.riposte.server.http.HttpProcessingState in project riposte by Nike-Inc.
the class SmartHttpContentCompressor method write.
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
allowCompressionForThisRequest = false;
if (state != null) {
// We only want to allow compression if the endpoint being hit is *not* a ProxyRouterEndpoint, the response is full, and the response size
// is greater than the threshold
boolean isFull = msg instanceof HttpResponse && msg instanceof LastHttpContent;
boolean endpointAllowed = endpointAllowsCompression(state.getEndpointForExecution());
boolean responseInfoAllowed = state.getResponseInfo() == null || !state.getResponseInfo().isPreventCompressedOutput();
if (isFull && endpointAllowed && responseInfoAllowed && ((LastHttpContent) msg).content().readableBytes() > responseSizeThresholdBytes) {
allowCompressionForThisRequest = true;
}
}
super.write(ctx, msg, promise);
}
use of com.nike.riposte.server.http.HttpProcessingState in project riposte by Nike-Inc.
the class ChannelPipelineFinalizerHandler method doChannelInactive.
/**
* This method is used as the final cleanup safety net for when a channel is closed. It guarantees that any
* {@link ByteBuf}s being held by {@link RequestInfo} or {@link ProxyRouterProcessingState} are {@link
* ByteBuf#release()}d so that we don't end up with a memory leak.
*
* <p>Note that we can't use {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)} for this
* purpose as it is only called if we close the connection in our application code. It won't be triggered if
* (for example) the caller closes the connection, and we need it to *always* run for *every* closed connection,
* no matter the source of the close. {@link ChannelInboundHandler#channelInactive(ChannelHandlerContext)} is always
* called, so we're using that.
*/
@Override
public PipelineContinuationBehavior doChannelInactive(ChannelHandlerContext ctx) throws Exception {
try {
// Grab hold of the things we may need when cleaning up.
HttpProcessingState httpState = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
ProxyRouterProcessingState proxyRouterState = ChannelAttributes.getProxyRouterProcessingStateForChannel(ctx).get();
if (httpState == null) {
if (proxyRouterState == null) {
logger.debug("This channel closed before it processed any requests. Nothing to cleanup. " + "current_span={}", Tracer.getInstance().getCurrentSpan());
} else {
// This should never happen, but if it does we'll try to release what we can and then return.
logger.error("Found a channel where HttpProcessingState was null, but ProxyRouterProcessingState " + "was not null. This should not be possible! " + "current_span={}", Tracer.getInstance().getCurrentSpan());
releaseProxyRouterStateResources(proxyRouterState);
}
// With httpState null, there's nothing left for us to do.
return PipelineContinuationBehavior.CONTINUE;
}
RequestInfo<?> requestInfo = httpState.getRequestInfo();
ResponseInfo<?> responseInfo = httpState.getResponseInfo();
if (logger.isDebugEnabled()) {
runnableWithTracingAndMdc(() -> logger.debug("Cleaning up channel after it was closed. closed_channel_id={}", ctx.channel().toString()), ctx).run();
}
// Handle the case where the response wasn't fully sent or tracing wasn't completed for some reason.
// We want to finish the distributed tracing span for this request since there's no other place it
// might be done, and if the request wasn't fully sent then we should spit out a log message so
// debug investigations can find out what happened.
// TODO: Is there a way we can handle access logging and/or metrics here, but only if it wasn't done elsewhere?
@SuppressWarnings("SimplifiableConditionalExpression") boolean tracingAlreadyCompleted = httpState.isTraceCompletedOrScheduled();
boolean responseNotFullySent = responseInfo == null || !responseInfo.isResponseSendingLastChunkSent();
if (responseNotFullySent || !tracingAlreadyCompleted) {
runnableWithTracingAndMdc(() -> {
if (responseNotFullySent) {
logger.warn("The caller's channel was closed before a response could be sent. This means that " + "an access log probably does not exist for this request. Distributed tracing " + "will be completed now if it wasn't already done. Any dangling resources will be " + "released. response_info_is_null={}", (responseInfo == null));
}
if (!tracingAlreadyCompleted) {
Span currentSpan = Tracer.getInstance().getCurrentSpan();
if (currentSpan != null && !currentSpan.isCompleted())
Tracer.getInstance().completeRequestSpan();
httpState.setTraceCompletedOrScheduled(true);
}
}, ctx).run();
}
// Tell the RequestInfo it can release all its resources.
if (requestInfo != null)
requestInfo.releaseAllResources();
releaseProxyRouterStateResources(proxyRouterState);
} catch (Throwable t) {
runnableWithTracingAndMdc(() -> logger.error("An unexpected error occurred during ChannelPipelineFinalizerHandler.doChannelInactive() - this " + "should not happen and indicates a bug that needs to be fixed in Riposte.", t), ctx).run();
}
return PipelineContinuationBehavior.CONTINUE;
}
Aggregations