Попробуйте вместо этого.Вместо того, чтобы использовать ZipOutputStream
, чтобы обернуть FileOutputStream
, записать свой zip-файл в файл, а затем скопировать его в поток вывода клиента, вместо этого просто используйте ZipOutputStream
, чтобы обернуть поток вывода клиента так, чтобы при добавлении записей zipи данные он идет непосредственно к клиенту.Если вы хотите также сохранить его в файл на сервере, тогда вы можете сделать ZipOutputStream
запись в разделенный поток вывода, чтобы записать оба местоположения одновременно.
@RequestMapping(value = "/download", method = RequestMethod.GET, produces = "application/zip")
public ResponseEntity<StreamingResponseBody> getFile() throws Exception {
InputStream[] streamsToZip = getStreamsFromAzure();
// You could cache already created zip files, maybe something like this:
// String[] pathsOfResourcesToZip = getPathsFromAzure();
// String zipId = getZipId(pathsOfResourcesToZip);
// if(isZipExist(zipId))
// // return that zip file
// else do the following
StreamingResponseBody streamResponse = clientOut -> {
FileOutputStream zipFileOut = new FileOutputStream("test.zip");
ZipOutputStream zos = new ZipOutputStream(new SplitOutputStream(clientOut, zipFileOut));
for (InputStream in : streamsToZip) {
addToZipFile(zos, in);
}
};
return ResponseEntity.ok()
.header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + "download.zip")
.contentType(MediaType.parseMediaType("application/zip")).body(streamResponse);
}
private void addToZipFile(ZipOutputStream zos, InputStream fis) throws IOException {
ZipEntry zipEntry = new ZipEntry(generateFileName());
zos.putNextEntry(zipEntry);
byte[] bytes = new byte[1024];
int length;
while ((length = fis.read(bytes)) >= 0) {
zos.write(bytes, 0, length);
}
zos.closeEntry();
fis.close();
}
public static class SplitOutputStream extends OutputStream {
private final OutputStream out1;
private final OutputStream out2;
public SplitOutputStream(OutputStream out1, OutputStream out2) {
this.out1 = out1;
this.out2 = out2;
}
@Override public void write(int b) throws IOException {
out1.write(b);
out2.write(b);
}
@Override public void write(byte b[]) throws IOException {
out1.write(b);
out2.write(b);
}
@Override public void write(byte b[], int off, int len) throws IOException {
out1.write(b, off, len);
out2.write(b, off, len);
}
@Override public void flush() throws IOException {
out1.flush();
out2.flush();
}
/** Closes all the streams. If there was an IOException this throws the first one. */
@Override public void close() throws IOException {
IOException ioException = null;
for (OutputStream o : new OutputStream[] {
out1,
out2 }) {
try {
o.close();
} catch (IOException e) {
if (ioException == null) {
ioException = e;
}
}
}
if (ioException != null) {
throw ioException;
}
}
}
При первом запросе на архивирование набора ресурсов вы не будете знать, каким будет размер полученного zip-файла, поэтому вы не можете отправить длину вместе с ответом, так как вы передаете файл как онзаархивирован.
Но если вы ожидаете, что будут повторные запросы для того же набора ресурсов, которые будут заархивированы, то вы можете кэшировать ваши zip-файлы и просто возвращать их при любых последующих запросах;Вы также будете знать длину кэшированного zip-файла, чтобы вы могли отправлять его и в ответе.
Если вы хотите сделать это, вам нужно будет последовательно создавать один и тот же идентификатор для каждой комбинации.из ресурсов, которые будут заархивированы, так что вы можете проверить, были ли эти ресурсы уже заархивированы и вернуть кэшированный файл, если они были.Возможно, вы сможете отсортировать идентификаторы (возможно, полные пути) ресурсов, которые будут заархивированы, и объединить их, чтобы создать идентификатор для zip-файла.