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



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

样本代码:

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"

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

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

  1. 去掉MyFaces ExtensionsFilter

  2. 不要让请求碰到MyFaces ExtensionsFilter

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

选项2在两个方面可行:

  1. 更改过滤器的URL模式,这样下载请求就不会碰到它。如果你能弄清楚你到底需要ExtensionsFilter的URL,那么你可以相应地更改它的<filter-mapping>,这样它只会在这些URL上生效,而不是在FacesServlet上全局生效。

    例如,当仅在/upload.jsf上调用时,用<url-pattern>:替换<servlet-name>

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

    只有当您实际从同一个页面执行下载操作时,这才是麻烦的。

  2. 更改下载请求的URL,使其不符合筛选器。假设您不能将这些文件放在公共web内容中,也不能将带有这些文件的文件夹添加为另一个上下文(例如,因为这些文件是动态生成的(,一种方法是将所有下载服务代码从JSF托管的bean移到普通的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());
        }
    }
    

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

    如下调用它(假设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");
    // ...
    

更改下载URL,使其与Web应用程序中的任何映射URL都不匹配,这样默认Servlet将处理它,而不需要任何这些讨厌的过滤器。根本不需要任何代码。

或者在前面粘贴一个ApacheHTTP,它将直接为文件提供服务,并将其他请求代理到Servlet容器。

您可以通过从扩展过滤器中排除下载URL来解决此问题,我们可以通过创建自定义扩展过滤器并排除下载URL,如下所示:

步骤1创建如下的自定义过滤器

public class CustomExtensionsFilter implements Filter { 

private ExtensionsFilter extensionFilter = new ExtensionsFilter();
private List excludedUrls;
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
        throws IOException, ServletException {
    String requestURI = ((HttpServletRequest) req).getRequestURI();
    
    if (!excludedUrls.contains(requestURI)) {
        System.out.println(" passing to  ExtensionsFilter  " + requestURI);
        extensionFilter.doFilter(req, resp, chain);
    }
    // Forward the request to the next filter or servlet in the chain.
    chain.doFilter(req, resp);
}
public void init(FilterConfig filterConfig) {
    String excludePattern = filterConfig.getInitParameter("excludedUrls");
    
    excludedUrls = Arrays.asList(excludePattern.split(","));
    extensionFilter.init(filterConfig);
}
public void destroy() {
    extensionFilter.destroy();
}

}

步骤2.用自定义扩展过滤器类替换扩展过滤器类

<filter>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <filter-class>com.filters.CustomExtensionsFilter</filter-class>
    <init-param>
        <param-name>maxFileSize</param-name>
        <param-value>20m</param-value>
    </init-param>
    
    <init-param>
        <param-name>excludedUrls</param-name>
        <param-value>/faces/largefiles/download.xhtml</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>MyFacesExtensionsFilter</filter-name>
    <servlet-name>faces</servlet-name>
</filter-mapping>

您可以在excludedUrls param 中添加更多以逗号分隔的URL

最新更新