Until now we used JBoss AS 7.1 which has a tomcat as front-server. We now upgraded to Wildfly (JBoss 8.0) which ships with undertow as a tomcat replacement.
For our filedownloads, we are reading the input stream of the file, and writing this to the external context's response output stream. This worked well in JBoss AS 7.1 - even for large files. In Undertow WE receive the following exception even for pretty "small" files:
13:04:43,292 ERROR [io.undertow.request] (default task-15) Blocking request failed HttpServerExchange{ GET /project/getFile.xhtml}: java.lang.RuntimeException: org.xnio.channels.FixedLengthOverflowException
at io.undertow.servlet.spec.HttpServletResponseImpl.responseDone(HttpServletResponseImpl.java:527)
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:287)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:227)
at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:73)
at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:146)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:168)
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:687)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [rt.jar:1.7.0_51]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [rt.jar:1.7.0_51]
at java.lang.Thread.run(Thread.java:744) [rt.jar:1.7.0_51]
Caused by: org.xnio.channels.FixedLengthOverflowException
at io.undertow.conduits.AbstractFixedLengthStreamSinkConduit.write(AbstractFixedLengthStreamSinkConduit.java:97)
at org.xnio.conduits.Conduits.writeFinalBasic(Conduits.java:132) [xnio-api-3.2.0.Final.jar:3.2.0.Final]
at io.undertow.conduits.AbstractFixedLengthStreamSinkConduit.writeFinal(AbstractFixedLengthStreamSinkConduit.java:137)
at org.xnio.conduits.ConduitStreamSinkChannel.writeFinal(ConduitStreamSinkChannel.java:104) [xnio-api-3.2.0.Final.jar:3.2.0.Final]
at io.undertow.channels.DetachableStreamSinkChannel.writeFinal(DetachableStreamSinkChannel.java:172)
at io.undertow.servlet.spec.ServletOutputStreamImpl.writeBufferBlocking(ServletOutputStreamImpl.java:580)
at io.undertow.servlet.spec.ServletOutputStreamImpl.close(ServletOutputStreamImpl.java:614)
at io.undertow.servlet.spec.HttpServletResponseImpl.closeStreamAndWriter(HttpServletResponseImpl.java:451)
at io.undertow.servlet.spec.HttpServletResponseImpl.responseDone(HttpServletResponseImpl.java:525)
... 9 more
The getFile.xhtml is invoking the download and the following code is used to copy the stream:
(Try, catch, logs and error handling removed to save some space)
public void downloadFile(FileEntity fileEntity) {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
ec.responseReset();
ec.setResponseContentType(getMimeType(fileEntity.getFile()));
ec.setResponseContentLength(new Long(fileEntity.getFile().length()).intValue());
ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + ConversionHelper.validateFilename(fileEntity.getDisplayFileName()) + "\"");
OutputStream output = ec.getResponseOutputStream();
FileInputStream fis = new FileInputStream(fileEntity.getFile());
IOUtils.copy(fis, output);
fc.responseComplete();
}
I noticed that removing the line
ec.setResponseContentLength(new Long(fileEntity.getFile().length()).intValue());
makes it work again. However the ResponseConentLength is "correct". Using
ec.setResponseContentLength(new Long(fileEntity.getFile().length()).intValue() + 9);
(note the +9) solves this. Using +8 -> FixedLengthOverflow, using +10 -> FixedLengthUnderflow
The +9 is independent of the filesize. Any idea? Looks strange to me...
Update:
Found the problem.
It has to do with the change of the server engine. Just not sure if it is directly related to undertow, or more an error with wildfly / JSF (or if it was an error in Jboss and now is working correctly):
To invoke the download, we used something like this in the getFile.xhtml:
value="#{getFileController.fileId}">
this would usually produce:
value
if the value of the label would be a string.
since we reset the response inside download(), is removed again. Then, using a fixed response size that's equal the file-length and calling responseComplete() basically ommits the trailing . Undertow however seems to ignore the responseComplete()-Call and tries to append to the response, notices, that the end of the (fixed) response stream is reached, and therefore throws the mentioned exception.
That's why providing a size of +9 solves this error - but causes corrupt files, cause \n will be appended to the file.
obviously the usage of an output label was bad practice there. But since we reseted and manually filled the response stream and called responseComplete() that was working fine.
the fix is obviously to not produce any (not required) html tags on that page:
value="#{getFileController.fileId}">
Beside the bad design there - shouldn't call responseComplete() ommit any additional writing to the response stream, including any write ATTEMPTS?
The docu says:
Signal the JavaServer Faces implementation that the HTTP response for this request has already been generated (such as an HTTP redirect), and that the request processing lifecycle should be terminated as soon as the current phase is completed.
So, if the Faces Implementation is told that the Response has been send - why would it try to append something?