【问题标题】:Buffering response in heap space cause problems for large files堆空间中的缓冲响应会导致大文件出现问题
【发布时间】:2023-03-18 17:59:01
【问题描述】:

我有一个 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"

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

【问题讨论】:

  • 下载是由ajax请求触发的吗?

标签: jsp jsf servlets myfaces


【解决方案1】:

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

  1. 摆脱 MyFaces ExtensionsFilter

  2. 不要让请求命中 MyFaces ExtensionsFilter

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

    只有在您实际从同一页面执行下载操作时,这才比较麻烦。

  2. 更改下载请求 URL,使其不会命中过滤器。如果您不能just put those files in the public web content, nor can add the folder with the files as another context(例如,因为这些文件是动态生成的),一种方法是将所有下载服务代码从 JSF 托管 bean 移动到普通的 vanilla 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,只需替换 @ 987654337@ 由通常的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>
    

    如果下载需要额外的参数,只需使用&lt;f:param&gt;传递它们:

    <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");
    // ...
    

【讨论】:

  • 感谢您的回答。让我问一下,在你的最后一个例子中,bean应该是jsp bean吧?不托管是什么意思?那么,如果它不是托管 bean,它不会调用 JSF 吗?它如何决定是否调用 JSF?
  • Faces servlet url-pattern 是 "/*" atm ,更改它会导致我无法处理的其他问题:)。在不改变它的情况下,我可以创建“/files”路径来调用 JSP 吗?
  • 它只是一个 JSF 托管的 bean。只是下载操作的 URL 不应该打到FacesServlet。如果您将下载 servlet 映射到更具体的 URL 模式(如上述答案中的 /files/*)上,则将 FacesServlet 映射到 /* 并不重要。
【解决方案2】:

更改下载 URL,使其与您的 web 应用程序中的任何映射 URL 不匹配,以便默认 Servlet 处理它,而无需任何这些讨厌的过滤器。完全不需要代码。

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

【讨论】:

    【解决方案3】:

    您可以通过从扩展过滤器中排除下载 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>
    

    您可以在 excludeUrls 参数中添加更多以逗号分隔的网址

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      • 2021-11-29
      • 1970-01-01
      • 2012-10-25
      • 2013-02-17
      • 2013-05-03
      相关资源
      最近更新 更多