Ao desenvolver aplicações web, frequentemente surge a necessidade de permitir que o usuário baixe um conjunto de arquivos de uma única vez. Em vez de enviar múltiplas requisições, uma abordagem altamente eficiente consiste em compactar dinamicamente esses arquivos no lado do servidor e transmitir o fluxo binário resultante diretamente para o navegador.
Implementação no Backend com Java
No serviodr, podemos utilizar a classe ZipOutputStream para criar o arquivo compactado em memória e escrevê-lo diretamente no OutputStream da resposta HTTP. A utilização do bloco try-with-resources garante o fechamento automático e seguro dos fluxos de entrada e saída, otimizando a estrutura original e evitando vazamentos de memória.
public void exportarArquivosCompactados(HttpServletResponse response, List<String> caminhosDosArquivos, String nomeDoPacote) {
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=\"" + nomeDoPacote + ".zip\"");
try (ZipOutputStream fluxoZip = new ZipOutputStream(response.getOutputStream())) {
byte[] buffer = new byte[8192];
for (String caminho : caminhosDosArquivos) {
File arquivoOrigem = new File(caminho);
if (!arquivoOrigem.exists()) {
continue;
}
ZipEntry entradaZip = new ZipEntry(arquivoOrigem.getName());
fluxoZip.putNextEntry(entradaZip);
try (FileInputStream fluxoEntrada = new FileInputStream(arquivoOrigem)) {
int bytesLidos;
while ((bytesLidos = fluxoEntrada.read(buffer)) != -1) {
fluxoZip.write(buffer, 0, bytesLidos);
}
}
fluxoZip.closeEntry();
}
fluxoZip.finish();
} catch (IOException excecao) {
System.err.println("Falha ao gerar o pacote ZIP: " + excecao.getMessage());
}
}
Processamento no Frontend com JavaScript
Para consumir esse fluxo binário sem recarregar a página, o cliente deve realizar uma requisição assíncrona configurada para receber um objeto Blob. A API fetch moderna simplifica o tratamento de cabeçalhos de autenticação e a manipulação da resposta em comparação com o tradicional XHR. Após receber os dados, um link temporário é gerado na memória para forçar o download imediato no navegador do usuário.
async function baixarPacoteImagens(urlRequisicao, tokenAutenticacao, nomeArquivo) {
try {
const resposta = await fetch(urlRequisicao, {
method: 'GET',
headers: {
'Authorization': `Bearer ${tokenAutenticacao}`
}
});
if (!resposta.ok) {
throw new Error('Erro na comunicação com o servidor');
}
const dadosBlob = await resposta.blob();
// Compatibilidade e gatilho de download
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(dadosBlob, nomeArquivo);
} else {
const urlTemporaria = window.URL.createObjectURL(dadosBlob);
const elementoLink = document.createElement('a');
elementoLink.href = urlTemporaria;
elementoLink.download = nomeArquivo;
document.body.appendChild(elementoLink);
elementoLink.click();
// Limpeza de recursos na memória
document.body.removeChild(elementoLink);
window.URL.revokeObjectURL(urlTemporaria);
}
} catch (erro) {
console.error('O download não pôde ser concluído:', erro);
}
}