我有一个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
显然正在服务器内存中缓冲整个响应,直到最后一位。因此,您基本上有两个选项:
-
去掉MyFaces
ExtensionsFilter
。 -
不要让请求碰到MyFaces
ExtensionsFilter
。
如果您的web应用程序中的某些功能需求确实需要选项1,那么选项1可能非常激烈,但如果可以找到替代方案,则选项1是可行的。例如,如果您只需要它来处理文件上传,那么您可以考虑使用一个替代组件库,甚至是标准的JSF 2.2组件库。
选项2在两个方面可行:
-
更改过滤器的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>
只有当您实际从同一个页面执行下载操作时,这才是麻烦的。
-
更改下载请求的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