I have a web server project, i get an exception while trying to download large files. The file is read and written to ServletOutputStream via streams.
Sample code :
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();
}
When i look at the backtrace, i see some filters are executed.
Here is the some parts of the exception :
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)
When i look at that ExtensionFilter code :
http://grepcode.com/file/repo1.maven.org/maven2/org.apache.myfaces.tomahawk/tomahawk12/1.1.7/org/apache/myfaces/webapp/filter/ExtensionsFilter.java
There is a part on this page :
"When the ExtensionsFilter is enabled, and the DefaultAddResources implementation is
used then there is no way to avoid having the response buffered in memory"
I guess these filters buffer the response on heap and cause the problem. Is there a way to prevent this filters apply on a spesific page/link? Or should i follow another way to handle this?
The MyFaces ExtensionsFilter
is apparently buffering the entire response in server's memory until the last bit. You've thus basically 2 options:
Get rid of the MyFaces ExtensionsFilter
.
Don't let the request hit the MyFaces ExtensionsFilter
.
Option 1 is maybe drastic if you actually need it for some functional requirement in your web application, but doable if an alternative can be found. E.g. if you merely needed it to process file uploads, then you may consider using an alternative component library or even standard JSF 2.2 one for this.
Option 2 is doable in 2 ways:
Change the filter's URL pattern so that the download request doesn't hit it. If you can figure out on which URLs exactly you need the ExtensionsFilter
, then you could alter its <filter-mapping>
accordingly so that it only kicks in on exactly those URLs instead of globally on the FacesServlet
.
E.g. When it should be invoked on /upload.jsf
only, replace <servlet-name>
by <url-pattern>
:
<filter-mapping>
<filter-name>MyFacesExtensionsFilter</filter-name>
<url-pattern>/upload.jsf</url-pattern>
</filter-mapping>
This is only troublesome when you actually perform the download action from the very same page.
Change the download request URL so that it doesn't hit the filter. Provided that you can't just put those files in the public web content, nor can add the folder with the files as another context (e.g. because those files are generated on the fly), one way would be moving all the download serving code from the JSF managed bean to a plain vanilla servlet. Then just let the link URL or form action point to that servlet instead. As that request won't hit the FacesServlet
, the ExtensionsFilter
won't be hit either.
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());
}
}
(note: if you're still not on Servlet 3.0 yet, just replace @WebServlet
by the usual servlet mapping in web.xml
; and if you're still not on Java 7 either, just replace Files#copy()
by the usual InputStream
/OutputStream
loop boilerplate)
Invoke it like follows (assuming legacy JSF 1.2 on JSP, given the fact that you linked to source code of Tomahawk for JSF 1.2; and thus EL in template text isn't supported).
<h:outputLink value="#{request.contextPath}/files/#{bean.filename}">
<h:outputText value="Download #{bean.filename}" />
</h:outputLink>
If the download requires additional parameters, just pass them using <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>
which can then be obtained in the servlet as follows:
String foo = request.getParameter("foo");
String bar = request.getParameter("bar");
// ...
Change the download URL so that it doesn't match any mapped URL in your webapp, so that the default Servlet will handle it, without any of these pesky filters. No code required at all.
Or stick an Apache HTTP in front that will serve the file directly and proxy other requests to your Servlet container.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With