Obsługuj pobieranie plików z serwera ajax

392

Mam aplikację javascript, która wysyła żądania ajax POST do określonego adresu URL. Odpowiedź może być łańcuchem JSON lub plikiem (jako załącznik). Mogę łatwo wykryć typ zawartości i dyspozycję zawartości w moim wywołaniu ajax, ale kiedy wykryję, że odpowiedź zawiera plik, jak mogę zaoferować klientowi pobranie go? Przeczytałem tutaj wiele podobnych wątków, ale żaden z nich nie zawiera odpowiedzi, której szukam.

Proszę, proszę, nie publikuj odpowiedzi sugerujących, że nie powinienem używać do tego ajax, lub że powinienem przekierować przeglądarkę, ponieważ żadna z tych opcji nie jest możliwa. Użycie zwykłego formularza HTML również nie jest opcją. Muszę pokazać klientowi okno dialogowe pobierania. Czy można to zrobić i jak?

Pavle Predic
źródło
Dla tych, którzy czytają ten artykuł, przeczytaj ten post: stackoverflow.com/questions/20830309/…
sobhan
Usunąłem twoje rozwiązanie z pytania. Możesz opublikować go jako post z odpowiedzią poniżej, ale nie należy on do postu z pytaniem.
Martijn Pieters

Odpowiedzi:

111

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

Chcesz mieć rodzaj aplikacji / pobierania - po prostu wyszukaj, jak zapewnić pobieranie dla dowolnego używanego języka.


źródło
35
Jak stwierdzono w pytaniu: „Używanie zwykłego formularza HTML również nie jest opcją”.
Pavle Predic 18.04.13
13
Nie, ponieważ użycie zwykłego testu POST prowadzi przeglądarkę do adresu URL POST. Nie chcę nawigować poza stroną. Chcę wykonać żądanie w tle, przetworzyć odpowiedź i przedstawić ją klientowi.
Pavle Predic 18.04.13
6
Jeśli serwer odsyła nagłówki, tak jak druga odpowiedź, otwiera się w nowym oknie - zrobiłem to wcześniej.
1
@PavlePredic skończyło się na tym, jak zarządzać obydwoma scenariuszami odpowiedzi, tj. Odpowiedzią tekstową JSON lub odpowiedzią na pobranie pliku?
Użytkownik sieci Web,
9
Odpowiedź nie jest jasna, a proponowane rozwiązanie nie działa.
stack247
530

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

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;
        if (typeof File === 'function') {
            try {
                blob = new File([this.response], filename, { type: type });
            } catch (e) { /* Edge */ }
        }
        if (typeof blob === 'undefined') {
            blob = 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.href = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location.href = downloadUrl;
            }

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

Oto stara wersja korzystająca z jQuery.ajax. Może przekształcać dane binarne, gdy odpowiedź zostanie przekonwertowana na ciąg jakiegoś zestawu 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.href = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location.href = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
});
Jonathan Amend
źródło
1
Wielkie dzięki ! Musiałem jednak dodać „Rozmieszczenie treści” zarówno do „Nagłówków kontroli dostępu - ujawnienia”, jak i „Nagłówków kontroli dostępu” w odpowiedzi HTTP, aby działało.
JulienD
1
Nie działa, gdy plik jest większy niż 500 MB, może powinniśmy użyć innego interfejsu API?
hirra
Co z usunięciem elementu z DOM w części do czyszczenia (a nie tylko adresu URL)? document.body.removeChild(a);
Scoregraphic,
@hirra użyj ResponceType „blob” zamiast „arraybuffer” i przepisz funkcję wywołania zwrotnego onload w ten sposób, że var blob to this.response (var blob = this.response;) więcvar blob =this.responce; /** if (typeof File === 'function') { try { blob = new File([this.response], filename, { type: type }); } catch (e) { /* Edge */ } } if (typeof blob === 'undefined') { blob = new Blob([this.response], { type: type }); } */
Chris Tobba
1
To idealne rozwiązanie. Tylko jedna mała zmiana. W maszynopisie potrzebowałem window.location.href = downloadUrlzamiastwindow.location = downloadUrl
michal.jakubeczy
39

Napotkałem ten sam problem i udało mi się go rozwiązać. Oto mój przypadek użycia.

Opublikuj dane JSON na serwerze i otrzymaj plik programu Excel. Ten plik programu Excel jest tworzony przez serwer i zwracany w odpowiedzi na 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 kodu wykonuje tylko następujące czynności

  • Publikowanie tablicy jako JSON na serwerze za pomocą XMLHttpRequest.
  • Po pobraniu treści jako obiektu blob (plik binarny) tworzymy adres URL do pobrania i dołączamy go do niewidocznego linku „a”, a następnie klikamy go.

Tutaj musimy ostrożnie ustawić kilka rzeczy po stronie serwera. Ustawiłem kilka nagłówków w Python Django HttpResponse. Musisz odpowiednio je 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ż tutaj pobieram xls (excel), dostosowałem contentType do ponad jednego. Musisz ustawić go zgodnie z typem pliku. Możesz użyć tej techniki do pobierania dowolnego rodzaju plików.

Naren Yellavula
źródło
33

Jakiego języka używasz po stronie serwera? W mojej aplikacji mogę łatwo pobrać plik z wywołania 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 faktycznie przekieruje przeglądarkę na tę stronę pobierania, ale jak już powiedział @ahren w swoim komentarzu, nie będzie nawigować poza bieżącą stronę.

Chodzi o ustawienie prawidłowych nagłówków, więc jestem pewien, że znajdziesz odpowiednie rozwiązanie dla używanego języka po stronie serwera, jeśli nie jest to PHP.

Obsługa odpowiedzi po stronie klienta

Zakładając, że już wiesz, jak wykonać wywołanie AJAX, po stronie klienta wykonasz żądanie AJAX do serwera. Serwer następnie generuje link, z którego można pobrać ten plik, np. „Adres URL przesyłania dalej”, na który 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 do iframeswojego ciała i ustawiasz iframeSRC na adres URL, który właśnie otrzymałeś w ten sposób (używając jQuery dla ułatwienia tego przykładu):

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

Jeśli ustawiłeś prawidłowe nagłówki, jak pokazano powyżej, iframe wymusi okno pobierania bez odsuwania przeglądarki od bieżącej strony.

Uwaga

Dodatkowy dodatek w stosunku do twojego pytania; Myślę, że najlepiej jest zawsze zwracać JSON, gdy żąda się czegoś z technologią AJAX. Po otrzymaniu odpowiedzi JSON możesz zdecydować po stronie klienta, co z nią zrobić. Być może na przykład później chcesz, aby użytkownik kliknął łącze pobierania do adresu URL zamiast wymuszać bezpośrednie pobieranie, w bieżącej konfiguracji musisz zaktualizować zarówno po stronie klienta, jak i serwera.

Robin van Baalen
źródło
24

Dla tych, którzy szukają rozwiązania z perspektywy Angular, zadziałało to 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();
});
Tim Hettler
źródło
Pomogło to, ale muszę zachować oryginalną nazwę pliku. Widzę nazwę pliku w nagłówkach odpowiedzi w „Content-Disposition”, ale nie mogę znaleźć tej wartości w obiekcie odpowiedzi w kodzie. Ustawienie link.download = ""powoduje utworzenie losowej nazwy pliku guid i link.download = nullutworzenie pliku o nazwie „null”.
Marie
@Marie, możesz zapisać nazwę pliku w momencie przesyłania, korzystając z właściwości INPUTelementu HTMLInputElement.files. Zobacz dokumentację MDN na wejściu pliku, aby uzyskać więcej szczegółów.
Tim Hettler
Rozmiar
kropli
22

Oto, jak mam 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();
  }
});

Zaktualizowana odpowiedź za pomocą download.js

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});

Mayur Padshala
źródło
Dziękuję za to, właśnie tego dzisiaj użyłem. Fantastyczne
Ryan Wilson,
Cześć, czy muszę mieć jQuery 3.0>, aby to działało?
gbade_
Dostaję również pusty plik pdf z dwoma podanymi przez ciebie przykładami. Usiłuję go pobrać plik pdf.
gbade_
@gbade_ Nie, nie potrzebujesz żadnej konkretnej wersji jQuery. Powinien działać poprawnie ze wszystkimi wersjami. Czy sprawdziłeś, czy pobierany plik PDF ma prawidłowe nagłówki CORS? Wszelkie błędy na konsoli mogą pomóc w debugowaniu
Mayur Padshala,
success: function (response, status, request) { download(response, "filename.txt", "application/text"); }
Działa
12

Widzę, że już znalazłeś rozwiązanie, ale chciałem tylko dodać informacje, które mogą pomóc komuś, kto próbuje osiągnąć to samo przy dużych żądaniach POST.

Miałem ten sam problem kilka tygodni temu, w rzeczywistości nie można uzyskać „czystego” pobrania przez AJAX, Grupa Filament utworzyła wtyczkę jQuery, która działa dokładnie tak, jak już się dowiedziałeś, nazywa się jQuery File Pobierz jednak ta technika ma wadę.

Jeśli wysyłasz duże żądania za pośrednictwem AJAX (powiedzmy, pliki + 1 MB), będzie to miało negatywny wpływ na czas reakcji. W wolnych połączeń internetowych musisz czekać wiele , dopóki żądanie jest wysyłane, a także czekać na pliku do pobrania. To nie jest jak natychmiastowe „kliknięcie” => „popup” => „rozpoczęcie pobierania”. To bardziej jak „kliknięcie” => „poczekaj, aż dane zostaną wysłane” => „poczekaj na odpowiedź” => „rozpocznij pobieranie”, co sprawia, że ​​plik ma dwukrotnie większy rozmiar, ponieważ musisz poczekać na wysłanie żądania przez AJAX i odzyskaj jako plik do pobrania.

Jeśli pracujesz z małymi rozmiarami plików <1 MB, nie zauważysz tego. Ale jak odkryłem we własnej aplikacji, w przypadku większych plików jest to prawie nie do zniesienia.

Moja aplikacja pozwala użytkownikom eksportować obrazy generowane dynamicznie, obrazy te są wysyłane za pośrednictwem żądań POST w formacie base64 na serwer (jest to jedyny możliwy sposób), a następnie przetwarzane i wysyłane z powrotem do użytkowników w postaci plików .png, .jpg, base64 ciągi obrazów + 1 MB są ogromne, co zmusza użytkowników do czekania dłużej niż jest to konieczne do rozpoczęcia pobierania pliku. W wolnych połączeniach internetowych może to być naprawdę denerwujące.

Moim rozwiązaniem było tymczasowe zapisanie pliku na serwerze, gdy będzie on gotowy, dynamiczne generowanie linku do pliku w postaci przycisku, który zmienia się między stanami „Proszę czekać ...” i „Pobierz” i jednocześnie czas, 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 jest bardziej znośny dla użytkowników, a także przyspiesza.

Aktualizacja 30 września 2014:

Minęły miesiące, odkąd to opublikowałem, wreszcie znalazłem lepsze podejście do przyspieszenia pracy z dużymi ciągami base64. Teraz przechowuję ciągi base64 w bazie danych (używając pól longtext lub longblog), następnie przekazuję jego identyfikator rekordu poprzez pobieranie pliku jQuery, w końcu w pliku skryptu pobierania przeszukuję bazę danych przy użyciu tego identyfikatora, aby pobrać ciąg base64 i przekazać go funkcja pobierania.

Pobierz przykładowy skrypt:

<?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 o wiele więcej niż to, o co poprosił PO, ale czułem, że dobrze byłoby zaktualizować moją odpowiedź o moje ustalenia. Kiedy szukałem rozwiązań mojego problemu, przeczytałem wiele wątków „Pobierz z danych AJAX POST”, które nie dały mi odpowiedzi, której szukałem, mam nadzieję, że te informacje pomogą komuś, kto chce osiągnąć coś takiego.

José SAYAGO
źródło
jQuery File DownloadTylko przekierować mnie do adresu URL. Ja nazywam to tak: jQuery.download("api/ide/download-this-file.php", {filePath: path2Down}, "POST");.
Casper
5

Chcę wskazać pewne trudności, które pojawiają się podczas korzystania z techniki w zaakceptowanej odpowiedzi, tj. Przy użyciu formularza:

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

  2. Naprawdę nie wiadomo, kiedy prośba się zakończyła. Cóż, możesz użyć pliku cookie, który jest ustawiany w odpowiedzi, jak zrobiono to przez jquery.fileDownload , ale DALEKO od ideału. Nie będzie działać w przypadku równoczesnych żądań i ulegnie awarii, jeśli odpowiedź nigdy nie nadejdzie.

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

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

Skończyłem przy użyciu metody zapisywania pliku na S3 i wysyłania wstępnie podpisanego adresu URL, aby uzyskać plik.

tepez
źródło
5

Dla tych, którzy szukają bardziej nowoczesnego podejścia, możesz skorzystać z 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 takie podejście jest znacznie łatwiejsze do zrozumienia niż inne XMLHttpRequestrozwiązania. Ponadto ma podobną składnię do tego jQuerypodejścia, bez potrzeby dodawania żadnych 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ę kompatybilności przeglądarki można znaleźć pod następującym [linkiem] [1].

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 żądania. Ponieważ wysyłam JSON, muszę dodać Content-Typenagłówek i ustawić go na application/json; charset=utf-8, aby serwer wiedział, jaki typ żądania otrzyma.

Alain Cruz
źródło
1
Niesamowite! Aby otworzyć to w nowej karcie zamiast wyskakującego okienka pobierania: użyj polecenia `` const window = open (downloadUrl, "_blank"); if (window! == null) window.focus (); ``
andy
Czy mogę to zrobić dla wielu zestawów danych? Przykład: odzyskaj {fileOne: data, fileTwo: data, fileThree: data} z wywołania interfejsu API i wygeneruj trzy pobrane pliki jednocześnie? Dzięki!
il0v3d0g
Hmmm, nie próbowałem tego. Ale zawsze możesz skompresować obrazy w pliku zip i pobrać go. Sprawdzę, czy to możliwe.
Alain Cruz
4

Oto moje rozwiązanie przy użyciu tymczasowego ukrytego formularza.

//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.

Ludovic Martin
źródło
3

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 robienia tego dokładnie jest jquery.redirect . Zapewnia interfejs API podobny do standardowej jQuery.postmetody:

$.redirect(url, [values, [method, [target]]])
KurtPreston
źródło
3

To pytanie sprzed 3 lat, ale dzisiaj miałem ten sam problem. Szukałem twojego edytowanego rozwiązania, ale myślę, że może poświęcić wydajność, ponieważ musi złożyć podwójne żądanie. Jeśli więc ktoś potrzebuje innego rozwiązania, które nie oznacza dwukrotnego wezwania serwisu, to właśnie 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 window.location (). Następnie wystarczy po prostu przesłać formularz z jquery, aby zadzwonić do serwisu i pobrać plik. Jest to dość proste, ale w ten sposób możesz pobrać za pomocą POST . Teraz myślę, że może to być łatwiejsze, jeśli usługa, do której dzwonisz, to GET , ale to nie moja sprawa.

Jairo Miranda
źródło
1
To nie jest post ajax, ponieważ pytanie używa ajax
Nidhin David
jest to tylko rozwiązanie powyższego problemu, jednak nie dotyczy wywołań ajax.
Nomi Ali
1

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 skomplikowanym przypadku dane muszą być przetwarzane poprawnie. Pod maską FileSaver.js zaimplementuj to samo podejście, co odpowiedź Jonathana Amend .

Armando
źródło
1
.. ale czy możesz pobierać pliki zwykle na iOS?
Alex Marshall,
1

Aby uzyskać odpowiedź Jonathana Amendsa na pracę 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 });

Wolę zamieścić to jako komentarz, ale nie mam na to wystarczającej reputacji

fstrandner
źródło
0

Oto moje rozwiązanie, zebrane z różnych źródeł: Implementacja po stronie serwera:

    String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
    // Set headers
    response.setHeader("content-disposition", "attachment; filename =" + fileName);
    response.setContentType(contentType);
    // Copy file to output stream
    ServletOutputStream servletOutputStream = response.getOutputStream();
    try (InputStream inputStream = new FileInputStream(file)) {
        IOUtils.copy(inputStream, servletOutputStream);
    } finally {
        servletOutputStream.flush();
        Utils.closeQuitely(servletOutputStream);
        fileToDownload = null;
    }

Implementacja po stronie klienta (przy użyciu jquery):

$.ajax({
type: 'POST',
contentType: 'application/json',
    url: <download file url>,
    data: JSON.stringify(postObject),
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert(errorThrown);
    },
    success: function(message, textStatus, response) {
       var header = response.getResponseHeader('Content-Disposition');
       var fileName = header.split("=")[1];
       var blob = new Blob([message]);
       var link = document.createElement('a');
       link.href = window.URL.createObjectURL(blob);
       link.download = fileName;
       link.click();
    }
});   
Dvs Prajapati
źródło
0

istnieje inne rozwiązanie, aby pobrać stronę internetową w ajax. Mam jednak na myśli stronę, którą najpierw trzeba przetworzyć, a następnie pobrać.

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

1) Tylko wywołania strony są wykonywane w wywołaniu ajax.

$ .post („CalculusPage.php”, {calcusFunction: true, ID: 29, data1: „a”, data2: „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 obliczonej w wywołaniu ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Na przykład: w CalculusPage.php

    if (! pusty ($ _ POST [„rachunek funkcji”])) 
    {
        $ 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 („Content-Disposition: inline; filename = $ filename”);

    ...

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

Netluke
źródło
0

Jeśli odpowiedzią jest bufor macierzy , wypróbuj to w ramach zdarzenia onsuccess w Ajax:

 if (event.data instanceof ArrayBuffer) {
          var binary = '';
          var bytes = new Uint8Array(event.data);
          for (var i = 0; i < bytes.byteLength; i++) {
              binary += String.fromCharCode(bytes[i])
          }
          $("#some_id").append("<li><img src=\"data:image/png;base64," + window.btoa(binary) + "\"/></span></li>");
          return;
      }
  • gdzie event.data jest odpowiedzią otrzymaną w funkcji powodzenia zdarzenia xhr.
Abhishek Sinha
źródło
0

Poniżej znajduje się moje rozwiązanie do pobierania wielu plików w zależności od listy, która składa się z niektórych identyfikatorów i wyszukiwania w bazie danych, pliki zostaną określone i będą gotowe do pobrania - jeśli takie istnieją. Wzywam akcję C # MVC dla każdego pliku za pomocą Ajax.

I tak, jak powiedzieli inni, można to zrobić w jQuery Ajax. Zrobiłem to z sukcesem Ajax i zawsze wysyłam odpowiedź 200.

To jest klucz:

  success: function (data, textStatus, xhr) {

A to jest mój kod:

var i = 0;
var max = 0;
function DownloadMultipleFiles() {
            if ($(".dataTables_scrollBody>tr.selected").length > 0) {
                var list = [];
                showPreloader();
                $(".dataTables_scrollBody>tr.selected").each(function (e) {
                    var element = $(this);
                    var orderid = element.data("orderid");
                    var iscustom = element.data("iscustom");
                    var orderlineid = element.data("orderlineid");
                    var folderPath = "";
                    var fileName = "";

                    list.push({ orderId: orderid, isCustomOrderLine: iscustom, orderLineId: orderlineid, folderPath: folderPath, fileName: fileName });
                });
                i = 0;
                max = list.length;
                DownloadFile(list);
            }
        }

Następnie dzwoniąc:

function DownloadFile(list) {
        $.ajax({
            url: '@Url.Action("OpenFile","OrderLines")',
            type: "post",
            data: list[i],
            xhrFields: {
                responseType: 'blob'
            },
            beforeSend: function (xhr) {
                xhr.setRequestHeader("RequestVerificationToken",
                    $('input:hidden[name="__RequestVerificationToken"]').val());

            },
            success: function (data, textStatus, 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 a = document.createElement('a');
                    var url = window.URL.createObjectURL(data);
                    a.href = url;
                    a.download = filename;
                    document.body.append(a);
                    a.click();
                    a.remove();
                    window.URL.revokeObjectURL(url);
                }
                else {
                    getErrorToastMessage("Production file for order line " + list[i].orderLineId + " does not exist");
                }
                i = i + 1;
                if (i < max) {
                    DownloadFile(list);
                }
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {

            },
            complete: function () {
                if(i===max)
                hidePreloader();
            }
        });
    }

C # MVC:

 [HttpPost]
 [ValidateAntiForgeryToken]
public IActionResult OpenFile(OrderLineSimpleModel model)
        {
            byte[] file = null;

            try
            {
                if (model != null)
                {
                    //code for getting file from api - part is missing here as not important for this example
                    file = apiHandler.Get<byte[]>(downloadApiUrl, token);

                    var contentDispositionHeader = new System.Net.Mime.ContentDisposition
                    {
                        Inline = true,
                        FileName = fileName
                    };
                    //    Response.Headers.Add("Content-Disposition", contentDispositionHeader.ToString() + "; attachment");
                    Response.Headers.Add("Content-Type", "application/pdf");
                    Response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);
                    Response.Headers.Add("Content-Transfer-Encoding", "binary");
                    Response.Headers.Add("Content-Length", file.Length.ToString());

                }
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error getting pdf", null);
                return Ok();
            }

            return File(file, System.Net.Mime.MediaTypeNames.Application.Pdf);
        }

Dopóki zwrócisz odpowiedź 200, sukces w Ajaxie może z nią współpracować, możesz sprawdzić, czy plik faktycznie istnieje, czy nie, ponieważ poniższy wiersz w tym przypadku byłby fałszywy i możesz poinformować użytkownika o tym:

 if (disposition && disposition.indexOf('attachment') !== -1) {
tykający
źródło
0

Potrzebowałem rozwiązania podobnego do tego @ alain-cruz, ale w nuxt / vue z wieloma pobraniami. Wiem, że przeglądarki blokują pobieranie wielu plików, a także mam interfejs API, który zwraca zestaw danych w formacie csv. Na początku miałem używać JSZip, ale potrzebowałem wsparcia IE, więc oto moje rozwiązanie. Jeśli ktoś może mi pomóc to poprawić, byłoby świetnie, ale jak dotąd działa dla mnie.

API zwraca:

data : {
  body: {
    fileOne: ""col1", "col2", "datarow1.1", "datarow1.2"...so on",
    fileTwo: ""col1", "col2"..."
  }
}

page.vue:

<template>
  <b-link @click.prevent="handleFileExport">Export<b-link>
</template>

export default = {
   data() {
     return {
       fileNames: ['fileOne', 'fileTwo'],
     }
   },
  computed: {
    ...mapState({
       fileOne: (state) => state.exportFile.fileOne,
       fileTwo: (state) => state.exportFile.fileTwo,
    }),
  },
  method: {
    handleExport() {
      //exportFileAction in store/exportFile needs to return promise
      this.$store.dispatch('exportFile/exportFileAction', paramsToSend)
        .then(async (response) => {
           const downloadPrep = this.fileNames.map(async (fileName) => {
           // using lodash to get computed data by the file name
           const currentData = await _.get(this, `${fileName}`);
           const currentFileName = fileName;
           return { currentData, currentFileName };
         });
         const response = await Promise.all(downloadPrep);
         return response;
       })
       .then(async (data) => {
         data.forEach(({ currentData, currentFileName }) => {
           this.forceFileDownload(currentData, currentFileName);
         });
       })
       .catch(console.error);
    },
    forceFileDownload(data, fileName) {
     const url = window.URL
         .createObjectURL(new Blob([data], { type: 'text/csv;charset=utf-8;' }));
     const link = document.createElement('a');
     link.href = url;
     link.setAttribute('download', `${fileName}.csv`);
     document.body.appendChild(link);
     link.click();
   },
}
il0v3d0g
źródło