baixar arquivo usando uma solicitação ajax

95

Desejo enviar uma "solicitação de download de Ajax" quando clico em um botão, então tentei desta forma:

javascript:

var xhr = new XMLHttpRequest();
xhr.open("GET", "download.php");
xhr.send();

download.php:

<?
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename= file.txt");
header("Content-Transfer-Encoding: binary");    
readfile("file.txt");
?>

mas não funciona conforme o esperado, como posso fazer? Agradeço antecipadamente

Manuel Di Iorio
fonte
2
Isso não vai funcionar, veja [esta questão] [1]. [1]: stackoverflow.com/questions/8771342/…
olegsv
2
Dolocation.href='download.php';
Musa

Respostas:

92

Atualização em 27 de abril de 2015

O atributo de download está em alta e chegando à cena do HTML5 . É compatível com Firefox e Chrome, e em breve chegará ao IE11. Dependendo de suas necessidades, você pode usá-lo em vez de uma solicitação AJAX (ou usando window.location), desde que o arquivo que deseja baixar seja da mesma origem do seu site.

Você sempre pode fazer a solicitação AJAX / window.locationum fallback usando algum JavaScript para testar se downloadé compatível e, se não for, alternando para chamar window.location.

Resposta original

Você não pode fazer com que uma solicitação AJAX abra o prompt de download, pois é necessário navegar fisicamente até o arquivo para solicitar o download. Em vez disso, você pode usar uma função de sucesso para navegar até download.php. Isso abrirá o prompt de download, mas não mudará a página atual.

$.ajax({
    url: 'download.php',
    type: 'POST',
    success: function() {
        window.location = 'download.php';
    }
});

Mesmo que isso responda à pergunta, é melhor apenas usar window.locatione evitar totalmente a solicitação AJAX.

Steven Lambert
fonte
39
Isso não chama o link duas vezes? Estou em um barco semelhante ... Estou passando muitas informações de segurança em cabeçalhos e sou capaz de analisar o objeto de arquivo na função de sucesso, mas não sei como acionar um prompt de download.
user1447679
3
Ele chama a página duas vezes, portanto, se você estiver consultando um banco de dados nessa página, isso significa 2 viagens ao banco de dados.
mmmmmm
1
@ user1447679 consulte para obter uma solução alternativa: stackoverflow.com/questions/38665947/…
John
1
Deixe-me explicar como isso me ajudou ... o exemplo poderia ter sido mais completo. com "download.php? get_file = true" ou algo assim ... Eu tenho uma função ajax que verifica alguns erros em um envio de formulário e, em seguida, cria um arquivo csv. Se a verificação de erro falhar, ele deve voltar com o motivo da falha. Se ele criar o CSV, estará dizendo ao pai que "vá em frente e busque o arquivo". Eu faço isso postando no arquivo ajax com a variável de formulário e postando um parâmetro DIFFERENT no mesmo arquivo dizendo "passe-me o arquivo que você acabou de criar" (caminho / nome está codificado no arquivo ajax).
Scott
4
Mas enviará solicitação 2 vezes, isso não é adequado
Dharmendrasinh Chudasama
43

Na verdade, você não precisa de Ajax para isso. Se você apenas definir "download.php" como o href no botão, ou, se não for um link, use:

window.location = 'download.php';

O navegador deve reconhecer o download binário e não carregar a página real, mas apenas servir o arquivo como um download.

Jelle Kralt
fonte
3
A linguagem de programação que você está usando para alterar window.location é JavaScript.
mikemaccana,
1
Você está certo @mikemaccana, eu realmente quis dizer ajax :).
Jelle Kralt
2
Tenho estado à caça de alto e baixo para uma solução e isso é tão elegante e perfeito. Muito obrigado.
Yangshun Tay
2
Claro, esta solução só funcionará se for um arquivo estático que já existe.
Krillgar de
1
Se o servidor responder com um erro, não haverá como permanecer na página principal sem ser redirecionado para uma página de erro pelo navegador. Pelo menos é isso que o Chrome faz quando o resultado de window.location retorna 404.
Ian
43

Para fazer o navegador baixar um arquivo, você precisa fazer a solicitação assim:

 function downloadFile(urlToSend) {
     var req = new XMLHttpRequest();
     req.open("GET", urlToSend, true);
     req.responseType = "blob";
     req.onload = function (event) {
         var blob = req.response;
         var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
         var link=document.createElement('a');
         link.href=window.URL.createObjectURL(blob);
         link.download=fileName;
         link.click();
     };

     req.send();
 }
João marcos
fonte
7
Isso funciona para mim, mas no firefox, eu precisava primeiro colocar uma tag <a> no DOM e referenciá-la como meu link em vez de criar um imediatamente para que o arquivo fosse baixado automaticamente.
Erik Donohoo
funciona, mas o que acontece se o arquivo for criado durante a execução? ele não funciona como quando o arquivo já foi criado.
Diego
@Taha eu testei isso no Edge e pareceu funcionar. Não sei sobre o IE embora. Meu cliente não tem como alvo os usuários do IE ;-) Tenho sorte? : D
Sнаđошƒаӽ
1
@ErikDonohoo Você pode criar a <a>tag com JS, "on the fly" também, mas precisa anexá-la a document.body. Você certamente deseja ocultá-lo ao mesmo tempo.
Márton Tamás
Obrigado @ João, há muito tempo que procurava esta solução.
Abhishek Gharai
20

Solução para vários navegadores, testada no Chrome, Firefox, Edge, IE11.

No DOM, adicione uma tag de link oculto:

<a id="target" style="display: none"></a>

Então:

var req = new XMLHttpRequest();
req.open("GET", downloadUrl, true);
req.responseType = "blob";
req.setRequestHeader('my-custom-header', 'custom-value'); // adding some headers (if needed)

req.onload = function (event) {
  var blob = req.response;
  var fileName = null;
  var contentType = req.getResponseHeader("content-type");

  // IE/EDGE seems not returning some response header
  if (req.getResponseHeader("content-disposition")) {
    var contentDisposition = req.getResponseHeader("content-disposition");
    fileName = contentDisposition.substring(contentDisposition.indexOf("=")+1);
  } else {
    fileName = "unnamed." + contentType.substring(contentType.indexOf("/")+1);
  }

  if (window.navigator.msSaveOrOpenBlob) {
    // Internet Explorer
    window.navigator.msSaveOrOpenBlob(new Blob([blob], {type: contentType}), fileName);
  } else {
    var el = document.getElementById("target");
    el.href = window.URL.createObjectURL(blob);
    el.download = fileName;
    el.click();
  }
};
req.send();
Leo
fonte
Bom código genérico. @leo você pode melhorar este código adicionando cabeçalhos personalizados como Authorization?
vibs2006
1
Obrigado @leo. É útil. O que você sugere adicionar window.URL.revokeObjectURL(el.href);depois el.click()?
vibs2006
O nome do arquivo estará errado se a disposição do conteúdo especificar um nome de arquivo não UTF8.
Mathew Alden
14

É possível. Você pode ter o download iniciado de dentro de uma função ajax, por exemplo, logo após o arquivo .csv ser criado.

Eu tenho uma função ajax que exporta um banco de dados de contatos para um arquivo .csv e, assim que termina, ele inicia automaticamente o download do arquivo .csv. Então, depois que obtenho o responseText e tudo está OK, redireciono o navegador assim:

window.location="download.php?filename=export.csv";

Meu arquivo download.php se parece com este:

<?php

    $file = $_GET['filename'];

    header("Cache-Control: public");
    header("Content-Description: File Transfer");
    header("Content-Disposition: attachment; filename=".$file."");
    header("Content-Transfer-Encoding: binary");
    header("Content-Type: binary/octet-stream");
    readfile($file);

?>

Não há atualização de página e o download do arquivo é iniciado automaticamente.

NOTA - Testado nos seguintes navegadores:

Chrome v37.0.2062.120 
Firefox v32.0.1
Opera v12.17
Internet Explorer v11
Pedro Sousa
fonte
1
Isso não é perigoso em termos de segurança?
Mickael Bergeron Néron
@ MickaelBergeronNéron Por quê?
Pedro Sousa
5
Eu pensaria que sim porque qualquer um pode chamar download.php? Filename = [something] e tentar alguns nomes de caminho e arquivo, especialmente os comuns, e isso pode até ser feito dentro de um loop dentro de um programa ou script.
Mickael Bergeron Néron
o .htaccess não evitaria isso?
Pedro Sousa
9
@PedroSousa .. não. htaccess controla o acesso à estrutura de arquivos por meio do Apache. Como o acesso atingiu um script PHP, o htaccess agora para de funcionar. Isso É MUITO uma brecha de segurança porque, de fato, qualquer arquivo que o PHP (e o usuário sob o qual está sendo executado) pode ler, por isso pode entregar no arquivo de leitura ... Deve-se sempre limpar o arquivo solicitado para ser lido
Prof83
0

Decodificar um nome de arquivo do cabeçalho é um pouco mais complexo ...

    var filename = "default.pdf";
    var disposition = req.getResponseHeader('Content-Disposition');

    if (disposition && disposition.indexOf('attachment') !== -1) 
    {
       var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
       var matches = filenameRegex.exec(disposition);

       if (matches != null && matches[1]) 
           filename = matches[1].replace(/['"]/g, '');
    }
Lumic
fonte
3
Formate todo o seu bloco de código e forneça alguma explicação adicional para o seu processo para benefício futuro do leitor.
mickmackusa de
0

Esta solução não é muito diferente das anteriores, mas para mim funciona muito bem e acho que está limpa.

Sugiro codificar em base64 o lado do servidor de arquivos (base64_encode (), se você estiver usando PHP) e enviar os dados codificados em base64 para o cliente

No cliente, você faz isso:

 let blob = this.dataURItoBlob(THE_MIME_TYPE + "," + response.file);
 let uri = URL.createObjectURL(blob);
 let link = document.createElement("a");
 link.download = THE_FILE_NAME,
 link.href = uri;
 document.body.appendChild(link);
 link.click();
 document.body.removeChild(link);

Este código coloca os dados codificados em um link e simula um clique no link, depois o remove.

martinetil
fonte
Você pode apenas tornar a tag oculta e preencher o href dinamicamente. não há necessidade de adicionar e remover
BPeela
0

Suas necessidades são atendidas por window.location('download.php');
Mas acho que você precisa passar o arquivo a ser baixado, nem sempre baixar o mesmo arquivo, e é por isso que você está usando um pedido, uma opção é criar um arquivo php tão simples como showfile.php e faça um pedido como

var myfile = filetodownload.txt
var url = "shofile.php?file=" + myfile ;
ajaxRequest.open("GET", url, true);

showfile.php

<?php
$file = $_GET["file"] 
echo $file;

em que arquivo é o nome do arquivo passado por Get ou Post na solicitação e, em seguida, captura a resposta em uma função simplesmente

if(ajaxRequest.readyState == 4){
                        var file = ajaxRequest.responseText;
                        window.location = 'downfile.php?file=' + file;  
                    }
                }
Lisandro
fonte
0

existe outra solução para baixar uma página da web em ajax. Mas estou me referindo a uma página que primeiro deve ser processada e depois baixada.

Primeiro você precisa separar o processamento da página do download dos resultados.

1) Apenas os cálculos da página são feitos na chamada ajax.

$ .post ("CalculusPage.php", {calculusFunction: true, ID: 29, data1: "a", data2: "b"},

       função (dados, status) 
       {
            if (status == "sucesso") 
            {
                / * 2) Na resposta é baixada a página que usa os cálculos anteriores. Por exemplo, pode ser uma página que imprime os resultados de uma tabela calculada na chamada ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Por exemplo: no CalculusPage.php

    if (! empty ($ _ POST ["calculusFunction"])) 
    {
        $ ID = $ _POST ["ID"];

        $ query = "INSERT INTO ExamplePage (data1, data2) VALUES ('". $ _ POST ["data1"]. "', '". $ _ POST ["data2"]. "') WHERE id =". $ ID;
        ...
    }

// Por exemplo: no DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "SELECT * FROM ExamplePage WHERE id =". $ ID;
    ...

    $ filename = "Export_Data.xls";
    cabeçalho ("Content-Type: application / vnd.ms-excel");
    header ("Content-Disposition: inline; filename = $ filename");

    ...

Espero que esta solução possa ser útil para muitos, como foi para mim.

Netluke
fonte
0

Para aqueles que procuram uma abordagem mais moderna, você pode usar o fetch API. O exemplo a seguir mostra como fazer download de um arquivo de planilha. Isso é feito facilmente com o código a seguir.

fetch(url, {
    body: JSON.stringify(data),
    method: 'POST',
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    },
})
.then(response => response.blob())
.then(response => {
    const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
    const downloadUrl = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = downloadUrl;
    a.download = "file.xlsx";
    document.body.appendChild(a);
    a.click();
})

Acredito que essa abordagem seja muito mais fácil de entender do que outras XMLHttpRequestsoluções. Além disso, possui uma sintaxe semelhante à jQueryabordagem, sem a necessidade de adicionar quaisquer bibliotecas adicionais.

Claro, eu aconselharia verificar para qual navegador você está desenvolvendo, já que essa nova abordagem não funcionará no IE. Você pode encontrar a lista completa de compatibilidade de navegadores no link a seguir .

Importante : neste exemplo, estou enviando uma solicitação JSON a um servidor que está escutando no dado url. Isso urldeve ser definido, no meu exemplo, estou assumindo que você conhece esta parte. Além disso, considere os cabeçalhos necessários para que sua solicitação funcione. Como estou enviando um JSON, devo adicionar o Content-Typecabeçalho e defini-lo application/json; charset=utf-8como, para permitir que o servidor saiba o tipo de solicitação que receberá.

Alain Cruz
fonte
0

A solução @Joao Marcos funciona para mim, mas tive que modificar o código para fazê-lo funcionar no IE, abaixo se é o código

       downloadFile(url,filename) {
        var that = this;
        const extension =  url.split('/').pop().split('?')[0].split('.').pop();

        var req = new XMLHttpRequest();
        req.open("GET", url, true);
        req.responseType = "blob";
        req.onload = function (event) {
            const fileName = `${filename}.${extension}`;
            const blob = req.response;

            if (window.navigator.msSaveBlob) { // IE
                window.navigator.msSaveOrOpenBlob(blob, fileName);
            } 
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);                
            link.download = fileName;
            link.click();
            URL.revokeObjectURL(link.href);

        };

        req.send();
    },
Jemil Oyebisi
fonte