When downloading files from a remote server, a single file can be streamed directly to the client. However, when multiple files are requested, they must be grouped into a temporary ZIP archive on the server. After the archive is sent to the client via the browser's native download dialog, the temporary file should be removed from the server's filesystem.
Backend Implementation
The server determines whether to send a single file or a compressed archive based on the number of requested items. The temporary archive is created within the application's real path, ensuring it resides in a writable directory like Tomcat's webapps folder.
File archive = null;
ZipOutputStream zipOut = null;
String archiveName = "";
if (itemIds.length == 1 && fileMeta.getType() == 0) {
RemoteResource resource = resourceService.getById(fileMeta.getId());
URL remoteUrl = new URL("http://remote-host/" + resource.getStorageId());
HttpURLConnection connection = (HttpURLConnection) remoteUrl.openConnection();
connection.setRequestMethod("GET");
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setRequestProperty("Content-Type", "application/octet-stream");
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Charset", "UTF-8");
byte[] data = toByteArray(connection.getInputStream());
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" +
new String(fileMeta.getName().getBytes("UTF-8"), "ISO-8859-1"));
response.getOutputStream().write(data);
} else {
for (String id : itemIds) {
fileMeta = structureService.getById(Integer.parseInt(id));
if (archive == null) {
archiveName = request.getServletContext().getRealPath("/") + "/" + fileMeta.getName() + ".zip";
archive = new File(archiveName);
zipOut = new ZipOutputStream(new FileOutputStream(archive));
}
addEntryToZip(zipOut, fileMeta, fileMeta.getName());
}
// Stream the generated ZIP file to the response
streamFileToResponse(archiveName.substring(archiveName.lastIndexOf("/") + 1), archive, response);
}
if (zipOut != null) zipOut.close();
if (archive != null) archive.delete(); // Clean up the temporary archive
File Streaming Helper
A utility method handles reading the generated archive and writing it to the HTTP response output stream, setting the appropriate headers for a ZIP download.
public static void streamFileToResponse(String fileName, File file, HttpServletResponse response) {
try {
BufferedInputStream input = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[input.available()];
input.read(buffer);
input.close();
BufferedOutputStream output = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=" +
new String(fileName.getBytes("UTF-8"), "ISO-8859-1"));
output.write(buffer);
output.flush();
output.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
Frontend Download Trigger
To initiate the download using the browser's native save dialog instead of handling raw data via AJAX, a hidden iframe can be dynamically injected into the DOM. Pointing the iframe's source to the download URL automatically triggers the file save prompt.
function triggerNativeDownload(downloadUrl) {
const helper = {};
if (!helper.frame) {
const iframe = document.createElement("iframe");
helper.frame = iframe;
document.body.appendChild(helper.frame);
}
helper.frame.src = downloadUrl;
helper.frame.style.display = "none";
}