【问题标题】:Servlet IOException when streaming a Byte Array流式传输字节数组时的 Servlet IOException
【发布时间】:2010-06-01 21:40:17
【问题描述】:

我偶尔会从我拥有的 Servlet 中收到一个 IOException,该 Servlet 将字节数组写入输出流以提供文件下载功能。

此下载 servlet 具有合理的流量(每月 10 万次点击),这种异常很少发生,大约每月 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)

以及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");
        }
    }

【问题讨论】:

标签: java tomcat servlets


【解决方案1】:

处理这个问题的正确方法是什么?捕获所有 IOExceptions?

捕获所有 IOExceptions 是合理的,但以下是狡猾的:

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

首先,您应该使用日志框架(例如 log4j),而不是通过调用 e.printStackTrace() 将诊断信息写入 stderr。

其次,在响应输出中写入错误消息可能是错误的。

  • 该消息没有提供任何信息。
  • 此时客户端不会收到错误消息。
  • 如果它期待错误消息,客户端将无法区分错误消息和真实数据......如果没有对所有服务器错误消息的硬连线知识。

向 HTTP 客户端报告错误的首选方法是在 HTTP 响应中设置 4xx 或 5xx 状态代码,并(理想情况下)设置错误消息。但是你只能在响应没有“提交”的情况下这样做,并且打开响应输出流会提交响应。

最后,一般情况下,您不能采用这种方法向客户端写入 I/O 异常的错误消息。如果 I/O 异常表明输出连接已断开,则将消息写入响应流只会引发另一个异常。

【讨论】:

    【解决方案2】:

    我怀疑客户端(浏览器)在将字节数组写入套接字期间断开连接。

    【讨论】:

    • 处理这个问题的正确方法是什么?捕获所有 IOExceptions?
    • 你可以抓住它,但你不能以任何明智的方式处理它。您无法恢复响应,也无法再向客户端写入任何位。您可以最高记录它。但这些信息有多大用处?客户端刚刚决定中止下载或导航离开,或者网络中断,或者它的 PC 已经崩溃。统计数据很有趣,但实际上仅此而已。
    【解决方案3】:

    如果客户端断开连接(即取消下载或关闭浏览器),您将收到IOException

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

    【讨论】:

      【解决方案4】:

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

      正常情况下从HTTP请求中记录字节数组大小和“User-Agent”字段,并在抛出异常时肯定记录此信息。

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

      另外,我建议您查看commons-fileupload。即使您没有在项目中使用它,也请浏览源代码并查看它们是如何下载文件的。他们在文件上传过程中曾经有很多 IOExceptions,但这些在以后的版本中得到了修复。您可以尝试找出究竟修复了什么。

      【讨论】:

      • 在 Java 中的任何地方都没有“字节数组太大而无法一次写入”这样的东西。这个输出流已经被分块了,查看堆栈跟踪。
      • @EJP。是的,我知道,但是 Java 抽象和现实世界是两个不同的东西。
      • 一句无意义的话。我再说一遍,没有字节数组太大而无法在 Java 中的任何地方编写。如果您有相反的证据,请提供。我们这里讨论的是阻塞 I/O,它会一直循环直到写入完成或发生异常。
      • @EJP。这不是 OP 在这里得到的——例外吗?
      • 不是因为你给出的原因,这是虚假的。他几乎肯定得到了它,因为客户端已经断开连接,如“ClientAbortException”所示。我想查看与 IOException 关联的文本。我希望它是“连接重置”。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-09-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-02
      • 1970-01-01
      相关资源
      最近更新 更多