Workaround ClientAbortException and IOException

rest_jersey2_client_abort_exception_thumb

This tutorial provides a workaround for the ClientAbortException (IOException) that may occur when writing output streams via a Response in a Jersey REST web service. There is a previous post dealing with video streaming where you can find the Github repository in a working example.

1. ClientAbortException

The stack trace for the ClientAbortException looks like this:

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: org.apache.catalina.connector.ClientAbortException: java.io.IOException: An established connection was aborted by the software in your host machine
	at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:92)
	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
	at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1130)
	at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:711)
	at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:444)
	at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:434)
	at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:329)
	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:317)
	at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
	at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
	at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473)
	at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427)
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388)
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341)
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:798)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1434)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:745)

The ClientAbortException occurs when sending a Response to a client and the client closes the connection before all (stream) data was transmitted.

2. Workaround for ClientAbortException

This exception is not catchable in the REST endpoint, hence we propose the workaround.

2.1 Disable internal Jersey logger

One answer in the web is to disable the internal Jersey logger:

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);
    }
}

Since this disables all the internal Jersey exceptions and results in the loss of important exceptions, we found another way to go.

2.2 WriterInterceptor

You can detect, intercept and ignore exceptions using a WriterInterceptor. In our case, we want to ignore only the ClientAbortException.

/**
 * 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 );
        }
    }
}

This class uses the org.apache.commons.io.output.ProxyOutputStream from the Apache Commons IO library. If you are working with Tomcat, use the org.apache.catalina.connector.ClientAbortException instead of calling the setOutputStream method. Therefore removing the need for the two nested classes and the dependency on org.apache.commons.io.output.ProxyOutputStream.

You can register the ClientAbortExceptionWriterInterceptor in your web.xml or a ResourceConfig class. In the web.xml, simply add the following init parameters in your servlet configuration.

	<servlet>
		<servlet-name>...</servlet-name>
		<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
		<init-param>
			<param-name> ...</param-name>
		</init-param>
		<init-param>
			<param-name>com.ws.rs.ext.WriterInterceptor</param-name>
			<param-value>com.tutorialacademy.rest.streaming.ClientAbortExceptionWriterInterceptor</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

Using the ResourceConfig as class:

public class RestConfig extends ResourceConfig {
	
	public RestConfig() {
		register( ClientAbortExceptionWriterInterceptor.class );
	}
}

Nevertheless you have to register that ResourceConfig in your web.xml as well:

  <servlet>
    <servlet-name>...</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>javax.ws.rs.Application</param-name>
      <param-value>com.tutorialacademy.rest.streaming.RestConfig</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

We recommend the second approach to register multiple components like filters, interceptors and web services. For just testing you can register the Interceptor in the web.xml directly.

3. Conclusion

Following these steps should remove the ClientAbortException and therefore clear up your logging output. Keep in mind that this workaround ignores the exception. This is not a proper way in terms of catching and handling exceptions. But since the server basically has no influence whether the client closes the connection early, there is not much you can do (or handle the exception) anyways.

If you have problems or errors, feel free to comment and ask.

Facebooktwitterredditpinterestlinkedinmail

Related posts

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.