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

vkx*_*vkx 3 jsf jsp 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();
}
Run Code Online (Sandbox Code Playgroud)

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

以下是例外的一些部分:

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)
Run Code Online (Sandbox Code Playgroud)

当我查看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"
Run Code Online (Sandbox Code Playgroud)

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

Bal*_*usC 5

MyFaces ExtensionsFilter显然是缓冲服务器内存中的整个响应,直到最后一点.你基本上有两个选择:

  1. 摆脱MyFaces ExtensionsFilter.

  2. 不要让请求命中MyFaces ExtensionsFilter.

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

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

  1. 更改过滤器的URL模式,以便下载请求不会命中它.如果你可以确定你需要哪些URL ExtensionsFilter,那么你可以相应地改变它<filter-mapping>,这样它只会在这些URL上而不是在全局上FacesServlet.

    例如,当它应该/upload.jsf仅被调用时,替换<servlet-name><url-pattern>:

    <filter-mapping>
        <filter-name>MyFacesExtensionsFilter</filter-name>
        <url-pattern>/upload.jsf</url-pattern>
    </filter-mapping>
    
    Run Code Online (Sandbox Code Playgroud)

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

  2. 更改下载请求URL,使其不会访问过滤器.前提是您不能将这些文件放在公共Web内容中,也不能将文件夹作为另一个上下文添加(例如,因为这些文件是动态生成的),一种方法是移动所有下载服务代码JSF托管bean到普通的servilla servlet.然后让链接URL或表单操作指向该servlet.由于该请求不会被击中FacesServlet,因此ExtensionsFilter也不会被击中.

    例如

    @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());
        }
    
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    <h:outputLink value="#{request.contextPath}/files/#{bean.filename}">
        <h:outputText value="Download #{bean.filename}" />
    </h:outputLink>
    
    Run Code Online (Sandbox Code Playgroud)

    如果下载需要其他参数,只需使用<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>
    
    Run Code Online (Sandbox Code Playgroud)

    然后可以在servlet中获得如下:

    String foo = request.getParameter("foo");
    String bar = request.getParameter("bar");
    // ...
    
    Run Code Online (Sandbox Code Playgroud)