Java 流式传输字节数组时出现Servlet IOException

Java 流式传输字节数组时出现Servlet IOException,java,tomcat,servlets,Java,Tomcat,Servlets,我偶尔会从一个Servlet得到一个IOException,它将一个字节数组写入outputstream以提供文件下载功能 这个下载servlet有一个合理的流量(每月点击100k次),这种异常很少发生,大约每月1-2次 我尝试使用完全相同的Base64字符串重新创建异常,没有引发异常,Servlet的行为符合设计 此IO异常是否由我的应用程序无法控制的原因引起?例如 网络问题或用户重置连接?我尝试在堆栈的这一点上搜索IOException的原因,但没有结果 该环境在CentOS 5.3上运行T

我偶尔会从一个Servlet得到一个IOException,它将一个字节数组写入outputstream以提供文件下载功能

这个下载servlet有一个合理的流量(每月点击100k次),这种异常很少发生,大约每月1-2次

我尝试使用完全相同的Base64字符串重新创建异常,没有引发异常,Servlet的行为符合设计

此IO异常是否由我的应用程序无法控制的原因引起?例如 网络问题或用户重置连接?我尝试在堆栈的这一点上搜索IOException的原因,但没有结果

该环境在CentOS 5.3上运行Tomcat 5.5,Apache HTTP服务器使用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)
下载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");
        }
    }

我怀疑客户端(浏览器)在将字节数组写入套接字的过程中正在断开连接。

尝试在发生此异常时收集有关环境的更多信息并记录它

在正常情况下,从HTTP请求中记录字节数组大小和“用户代理”字段,并在引发异常时明确记录此信息

可能是字节数组太大,无法一次写入,您可能需要将其切分为几个块

还有,我建议你看看。即使您没有在项目中使用它,也可以浏览源代码并查看他们是如何下载该文件的。他们过去在上传文件时有很多IOException,但这些在以后的版本中得到了修复。您可以尝试找出修复的确切内容。

如果客户端断开连接(即取消下载或关闭浏览器),则您将获得一个
IOException

如果这不是应用程序的终端问题(可能是这种情况),那么您应该捕获此异常,什么也不做。如果您想收集有关客户端中止下载的频率的统计信息,您可能需要做一些日志记录


正确的处理方法是什么?捕获所有异常

捕获所有IOException是合理的,但以下是不可靠的:

    catch (Base64DecodingException e) {
        e.printStackTrace();
        response.getOutputStream().print("An error occurred");
    }
首先,您应该使用日志框架(例如log4j),而不是通过调用
e.printStackTrace()
将诊断写入stderr

其次,向响应输出写入类似的错误消息可能是错误的

  • 这条消息毫无信息
  • 此时客户端不会期望出现错误消息
  • 如果它需要错误消息,客户端将无法区分错误消息和真实数据。。。没有所有服务器错误消息的硬连线知识
向HTTP客户端报告错误的首选方法是在HTTP响应中设置4xx或5xx状态代码,并(理想情况下)设置错误消息。但是,只有当响应尚未“提交”并且打开响应输出流提交响应时,才能执行此操作


最后,一般来说,对于I/O异常,您不能采用这种方法向客户机写入错误消息。如果I/O异常指示输出连接已断开,则将消息写入响应流只会引发另一个异常。

处理此问题的正确方法是什么?捕获所有IOException?您可以捕获它,但无法以任何明智的方式处理它。您无法恢复响应,也无法再向客户端写入任何位。你最多可以记录它。但是这些信息有多有用呢?客户端刚刚决定中止下载或导航离开,或者网络断开,或者其PC崩溃。统计数据很有趣,但实际上仅此而已。相关主题:Java中任何地方都没有“字节数组太大而无法一次写入”这样的东西。这个输出流已经被分块,请参阅堆栈跟踪。@EJP。是的,我知道,但是Java抽象和真实世界是两码事。一句毫无意义的话。我重复一遍,没有字节数组太大而不能用Java编写的情况。如果你有相反的证据,请提供。我们在这里讨论的是阻塞I/O,它循环直到写入完成或出现异常。@EJP。这不是OP在这里得到的——例外吗?不是因为你给出的理由,这是假的。他几乎可以肯定得到了它,因为客户机已经断开了连接,正如“ClientAbortException”所示。我想查看与IOException关联的文本。我想是“连接重置”。