views:

178

answers:

4

I occasionally get an IOException from a Servlet I have that writes a byte array to the outputstream in order to provide a file download capability.

This download servlet has a reasonable amount of traffic (100k hits a month) and this exception occurs rarely, around 1-2 times a month.

I have tried recreating the exception by using the exact same Base64 String and no exception is raised and the Servlet behaves as designed.

Is this IO Exception being caused by something outside my applications control? For example a network issue or the user resetting the connection? I have tried to google the cause of an IOException at this point in the stack but to no avail.

The environment is running Tomcat 5.5 on CentOS 5.3 with an Apache HTTP Server acting as a proxy using proxy_ajp.

ClientAbortException:  java.io.IOException

at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBufferjava:366)
    at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:352)
    at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:392)
    at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:381)
    at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:89)
    at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:83)
    at com.myApp.Download.doPost(Download.java:34)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:691)
    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:469)
    at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:403)
    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:301)
    at com.myApp.EntryServlet.service(EntryServlet.java:278)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
    at com.myApp.filters.RequestFilter.doFilter(RequestFilter.java:16)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:210)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:172)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:108)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:151)
    at org.apache.coyote.ajp.AjpAprProcessor.process(AjpAprProcessor.java:444)
    at org.apache.coyote.ajp.AjpAprProtocol$AjpConnectionHandler.process(AjpAprProtocol.java:472)
    at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1286)
    at java.lang.Thread.run(Thread.java:636)

Caused by: java.io.IOException
    at org.apache.coyote.ajp.AjpAprProcessor.flush(AjpAprProcessor.java:1200)
    at org.apache.coyote.ajp.AjpAprProcessor$SocketOutputBuffer.doWrite(AjpAprProcessor.java:1285)
    at org.apache.coyote.Response.doWrite(Response.java:560)
    at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBufferjava:361)

And the code in the Download Servlet:

@Override
    protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, java.io.IOException {     
        try {
            response.setContentType("application/pdf");
            response.setHeader("Pragma", "");
            response.setHeader("Cache-Control", "");
            response.setHeader("Content-Disposition", "Inline; Filename=myPDFFile..pdf");
            ServletOutputStream out = response.getOutputStream();
            byte[] downloadBytes = Base64.decode((String)request.getAttribute("fileToDownloadBase64"));
            out.write(downloadBytes);
        } catch (Base64DecodingException e) {
            e.printStackTrace();
            response.getOutputStream().print("An error occurred");
        }
    }
+2  A: 

I suspect the client (browser) is disconnecting during the writing of the byte array to the socket.

Brian Agnew
What is the proper way to handle this? Catch all IOExceptions?
MontyBongo
I would do that, yes.
Brian Agnew
You can catch it, but you cannot handle it in any sensible manner. You cannot revert the response nor write any bit to the client anymore. You can at highest log it. But how useful is this information? The client just decided to abort the download or to navigate away, or the network was out, or its PC has crashed. Funny for statistics, but really nothing more than that.
BalusC
A: 

Try to collect more information about environment when this exception happens and log it.

Under normal conditions log byte array size and the "User-Agent" field from the HTTP request, and definitely log this information when exception is thrown.

It may be that the byte array is too large to be written in a single pass and you may need to chop it up in a few chunks.

Also, I recommend you to look at commons-fileupload. Even if you don't use it in your project, browse the source code and see how they download the file. They used to have a lot of IOExceptions during file upload, but those were fixed in later versions. You may try to figure out what exactly was fixed.

Alexander Pogrebnyak
There is no such thing a a 'byte array too large to be written in a single pass' anywhere in Java.And this output stream is already being chunked, see the stack trace.
EJP
@EJP. Yeah, I know, but Java abstraction and a real world are two different things.
Alexander Pogrebnyak
A meaningless remark. I repeat there is no such thing as a byte array too large to be written anywhere in Java. If you have evidence to the contrary please provide it. We are talking about blocking I/O here which loops until the write is complete or an exception occurs.
EJP
@EJP. Isn't it what OP gets here -- exception?
Alexander Pogrebnyak
Not for the reason you gave, which is spurious. He almost certainly got it because the client had already disconnected, as indicated by the 'ClientAbortException'. I would like to see the text associated with the IOException. I expect it was 'connection reset'.
EJP
+1  A: 

If the client disconnects (i.e. cancels the download or closes the browser) then you will get an IOException.

If this is not a terminal problem for your application which is probably the case then you should catch this exception and do nothing. You could perhaps do some logging if you want to gather statistics about how often downloads are aborted by the client.

Tendayi Mawushe
+1  A: 

What is the proper way to handle this? Catch all IOExceptions?

Catching all IOExceptions is reasonable, but the following is dodgy:

    catch (Base64DecodingException e) {
        e.printStackTrace();
        response.getOutputStream().print("An error occurred");
    }

First, you should be using a logging framework (e.g. log4j) rather than writing diagnostics to stderr by calling e.printStackTrace().

Second, writing an error message like to the response output is probably wrong.

  • That message is uninformative.
  • The client won't be expecting an error message at that point.
  • If it is expecting an error message, the client won't be able to distinguish an error message from real data ... without hard-wired knowledge of all of the server error messages.

The preferred way to report errors to an HTTP client is to set a 4xx or 5xx status code in the HTTP response, and (ideally) set an error message. But you can only do this if the response has not "committed", and opening the response output stream commits the response.

Finally, you cannot take that approach with writing error message to the client for I/O exceptions in general. If the I/O exception was to indicate that the output connection is broken, writing a message to the response stream will just throw another exception.

Stephen C