Introdução
Você pode fazer tudo passar ExternalContext
. No JSF 1.x, você pode obter o HttpServletResponse
objeto bruto ExternalContext#getResponse()
. No JSF 2.x, você pode usar um monte de novos métodos delegados, como ExternalContext#getResponseOutputStream()
sem a necessidade de pegar HttpServletResponse
o JSF sob os capuzes.
Na resposta, você deve definir o Content-Type
cabeçalho para que o cliente saiba qual aplicativo associar ao arquivo fornecido. E, você deve definir o Content-Length
cabeçalho para que o cliente possa calcular o andamento do download, caso contrário, será desconhecido. E, você deve definir o Content-Disposition
cabeçalho para attachment
se quiser uma caixa de diálogo Salvar como , caso contrário, o cliente tentará exibi-la embutida. Finalmente, basta escrever o conteúdo do arquivo no fluxo de saída da resposta.
A parte mais importante é chamar FacesContext#responseComplete()
para informar ao JSF que ele não deve realizar navegação e renderização após você ter escrito o arquivo para a resposta, caso contrário, o final da resposta será poluído com o conteúdo HTML da página, ou em versões mais antigas do JSF , você receberá um IllegalStateException
com uma mensagem como getoutputstream() has already been called for this response
quando a implementação JSF chama getWriter()
para renderizar HTML.
Desligue o ajax / não use o comando remoto!
Você só precisa se certificar de que o método de ação não seja chamado por uma solicitação ajax, mas que seja chamado por uma solicitação normal conforme você dispara com <h:commandLink>
e <h:commandButton>
. Solicitações Ajax e comandos remotos são manipulados por JavaScript que, por sua vez, por motivos de segurança, não tem recursos para forçar um diálogo Salvar como com o conteúdo da resposta ajax.
No caso de você estar usando, por exemplo <p:commandXxx>
, PrimeFaces , então você precisa certificar-se de desligar explicitamente o ajax via ajax="false"
atributo. Caso você esteja usando ICEfaces, então você precisa aninhar um <f:ajax disabled="true" />
no componente de comando.
Exemplo genérico de JSF 2.x
public void download() throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
ec.responseReset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
ec.setResponseContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ExternalContext#getMimeType() for auto-detection based on filename.
ec.setResponseContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.
OutputStream output = ec.getResponseOutputStream();
// Now you can write the InputStream of the file to the above OutputStream the usual way.
// ...
fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.
}
Exemplo genérico de JSF 1.x
public void download() throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse();
response.reset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
response.setContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ServletContext#getMimeType() for auto-detection based on filename.
response.setContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.
OutputStream output = response.getOutputStream();
// Now you can write the InputStream of the file to the above OutputStream the usual way.
// ...
fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.
}
Exemplo comum de arquivo estático
Caso você precise transmitir um arquivo estático do sistema de arquivos do disco local, substitua o código conforme abaixo:
File file = new File("/path/to/file.ext");
String fileName = file.getName();
String contentType = ec.getMimeType(fileName); // JSF 1.x: ((ServletContext) ec.getContext()).getMimeType(fileName);
int contentLength = (int) file.length();
// ...
Files.copy(file.toPath(), output);
Exemplo de arquivo dinâmico comum
Caso precise transmitir um arquivo gerado dinamicamente, como PDF ou XLS, basta fornecer output
onde a API em uso espera um OutputStream
.
Por exemplo, iText PDF:
String fileName = "dynamic.pdf";
String contentType = "application/pdf";
// ...
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, output);
document.open();
// Build PDF content here.
document.close();
Por exemplo, Apache POI HSSF:
String fileName = "dynamic.xls";
String contentType = "application/vnd.ms-excel";
// ...
HSSFWorkbook workbook = new HSSFWorkbook();
// Build XLS content here.
workbook.write(output);
workbook.close();
Observe que você não pode definir o comprimento do conteúdo aqui. Portanto, você precisa remover a linha para definir o comprimento do conteúdo da resposta. Tecnicamente, isso não é problema, a única desvantagem é que o usuário final verá um progresso de download desconhecido. Caso isso seja importante, você realmente precisa gravar em um arquivo local (temporário) primeiro e, em seguida, fornecê-lo conforme mostrado no capítulo anterior.
Método de utilidade
Se você estiver usando a biblioteca de utilitários JSF OmniFaces , poderá usar um dos três Faces#sendFile()
métodos convenientes usando a File
, ou an InputStream
, ou a byte[]
e especificando se o arquivo deve ser baixado como um anexo ( true
) ou embutido ( false
).
public void download() throws IOException {
Faces.sendFile(file, true);
}
Sim, este código está completo como está. Você não precisa invocar responseComplete()
e assim por diante. Este método também lida corretamente com cabeçalhos específicos do IE e nomes de arquivo UTF-8. Você pode encontrar o código-fonte aqui .
InputStream
infraestrutura parap:fileDownload
, e ainda não consegui converterOutputStream
paraInputStream
. Agora está claro que mesmo um ouvinte de ação pode alterar o tipo de conteúdo da resposta e, de qualquer maneira, a resposta será respeitada como um download de arquivo no lado do agente do usuário. Obrigado!