How to cancel a dynamic data delivery to client?

Good morning,
I have a dynamic byte array that needs to be sent to the user as a “file download” like this:

        Anchor anchor = new Anchor(new StreamResource(filename, () ->
                new ByteArrayInputStream(api.get());
        ), "");

        Button button = new Button("JSON", VaadinIcon.CLOUD_DOWNLOAD.create());
        anchor.getElement().setAttribute("download", true);
        anchor.getElement().appendChild(button.getElement());

Unfortunately, the api.get() does not always deliver a response. It might be that there is no data available leading to a null. In this case, I would like to show a Notification to the end user.

How can I cancel the file delivery to the user if I have no data available? Even when raising a NullPointerExcepiton on the call a broken empty file is downloaded in the users browser. Any ideas are appreciated. Thanks!

My current solution shows a Notification, but downloads an empty, broken file anyhow, which is really not nice:

        anchor.setHref(new StreamResource(filename, () -> {
            byte[] bytes = Objects.requireNonNullElseGet(api.get(), () -> new byte[]{});
            ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);

            getUI().ifPresent(ui ->
                    ui.access(() -> {
                        progressBar.setVisible(false);
                        button.setEnabled(true);

                        if (bytes.length == 0) {
                            Notification.show("No data for selection", 2000, Notification.Position.MIDDLE);
                        }
                    })
            );

            return inputStream;
        }));

There is technically no way to stop an already started download.

Ideas to fix this challenge:

  • create a two-click download “Generate Download” button that calls the API, checks for the result and shows a Download link or notification
  • query the API multiple times until it returns an appropriate result
  • explain to the user that empty files are bad
  • fix the API

Hi @quirky-zebra , Thank you very much for the idea input. My hope was, that I can initiate the download from the backend side. But if I understand correctly, the click of the button initiates it. I think the two-click download is my way to go.

You could also try to play around with the DynamicFileDownloader of @quintessential-ibex and see if you can adopt his ideas to e.g. return 404 on the Download Processing if nothing could be created (low level and fiddling) https://github.com/viritin/flow-viritin/blob/v23/src/main/java/org/vaadin/firitin/components/DynamicFileDownloader.java

I made a new release :sunglasses:

Now if if you throw a runtime exception during file generation, the file generation stops (like before), but it doesn’t print out the exception like before :blush:

Hi @quintessential-ibex Thank you very much for your input. I just tried flow-vitrinyesterday and it is of great help. Really nice wrapper! With the old version the file download continued upon throwing an exception. So, I am excited to try your new release. :slightly_smiling_face:

1.6.1 ought to be in the Maven repository.

HI @quintessential-ibex, I kindly ask you for help. I have updated to 1.6.1. and see the change you have made in the handleRequest. Anyhow, even without writing to the the outputStream a broken file is downloaded by the browser as shown in the screenshots. The problem is, that the user thinks he has received a file, but when opening it, it is empty. My goal is, to not let the browser download anything and instead raise a message to the user. Here is my example code:

add(new DynamicFileDownloader("Throw Exception", "foobar.txt",
        outputStream -> {
            throw new RuntimeException("Test");
        }).asButton()
);

Any idea?

Yeah, what is already sent over the wire is there. I don’t know (and I don’t think) there is much we can do there at HTTP level. Heades are already sent.

What you could do is show some BIG red warning notification in the UI. If somebody gets a better idea what we could do, I’m all ears.

One other workaround: first write the file to a temporary byte array (or tmp file) and when you have that build, only then start to stream that to the end user. But, that comsumes either memory or disk space.

Thank you. I already implemented the warning notification for the user. At least it gives immediate feedback. But could you elaborate on the “headers already sent” topic? Does this mean, that the header is already sent, even if I don’t write a single byte to the outputstream?

Well if you haven written anything to the body of an http request, then I guess it should be possible, but my component don’t don’t provide reference to request object in the callback anymore.

Tried this and it seems to work to some extent. Try to use 500 for failed file generation · viritin/flow-viritin@f810a86 · GitHub

Might depend on how the servlet implementation buffers the body stream :man_shrugging:

Can you try the change locally (copy the class from github)

Dear @quintessential-ibex , Thank you very much giving it a new try. I have used the suggested changes, but the browsers still downloads the empty foobar.txt file.

It might be that there is nothing we can do (except to start suggesting some new browser features). IIRC, when I tested locally, Safari didn’t respect 500 at all, but Chrome and Firefox showed some error in the download (and didn’t create the empty/half downloaded fiel).

Was your trial in local environment or in production? In production, possible front proxy might affect as well.