3

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...

dognose
  • 20,360
  • 9
  • 61
  • 107
  • 1
    put a solution to answer so this question does not look like missing an answer – Leos Literak May 08 '14 at 20:06
  • @LeosLiterak might refactor depending on further answers. As of now I have still outstanding questions and maybe someone could provide a answer that deserves it. (Meaning: I know the cause and fixed it, but I do not know the "root-cause" :P ) – dognose May 08 '14 at 20:12
  • @LeosLiterak Moved the update of the post to another answer and flagged it as accepted. – dognose Mar 13 '18 at 14:03

2 Answers2

2

If you call output.close() it should have the effect you are after. This looks like an issue with JSF, and not related to Undertow.

Stuart Douglas
  • 847
  • 5
  • 4
  • Actually this does sound like an Undertow bug, because you have set the content length the output steam should close automatically when the content length has been hit. It is probably because stuff is written and then reset before the real response has been written. I have filed https://issues.jboss.org/browse/UNDERTOW-236. – Stuart Douglas May 09 '14 at 03:59
  • thx for filling the ticket. Will monitor and provide additional infos if required - but should be easy to reproduce i'd say. – dognose May 09 '14 at 11:50
1

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:

    <f:metadata>
        <f:viewParam name="fileId" required="true"
            value="#{getFileController.fileId}"></f:viewParam>
    </f:metadata>

    <h:outputLabel value="#{getFileController.download()}" />

this would usually produce:

<label>value</label>

if the value of the label would be a string.

since we reset the response inside download(), <label> is removed again. Then, using a fixed response size that's equal the file-length and calling responseComplete() basically ommits the trailing </label>. Undertow however seems to ignore the responseComplete()-Call and tries to append </label> 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 </label>\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:

    <f:metadata>
        <f:viewParam name="fileId" required="true"
            value="#{getFileController.fileId}"></f:viewParam>
        <f:event listener="#{getFileController.dlNow()}" type="preRenderView"></f:event>
    </f:metadata>

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?

dognose
  • 20,360
  • 9
  • 61
  • 107