pobierz plik przy użyciu żądania AJAX

96

Chcę wysłać „żądanie pobrania AJAX” po kliknięciu przycisku, więc spróbowałem w ten sposób:

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

ale nie działa zgodnie z oczekiwaniami, jak mogę to zrobić? Z góry dziękuję

Manuel Di Iorio
źródło
2
To nie zadziała, zobacz [to pytanie] [1]. [1]: stackoverflow.com/questions/8771342/ ...
olegsv
2
Dolocation.href='download.php';
Musa
wypróbuj ten stackoverflow.com/a/42235655/2282880
Meloman,

Odpowiedzi:

94

Aktualizacja 27 kwietnia 2015 r

Przejście do sceny HTML5 to atrybut pobierania . Jest obsługiwany w przeglądarkach Firefox i Chrome, a wkrótce pojawi się w IE11. W zależności od potrzeb możesz go użyć zamiast żądania AJAX (lub użyć window.location), o ile plik, który chcesz pobrać, ma to samo źródło co Twoja witryna.

Zawsze możesz wykonać żądanie / window.locationrezerwę AJAX, używając JavaScript do sprawdzenia, czy downloadjest obsługiwana, a jeśli nie, przełączając ją na wywołanie window.location.

Oryginalna odpowiedź

Żądanie AJAX nie może otworzyć monitu pobierania, ponieważ fizycznie musisz przejść do pliku, aby wyświetlić monit o pobranie. Zamiast tego możesz użyć funkcji sukcesu, aby przejść do download.php. Spowoduje to otwarcie monitu o pobranie, ale nie zmieni bieżącej strony.

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

Mimo że to odpowiada na pytanie, lepiej po prostu użyć window.locationi całkowicie uniknąć żądania AJAX.

Steven Lambert
źródło
39
Czy to nie wywołuje łącza dwukrotnie? Jestem na podobnej łodzi ... Przekazuję wiele informacji dotyczących bezpieczeństwa w nagłówkach i jestem w stanie przeanalizować obiekt pliku w funkcji sukcesu, ale nie wiem, jak wywołać monit o pobranie.
user1447679
3
Wywołuje stronę dwukrotnie, więc jeśli odpytujesz bazę danych na tej stronie, oznacza to 2 podróże do DB.
mmmmmm
1
@ user1447679 zobacz alternatywne rozwiązanie: stackoverflow.com/questions/38665947/ ...
John
1
Pozwólcie, że wyjaśnię, jak mi to pomogło ... przykład mógłby być pełniejszy. z "download.php? get_file = true" czy coś takiego ... Mam funkcję Ajax, która sprawdza niektóre błędy przy przesyłaniu formularza, a następnie tworzy plik csv. Jeśli sprawdzanie błędów się nie powiedzie, musi powrócić z informacją, dlaczego się nie udało. Jeśli tworzy plik CSV, mówi rodzicowi, że „idź i pobierz plik”. Robię to, wysyłając do pliku ajax ze zmienną formularza, a następnie wysyłając RÓŻNY parametr do tego samego pliku, mówiąc „podaj mi właśnie utworzony plik” (ścieżka / nazwa jest na stałe zakodowana w pliku ajax).
Scott,
4
Ale wyśle ​​prośbę 2 razy, to nie jest właściwe
Dharmendrasinh Chudasama,
45

Aby przeglądarka pobierała plik, musisz wykonać takie żądanie:

 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
źródło
7
To działa dla mnie, ale w przeglądarce Firefox musiałem najpierw umieścić tag <a> w DOM i odwołać się do niego jako mojego łącza, zamiast tworzyć go w locie, aby plik mógł zostać pobrany automatycznie.
Erik Donohoo
działa, ale co się stanie, jeśli plik jest tworzony podczas wykonywania? nie działa tak, jak wtedy, gdy plik jest już utworzony.
Diego
@Taha Przetestowałem to na Edge i wydawało się, że działa. Nie wiem jednak o IE. Mój klient nie jest przeznaczony dla użytkowników IE ;-) Czy mam szczęście? : D
Sнаđошƒаӽ
1
@ErikDonohoo Możesz utworzyć <a>tag za pomocą JS, również „w locie”, ale musisz go dołączyć do document.body. Na pewno chcesz to ukryć w tym samym czasie.
Márton Tamás
Dzięki @ João, szukałem tego rozwiązania od bardzo dawna.
Abhishek Gharai
44

W rzeczywistości wcale nie potrzebujesz do tego Ajaxa. Jeśli po prostu ustawisz „download.php” jako href na przycisku lub, jeśli nie jest to link, użyj:

window.location = 'download.php';

Przeglądarka powinna rozpoznać pobieranie binarne i nie ładować rzeczywistej strony, ale po prostu udostępniać plik jako plik do pobrania.

Jelle Kralt
źródło
3
Język programowania, którego używasz do zmiany, window.location to JavaScript.
mikemaccana
1
Masz rację @mikemaccana, właściwie miałem na myśli ajax :).
Jelle Kralt
2
Polowałem wysoko i nisko w poszukiwaniu rozwiązania, a to jest tak eleganckie i doskonałe. Dziękuję bardzo.
Yangshun Tay
2
Oczywiście to rozwiązanie zadziała tylko wtedy, gdy jest to plik statyczny, który już istnieje.
krillgar
1
Jeśli serwer odpowie z błędem, nie będzie możliwości pozostania na stronie głównej bez przekierowania na stronę błędu przez przeglądarkę. Przynajmniej tak robi Chrome, gdy wynik window.location zwraca 404.
Ian
21

Rozwiązanie dla różnych przeglądarek, przetestowane na Chrome, Firefox, Edge, IE11.

W DOM dodaj ukryty tag linku:

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

Następnie:

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();
Lew
źródło
Dobry kod ogólny. @leo czy możesz ulepszyć ten kod, dodając niestandardowe nagłówki, takie jak Authorization?
vibs2006
1
Dzięki @leo. Jest to pomocne. Co sugerujesz dodać window.URL.revokeObjectURL(el.href);później el.click()?
vibs2006
Nazwa pliku będzie nieprawidłowa, jeśli dyspozycja zawartości określa nazwę pliku inną niż UTF8.
Mathew Alden
14

To jest możliwe. Pobieranie można rozpocząć z poziomu funkcji Ajax, na przykład zaraz po utworzeniu pliku .csv.

Mam funkcję Ajax, która eksportuje bazę danych kontaktów do pliku .csv, a zaraz po jego zakończeniu automatycznie rozpoczyna pobieranie pliku .csv. Tak więc po otrzymaniu odpowiedzi i wszystko jest w porządku, przekierowuję przeglądarkę w ten sposób:

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

Mój plik download.php wygląda następująco:

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

?>

Nie ma żadnego odświeżania strony, a pobieranie pliku rozpoczyna się automatycznie.

UWAGA - Przetestowano w następujących przeglądarkach:

Chrome v37.0.2062.120 
Firefox v32.0.1
Opera v12.17
Internet Explorer v11
Pedro Sousa
źródło
1
Czy nie jest to niebezpieczne z punktu widzenia bezpieczeństwa?
Mickael Bergeron Néron
@ MickaelBergeronNéron Dlaczego?
Pedro Sousa
5
Myślę, że tak, ponieważ każdy może wywołać download.php? Filename = [coś] i wypróbować niektóre ścieżki i nazwy plików, zwłaszcza te popularne, a można to zrobić nawet wewnątrz pętli w programie lub skrypcie.
Mickael Bergeron Néron
czy .htaccess by tego nie uniknął?
Pedro Sousa
9
@PedroSousa .. no. htaccess kontroluje dostęp do struktury plików przez Apache. Ponieważ dostęp osiągnął skrypt PHP, htaccess przestał działać. Jest to BARDZO DUŻA luka w zabezpieczeniach, ponieważ rzeczywiście każdy plik, który PHP (i użytkownik, na którym jest uruchamiany) może odczytać, więc może dostarczyć go do pliku readfile ... Zawsze należy wyczyścić żądany plik do odczytu
Prof
0

Dekodowanie nazwy pliku z nagłówka jest nieco bardziej złożone ...

    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
źródło
3
Sformatuj cały blok kodu i podaj dodatkowe wyjaśnienie procesu, aby czytelnik przyniósł korzyści w przyszłości.
mickmackusa
0

To rozwiązanie nie różni się zbytnio od powyższych, ale jak dla mnie działa bardzo dobrze i uważam, że jest czyste.

Proponuję zakodować base64 po stronie serwera plików (base64_encode (), jeśli używasz PHP) i wysłać zakodowane dane base64 do klienta

Na kliencie robisz to:

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

Ten kod umieszcza zakodowane dane w odsyłaczu i symuluje kliknięcie odsyłacza, a następnie usuwa je.

martinetyl
źródło
Możesz po prostu ukryć znacznik a i dynamicznie wypełniać href. nie trzeba dodawać i usuwać
BPeela
0

Twoje potrzeby są zaspokojone przez window.location('download.php');
Ale myślę, że musisz przekazać plik do pobrania, a nie zawsze pobrać ten sam plik i dlatego używasz żądania, jedną z opcji jest utworzenie pliku php tak prostego jak showfile.php i wykonaj prośbę, taką jak

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

showfile.php

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

gdzie plik jest nazwą pliku przekazaną w żądaniu za pośrednictwem funkcji Get lub Post, a następnie po prostu przechwyć odpowiedź w funkcji

if(ajaxRequest.readyState == 4){
                        var file = ajaxRequest.responseText;
                        window.location = 'downfile.php?file=' + file;  
                    }
                }
lisandro
źródło
0

istnieje inne rozwiązanie umożliwiające pobranie strony internetowej w Ajax. Ale mam na myśli stronę, którą należy najpierw przetworzyć, a następnie pobrać.

Najpierw musisz oddzielić przetwarzanie strony od pobierania wyników.

1) W wywołaniu Ajax wykonywane są tylko obliczenia strony.

$ .post ("CalculusPage.php", {rachunekFunkcja: prawda, ID: 29, dane1: "a", dane2: "b"},

       funkcja (dane, status) 
       {
            if (status == "sukces") 
            {
                / * 2) W odpowiedzi pobierana jest strona wykorzystująca poprzednie obliczenia. Na przykład może to być strona, która drukuje wyniki tabeli obliczone w wywołaniu Ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Na przykład: w CalculusPage.php

    jeśli (! puste ($ _ POST ["funkcja kalkulacji"])) 
    {
        $ ID = $ _POST ["ID"];

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

// Na przykład: w DownloadPage.php

    $ ID = $ _GET ["ID"];

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

    $ filename = "Export_Data.xls";
    nagłówek ("Content-Type: application / vnd.ms-excel");
    nagłówek ("Dyspozycja zawartości: inline; filename = $ filename");

    ...

Mam nadzieję, że to rozwiązanie może być przydatne dla wielu, tak jak dla mnie.

netluke
źródło
0

Dla tych, którzy szukają bardziej nowoczesnego podejścia, możesz użyć fetch API. Poniższy przykład pokazuje, jak pobrać plik arkusza kalkulacyjnego. Można to łatwo zrobić za pomocą następującego kodu.

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

Uważam, że to podejście jest dużo łatwiejsze do zrozumienia niż inne XMLHttpRequestrozwiązania. Ma również podobną składnię do jQuerypodejścia, bez konieczności dodawania jakichkolwiek dodatkowych bibliotek.

Oczywiście radziłbym sprawdzić, którą przeglądarkę tworzysz, ponieważ to nowe podejście nie będzie działać w IE. Pełną listę zgodności przeglądarek można znaleźć pod poniższym linkiem .

Ważne : W tym przykładzie wysyłam żądanie JSON do serwera nasłuchującego na podanym url. To urlmusi być ustawione, na moim przykładzie zakładam, że znasz tę część. Weź również pod uwagę nagłówki potrzebne do działania żądania. Ponieważ wysyłam JSON, muszę dodać Content-Typenagłówek i ustawić go na application/json; charset=utf-8, aby poinformować serwer o typie żądania, które otrzyma.

Alain Cruz
źródło
0

Rozwiązanie @Joao Marcos działa u mnie, ale musiałem zmodyfikować kod, aby działał w IE, poniżej czy jak wygląda kod

       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
źródło