descargar archivo usando una solicitud ajax

95

Quiero enviar una "solicitud de descarga ajax" cuando hago clic en un botón, así que lo intenté de esta manera:

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");
?>

pero no funciona como se esperaba, ¿cómo puedo hacerlo? Gracias de antemano

Manuel Di Iorio
fuente
2
Esto no funcionará, consulte [esta pregunta] [1]. [1]: stackoverflow.com/questions/8771342/…
olegsv
2
Dolocation.href='download.php';
Musa

Respuestas:

92

Actualización 27 de abril de 2015

Próximamente en la escena HTML5 está el atributo de descarga . Es compatible con Firefox y Chrome, y pronto llegará a IE11. Dependiendo de sus necesidades, puede usarlo en lugar de una solicitud AJAX (o usar window.location) siempre que el archivo que desea descargar esté en el mismo origen que su sitio.

Siempre puede realizar la solicitud AJAX / window.locationuna reserva utilizando algo de JavaScript para probar si downloades compatible y, si no, cambiarlo para llamar window.location.

Respuesta original

No puede hacer que una solicitud AJAX abra el mensaje de descarga, ya que tiene que navegar físicamente hasta el archivo para solicitar la descarga. En su lugar, puede usar una función de éxito para navegar a download.php. Esto abrirá el mensaje de descarga, pero no cambiará la página actual.

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

Aunque esto responde a la pregunta, es mejor usar window.locationy evitar la solicitud AJAX por completo.

Steven Lambert
fuente
39
¿No llama esto dos veces al enlace? Estoy en un barco similar ... Estoy pasando mucha información de seguridad en los encabezados y puedo analizar el objeto de archivo en la función de éxito, pero no sé cómo activar un mensaje de descarga.
user1447679
3
Llama a la página dos veces, por lo que si está consultando una base de datos en esa página, esto significa 2 viajes a DB.
mmmmmm
1
@ user1447679 ver para una solución alternativa: stackoverflow.com/questions/38665947/…
Juan
1
Déjame explicarte cómo me ayudó esto ... el ejemplo podría haber sido más completo. con "download.php? get_file = true" o algo ... Tengo una función ajax que verifica algún error en el envío de un formulario y luego crea un archivo csv. Si la comprobación de errores falla, debe responder por qué falló. Si crea el CSV, le dice al padre que "vaya y busque el archivo". Lo hago publicando en el archivo ajax con la variable de formulario y luego publicando un parámetro DIFERENTE en el mismo archivo que dice "pásame el archivo que acabas de crear" (la ruta / nombre está codificado en el archivo ajax).
Scott
4
Pero enviará la solicitud 2 veces, eso no es correcto
Dharmendrasinh Chudasama
44

Para que el navegador descargue un archivo, debe realizar la solicitud de esta manera:

 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
fuente
7
Esto funciona para mí, pero en Firefox, primero necesitaba poner una etiqueta <a> en el DOM y hacer referencia a ella como mi enlace en lugar de crear una sobre la marcha para que el archivo se descargue automáticamente.
Erik Donohoo
funciona, pero ¿qué sucede si el archivo se está creando durante la ejecución? no funciona como cuando el archivo ya está creado.
Diego
@Taha Probé esto en Edge y pareció funcionar. Aunque no sé sobre IE. Mi cliente no se dirige a los usuarios de IE ;-) ¿Tengo suerte? : D
Sнаđошƒаӽ
1
@ErikDonohoo Puede crear la <a>etiqueta con JS, también "sobre la marcha", pero debe agregarla document.body. Ciertamente quieres ocultarlo al mismo tiempo.
Márton Tamás
Gracias @ João, estaba buscando esta solución desde hace mucho tiempo.
Abhishek Gharai
43

En realidad, no necesitas ajax en absoluto para esto. Si simplemente configura "download.php" como href en el botón, o, si no es un enlace, use:

window.location = 'download.php';

El navegador debe reconocer la descarga binaria y no cargar la página real, sino servir el archivo como descarga.

Jelle Kralt
fuente
3
El lenguaje de programación que está utilizando para cambiar window.location es JavaScript.
mikemaccana
1
Tienes razón @mikemaccana, en realidad me refería a ajax :).
Jelle Kralt
2
He estado buscando por todas partes una solución y esta es tan elegante y perfecta. Muchas gracias.
Yangshun Tay
2
Por supuesto, esta solución solo funcionará si es un archivo estático que ya existe.
krillgar
1
Si el servidor responde con un error, no habrá forma de permanecer en su página principal sin ser redirigido a una página de error por el navegador. Al menos esto es lo que hace Chrome cuando el resultado de window.location devuelve 404.
Ian
20

Solución de navegador cruzado, probada en Chrome, Firefox, Edge, IE11.

En el DOM, agregue una etiqueta de enlace oculta:

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

Luego:

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();
León
fuente
Buen código genérico. @leo ¿puedes mejorar este código agregando encabezados personalizados como Authorization?
vibs2006
1
Gracias @leo. Es útil ¿También qué sugieres agregar window.URL.revokeObjectURL(el.href);después el.click()?
vibs2006
El nombre de archivo será incorrecto si la disposición del contenido especifica un nombre de archivo que no sea UTF8.
Mathew Alden
14

Es posible. Puede iniciar la descarga desde dentro de una función ajax, por ejemplo, justo después de crear el archivo .csv.

Tengo una función ajax que exporta una base de datos de contactos a un archivo .csv y, justo después de que finaliza, inicia automáticamente la descarga del archivo .csv. Entonces, después de obtener el responseText y todo está bien, redirijo el navegador de esta manera:

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

Mi archivo download.php se ve así:

<?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);

?>

No hay ninguna actualización de página y el archivo comienza a descargarse automáticamente.

NOTA : probado en los siguientes navegadores:

Chrome v37.0.2062.120 
Firefox v32.0.1
Opera v12.17
Internet Explorer v11
Pedro Sousa
fuente
1
¿No es esto peligroso desde el punto de vista de la seguridad?
Mickael Bergeron Néron
@ MickaelBergeronNéron ¿Por qué?
Pedro Sousa
5
Creo que sí porque cualquiera puede llamar a download.php? Filename = [algo] y probar algunas rutas y nombres de archivos, especialmente los más comunes, y esto incluso podría hacerse dentro de un bucle dentro de un programa o un script.
Mickael Bergeron Néron
¿No lo evitaría .htaccess?
Pedro Sousa
9
@PedroSousa .. no. htaccess controla el acceso a la estructura de archivos a través de Apache. Dado que el acceso ha llegado a un script PHP, htaccess ahora deja de funcionar. Esto ES MUCHO un agujero de seguridad porque, de hecho, cualquier archivo que PHP (y el usuario bajo el que se está ejecutando) puede leer, por lo que puede entregarlo en el archivo de lectura ... Uno siempre debe desinfectar el archivo solicitado para ser leído
Prof
0

Decodificar un nombre de archivo desde el encabezado es un poco más complejo ...

    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
fuente
3
Formatee todo el bloque de código y proporcione una explicación adicional de su proceso para beneficio futuro del lector.
mickmackusa
0

Esta solución no es muy diferente a las anteriores, pero para mí funciona muy bien y creo que está limpia.

Sugiero codificar en base64 el lado del servidor de archivos (base64_encode (), si está usando PHP) y enviar los datos codificados en base64 al cliente

En el cliente haces esto:

 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 los datos codificados en un enlace y simula un clic en el enlace, luego lo elimina.

martinetil
fuente
Puede ocultar la etiqueta a y completar el href dinámicamente. no es necesario agregar y eliminar
BPeela
0

Tus necesidades están cubiertas por window.location('download.php');
Pero creo que necesitas pasar el archivo a descargar, no siempre descargar el mismo archivo, y es por eso que estás usando una solicitud, una opción es crear un archivo php tan simple como showfile.php y haz una solicitud como

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

showfile.php

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

donde archivo es el nombre del archivo pasado a través de Obtener o Publicar en la solicitud y luego capturar la respuesta en una función simplemente

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

hay otra solución para descargar una página web en ajax. Pero me refiero a una página que primero debe procesarse y luego descargarse.

Primero, debe separar el procesamiento de la página de la descarga de resultados.

1) Solo se realizan los cálculos de la página en la llamada ajax.

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

       función (datos, estado) 
       {
            si (estado == "éxito") 
            {
                / * 2) En la respuesta se descarga la página que utiliza los cálculos anteriores. Por ejemplo, puede ser una página que imprima los resultados de una tabla calculada en la llamada ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Por ejemplo: en CalculusPage.php

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

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

// Por ejemplo: en DownloadPage.php

    $ ID = $ _GET ["ID"];

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

    $ filename = "Export_Data.xls";
    encabezado ("Tipo de contenido: aplicación / vnd.ms-excel");
    encabezado ("Disposición de contenido: en línea; nombre de archivo = $ nombre de archivo");

    ...

Espero que esta solución pueda ser útil para muchos, como lo fue para mí.

netluke
fuente
0

Para aquellos que buscan un enfoque más moderno, puede usar el fetch API. El siguiente ejemplo muestra cómo descargar un archivo de hoja de cálculo. Se hace fácilmente con el siguiente código.

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();
})

Creo que este enfoque es mucho más fácil de entender que otras XMLHttpRequestsoluciones. Además, tiene una sintaxis similar al jQueryenfoque, sin la necesidad de agregar bibliotecas adicionales.

Por supuesto, le aconsejaría comprobar en qué navegador está desarrollando, ya que este nuevo enfoque no funcionará en IE. Puede encontrar la lista completa de compatibilidad del navegador en el siguiente enlace .

Importante : En este ejemplo, estoy enviando una solicitud JSON a un servidor que escucha el archivo url. Esto urldebe establecerse, en mi ejemplo, supongo que conoce esta parte. Además, considere los encabezados necesarios para que su solicitud funcione. Como estoy enviando un JSON, debo agregar el Content-Typeencabezado y configurarlo application/json; charset=utf-8para que el servidor sepa el tipo de solicitud que recibirá.

Alain Cruz
fuente
0

La solución de @Joao Marcos funciona para mí, pero tuve que modificar el código para que funcione en IE, a continuación, si se ve el 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
fuente