views:

383

answers:

2

I have a servlet that allows users to download (potentially large) zip files from a web page. If the user clicks on a link to download a zip file, code similar to the following is executed in the servlet:

response.setContentType("application/zip");
response.setHeader("Content-disposition", "attachment; filename=foo.zip");
response.setHeader("Pragma", "");
response.setHeader("Cache-Control", "no-store");

ZipOutputStream out = new ZipOutputStream(response.getOutputStream());
// write entries to the zip file...
...
out.close()

However, if the user refreshes or navigates away from the page after the download begins and before it completes (in Firefox 3.5.7), the download will fail. The following error pops up:

C:\blah\foo.zip.part could not be saved, because the source file could not be read.

Try again later, or contact the server administrator.

Any ideas on how I can make sure the download continues in this case?

UPDATE: The link that initiates the download is a plain vanilla link. Interestingly, the behavior is different on IE. Clicking on links elsewhere on the site (from the currently loaded screen) seem not to load (the browser status bar says "Waiting for https://mysite/clicked_linky.do..."), blocking until the download completes. Typing a different URL into the address bar or using a shortcut/favorite link navigates away from the page, but the download continues as expected. Only Firefox seems to display the exact behavior I described above, although the IE blocking is not optimal.

+4  A: 

This should in fact not happen. The download counts as a separate request which is supposed to be run in the background independently from the parent page once invoked. How exactly are you firing the download request? By a plain vanilla link or a link which (incorrectly) fires an ajaxical request to run the download?

At any way, you at least clearly want to be able to resume downloads. In this case you need to send at least the Accept-Ranges, ETag and Last-Modified response headers along the download accordingly. The client can then ask to resume the download by sending the If-Range and Range request headers with respectively the file identifier and a specified byte range which you could use in combination with RandomAccessFile to send the remaining bytes. You can find more information and a servlet sample in this article.

That's the theory. In your particular case, it's a bit tricker as you're zipping the files on the fly. You'll need to write the zip into a temporary folder of the server's local disk file system first and then stream from it and finally delete the file only when the download is successfully completed (i.e. the out.close() didn't throw IOException). You can identify the associated zip file with help of request parameter or pathinfo or maybe a key in session.

Update: as per your update: I honestly don't know and I've never experienced it, but at least I can tell that you're not the only one who suffered from this problem. At least, implementing the resume capabilities as described before may be a solution to this particular problem as Firefox would then automatically resume the download without jerking about an incomplete part.

Update 2: after having a little thought after reading your update and the browser behaviours, it look like that there's a fairly huge time gap between firing the actual request and the arrival of the response headers. I don't know the exact details how you load the files, but it look like that there is a time cost in gathering the ZIP files (maybe you're loading them from a networked filesystem or database beforehand?) and that you set/send the response headers only after you have gathered all the ZIP files. Try setting the headers and doing the output.flush() before doing the expensive task. This way the browser will get the headers as soon as possible and it will know what it may expect.

BalusC
I agree, a standalone download servlet request should not mess up if the client takes the browser to another page, or refreshes. I've implemented a download servlet in Java and it doesn't experience any of these problems.
Kaleb Brasee
It's a plain vanilla link. I updated the question.
Jeff
A: 

I suspect it's an artefact of using servlets - probably as a result of reassigning the thread. Certainly I have no such problem with a similar setup written in PHP (where each request is handled by a (effectively) new process.

HTH

C.

symcbean