ClientAbortException при использовании Jersey 2.13

Я использую Jersey 2.13 в своем веб-приложении для получения данных async. Есть случаи, когда запросы занимают некоторое время (например, при выполнении сложных отчетов), пока их ответ не вернется к клиенту.

Когда клиент не ожидает ответа async (оставляет страницу, закрывает браузер и т.д.), генерируется исключение ClientAbortException. Такое поведение ожидается, но оно наводняет мои файлы журналов трассировкой стека, потому что каждый отдельный запрос async, который отменяется до ответа, возвращает трассировку стека.

Трассировка стека выглядит следующим образом:

Oct 15, 2014 2:25:23 PM org.glassfish.jersey.server.ServerRuntime$Responder writeResponse
SEVERE: An I/O error has occurred while writing a response message entity to the container output stream.
org.glassfish.jersey.server.internal.process.MappableException: ClientAbortException:  java.io.IOException
                at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:91)
                at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
                at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1154)
                at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:621)
                at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:377)
                at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:367)
                at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:274)
                at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
                at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
                at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
                at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
                at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
                at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:297)
                at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:254)
                at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1030)
                at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:373)
                at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:381)
                at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:344)
                at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
                at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
                at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
                at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
                at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
                at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
                at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
                at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
                at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
                at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:409)
                at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1044)
                at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
                at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2441)
                at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2430)
                at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
                at java.lang.Thread.run(Unknown Source)
Caused by: ClientAbortException:  java.io.IOException
                at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:413)
                at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:480)
                at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:366)
                at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:438)
                at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:426)
                at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:91)
                at org.glassfish.jersey.servlet.internal.ResponseWriter$NonCloseableOutputStreamWrapper.write(ResponseWriter.java:298)
                at org.glassfish.jersey.message.internal.CommittingOutputStream.write(CommittingOutputStream.java:229)
                at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$UnCloseableOutputStream.write(WriterInterceptorExecutor.java:299)
                at com.fasterxml.jackson.core.json.UTF8JsonGenerator._flushBuffer(UTF8JsonGenerator.java:1862)
                at com.fasterxml.jackson.core.json.UTF8JsonGenerator.close(UTF8JsonGenerator.java:1087)
                at com.fasterxml.jackson.jaxrs.base.ProviderBase.writeTo(ProviderBase.java:637)
                at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:265)
                at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:250)
                at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
                at org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:106)
                at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
                at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85)
                ... 38 more
Caused by: java.io.IOException
                at org.apache.coyote.http11.InternalAprOutputBuffer.flushBuffer(InternalAprOutputBuffer.java:205)
                at org.apache.coyote.http11.InternalAprOutputBuffer.access$100(InternalAprOutputBuffer.java:37)
                at org.apache.coyote.http11.InternalAprOutputBuffer$SocketOutputBuffer.doWrite(InternalAprOutputBuffer.java:235)
                at org.apache.coyote.http11.filters.ChunkedOutputFilter.doWrite(ChunkedOutputFilter.java:117)
                at org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:192)
                at org.apache.coyote.Response.doWrite(Response.java:517)
                at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:408)
                ... 55 more

Я также использую средство ExceptionMapper для переноса нескольких Исключений, но это не работает для

org.glassfish.jersey.server.internal.process.MappableException

и для

org.apache.catalina.connector.ClientAbortException

Есть ли способ поймать это исключение и предотвратить печать всей трассировки стека?

EDIT:

Ищем ответ...

Ответ 1

После копания в кодировании Джерси, я узнал, что единственный способ архивировать это - отключить внутренний Logger от Джерси. Это можно сделать в классе, который расширяет ResourceConfig.

@ApplicationPath("api")
public class Application extends ResourceConfig {

    private final static Logger ORG_GLASSFISH_JERSEY_LOGGER = Logger
            .getLogger("org.glassfish.jersey");
    static {
        ORG_GLASSFISH_JERSEY_LOGGER.setLevel(Level.OFF);
    }
}

Ответ 2

Я работал над этой проблемой, добавив низкоприоритетный WriterInterceptor, который обнаруживает и игнорирует исключения, возникающие при написании ответов. Если вы работаете на Tomcat и не против зависимости от классов Tomcat, вы можете использовать org.apache.catalina.connector.ClientAbortException скорее вызов setOutputStream, который устранит необходимость в двух вложенных классах (и зависимости от org.apache.commons.io.output.ProxyOutputStream, который также можно было бы избежать также с помощью пользовательского подкласса OutputStream).

import java.io.IOException;
import java.io.OutputStream;

import javax.annotation.Priority;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;

import org.apache.commons.io.output.ProxyOutputStream;

/**
 * Ignore exceptions when writing a response, which almost always means the
 * client disconnected before reading the full response.
 */
@Provider
@Priority(1)
public class ClientAbortExceptionWriterInterceptor implements WriterInterceptor {
    @Override
    public void aroundWriteTo(WriterInterceptorContext context) throws IOException {
        context.setOutputStream(new ClientAbortExceptionOutputStream(context.getOutputStream()));
        try {
            context.proceed();
        } catch (Throwable t) {
            for (Throwable cause = t; cause != null; cause = cause.getCause()) {
                if (cause instanceof ClientAbortException) {
                    return;
                }
            }
            throw t;
        }
    }

    private static class ClientAbortExceptionOutputStream extends ProxyOutputStream {
        public ClientAbortExceptionOutputStream(OutputStream out) {
            super(out);
        }

        @Override
        protected void handleIOException(IOException e) throws IOException {
            throw new ClientAbortException(e);
        }
    }

    @SuppressWarnings("serial")
    private static class ClientAbortException extends IOException {
        public ClientAbortException(IOException e) {
            super(e);
        }
    }
}

Ответ 3

Спасибо @Chip, он также работает, помещая код прямо в WS.

@Path("/myWS")
public class MyWS {   

    private final static Logger ORG_GLASSFISH_JERSEY_LOGGER = Logger.getLogger("org.glassfish.jersey");
    static {
        ORG_GLASSFISH_JERSEY_LOGGER.setLevel(Level.OFF);
    }    

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/version")
    public String version() {
        return "1.0.25";
    }

}

Ответ 4

Я также столкнулся с этим и наконец нашел руководство, чтобы "решить" это.

https://tutorial-academy.com/jersey-workaround-clientabortexception-ioexception/

Есть два варианта, где первый является тем, который в настоящее время является принятым ответом. Другим предпочтительным способом является добавление WriterInterceptor для удаления исключения ClientAbortException. Мой личный поворот заключается в том, чтобы предупредить это возникновение.

Если URL недоступен, я добавляю свою реализацию здесь. Не забудьте зарегистрировать его в своем контексте Джерси.

import org.apache.catalina.connector.ClientAbortException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Priority;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
import java.io.IOException;


@Provider
@Priority(1)
public class ClientAbortExceptionWriterInterceptor implements WriterInterceptor {
private static final Logger logger = LoggerFactory.getLogger(ClientAbortExceptionWriterInterceptor.class);

@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException {
    try {
        context.proceed();
    } catch (Throwable t) {
        for (Throwable cause = t; cause != null; cause = cause.getCause()) {
            if (cause instanceof ClientAbortException) {
                logger.warn("Client aborted request.", cause);
                return;
            }
        }
        throw t;
    }
}

}