1

we're currently working on the service that would archive the data and return it to the user as a ZipOutputStream. What we're currently looking for is an option to completely terminate the operation if something goes wrong on the server side. With our current implementation (just closing the response output stream) errors result in a malformed zip at the user side, but it can't be told if the archive is malformed or not before attempting to unzip it. The desired behavior would be something like download termination (from a browser perspective, for instance, it would result in an unsuccessful download indication (red cross icon or something similar, depending on the browser) explicitly telling the user that something went wrong). We're using Spring Boot, so any java code examples would really be appreciated, but if you know the underlying HTTP mechanism that is responsible for this kind of behavior, and can point in the right direction, that would be much appreciated too. Here's what we have as of now (output being a response output stream of a Spring REST controller (HttpServletResponse.getOutputStream()) :

try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
    try {
        for (ZipRecordFile fileInfo : zipRecord.listZipFileOverride()) {
            InputStream fileStream = getFileStream(fileInfo.s3region(), fileInfo.s3bucket(),
            fileInfo.s3key());
            ZipEntry zipEntry = new ZipEntry(fileInfo.fileName());
            zipOutputStream.putNextEntry(zipEntry);
            fileStream.transferTo(zipOutputStream);
        }
    }
    catch (Exception e) {
        outputStream.close();
    }
}
pcsutar
  • 1,715
  • 2
  • 9
  • 14
ZeeG
  • 33
  • 7
  • tried calling `zipOutputStream.finish()` instead of `outputStream.close()`, didn't help at all, the archive is good and well and even can be unzipped properly. – ZeeG Dec 27 '21 at 13:57
  • why not removing the zip if an exception is caught? – jhamon Dec 27 '21 at 14:02
  • @jhamon not entirely understand what you mean by "removing a zip". There's no zip to remove at any point, there's only a ZipOutputStream that is streaming the data to the client resulting in a zip archive on a client side – ZeeG Dec 27 '21 at 17:01

1 Answers1

2

There isn't a (clean) way to do what you want:

Once you have started writing the ZIP file to the output stream, it is too late to change the HTTP response code. The response code is sent at the start of response.

Therefore, there is no proper way for the HTTP server to tell the HTTP client: "Hey ... ignore that ZIP file I sent you 'cos it is corrupt".

So what are the alternatives?

  1. On the server side, create the entire ZIP as an in-memory object or write it to a temporary file. If you succeed, send an 2xx response followed by the ZIP data. If you fail, send a 4xx or 5xx response.

    The main problem is that you need enough memory or file system space to hold the ZIP file.

  2. Redesign your HTTP API so that the client can sent a second request to check if the first request's response contained a complete ZIP file.

  3. You might be able to exploit MIME multipart encoding; see RFC 1341. Each part of a well-formed MIME multipart has a start marker and an end-marker. What you could try is to have your web-app construct the multipart stream containing the ZIP "by hand". If it decides it must abort the ZIP, it could just close the output stream without adding the required end marker.

    The main problem with this is that you are depending on the HTTP stack on the client side to tell the browser (or whatever) that the multipart is corrupted. Furthermore, the browser (or whatever) must not pass on the partial (i.e. corrupt) ZIP file on to the user. I'm not sure if you can rely on (particular) web browsers to do that.

  4. If you are running the download via custom code on the client side, you could conceivably implement your own encapsulation protocol. The effect would be the same as for 3 ... but you wouldn't be abusing the MIME spec.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Thanks for the response! Yeah, we've considered the first option, but we don't want to load all the content in memory since the files might be quite big. As for the third option, in our current implementation Spring is performing chunked streaming as a response. I'm not sure if this is what you meant by a multipart stream. As far as I understand, closing stream mid-way, as we currently do in catch block, sends an empty chunk indicating the end of the chunked response, which, in turn, results in a malformed archive, but not in the "failed" download, as desired. – ZeeG Dec 27 '21 at 16:52
  • By "multipart stream" I mean the output stream which you would be assembling consisting of files wrapped in a ZIP wrapped as a part in a MIME multipart. The point is that the part has a distinct end marker, and there is a chance that the browser (or whatever) will detect and handle a missing marker differently to what you are currently seeing. Indeed, if you can arrange to handle the decoding of the multipart in (client side) application code, you can make sure that it is detected and handled differently. – Stephen C Dec 28 '21 at 14:12