到目前为止,我们使用的是JBoss AS 7.1,它的前端服务器是tomcat。我们现在升级到Wildfly (JBoss 8.0),它与undertow一起发布,作为tomcat的替代品。
对于filedownloads,我们读取文件的输入流,并将其写入外部上下文的响应输出流。这在JBoss AS 7.1中工作得很好——即使对于大文件也是如此。在Undertow中,即使对于非常"小"的文件,我们也会收到以下异常:
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
getFile.xhtml
正在调用下载,下面的代码用于复制流:
(删除Try, catch,日志和错误处理以节省空间)
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();
}
我注意到删除
一行ec.setResponseContentLength(new Long(fileEntity.getFile().length()).intValue());
使它再次工作。然而,ResponseConentLength是"正确的"。使用
ec.setResponseContentLength(new Long(fileEntity.getFile().length()).intValue() + 9);
(注意+9
)解决了这个问题。使用+8
-> FixedLengthOverflow
,使用+10
-> FixedLengthUnderflow
+9
与文件大小无关。任何想法?
如果您调用output.close(),它应该有您想要的效果。这看起来像是JSF的问题,而与Undertow无关。
找到问题了
这与服务器引擎的变化有关。只是不确定它是否与undertow直接相关,或者更多的是wildfly/JSF的错误(或者如果它是Jboss中的错误,现在工作正常):
为了调用下载,我们在getFile.xhtml:
中使用了如下代码 <f:metadata>
<f:viewParam name="fileId" required="true"
value="#{getFileController.fileId}"></f:viewParam>
</f:metadata>
<h:outputLabel value="#{getFileController.download()}" />
通常会产生:
<label>value</label>
如果标签的值是字符串,则使用。
由于我们重置了download()
内部的响应,因此<label>
再次被删除。然后,使用与文件长度相等的固定响应大小并调用responseComplete()
基本上会提交末尾的</label>
。然而,Undertow似乎忽略了responseComplete()
-Call,并试图将</label>
附加到响应中,注意到(固定)响应流的末端已经到达,因此抛出上述异常。
这就是为什么提供+9
的大小解决了这个错误-但会导致损坏的文件,因为</label>n
将追加到文件。
显然在这里使用输出标签是不好的做法。但由于我们重置并手动填充响应流并调用responseComplete()
,因此工作正常。
修复显然是不产生任何(不需要的)HTML标签在该页上:
<f:metadata>
<f:viewParam name="fileId" required="true"
value="#{getFileController.fileId}"></f:viewParam>
<f:event listener="#{getFileController.dlNow()}" type="preRenderView"></f:event>
</f:metadata>
除了糟糕的设计之外-不应该调用responseComplete()
向响应流提交任何额外的写入,包括任何写尝试?
文档说:
所以,如果Faces Implementation 被告知响应已经发送-为什么它会尝试附加一些东西?通知HTTP响应的JavaServer Faces实现此请求已经生成(例如HTTP重定向),并且请求处理生命周期应该尽快终止当当前阶段完成时