堆空间中的缓冲响应会导致大文件出现问题

时间:2014-07-01 08:25:52

标签: jsp jsf servlets myfaces

我有一个Web服务器项目,我在尝试下载大文件时遇到异常。该文件通过流读取并写入ServletOutputStream。

示例代码:

private void readFromInput(BufferedInputStream fis,
    ServletOutputStream sout) throws IOException
    {
    byte[] buf = new byte[4096];
    int c = 0;
    while ((c = fis.read(buf)) != -1)
    {
        sout.write(buf, 0, c);    
    }
    fis.close();
}

当我查看回溯时,我看到一些过滤器被执行。

以下是例外的部分内容:

javax.servlet.ServletException: #{DownloaderBean.actionDownload}: 
java.lang.OutOfMemoryError: Java heap space
javax.faces.webapp.FacesServlet.service(FacesServlet.java:256)
org.apache.myfaces.webapp.filter.ExtensionsFilter.doFilter(ExtensionsFilter.java:144)
org.ajax4jsf.framework.ajax.xmlfilter.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:127)
org.ajax4jsf.framework.ajax.xmlfilter.BaseFilter.doFilter(BaseFilter.java:277)
....
....
....

java.lang.OutOfMemoryError: Java heap space
java.io.ByteArrayOutputStream.write(Unknown Source)
org.apache.myfaces.webapp.filter.ExtensionsResponseWrapper$MyServletOutputStream.write(ExtensionsResponseWrapper.java:135)

当我查看ExtensionFilter代码时:

http://grepcode.com/file/repo1.maven.org/maven2/org.apache.myfaces.tomahawk/tomahawk12/1.1.7/org/apache/myfaces/webapp/filter/ExtensionsFilter.java

此页面上有一部分内容:

"When the ExtensionsFilter is enabled, and the DefaultAddResources implementation is 
used then there is no way to avoid having the response buffered in memory"

我猜这些过滤器会缓冲堆上的响应并导致问题。有没有办法阻止此过滤器应用于特定页面/链接?或者我应该采用另一种方式来处理这个问题?

2 个答案:

答案 0 :(得分:5)

MyFaces ExtensionsFilter显然正在缓冲服务器内存中的整个响应,直到最后一位。因此,您基本上有两种选择:

  1. 摆脱MyFaces ExtensionsFilter

  2. 请勿让请求到达MyFaces ExtensionsFilter

  3. 如果您的Web应用程序中的某些功能需求确实需要它,那么选项1可能会非常激烈,但如果可以找到替代方案,则可行。例如。如果您只是需要它来处理文件上传,那么您可以考虑使用替代组件库甚至标准JSF 2.2。

    选项2可以通过两种方式实现:

    1. 更改过滤器的网址格式,以便下载请求无法点击。如果您可以确定ExtensionsFilter确切需要哪些网址,那么您可以相应地更改其<filter-mapping>,以便它只在FacesServlet上完全启用这些网址而不是全局网址。

      E.g。如果仅在/upload.jsf上调用它,请将<servlet-name>替换为<url-pattern>

      <filter-mapping>
          <filter-name>MyFacesExtensionsFilter</filter-name>
          <url-pattern>/upload.jsf</url-pattern>
      </filter-mapping>
      

      当您从同一页面实际执行下载操作时,这很麻烦。

    2. 更改下载请求网址,使其无法访问过滤器。如果您不能just put those files in the public web content, nor can add the folder with the files as another context(例如,因为这些文件是动态生成的),一种方法是将所有下载服务代码从JSF托管bean移动到普通的servilla servlet。然后让链接URL或表单操作指向该servlet。由于该请求未能点击FacesServletExtensionsFilter也不会被点击。

      E.g。

      @WebServlet("/files/*")
      public class FileServlet extends HttpServlet {
      
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              String filename = request.getPathInfo().substring(1);
      
              // Just do your job to get the File or InputStream, depending on the functional requirements.
              // This kickoff example just allocates a file in the file system.
              File file = new File("/path/to/files", filename);
              response.setHeader("Content-Type", getServletContext().getMimetype(filename));
              response.setHeader("Content-Length", String.valueOf(file.length()));
              response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
              Files.copy(file.toPath(), response.getOutputStream());
          }
      
      }
      

      (注意:如果你还没有使用Servlet 3.0,只需用@WebServlet中常用的servlet映射替换web.xml;如果你还没有在Java 7上,只需用通常的Files#copy() / InputStream循环样板替换OutputStream

      调用它如下(假设您在JSP上使用旧版JSF 1.2,因为您链接到了JSF 1.2的Tomahawk源代码;因此不支持模板文本中的EL)。

      <h:outputLink value="#{request.contextPath}/files/#{bean.filename}">
          <h:outputText value="Download #{bean.filename}" />
      </h:outputLink>
      

      如果下载需要其他参数,请使用<f:param>

      传递它们
      <h:outputLink value="#{request.contextPath}/files/#{bean.filename}">
          <f:param name="foo" value="#{bean.foo}" />
          <f:param name="bar" value="#{bean.bar}" />
          <h:outputText value="Download #{bean.filename}" />
      </h:outputLink>
      
      然后可以在servlet中获取

      ,如下所示:

      String foo = request.getParameter("foo");
      String bar = request.getParameter("bar");
      // ...
      

答案 1 :(得分:1)

更改下载网址,使其与您的网络应用中的任何映射网址都不匹配,以便默认的Servlet可以处理它,而不会有任何这些讨厌的过滤器。根本不需要代码。

或者在前面粘贴一个Apache HTTP,直接提供文件并将其他请求代理到您的Servlet容器。