Search in sources :

Example 11 with ErrorResponseBody

use of com.nike.riposte.server.error.handler.ErrorResponseBody in project riposte by Nike-Inc.

the class ChannelPipelineFinalizerHandler method finalizeChannelPipeline.

/**
     * This will first check the given state to see if a response was sent to the user. If not then this method will
     * send a generic error to the user so they get some response (so this method is kind of a backstop in case requests
     * somehow slip through our pipeline without being handled, which should never happen, but we have to have this just
     * in case). Then it will clean out the state so that it is ready for the next request.
     * <p/>
     * If the state indicates that a response was already sent then this method will only clean out the state for the
     * next request and will not send an error.
     */
protected void finalizeChannelPipeline(ChannelHandlerContext ctx, Object msg, HttpProcessingState state, Throwable cause) throws JsonProcessingException {
    RequestInfo<?> requestInfo = exceptionHandlingHandler.getRequestInfo(state, msg);
    //      is sent it will update the state.isResponseSent() so that further calls will return true.
    if (!state.isResponseSendingStarted()) {
        String errorMsg = "Discovered a request that snuck through without a response being sent. This should not " + "be possible and indicates a major problem in the channel pipeline.";
        logger.error(errorMsg, new Exception("Wrapper exception", cause));
        // Send a generic unhandled error response with a wrapper exception so that the logging info output by the
        //      exceptionHandlingHandler will have the overview of what went wrong.
        Exception exceptionToUse = new Exception(errorMsg, cause);
        ResponseInfo<ErrorResponseBody> responseInfo = exceptionHandlingHandler.processUnhandledError(state, msg, exceptionToUse);
        responseSender.sendErrorResponse(ctx, requestInfo, responseInfo);
    }
    ctx.flush();
    //      the metrics for this request, so make sure we only do it if appropriate.
    if (metricsListener != null && !state.isRequestMetricsRecordedOrScheduled()) {
        //      conditions), otherwise do it when the response finishes.
        if (!state.isResponseSendingLastChunkSent()) {
            // TODO: Somehow mark the state as a failed request and update the metrics listener to handle it
            metricsListener.onEvent(ServerMetricsEvent.RESPONSE_SENT, state);
        } else {
            // We need to use a copy of the state in case the original state gets cleaned.
            HttpProcessingState stateCopy = new HttpProcessingState(state);
            stateCopy.getResponseWriterFinalChunkChannelFuture().addListener((ChannelFutureListener) channelFuture -> {
                if (channelFuture.isSuccess())
                    metricsListener.onEvent(ServerMetricsEvent.RESPONSE_SENT, stateCopy);
                else {
                    metricsListener.onEvent(ServerMetricsEvent.RESPONSE_WRITE_FAILED, null);
                }
            });
        }
        state.setRequestMetricsRecordedOrScheduled(true);
    }
    // Make sure to clear out request info chunks, multipart data, and any other resources to prevent reference
    //      counting memory leaks (or any other kind of memory leaks).
    requestInfo.releaseAllResources();
    //      channel if it sits unused longer than the timeout value before the next request arrives.
    if (workerChannelIdleTimeoutMillis > 0 && ctx.pipeline().get(IDLE_CHANNEL_TIMEOUT_HANDLER_NAME) == null) {
        ctx.pipeline().addFirst(IDLE_CHANNEL_TIMEOUT_HANDLER_NAME, new IdleChannelTimeoutHandler(workerChannelIdleTimeoutMillis, "ServerWorkerChannel"));
    }
    //      request is broken. We can't do anything except kill the channel.
    if ((cause != null) && state.isResponseSendingStarted() && !state.isResponseSendingLastChunkSent()) {
        runnableWithTracingAndMdc(() -> logger.error("Received an error in ChannelPipelineFinalizerHandler after response sending was started, but " + "before it finished. Closing the channel. unexpected_error={}", cause.toString()), ctx).run();
        ctx.channel().close();
    }
}
Also used : HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) Span(com.nike.wingtips.Span) RequestInfo(com.nike.riposte.server.http.RequestInfo) LoggerFactory(org.slf4j.LoggerFactory) ChannelInboundHandler(io.netty.channel.ChannelInboundHandler) ResponseInfo(com.nike.riposte.server.http.ResponseInfo) Tracer(com.nike.wingtips.Tracer) PipelineContinuationBehavior(com.nike.riposte.server.handler.base.PipelineContinuationBehavior) ErrorResponseBody(com.nike.riposte.server.error.handler.ErrorResponseBody) ChannelHandlerContext(io.netty.channel.ChannelHandlerContext) ByteBuf(io.netty.buffer.ByteBuf) ChannelPromise(io.netty.channel.ChannelPromise) MetricsListener(com.nike.riposte.metrics.MetricsListener) ChannelFutureListener(io.netty.channel.ChannelFutureListener) Logger(org.slf4j.Logger) ChannelOutboundHandler(io.netty.channel.ChannelOutboundHandler) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException) BaseInboundHandlerWithTracingAndMdcSupport(com.nike.riposte.server.handler.base.BaseInboundHandlerWithTracingAndMdcSupport) IDLE_CHANNEL_TIMEOUT_HANDLER_NAME(com.nike.riposte.server.channelpipeline.HttpChannelInitializer.IDLE_CHANNEL_TIMEOUT_HANDLER_NAME) LastOutboundMessage(com.nike.riposte.server.channelpipeline.message.LastOutboundMessage) ChannelAttributes(com.nike.riposte.server.channelpipeline.ChannelAttributes) AsyncNettyHelper.runnableWithTracingAndMdc(com.nike.riposte.util.AsyncNettyHelper.runnableWithTracingAndMdc) ResponseSender(com.nike.riposte.server.http.ResponseSender) ProxyRouterProcessingState(com.nike.riposte.server.http.ProxyRouterProcessingState) ServerMetricsEvent(com.nike.riposte.server.metrics.ServerMetricsEvent) HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) ErrorResponseBody(com.nike.riposte.server.error.handler.ErrorResponseBody) JsonProcessingException(com.fasterxml.jackson.core.JsonProcessingException)

Example 12 with ErrorResponseBody

use of com.nike.riposte.server.error.handler.ErrorResponseBody in project riposte by Nike-Inc.

the class ExceptionHandlingHandler method doExceptionCaught.

@Override
public PipelineContinuationBehavior doExceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    // We expect to end up here when handlers previously in the pipeline throw an error, so do the normal
    //      processError call.
    HttpProcessingState state = getStateAndCreateIfNeeded(ctx, cause);
    if (state.isResponseSendingStarted()) {
        logger.info("A response has already been started. Ignoring this exception since it's secondary. NOTE: This often " + "occurs when an error happens repeatedly on multiple chunks of a request or response - only the " + "first one is processed into the error sent to the user. The original error is probably higher up in " + "the logs. ignored_secondary_exception=\"{}\"", cause.toString());
        return PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT;
    } else {
        ResponseInfo<ErrorResponseBody> responseInfo = processError(state, null, cause);
        if (shouldForceConnectionCloseAfterResponseSent(cause))
            responseInfo.setForceConnectionCloseAfterResponseSent(true);
        state.setResponseInfo(responseInfo);
    }
    return PipelineContinuationBehavior.CONTINUE;
}
Also used : HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) ErrorResponseBody(com.nike.riposte.server.error.handler.ErrorResponseBody)

Example 13 with ErrorResponseBody

use of com.nike.riposte.server.error.handler.ErrorResponseBody in project riposte by Nike-Inc.

the class ResponseSenderHandler method sendResponse.

protected void sendResponse(ChannelHandlerContext ctx, Object msg) throws JsonProcessingException {
    try {
        HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
        if (state.isResponseSendingLastChunkSent()) {
            if (logger.isDebugEnabled()) {
                runnableWithTracingAndMdc(() -> logger.debug("A response has already been sent. " + "Ignoring this method call to send response."), ctx).run();
            }
            return;
        }
        RequestInfo<?> requestInfo = state.getRequestInfo();
        if (requestInfo == null)
            requestInfo = RequestInfoImpl.dummyInstanceForUnknownRequests();
        ResponseInfo<?> responseInfo = state.getResponseInfo();
        Endpoint<?> endpointExecuted = state.getEndpointForExecution();
        ObjectMapper customSerializer = (endpointExecuted == null) ? null : endpointExecuted.customResponseContentSerializer(requestInfo);
        if (msg != null && msg instanceof ChunkedOutboundMessage) {
            // Chunked message. Stream it out.
            responseSender.sendResponseChunk(ctx, requestInfo, responseInfo, (ChunkedOutboundMessage) msg);
        } else {
            // Full message. Send it.
            if (containsErrorResponseBody(responseInfo)) {
                //noinspection unchecked
                responseSender.sendErrorResponse(ctx, requestInfo, (ResponseInfo<ErrorResponseBody>) responseInfo);
            } else
                responseSender.sendFullResponse(ctx, requestInfo, responseInfo, customSerializer);
        }
    } catch (Throwable t) {
        runnableWithTracingAndMdc(() -> logger.error("An unexpected error occurred while attempting to send a response.", t), ctx).run();
        throw t;
    }
}
Also used : HttpProcessingState(com.nike.riposte.server.http.HttpProcessingState) ErrorResponseBody(com.nike.riposte.server.error.handler.ErrorResponseBody) ChunkedOutboundMessage(com.nike.riposte.server.channelpipeline.message.ChunkedOutboundMessage) ObjectMapper(com.fasterxml.jackson.databind.ObjectMapper)

Example 14 with ErrorResponseBody

use of com.nike.riposte.server.error.handler.ErrorResponseBody in project riposte by Nike-Inc.

the class ExceptionHandlingHandler method processError.

/**
     * Attempts to process the given error using the "normal" error handler {@link #riposteErrorHandler} to produce the
     * most specific error response possible for the given error. If that fails for any reason then the unhandled error
     * handler will take over to guarantee the user gets a generic error response that still follows our error contract.
     * If you already know your error is a non-normal unhandled error of the "how did we get here, this should never
     * happen" variety you can (and should) directly call {@link #processUnhandledError(HttpProcessingState, Object,
     * Throwable)} instead.
     */
protected ResponseInfo<ErrorResponseBody> processError(HttpProcessingState state, Object msg, Throwable cause) throws JsonProcessingException {
    RequestInfo<?> requestInfo = getRequestInfo(state, msg);
    try {
        ErrorResponseInfo contentFromErrorHandler = riposteErrorHandler.maybeHandleError(cause, requestInfo);
        if (contentFromErrorHandler != null) {
            // The regular error handler did handle the error. Setup our ResponseInfo.
            ResponseInfo<ErrorResponseBody> responseInfo = new FullResponseInfo<>();
            setupResponseInfoBasedOnErrorResponseInfo(responseInfo, contentFromErrorHandler);
            return responseInfo;
        }
    } catch (Throwable errorHandlerFailed) {
        logger.error("An unexpected problem occurred while trying to handle an error.", errorHandlerFailed);
    }
    //      so the riposteUnhandledErrorHandler should take care of it.
    return processUnhandledError(state, msg, cause);
}
Also used : ErrorResponseInfo(com.nike.riposte.server.error.handler.ErrorResponseInfo) ErrorResponseBody(com.nike.riposte.server.error.handler.ErrorResponseBody) FullResponseInfo(com.nike.riposte.server.http.impl.FullResponseInfo)

Example 15 with ErrorResponseBody

use of com.nike.riposte.server.error.handler.ErrorResponseBody in project riposte by Nike-Inc.

the class ResponseSender method sendErrorResponse.

/**
     * Sets an error_uid header based on the given error response's {@link ErrorResponseBody#errorId()} and replaces the
     * {@link ErrorResponseBody} found in the {@link ResponseInfo#getContentForFullResponse()} with the String result of
     * calling {@link ErrorResponseBodySerializer#serializeErrorResponseBodyToString(ErrorResponseBody)} on {@link
     * #errorResponseBodySerializer}. The modified {@link ResponseInfo} is then sent to {@link
     * #sendFullResponse(io.netty.channel.ChannelHandlerContext, RequestInfo, ResponseInfo, ObjectMapper)} for passing
     * back to the client.
     * <p/>
     * NOTE: This assumes a full (not chunked) response, and uses {@link ResponseInfo#getContentForFullResponse()} to
     * retrieve the {@link ErrorResponseBody} object. Therefore this method will throw an {@link
     * IllegalArgumentException} if you pass in a response object that returns true for {@link
     * ResponseInfo#isChunkedResponse()}.
     */
public void sendErrorResponse(ChannelHandlerContext ctx, RequestInfo requestInfo, ResponseInfo<ErrorResponseBody> responseInfo) throws JsonProcessingException {
    if (responseInfo.isChunkedResponse()) {
        throw new IllegalArgumentException("The responseInfo argument is marked as being a chunked response, but " + "sendErrorResponse(...) only works with full responses");
    }
    responseInfo.getHeaders().set("error_uid", responseInfo.getContentForFullResponse().errorId());
    @SuppressWarnings("UnnecessaryLocalVariable") ErrorResponseBody bodyToSerialize = responseInfo.getContentForFullResponse();
    if (bodyToSerialize != null) {
        String errorBodyAsString = errorResponseBodySerializer.serializeErrorResponseBodyToString(bodyToSerialize);
        //noinspection unchecked
        ((ResponseInfo) responseInfo).setContentForFullResponse(errorBodyAsString);
    }
    sendFullResponse(ctx, requestInfo, responseInfo, defaultResponseContentSerializer);
}
Also used : OutboundMessageSendHeadersChunkFromResponseInfo(com.nike.riposte.server.channelpipeline.message.OutboundMessageSendHeadersChunkFromResponseInfo) ErrorResponseBody(com.nike.riposte.server.error.handler.ErrorResponseBody)

Aggregations

ErrorResponseBody (com.nike.riposte.server.error.handler.ErrorResponseBody)24 Test (org.junit.Test)18 HttpProcessingState (com.nike.riposte.server.http.HttpProcessingState)9 JsonProcessingException (com.fasterxml.jackson.core.JsonProcessingException)8 IncompleteHttpCallTimeoutException (com.nike.riposte.server.error.exception.IncompleteHttpCallTimeoutException)6 TooManyOpenChannelsException (com.nike.riposte.server.error.exception.TooManyOpenChannelsException)6 ErrorResponseInfo (com.nike.riposte.server.error.handler.ErrorResponseInfo)6 FullResponseInfo (com.nike.riposte.server.http.impl.FullResponseInfo)6 PipelineContinuationBehavior (com.nike.riposte.server.handler.base.PipelineContinuationBehavior)5 RequestInfo (com.nike.riposte.server.http.RequestInfo)5 List (java.util.List)5 ErrorResponseInfo (com.nike.backstopper.handler.ErrorResponseInfo)4 ResponseInfo (com.nike.riposte.server.http.ResponseInfo)4 RequestInfoForLogging (com.nike.backstopper.handler.RequestInfoForLogging)3 DefaultErrorContractDTO (com.nike.backstopper.model.DefaultErrorContractDTO)2 ErrorResponseBodyImpl (com.nike.backstopper.model.riposte.ErrorResponseBodyImpl)2 ErrorResponseInfoImpl (com.nike.backstopper.model.riposte.ErrorResponseInfoImpl)2 ChannelAttributes (com.nike.riposte.server.channelpipeline.ChannelAttributes)2 RiposteErrorHandler (com.nike.riposte.server.error.handler.RiposteErrorHandler)2 UseDataProvider (com.tngtech.java.junit.dataprovider.UseDataProvider)2