我有一个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代码时:
此页面上有一部分内容:
"When the ExtensionsFilter is enabled, and the DefaultAddResources implementation is
used then there is no way to avoid having the response buffered in memory"
我猜这些过滤器会缓冲堆上的响应并导致问题。有没有办法阻止此过滤器应用于特定页面/链接?或者我应该采用另一种方式来处理这个问题?
答案 0 :(得分:5)
MyFaces ExtensionsFilter
显然正在缓冲服务器内存中的整个响应,直到最后一位。因此,您基本上有两种选择:
摆脱MyFaces ExtensionsFilter
。
请勿让请求到达MyFaces ExtensionsFilter
。
如果您的Web应用程序中的某些功能需求确实需要它,那么选项1可能会非常激烈,但如果可以找到替代方案,则可行。例如。如果您只是需要它来处理文件上传,那么您可以考虑使用替代组件库甚至标准JSF 2.2。
选项2可以通过两种方式实现:
更改过滤器的网址格式,以便下载请求无法点击。如果您可以确定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>
当您从同一页面实际执行下载操作时,这很麻烦。
更改下载请求网址,使其无法访问过滤器。如果您不能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。由于该请求未能点击FacesServlet
,ExtensionsFilter
也不会被点击。
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容器。