Pobierz Plik z AJAX post

Mam aplikację javascript, który wysyła żądania Ajax POST do określonego adresu URL. Odpowiedź może być łańcuchem JSON lub może to być plik (jako załącznik). Mogę łatwo wykryć Content-Type I Content-Disposition w moim wywołaniu ajax, ale gdy wykryję, że odpowiedź zawiera plik, Jak mogę zaoferować klientowi, aby go pobrać? Przeczytałem tu wiele podobnych wątków, ale żaden z nich nie daje odpowiedzi, której szukam.

Proszę, proszę, proszę nie zamieszczać odpowiedzi sugerujących, że I nie powinienem używać ajax do tego lub tamtego powinienem przekierować przeglądarkę, ponieważ żadna z tych opcji nie jest dostępna. Używanie zwykłego formularza HTML również nie wchodzi w grę. To, czego potrzebuję, to pokazać klientowi okno pobierania. Czy można to zrobić i jak?

EDIT:

Najwyraźniej nie można tego zrobić, ale istnieje proste obejście, jak sugeruje przyjęta odpowiedź. Dla każdego, kto natknie się na ten problem w przyszłości, oto jak go rozwiązałem:

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, request) {
        var disp = request.getResponseHeader('Content-Disposition');
        if (disp && disp.search('attachment') != -1) {
            var form = $('<form method="POST" action="' + url + '">');
            $.each(params, function(k, v) {
                form.append($('<input type="hidden" name="' + k +
                        '" value="' + v + '">'));
            });
            $('body').append(form);
            form.submit();
        }
    }
});

Więc zasadniczo, wystarczy wygenerować HTML formularz z tymi samymi paramami, które były używane w żądaniu AJAX i prześlij go.

Author: jwfearn, 2013-04-18

14 answers

Utwórz formularz, użyj metody POST, Prześlij formularz - nie ma potrzeby stosowania ramki iframe. Gdy strona serwera odpowie na żądanie, napisz nagłówek odpowiedzi dla typu MIME pliku, a wyświetli się okno pobierania-robiłem to wiele razy.

Chcesz treści-rodzaj aplikacji / pobierania-po prostu wyszukaj, jak zapewnić pobieranie dla dowolnego języka, którego używasz.

 96
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-18 15:00:21

Nie poddawaj się tak szybko, ponieważ można to zrobić (w nowoczesnych przeglądarkach) za pomocą części pliku:

Edycja 2017-09-28: zaktualizowano, aby używać konstruktora plików, gdy jest dostępny, aby działał w Safari > = 10.1.

Edit 2015-10-16: jQuery ajax nie jest w stanie poprawnie obsłużyć odpowiedzi binarnych (nie można ustawić responseType), więc lepiej jest użyć zwykłego wywołania XMLHttpRequest.

var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
    if (this.status === 200) {
        var filename = "";
        var disposition = xhr.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, '');
        }
        var type = xhr.getResponseHeader('Content-Type');

        var blob = typeof File === 'function'
            ? new File([this.response], filename, { type: type })
            : new Blob([this.response], { type: type });
        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params));

Oto stara wersja używająca jQuery.ajax. Może zmanipulować dane binarne, gdy odpowiedź jest konwertowana na ciąg znaków.

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, xhr) {
        // check for a filename
        var filename = "";
        var disposition = xhr.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, '');
        }

        var type = xhr.getResponseHeader('Content-Type');
        var blob = new Blob([response], { type: type });

        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
});
 449
Author: Jonathan Amend,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-28 15:52:59

Jakiego języka używasz po stronie serwera? W mojej aplikacji mogę łatwo pobrać plik z połączenia AJAX, ustawiając odpowiednie nagłówki w odpowiedzi PHP:

Ustawianie nagłówków po stronie serwera

header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);

header("Content-type: " . $mimeType);

// $strFileName is, of course, the filename of the file being downloaded. 
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\""); 

header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));

// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;

To w rzeczywistości "przekieruje" przeglądarkę do tej strony pobierania, ale jak już powiedział @ahren w swoim komentarzu, nie będzie nawigować od bieżącej strony.

Chodzi o ustawienie poprawnych nagłówków, więc jestem pewien, że znajdziesz odpowiednie rozwiązanie dla języka po stronie serwera, w którym jesteś using if it ' s not PHP.

Obsługa klienta odpowiedzi

Zakładając, że już wiesz, jak wykonać połączenie AJAX, po stronie klienta wykonujesz żądanie AJAX do serwera. Następnie serwer generuje łącze, z którego można pobrać ten plik, np. adres URL "do przodu", do którego chcesz wskazać. Na przykład serwer odpowiada:

{
    status: 1, // ok
    // unique one-time download token, not required of course
    message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}

Podczas przetwarzania odpowiedzi, wstrzykujesz iframe do swojego ciała i ustawiasz SRC iframe na adres URL, który właśnie otrzymałeś this (using jQuery for the easy of this example):

$("body").append("<iframe src='" + data.message +
  "' style='display: none;' ></iframe>");

Jeśli Ustawiłeś poprawne nagłówki, jak pokazano powyżej, ramka iframe wymusi okno pobierania bez przechodzenia przez przeglądarkę od bieżącej strony.

Uwaga

Dodatkowy dodatek w związku z twoim pytaniem; myślę, że najlepiej zawsze zwracać JSON przy żądaniu rzeczy z technologią AJAX. Po otrzymaniu odpowiedzi JSON możesz zdecydować po stronie klienta, co z nią zrobić. Może, na przykład, później chcesz, aby użytkownik kliknął link pobierania do adresu URL zamiast wymuszać pobieranie bezpośrednio, w bieżącej konfiguracji będziesz musiał zaktualizować zarówno Klienta, jak i serwer, aby to zrobić.

 28
Author: Robin van Baalen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-23 19:39:36

Stanąłem przed tym samym problemem i z powodzeniem go rozwiązałem. Mój przypadek użycia jest taki.

"Wyślij dane JSON na serwer i odbierz plik excel. Ten plik excel jest tworzony przez serwer i zwracany jako odpowiedź do klienta. Pobierz tę odpowiedź jako plik o niestandardowej nazwie w przeglądarce "

$("#my-button").on("click", function(){

// Data to post
data = {
    ids: [1, 2, 3, 4, 5]
};

// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    var a;
    if (xhttp.readyState === 4 && xhttp.status === 200) {
        // Trick for making downloadable link
        a = document.createElement('a');
        a.href = window.URL.createObjectURL(xhttp.response);
        // Give filename you wish to download
        a.download = "test-file.xls";
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
    }
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});

Powyższy fragment po prostu wykonuje

  • umieszczanie tablicy jako JSON na serwerze przy użyciu XMLHttpRequest.
  • Po pobraniu zawartości jako blob (binary), tworzymy adres URL do pobrania i dołączamy go do niewidocznego łącza "a", a następnie klikamy go.

Tutaj musimy starannie ustawić kilka rzeczy po stronie serwera. Ustawiłem kilka nagłówków w Pythonie Django HttpResponse. Musisz je odpowiednio ustawić, Jeśli używasz innych języków programowania.

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

Ponieważ pobieram tutaj xls (excel), dostosowałem contentType do powyższego. Musisz ustawić go zgodnie z typem pliku. Możesz użyć tej techniki, aby pobrać dowolny rodzaj plików.

 26
Author: Naren Yellavula,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-09 12:38:11

Dla tych, którzy szukają rozwiązania z kątowej perspektywy, to zadziałało dla mnie:

$http.post(
  'url',
  {},
  {responseType: 'arraybuffer'}
).then(function (response) {
  var headers = response.headers();
  var blob = new Blob([response.data],{type:headers['content-type']});
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = "Filename";
  link.click();
});
 18
Author: Tim Hettler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-07 19:05:21

Widzę, że już znalazłeś rozwiązanie, jednak chciałem tylko dodać kilka informacji, które mogą pomóc komuś, kto próbuje osiągnąć to samo z dużymi prośbami o posty.

Miałem ten sam problem kilka tygodni temu, rzeczywiście nie jest możliwe, aby osiągnąć "czysty" pobierz za pośrednictwem AJAX, Filament Grupa stworzył wtyczkę jQuery, który działa dokładnie tak, jak już się dowiedział, to się nazywa jQuery File Download Jednak jest minusem tej techniki.

Jeśli wysyłasz Duże żądania przez AJAX (powiedzmy pliki +1MB) negatywnie wpłynie to na responsywność. W wolnych połączeniach internetowych będziesz musiał dużo czekać , aż żądanie zostanie wysłane, a także czekać na pobranie pliku. To nie jest jak natychmiastowe "kliknij" = > "popup" = > "start pobierania". To bardziej jak "Kliknij" = > "poczekaj, aż dane zostaną wysłane" => "poczekaj na odpowiedź" = > "start pobierania" co sprawia, że wygląda na to, że plik podwoi swój rozmiar, ponieważ będziesz musiał poczekać na wysłanie żądania przez AJAX i dostać go z powrotem jako plik do pobrania.

Jeśli pracujesz z małymi plikami o rozmiarze

Moja aplikacja pozwala użytkownikom eksportować obrazy dynamicznie generowane, obrazy te są wysyłane za pośrednictwem żądań POST w formacie base64 do serwera( jest to jedyny możliwy sposób), a następnie przetwarzane i odsyłane do użytkowników w formie .png,pliki jpg, ciągi base64 dla obrazków +1MB są ogromne, wymusza to na użytkownikach oczekiwanie na rozpoczęcie pobierania pliku więcej niż jest to konieczne. W wolnych połączeniach internetowych może to być naprawdę irytujące.

Moim rozwiązaniem było tymczasowe zapisanie pliku na serwer, gdy będzie gotowy, dynamiczne wygenerowanie linku do pliku w postaci przycisku zmieniającego się pomiędzy "please wait... stany " i "pobierz" i jednocześnie wydrukuj obraz base64 w wyskakującym oknie podglądu, aby użytkownicy mogli "kliknąć prawym przyciskiem myszy" i zapisać go. To sprawia, że cały czas oczekiwania bardziej znośne dla użytkowników,a także przyspieszyć.

Update Sep 30, 2014:

Miesiące minęły odkąd to opublikowałem, w końcu znalazłem lepsze podejście do przyspieszenia rzeczy podczas pracy z big Base64 strings. Teraz przechowuję ciągi Base64 do bazy danych( używając pól longtext lub longblog), następnie przekazuję jego identyfikator rekordu przez plik jQuery do pobrania, w końcu w pliku skryptu pobierania odpytywam bazę danych za pomocą tego identyfikatora, aby wyciągnąć ciąg base64 i przekazać to poprzez funkcję pobierania.

Przykład Skryptu Do Pobrania:

<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data       = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64     = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize    = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>

wiem, że jest to znacznie wykraczające poza to, o co prosiła po, jednak uznałem, że dobrze byłoby zaktualizować moją odpowiedź z moimi ustaleniami. Kiedy szukałem rozwiązania mojego problemu, czytałem wiele "Pobierz z AJAX POST data" wątków, które nie dały mi odpowiedzi, której szukałem, mam nadzieję, że ta informacja pomoże komuś, kto chce osiągnąć coś takiego.

 11
Author: José SAYAGO,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-30 22:00:52

Oto Jak to działa https://stackoverflow.com/a/27563953/2845977

$.ajax({
  url: '<URL_TO_FILE>',
  success: function(data) {
    var blob=new Blob([data]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
    link.click();
  }
});

Zaktualizowano odpowiedź za pomocą Pobierz.js

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});
 8
Author: Mayur Padshala,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-30 19:06:06

Chcę zwrócić uwagę na pewne trudności, które pojawiają się podczas korzystania z techniki w zaakceptowanej odpowiedzi, tj. za pomocą formularza post:

  1. Nie można ustawić nagłówków na żądanie. Jeśli twój schemat uwierzytelniania obejmuje nagłówki, Token JSON-Web-przekazany w nagłówku autoryzacji, musisz znaleźć inny sposób, aby go wysłać, na przykład jako parametr zapytania.

  2. Nie wiadomo, kiedy Prośba się skończyła. Możesz użyć pliku cookie, który zostanie ustawiony na odpowiedź, jako wykonane przez jquery.fileDownload , ale daleki od ideału. Nie będzie działać dla jednoczesnych żądań i pęknie, jeśli odpowiedź nigdy nie nadejdzie.

  3. Jeśli serwer odpowie na błąd, użytkownik zostanie przekierowany na stronę błędu.

  4. Możesz używać tylko typów zawartości obsługiwanych przez formularz . Co oznacza, że nie możesz używać JSON.

Skończyło się na użyciu metody zapisania pliku Na S3 i wysłaniu wstępnie podpisanego adresu URL, aby uzyskać plik.

 5
Author: tepez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-11 12:17:07

Jak stwierdzili inni, możesz utworzyć i przesłać formularz do pobrania za pomocą żądania POST. Jednak nie musisz tego robić ręcznie.

Jedną naprawdę prostą biblioteką do tego celu jest jquery.redirect . Dostarcza API podobne do standardowej metody jQuery.post:

$.redirect(url, [values, [method, [target]]])
 2
Author: KurtPreston,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-22 20:29:31

To pytanie sprzed 3 lat, ale dzisiaj miałem ten sam problem. Przejrzałem twoje edytowane rozwiązanie, ale myślę, że może poświęcić wydajność, ponieważ musi złożyć podwójną prośbę. Więc jeśli ktoś potrzebuje innego rozwiązania, które nie oznacza, aby zadzwonić do serwisu dwa razy, to tak to zrobiłem:

<form id="export-csv-form" method="POST" action="/the/path/to/file">
    <input type="hidden" name="anyValueToPassTheServer" value="">
</form>

Ten formularz służy tylko do wywołania usługi i unikania korzystania z okna.miejsce (). Po tym po prostu trzeba złożyć formularz z jQuery w celu wywołania serwis i pobierz plik. Jest to dość proste, ale w ten sposób można pobrać za pomocą POST . Teraz może być łatwiej, jeśli usługa, do której dzwonisz, to GET , ale to nie moja sprawa.

 2
Author: Jairo Miranda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-06 23:58:14

Oto moje rozwiązanie przy użyciu tymczasowej ukrytej formy.

//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();

//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
    form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});

//Make it part of the document and submit
$('body').append(form);
form.submit();

//Clean up
form.remove();

Zauważ, że masowo używam JQuery, ale możesz zrobić to samo z natywnym JS.

 2
Author: Ludovic Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-01 09:28:56

Użyłem tego FileSaver.js . W moim przypadku z plikami csv zrobiłem to (w coffescript):

  $.ajax
    url: "url-to-server"
    data: "data-to-send"
    success: (csvData)->
      blob = new Blob([csvData], { type: 'text/csv' })
      saveAs(blob, "filename.csv")

Myślę, że w najbardziej skomplikowanych przypadkach dane muszą być przetwarzane prawidłowo. Pod maską FileSaver.js wdraża takie samo podejście jak ODPOWIEDŹ Jonathana Amend .

 1
Author: Armando,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:36

Zobacz: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ zwróci obiekt blob jako odpowiedź, którą można następnie umieścić w filesaver

 1
Author: Samantha Adrichem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-27 12:54:16

To get Jonathan odpowiedź do pracy w Edge wprowadziłem następujące zmiany:

var blob = typeof File === 'function'
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

Do tego

var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

Wolałbym to zamieścić jako komentarz, ale nie mam na to wystarczającej reputacji

 1
Author: fstrandner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-13 08:24:50