Vaadin 7.2.3 push+long-polling communications exceptions on PDF download

I am experimenting with server push again with 7.2.3, using long-polling, and it mostly is working okay. I am also using Tomcat 7.0.47 on Java 1.7.0_25.

I set pushmode=automatic and transport=long-polling in my web.xml to test it out.

However, when I try to download a PDF file nothing ever appears. If I click to download again, I get this exception:


2014-06-26 18:02:00,954 ERROR (com.esignforms.open.vaadin.EsfVaadinUI) init().VaadinSession.ErrorHandler.error()
ClientAbortException: java.io.IOException: Broken pipe
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:413)
at org.apache.tomcat.util.buf.ByteChunk.append(ByteChunk.java:371)
at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:438)
at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:426)
at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:91)
at com.vaadin.server.DownloadStream.writeResponse(DownloadStream.java:305)
at com.vaadin.server.FileDownloader.handleConnectorRequest(FileDownloader.java:158)
at com.esignforms.open.vaadin.widget.OnDemandFileDownloader.handleConnectorRequest(OnDemandFileDownloader.java:116)
at com.vaadin.server.ConnectorResourceHandler.handleRequest(ConnectorResourceHandler.java:83)
at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1405)
at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:237)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.filters.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:108)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.filters.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:108)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:611)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1721)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:724)
Caused by: java.io.IOException: Broken pipe
at sun.nio.ch.FileDispatcherImpl.write0(Native Method)
at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:47)
at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:94)
at sun.nio.ch.IOUtil.write(IOUtil.java:51)
at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:466)
at org.apache.tomcat.util.net.SecureNioChannel.flush(SecureNioChannel.java:135)
at org.apache.tomcat.util.net.SecureNioChannel.write(SecureNioChannel.java:488)
at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:94)
at org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:174)
at org.apache.coyote.http11.InternalNioOutputBuffer.writeToSocket(InternalNioOutputBuffer.java:163)
at org.apache.coyote.http11.InternalNioOutputBuffer.flushBuffer(InternalNioOutputBuffer.java:242)
at org.apache.coyote.http11.InternalNioOutputBuffer.addToBB(InternalNioOutputBuffer.java:213)
at org.apache.coyote.http11.InternalNioOutputBuffer.access$000(InternalNioOutputBuffer.java:41)
at org.apache.coyote.http11.InternalNioOutputBuffer$SocketOutputBuffer.doWrite(InternalNioOutputBuffer.java:268)
at org.apache.coyote.http11.filters.ChunkedOutputFilter.doWrite(ChunkedOutputFilter.java:119)
at org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:192)
at org.apache.coyote.Response.doWrite(Response.java:517)
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:408)
… 37 more

If I turn pushmode back to ‘disabled’, then I can download the PDF just fine.

I don’t think they were directly related , but my catalina.out also showed this error:


Jun 26, 2014 6:00:52 PM com.vaadin.server.communication.PushHandler disconnect
SEVERE: Could not get UI. This should never happen
Jun 26, 2014 6:01:10 PM com.vaadin.server.communication.PushHandler disconnect
SEVERE: Could not get UI. This should never happen

Other types of download seem to work just fine… like HTML files, XML data, CSV data, Excel spreadsheet.

Quick update: When I run the same code on my local PC (in Eclipse), I am able to do the PDF downloads. So it’s something about the Internet being involved, which was the same stopper when I tried to use push in 7.1. That is why I had hoped that 7.2 with long-polling would perhaps work as I’ve read lots of issues for others who are behind proxies and the like (there are no proxies in my setup).

No thoughts on what this might be? I noted it has nothing to do with long polling, per se, as it happens regardless of transport mode when push is set to automatic.

I added a debug statement to help identify when the data is sent back to Vaadin for delivery, and if sometimes gets the PDF downloaded, but at other times it does not, and if it fails (seems to silently process the download request in that the button even happens, the PDF is returned, but there’s no download open/save, etc.)

2014-07-03 14:47:48,711 DEBUG (OpenESignForms) ReportDetailView downloadDocumentAsPDFSubButton.getStream() - tranId: 92f893ec-edd0-45be-ac81-e53d62926845; data.length: 67137
2014-07-03 14:48:11,567 DEBUG (OpenESignForms) ReportDetailView downloadDocumentAsPDFSubButton.getStream() - tranId: 92f893ec-edd0-45be-ac81-e53d62926845; data.length: 67137
2014-07-03 14:48:11,803 ERROR (com.esignforms.open.vaadin.EsfVaadinUI) init().VaadinSession.ErrorHandler.error()
ClientAbortException:  java.io.IOException: Broken pipe
        at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:371)
        at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:333)
        at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:101)
        at com.vaadin.server.DownloadStream.writeResponse(DownloadStream.java:310)
        at com.vaadin.server.FileDownloader.handleConnectorRequest(FileDownloader.java:158)
        at com.esignforms.open.vaadin.widget.OnDemandFileDownloader.handleConnectorRequest(OnDemandFileDownloader.java:116)
        at com.vaadin.server.ConnectorResourceHandler.handleRequest(ConnectorResourceHandler.java:83)
        at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1405)
        at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:237)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.apache.catalina.filters.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:108)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at com.esignforms.open.filter.AddExpiresHeaderFilter.doFilter(AddExpiresHeaderFilter.java:98)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.apache.catalina.filters.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:108)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:611)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
        at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1721)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:724)
Caused by: java.io.IOException: Broken pipe
        at sun.nio.ch.FileDispatcherImpl.write0(Native Method)
        at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:47)
        at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:94)
        at sun.nio.ch.IOUtil.write(IOUtil.java:51)
        at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:466)
        at org.apache.tomcat.util.net.SecureNioChannel.write(SecureNioChannel.java:477)
        at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:94)
        at org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:174)
        at org.apache.tomcat.util.net.SecureNioChannel.flush(SecureNioChannel.java:121)
        at org.apache.coyote.http11.InternalNioOutputBuffer.writeToSocket(InternalNioOutputBuffer.java:166)
        at org.apache.coyote.http11.InternalNioOutputBuffer.flushBuffer(InternalNioOutputBuffer.java:242)
        at org.apache.coyote.http11.InternalNioOutputBuffer.flush(InternalNioOutputBuffer.java:94)
        at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:805)
        at org.apache.coyote.Response.action(Response.java:174)
        at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:366)

My code to getStream() for the FileDownloader looks like this, so note that the debug occurs just before I return the BufferedInputStream:

                            @Override
                            public InputStream getStream() {                            
                                EsfVaadinUI vaadinUi = EsfVaadinUI.getInstance();
                                downloadSnapshotPopupButton.setPopupVisible(false);
                                TransactionDocument tranDoc = transaction.getTransactionDocument(tranPartyDoc.getTransactionDocumentId());
                                String snapshotDocument = tranPartyDoc.getSnapshotDocument();
                                if ( EsfString.isBlank(snapshotDocument) ) {
                                    vaadinUi.showStatus(vaadinUi.getMsg("ReportDetailView.button.downloadDocumentSnapshot.document.nodata"));
                                    return null;
                                }
                                // Convert to PDF
                                HtmlToPdf htmlToPdf = new HtmlToPdf();
                                byte[] data = tranDoc.getDocumentVersion().isLandscape() ? htmlToPdf.generateLandscapeSignedPdf(snapshotDocument) : htmlToPdf.generateSignedPdf(snapshotDocument);
                                if ( data == null ) {
                                    vaadinUi.showError(null, vaadinUi.getMsg("ReportDetailView.button.downloadDocumentSnapshot.documentAsPDF.nopdf.error"));
                                    return null;
                                }
                                vaadinUi.getEsfapp().debug("ReportDetailView downloadDocumentAsPDFSubButton.getStream() - tranId: " + transaction.getId() + "; data.length: " + data.length);
                                return new BufferedInputStream(new ByteArrayInputStream(data));
                            }

It seems to be a timing issue perhaps related to the use of multiple channels with PUSH enabled? If I turn push off, the downloads work every time. With push automatic, the error will occur on the second or third attempt (often the first download succeeds, the second seems to disappear, and the third results in the exception).

Hope someone has some insights on what might cause this and if there’s any solution in 7.2 that can allow me to try out push. Thanks!

I haven’t yet digged deeper into this, but at a glance, it seems that Atmosphere (which is the Push framework Vaadin uses) filters out
certain file types
. For a sanity check, could try if sending JPG and GIF files also fails?

That’s interesting, eh? I’ll give it a try, though sadly most of the file downloads I have are .pdf, .html and .xml. I can check for some images, but they are mostly uploads, not downloads, and when images are shown, they are done by an image servlet we have that fetches from the DB and returns them, not through a FileDownloader or related Vaadin classes so I don’t think they get into the push mix.

It’s too bad because, while I’m not yet taking specific advantage of push yet, this is the only issue I’ve run into so far compared to the 7.1 when push worked well on my desktop but failed miserably once deployed over the Internet.

I’ll see what I can do to test image downloads and see (heck, even then most images are .png in our world, and oddly that filter didn’t include them!).

I’m finding this harder to prove one way or another.

I do find that pushmode=automatic and transport=websocket|long-polling work just fine on my local PC. I can download PDFs at will. But when I move to a deployment over the Internet, everything works okay except for those PDF downloads. I sometimes get a successful download (figure that perhaps it is returned without needing push?), but mostly the PDF download just spins and ends as if it was downloaded, but the browser has nothing to show. And if I retry, I generally get the broken pipe exception. Turn pushmode=disabled and the PDFs download fine every time.

I’m not sure about that Atmosphere filter, but is there a way for me to override it’s init-param EXCLUDE_FILE to not include pdf|PDF in my web.xml for example?

Do any of these atmosphere warnings mean something is misconfigured on my end?

[font=courier new]
Jul 09, 2014 10:47:17 AM org.atmosphere.cpr.AtmosphereFramework doInitParams
WARNING: SessionSupport error. Make sure you define org.atmosphere.cpr.SessionSupport as a listener in web.xml instead

Jul 09, 2014 10:47:17 AM org.atmosphere.cpr.DefaultAnnotationProcessor fallbackToManualAnnotatedClasses
WARNING: Unable to detect annotations. Application may fail to deploy.
[/font]

By the way, I am able to rename the PDF downloads themselves to use an extension like .Pdf and that also fails the same way. Based on the regex in the Atmosphere EXCLUDE_FILE list, it shouldn’t have matched since they only accounted for .pdf or .PDF, so that may not be related. Of course, not even sure why they’d want to exclude some files in the first place.

Looks like pushmode=disabled will remain, but it’s a shame since it does solve a few timing issues we have. Like the button in the popup button layout has
setDisableOnClick(true)
so the button is disabled when clicked (to prevent double click downloads) and
thisButton.setPopupVisible(false)
in the getStream() overridden method for dyamic file creation for the download (to close the popup button’s layout once the download begins). Sometimes these don’t happen, but with push enabled, they get sent back and update the UI nicely.

As a test, with push enabled, I added code that disables push just before the download:

                PushMode currPushMode = vaadinUi.getPushConfiguration().getPushMode();
                if ( currPushMode != PushMode.DISABLED )
                    vaadinUi.getPushConfiguration().setPushMode(PushMode.DISABLED);

and re-enables at the end:
[font=courier new]

                    if ( currPushMode != PushMode.DISABLED )
                        vaadinUi.getPushConfiguration().setPushMode(currPushMode);          

[/font]
In this scenario, with push enabled, the PDF download work just fine. But the UI of course suffers. The closing of the popup button, for example, occurs several seconds later, and often the entire UI refreshes.

So, this is not a solution or workaround, but it does show that push and the FileDownloader with PDFs have some real issues. If I turn off push entirely, the PDF downloads work fine. If I temporarily disable just for the PDF downloads, the PDF downloads work fine, but the UI becomes odd. If I turn push off, the PDF downloads work fine, but then I don’t get push!

Hope someone can help resolve this. It’s been going on since 7.0 when push was first presented as a new capability. I’d like to make use of it. Thanks!

I upgraded to Java 1.8.0_11 and Tomcat 8.0.9 to see if this helped, but it does not.

It’s just so odd that I can download HTML and XML files repeatedly without any issues, but PDFs cause me problems in short order when using PUSH (and PDF downloads work fine with push disabled).

I changed my web.xml to try manual push, and added this to my ‘requestEnd’ callback via VaadinServletService:

                           vaadinUi.access(new Runnable() {
                                @Override
                                public void run() {
                                    vaadinUi.push();
                                }
                            });

With this in place, I no longer get exceptions on PDF downloads, but they do still get swallowed (you see my server code returns the PDF bytestream) on occasion with no indication in the browser that the download took place. Some of the PDF downloads work, while some “disappear.” The downloads of HTML and XML data never have any issues.

When push is automatic, where does the “ui.push()” take place? I’m wondering if there’s some place I be sure that push will behave like automatic, but perhaps avoid this PDF download conflict in FileDownloader’s overridden getStream() that seems to occur frequently but not always.

I added a “sleep 250msecs” in the ‘run()’ method above before the manual push, and now my PDFs seem to download reliably again. It seems there’s some sort of timing conflict, which is probably why the PDF sometimes can download and sometimes now when using ‘automatic’ push or ‘manual’ push without a delay. I’m not even sure what it means to spawn a runnable thread that sleeps 250msecs before doing a push in the ‘requestEnd’ method means, but it sounds like an odd thing to do. I tried 100msecs and worked “better” but I could still see occasional PDF downloads disappear.

Also, I noted that when I end my session on user logoff using VaadinService.getCurrentRequest().getWrappedSession().invalidate(); the system throws this exception:



Jul 26, 2014 12:55:50 PM com.vaadin.server.communication.PushHandler disconnect
SEVERE: Session expired before push was disconnected. This should never happen
com.vaadin.server.SessionExpiredException
at com.vaadin.server.VaadinService.findOrCreateVaadinSession(VaadinService.java:655)
at com.vaadin.server.VaadinService.findVaadinSession(VaadinService.java:518)
at com.vaadin.server.communication.PushHandler.disconnect(PushHandler.java:340)
at com.vaadin.server.communication.PushHandler.onDisconnect(PushHandler.java:319)
at org.atmosphere.cpr.AtmosphereResourceImpl.onDisconnect(AtmosphereResourceImpl.java:663)
at org.atmosphere.cpr.AtmosphereResourceImpl.notifyListeners(AtmosphereResourceImpl.java:582)
at org.atmosphere.cpr.AtmosphereResourceImpl.notifyListeners(AtmosphereResourceImpl.java:564)
at org.atmosphere.cpr.AsynchronousProcessor.completeLifecycle(AsynchronousProcessor.java:426)
at org.atmosphere.interceptor.OnDisconnectInterceptor.inspect(OnDisconnectInterceptor.java:64)
at org.atmosphere.cpr.AsynchronousProcessor.invokeInterceptors(AsynchronousProcessor.java:280)
at org.atmosphere.cpr.AsynchronousProcessor.action(AsynchronousProcessor.java:146)
at org.atmosphere.cpr.AsynchronousProcessor.suspended(AsynchronousProcessor.java:95)
at org.atmosphere.container.Servlet30CometSupport.service(Servlet30CometSupport.java:66)
at org.atmosphere.cpr.AtmosphereFramework.doCometSupport(AtmosphereFramework.java:1802)
at com.vaadin.server.communication.PushRequestHandler.handleRequest(PushRequestHandler.java:144)
at com.vaadin.server.communication.PushRequestHandler.handleSessionExpired(PushRequestHandler.java:177)
at com.vaadin.server.VaadinService.handleSessionExpired(VaadinService.java:1517)
at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1418)
at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:237)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)

Assuming this is the only way I can enable push (not even sure if it’s doing any real work with such a setup) – manual with a delay ui.push() – is there a way for me to disconnect push before I end the session to avoid the above exception? I want the HTTP session to end on user logoff to ensure all resources related to that session are no longer available.

We had the same problem with FileDownloader and Broken Pipes. We found that if FileDownloader extends a Button, then we never get a broken pipe error. However, if it extends other components like CustomField or Checkbox, we usually get this brone pipe error. I hope that helps.

We extend a Button and do see the issue (it’s tied to push more than anything), but that Button is often in PopupButton’s layout. Perhaps that clue, Abbas, will help team Vaadin pinpoint what’s going on. It’s appears to be a timing issue, too, since it sometimes work even with push enabled, but generally will not, but if we made push manual and add a delay and call to manual push, it seems to work, albeit at the cost of not using push in a normal and usable way (we don’t want to add a delay via manual push to every request).

I struggled with Push for quite a while and finally disabled it altogether. It is very random, works for some clients on some browsers, and doesn’t work for other clients, completely a 50-50 chance of working. I tried Long Polling, Streaming, and WebSockets with no success. To me, the whole Vaadin Push seems like an unstable feature, although it is not completely Vaadin’s fault.

I am actually working again on FileDownloader and experiencing the broken pipe problem again. Will update here if I managed to solve it.

One thing I tried and it seems to be working for every component (not just Buttons) was to subclass FileDownloader and add content-length to the response header. It is a hacky workaround, but might solve your timing issues with Push mode.

public class MyFileDownloader extends FileDownloader {

    private long fileSize = 0;
    
    public MyFileDownloader(Resource resource) {
        this(resource, 0);
    }
    
    public MyFileDownloader(Resource resource, long fileSize) {
        super(resource);
        this.fileSize = fileSize;
    }
    
    @Override
    public boolean handleConnectorRequest(VaadinRequest request,
            VaadinResponse response, String path) throws IOException {
        if (!path.matches("dl(/.*)?")) {
            // Ignore if it isn't for us
            return false;
        }

        Resource resource = getFileDownloadResource();
        if (resource instanceof ConnectorResource) {
            DownloadStream stream = ((ConnectorResource) resource).getStream();
            
            if ( stream == null )
                return false;

            if (stream.getParameter("Content-Disposition") == null) {
                // Content-Disposition: attachment generally forces download
                stream.setParameter("Content-Disposition",
                        "attachment; filename=\"" + stream.getFileName() + "\"");
            }

            // Content-Type to block eager browser plug-ins from hijacking the
            // file
            if (isOverrideContentType()) {
                stream.setContentType("application/octet-stream;charset=UTF-8");
            }
            if ( fileSize > 0 ) {
                stream.setParameter("Content-Length", "" + fileSize);
            }
            stream.writeResponse(request, response);
            return true;
        } else {
            return false;
        }
    }
}

I can look into this, but setting the size is not easily done when using a dynamic resource for the file downloader since the size is not known until the input stream is finally created and returned in the getStream() callback. I’ll have to see if I can conjure a scheme to allow it to be specified on the fly.

I was able to give this a test and it seemed to have no real impact. Sometimes the download works, sometimes the request just gets swallowed and the browser returns to normal without any download taking place and often throwing an exception about “broken pipes.”