Pobierz plik przez jQuery.Ajax

420

Mam działanie Struts2 po stronie serwera do pobierania plików.

<action name="download" class="com.xxx.DownAction">
    <result name="success" type="stream">
        <param name="contentType">text/plain</param>
        <param name="inputName">imageStream</param>
        <param name="contentDisposition">attachment;filename={fileName}</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

Jednak gdy wywołuję akcję za pomocą jQuery:

$.post(
  "/download.action",{
    para1:value1,
    para2:value2
    ....
  },function(data){
      console.info(data);
   }
);

w Firebug widzę, że dane są pobierane za pomocą strumienia binarnego . Zastanawiam się, jak otworzyć okno pobierania pliku, w którym użytkownik może zapisać plik lokalnie?

hguser
źródło
1
Oznacziłem go jako duplikat pomimo różnicy platform, ponieważ o ile widzę rozwiązanie jest takie samo (nie możesz i nie musisz tego robić przez Ajax).
Pekka
1
więc bez ajax po prostu użyj window.location = "download.action? para1 = wartość1 ...."?
hguser

Odpowiedzi:

676

Aktualizacja nowoczesnych przeglądarek 2019

Takie podejście polecam teraz z kilkoma zastrzeżeniami:

  • Wymagana jest stosunkowo nowoczesna przeglądarka
  • Jeśli plik ma być bardzo duży , prawdopodobnie powinieneś zrobić coś podobnego do oryginalnego podejścia (iframe i cookie), ponieważ niektóre z poniższych operacji mogą prawdopodobnie zajmować pamięć systemową co najmniej tak dużą, jak pobierany plik i / lub inny interesujący procesor skutki uboczne.

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(resp => resp.blob())
  .then(blob => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    // the filename you want
    a.download = 'todo-1.json';
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    alert('your file has downloaded!'); // or you know, something with better UX...
  })
  .catch(() => alert('oh no!'));

2012 Oryginalne podejście oparte na jQuery / iframe / Cookie

Bluish ma w tym całkowitą rację, nie można tego zrobić przez Ajax, ponieważ JavaScript nie może zapisywać plików bezpośrednio na komputerze użytkownika (ze względów bezpieczeństwa). Niestety, wskazując główne okno adresu URL na pobierany plik oznacza, że ​​masz niewielką kontrolę nad tym, co odczuwa użytkownik w momencie pobierania pliku.

Stworzyłem jQuery File Download, który pozwala na uzyskanie „podobnego do Ajaxa” pobierania plików wraz z wywołaniami zwrotnymi OnSuccess i OnFailure, aby zapewnić lepszą obsługę. Spójrz na mój post na blogu na temat typowego problemu, który rozwiązuje wtyczka, i niektóre sposoby jej używania, a także demonstrację pobierania pliku jQuery w akcji . Oto źródło

Oto prosta wersja demonstracyjna przypadku użycia źródła wtyczek z obietnicami. Strona demonstracyjna zawiera również wiele innych przykładów „lepszego UX”.

$.fileDownload('some/file.pdf')
    .done(function () { alert('File download a success!'); })
    .fail(function () { alert('File download failed!'); });

W zależności od przeglądarek, które chcesz obsługiwać, możesz użyć https://github.com/eligrey/FileSaver.js/, co pozwala na bardziej jednoznaczną kontrolę niż metoda pobierania pliku jQuery.

John Culviner
źródło
69
Uwielbiam to, co zbudowałeś, ale podejrzewam, że aby uzyskać więcej środków StackOverFlow, twoja odpowiedź tutaj powinna zawierać nieco więcej szczegółów. W szczególności o tym, jak rozwiązałeś problem.
AnthonyVO
14
byłoby miło, gdybyś wspomniał dokładnie, w jaki sposób ta „wtyczka” omija ograniczenia, zamiast zmuszać nas do odwiedzenia twojego bloga / źródła wtyczek, aby to zobaczyć. na przykład, czy zamiast tego publikuje w ramce iframe? czy zamiast tego wymaga zdalnego skryptu, aby zapisać plik i zwrócić do niego adres URL?
Kevin B,
2
@asgerhallas Jasne, ale to całkowicie bezużyteczne, jeśli wspomniany link zniknie.
Kevin B
26
Zgadzam się, że blog jest znacznie lepszym miejscem do umieszczenia długiego opisu sposobu korzystania z wtyczki i sposobu jej działania. ale mógłbyś przynajmniej dać krótki przegląd tego, jak ta wtyczka rozwiązuje problem. Na przykład rozwiązuje to problem, ponieważ serwer ustawia plik cookie, a javascript stale szuka pliku cookie, dopóki nie zostanie utworzony. Gdy już istnieje, możemy założyć, że pobieranie zostało zakończone. Dzięki takim informacjom można z łatwością szybko opracować własne rozwiązanie, a odpowiedź nie jest już w 100% oparta na blogu / wtyczce / jquery i można ją zastosować w innych bibliotekach.
Kevin B
1
Royi, jak rozumiem, AJAX nigdy nie obsługuje pobierania plików, w wyniku czego wyskakujące okno pobierania pliku zapisuje się na dysku. Czy znalazłeś sposób, którego nie jestem świadomy?
John Culviner
227

Nikt nie opublikował tego rozwiązania @ Pekka ... więc opublikuję to. To może komuś pomóc.

Nie musisz tego robić przez Ajax. Po prostu użyj

window.location="download.action?para1=value1...."
niebieskawy
źródło
4
Fajny ... ponieważ miałem problemy z obsługą monitu o pobranie pliku i używając jquery ajax..i to rozwiązanie działa idealnie dla mnie .. + 1
swapnesh
45
Zauważ, że wymaga to ustawienia przez serwer wartości nagłówka Content-Disposition dla „załącznika”, w przeciwnym razie przeglądarka przekieruje na (i wyświetli) treść odpowiedzi
brichins
21
Lub alternatywnie użyj, window.open(<url>, '_blank');aby upewnić się, że pobieranie nie zastąpi bieżącej zawartości przeglądarki (niezależnie od nagłówka Content-Disposition).
Christopher King
4
Problem z tym rozwiązaniem polega na tym, że jeśli operacja się nie powiedzie / serwer zwróci błąd, strona zostanie przekierowana na stronę błędu. Aby rozwiązać ten problem, użyj rozwiązania iFrame
kofifus
4
Prawdziwy problem z tym rozwiązaniem - pytanie dotyczy POSTzapytania.
Atomosk
35

Możesz z HTML5

Uwaga: Zwracane dane pliku MUSZĄ być zakodowane w standardzie base64, ponieważ nie można kodować danych binarnych JSON

W mojej AJAXodpowiedzi mam strukturę danych, która wygląda następująco:

{
    result: 'OK',
    download: {
        mimetype: string(mimetype in the form 'major/minor'),
        filename: string(the name of the file to download),
        data: base64(the binary data as base64 to download)
    }
}

Oznacza to, że mogę wykonać następujące czynności, aby zapisać plik przez AJAX

var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
    // Do it the HTML5 compliant way
    var blob = base64ToBlob(result.download.data, result.download.mimetype);
    var url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = result.download.filename;
    a.click();
    window.URL.revokeObjectURL(url);
}

Funkcja base64ToBlob została wzięta stąd i musi być używana zgodnie z tą funkcją

function base64ToBlob(base64, mimetype, slicesize) {
    if (!window.atob || !window.Uint8Array) {
        // The current browser doesn't have the atob function. Cannot continue
        return null;
    }
    mimetype = mimetype || '';
    slicesize = slicesize || 512;
    var bytechars = atob(base64);
    var bytearrays = [];
    for (var offset = 0; offset < bytechars.length; offset += slicesize) {
        var slice = bytechars.slice(offset, offset + slicesize);
        var bytenums = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            bytenums[i] = slice.charCodeAt(i);
        }
        var bytearray = new Uint8Array(bytenums);
        bytearrays[bytearrays.length] = bytearray;
    }
    return new Blob(bytearrays, {type: mimetype});
};

Jest to dobre, jeśli serwer wysyła dane do zapisania. Jednak nie do końca opracowałem sposób implementacji kreacji zastępczej HTML4

Luke Madhanga
źródło
1
a.click()Nie wydają się działać w Firefoksie ... jakiś pomysł?
bigpony
W niektórych przeglądarkach może być konieczne dodanie nazwy arevokeObjectURLdocument.body.appendChild(a)
domeny
uratował mi dzień (i być może też pracę :)) Nie jestem ekspertem od javascript pod żadnym względem ... więcej facet z javą. Nie mam jednak pojęcia, dlaczego proste „createObjectURL (new Blob ([atob (base64)]))” nie działa! Po prostu tak nie jest, podczas gdy cały instynkt mówi, że musi. grrr ...
apil.tamang
w linii var bytechars = atob(base64)generuje błąd JavaScript runtime error: InvalidCharacterError. Używam Chrome w wersji 75.0.3770.142, ale nie wiem, co tu jest nie tak.
Muflix,
27

1. Agnostyk ramowy: pobieranie pliku serwletu jako załącznika

<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>

2. Struts2 Framework: Akcja pobiera plik jako załącznik

<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>

Lepiej byłoby użyć <s:a>tagu wskazującego OGNL na adres URL utworzony za pomocą <s:url>tagu:

<!-- without JS, with Struts tags: THE RIGHT WAY -->    
<s:url action="downloadAction.action" var="url">
    <s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>

W powyższych przypadkach musisz napisać nagłówek Content-Disposition w odpowiedzi , określając, że plik musi zostać pobrany ( attachment), a nie otwarty przez przeglądarkę ( inline). Ci potrzebne do określenia typu zawartości też, a może chcesz dodać nazwę pliku i długość (w pomocy przeglądarki rysunek realistyczny progressbar).

Na przykład podczas pobierania pliku ZIP:

response.setContentType("application/zip");
response.addHeader("Content-Disposition", 
                   "attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...

Dzięki Struts2 (chyba że używasz akcji jako serwletu, na przykład włamania do bezpośredniego przesyłania strumieniowego ), nie musisz pisać niczego bezpośrednio w odpowiedzi; wystarczy użyć typu wyniku Stream i skonfigurowanie go w struts.xml będzie działać: PRZYKŁAD

<result name="success" type="stream">
   <param name="contentType">application/zip</param>
   <param name="contentDisposition">attachment;filename="${fileName}"</param>
   <param name="contentLength">${fileLength}</param>
</result>

3. Framework agnostic (framework / Struts2): otwieranie pliku serwletu (/ Action) w przeglądarce

Jeśli chcesz otworzyć plik w przeglądarce, zamiast go pobierać, ustawianie zawartości musi być ustawione na wartość wbudowaną , ale celem nie może być bieżąca lokalizacja okna; musisz kierować reklamy do nowego okna utworzonego przez javascript, <iframe>na stronie lub nowego okna utworzonego w locie za pomocą „przedyskutowanego” celu = „_ puste”:

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>
Andrea Ligios
źródło
2
Sir, Twój wkład: „Content-Disposition”, „inline; .... uratował dzień biednego programisty :)
Vedran Maricevic.
1
To jedyna odpowiedź, która wspomina o „window.open” (wspomina o tym jeden z komentarzy).
Andrew Koster,
To nie działa, jeśli masz wiele parametrów, ponieważ pojawi się too long urlbłąd.
Muflix,
25

Prostym sposobem, aby przeglądarka pobierała plik, jest 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();
 }

Spowoduje to otwarcie okna pobierania przeglądarki.

João Marcos
źródło
3
Dziękuję, skorzystałem z tego rozwiązania. Działa jak urok. Ponadto, jeśli nie otrzymasz obiektu blob z odpowiedzi, po prostu utwórz nowy obiekt blob.
fabio.sang
6
Lepsza wersja z łączem do
zaczyna się
Link z @startsWith_R naprawdę pomaga, jeśli pracujesz z IE11
alexventuraio
Dzięki, zadziałało dla mnie!
Zaki Mohammed
23

Stworzyłem niewiele funkcji jako rozwiązanie obejścia (zainspirowane wtyczką @JohnCulviner):

// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name

function ajax_download(url, data, input_name) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" +
                  "<input type=hidden name='" + input_name + "' value='" +
                  JSON.stringify(data) +"'/></form>" +
                  "</body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Demo ze zdarzeniem kliknięcia:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});
ndpu
źródło
To jednak wysyła dane w bardzo dziwny sposób do serwera. Zastanawiam się, czy można to zmienić, aby utworzyć zgodny test POST?
Shayne
16

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. Zrobiłem tutaj żądanie POST. Zamiast tego możesz wybrać prosty GET. Nie możemy pobrać pliku przez Ajax, musimy użyć XMLHttpRequest.

Tutaj musimy dokładnie 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
„Nie możemy pobrać pliku przez Ajax, musimy użyć XMLHttpRequest”. XMLHttpRequest jest z definicji AJAX. W przeciwnym razie świetne rozwiązanie dla nowoczesnych przeglądarek internetowych. Dla IE, który nie obsługuje HTMLAnchorElement.download, myślę o połączeniu go z zastrzeżoną metodą msSaveOrOpenBlob .
Tsahi Asher
15

Ok, w oparciu o kod ndpu, oto ulepszona (jak sądzę) wersja ajax_download; -

function ajax_download(url, data) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" 

    Object.keys(data).forEach(function(key){
        iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";

    });

        iframe_html +="</form></body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Użyj tego w ten sposób; -

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2});
});

Parametry są wysyłane jako odpowiednie parametry pocztowe, jakby pochodziły z danych wejściowych, a nie jako ciąg zakodowany w formacie json, jak w poprzednim przykładzie.

PRZESTROGA: Uważaj na możliwość zmiennego wstrzykiwania tych form. Może istnieć bezpieczniejszy sposób kodowania tych zmiennych. Ewentualnie rozważ ich ucieczkę.

Shayne
źródło
To działa przykład. Dzięki. Czy można to zrobić bez iframe, ale bez window.location?
Marek Bar
Podejrzewam, że możesz po prostu dołączyć ukryty formularz do dolnej części DOM. Być może warto również zbadać korzystanie z Shadow Dom, chociaż niekoniecznie jest to obsługiwane w starszych przeglądarkach.
Shayne
W tym kodzie pojawia się ten błąd. Uncaught SecurityError: Blocked a frame with origin "http://foo.bar.com" from accessing a frame with origin "null". The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
nieważne
Jak mogę odwzorować ten formularz na klasę modelu? Mam: @ResourceMapping() public void downloadFile(final ResourceRequest request, final ResourceResponse response, @ModelAttribute("downForm") FormModel model) ale to nie działa ..
bartex9 12.01.17
void: Prawdopodobnie byłby to problem bezpieczeństwa pochodzącego z różnych źródeł. To prawdopodobnie całe pytanie o przepełnienie stosu. @ bartex9: To zależy w dużej mierze od tego, jakiego rodzaju ramy używasz. Ale zasadą byłoby wziąć nazwę i ścieżkę i przechowywać to, wpychając sam plik do obszaru systemu plików dostępnego z sieci lub czegoś w rodzaju amazon S3 dla wysokiej dostępności
Shayne
8

Oto co zrobiłem, czysty JavaScript i HTML. Nie przetestowałem tego, ale powinno to działać we wszystkich przeglądarkach.

Funkcja Javascript

var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
     console.log("FILE LOAD DONE.. Download should start now");
});

Używanie tylko składników obsługiwanych we wszystkich przeglądarkach bez dodatkowych bibliotek.

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Oto kod kontrolera JAVA Spring po stronie mojego serwera.

@RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(@RequestParam(value = "param1", required = false) String param1,
    HttpServletRequest request, HttpServletResponse response)
            throws ParseException {

    Workbook wb = service.getWorkbook(param1);
    if (wb != null) {
        try {
            String fileName = "myfile_" + sdf.format(new Date());
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
            wb.write(response.getOutputStream());
            response.getOutputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }
manukyanv07
źródło
wygląda na to, że twoje zdarzenie ładowania nie jest wywoływane dla zawartości załącznika dyspozycji dyspozycji (ponieważ nic nie jest ładowane do iframe), jeśli to działa dla ciebie (dostajesz console.log) pls opublikuj próbkę
kofifus
Oto szybkie skrzypce jsfiddle.net/y2xezyoj, który uruchamia tyłek zdarzenia ładowania, gdy tylko plik pdf zostanie załadowany do ramki iframe. Skrzypce nie można pobrać, ponieważ klucz do pobierania znajduje się po stronie serwera „response.setHeader („ Treść -disposition "," załącznik ; nazwa_pliku = \ "" + nazwa pliku + ".xlsx \" ");"
manukyanv07
1
tak, to zadziała w takim przypadku, ale jeśli plik zostanie pobrany, to znaczy, że serwer wyśle ​​Content-Disposition: załącznik, wtedy zdarzenie ładowania nie uruchomi się, o co mi
chodziło
Masz całkowitą rację. Zdarzenie ładowania jest uruchamiane zaraz po zakończeniu pracy serwera. Rozpocznie się wysyłanie pliku. Właśnie tego szukałem: 1- zablokuj przycisk i pokaż przetwarzanie, aby użytkownik mógł uzyskać informację zwrotną o tym, co się dzieje. 2 - Następnie, gdy serwer jest przetwarzany i ma zamiar wysłać plik 3- (uruchamiane jest zdarzenie ładowania), w którym odblokowuję przycisk i usuwam spinner przetwarzania 4 - użytkownik jest teraz wyskakujący z zapisz plik lub przeglądarka zaczyna go pobierać w zdefiniowana lokalizacja pobierania. Przepraszam za mój angielski.
manukyanv07
5
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}
EL missaoui habib
źródło
Czy możesz wyjaśnić swoją odpowiedź? Pomogłoby to innym zrozumieć, co zrobiłeś, aby mogli zastosować twoje techniki w swoich sytuacjach.
Wai Ha Lee,
2
Tylko ostrzeżenie: Safari i IE nie obsługują tego downloadatrybutu, więc plik będzie miał nazwę „Nieznany”
Yangshun Tay
4

W Rails robię to w ten sposób:

function download_file(file_id) {
  let url       = '/files/' + file_id + '/download_file';
    $.ajax({
    type: 'GET',
    url: url,
    processData: false,
    success: function (data) {
       window.location = url;
    },
    error: function (xhr) {
     console.log(' Error:  >>>> ' + JSON.stringify(xhr));
    }
   });
 }

Sztuką jest window.location część. Metoda kontrolera wygląda następująco:

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end
aarkerio
źródło
2
Szybkie pytanie, czy to nie wygeneruje pliku dwa razy? Po wysłaniu żądania ajax. Następnie przekierowujesz stronę na ten sam adres URL. Jak możemy to wyeliminować?
coderhs
Nie w moim przypadku. Testowałem to jednak tylko w Chrome.
aarkerio
Ponieważ coderhs już poprawnie podaje, akcja jest wywoływana dwukrotnie.
Sven
Dla mnie też dzwoni dwa razy.
CSquared
4

Posługiwać się window.open https://developer.mozilla.org/en-US/docs/Web/API/Window/open

Na przykład możesz umieścić ten wiersz kodu w module obsługi kliknięć:

window.open('/file.txt', '_blank');

Otworzy się nowa karta (z powodu nazwy okna „_blank”) i ta karta otworzy adres URL.

Twój kod po stronie serwera powinien również mieć coś takiego:

res.set('Content-Disposition', 'attachment; filename=file.txt');

W ten sposób przeglądarka powinna poprosić użytkownika o zapisanie pliku na dysku, zamiast tylko pokazywania mu pliku. Spowoduje to również automatyczne zamknięcie właśnie otwartej karty.

Andrew Koster
źródło
4

Próbuję pobrać plik CSV, a następnie zrobić coś po zakończeniu pobierania. Więc muszę zaimplementować odpowiednią callbackfunkcję.

Używanie window.location="..."nie jest dobrym pomysłem, ponieważ nie mogę obsługiwać programu po zakończeniu pobierania. Coś w tym stylu, zmień nagłówek, aby nie był to dobry pomysł.

fetchjest dobrą alternatywą, jednak nie obsługuje IE 11 . I window.URL.createObjectURLnie może obsługiwać IE 11. Możesz to sprawdzić .

To jest mój kod, jest podobny do kodu Shahrukh Alam. Ale powinieneś uważać, aby nie window.URL.createObjectURLdoprowadzić do wycieków pamięci. Można odnieść to . Po nadejściu odpowiedzi dane zostaną zapisane w pamięci przeglądarki. Więc zanim klikniesz alink, plik został pobrany. Oznacza to, że po pobraniu możesz zrobić wszystko.

$.ajax({
    url: 'your download url',
    type: 'GET',
}).done(function (data, textStatus, request) {
    // csv => Blob
    var blob = new Blob([data]);

    // the file name from server.
    var fileName = request.getResponseHeader('fileName');

    if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
    window.navigator.msSaveOrOpenBlob(blob, fileName);
    } else { // for others
    var url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);

    //Do something after download 
    ...

    }
}).then(after_download)
}
Kyakya
źródło
4

Jak pobrać plik po otrzymaniu go przez AJAX

Jest to wygodne, gdy plik jest tworzony przez długi czas i musisz pokazać PRELOADER

Przykład podczas przesyłania formularza internetowego:

<script>
$(function () {
    $('form').submit(function () {
        $('#loader').show();
        $.ajax({
            url: $(this).attr('action'),
            data: $(this).serialize(),
            dataType: 'binary',
            xhrFields: {
                'responseType': 'blob'
            },
            success: function(data, status, xhr) {
                $('#loader').hide();
                // if(data.type.indexOf('text/html') != -1){//If instead of a file you get an error page
                //     var reader = new FileReader();
                //     reader.readAsText(data);
                //     reader.onload = function() {alert(reader.result);};
                //     return;
                // }
                var link = document.createElement('a'),
                    filename = 'file.xlsx';
                // if(xhr.getResponseHeader('Content-Disposition')){//filename 
                //     filename = xhr.getResponseHeader('Content-Disposition');
                //     filename=filename.match(/filename="(.*?)"/)[1];
                //     filename=decodeURIComponent(escape(filename));
                // }
                link.href = URL.createObjectURL(data);
                link.download = filename;
                link.click();
            }
        });
        return false;
    });
});
</script>

Opcjonalne funkcje zostały zakomentowane w celu uproszczenia przykładu.

Nie ma potrzeby tworzenia plików tymczasowych na serwerze.

W jQuery v2.2.4 OK. W starej wersji wystąpi błąd:

Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').
Mike S.
źródło
Aby uzyskać nazwę pliku z Content-Disposition, to dopasowanie działało dla mnie: filename.match(/filename=(.*)/)[1](bez podwójnych cudzysłowów i znaku zapytania) - regex101.com/r/2AsD4y/2 . Twoje rozwiązanie było jednak jedynym rozwiązaniem, które działało po wielu poszukiwaniach.
jstuardo
3

Dodanie jeszcze kilku rzeczy do powyższej odpowiedzi na pobranie pliku

Poniżej znajduje się kod java spring, który generuje bajt Array

@RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
    public ResponseEntity<byte[]> downloadReport(
            @RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {

        OutputStream out = new ByteArrayOutputStream();
        // write something to output stream
        HttpHeaders respHeaders = new HttpHeaders();
        respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        respHeaders.add("X-File-Name", name);
        ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
        return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
    }

Teraz w kodzie javascript za pomocą FileSaver.js, możesz pobrać plik z poniższym kodem

var json=angular.toJson("somejsobject");
var url=apiEndPoint+'some url';
var xhr = new XMLHttpRequest();
//headers('X-File-Name')
xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 201) {
        var res = this.response;
        var fileName=this.getResponseHeader('X-File-Name');
        var data = new Blob([res]);
        saveAs(data, fileName); //this from FileSaver.js
    }
}    
xhr.open('POST', url);
xhr.setRequestHeader('Authorization','Bearer ' + token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.send(json);

Powyższe spowoduje pobranie pliku

Dario nascimento
źródło
2

Ok, więc oto działający kod podczas korzystania z MVC i pobierania pliku z kontrolera

powiedzmy, że masz tablicę bajtów do zadeklarowania i wypełnienia, jedyne, co musisz zrobić, to użyć funkcji File (using System.Web.Mvc)

byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");

a następnie w tym samym kontrolerze dodaj te 2 funkcje

protected override void OnResultExecuting(ResultExecutingContext context)
    {
        CheckAndHandleFileResult(context);

        base.OnResultExecuting(context);
    }

    private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";

    /// <summary>
    /// If the current response is a FileResult (an MVC base class for files) then write a
    /// cookie to inform jquery.fileDownload that a successful file download has occured
    /// </summary>
    /// <param name="context"></param>
    private void CheckAndHandleFileResult(ResultExecutingContext context)
    {
        if (context.Result is FileResult)
            //jquery.fileDownload uses this cookie to determine that a file download has completed successfully
            Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
        else
            //ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
            if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
                Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
    }

a następnie będziesz mógł zadzwonić do kontrolera, aby pobrać i uzyskać oddzwonienie „sukces” lub „niepowodzenie”

$.fileDownload(mvcUrl('name of the controller'), {
            httpMethod: 'POST',
            successCallback: function (url) {
            //insert success code

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });
Yannick Richard
źródło
1

Znalazłem poprawkę, która chociaż tak naprawdę nie używa ajax, pozwala na użycie wywołania javascript w celu zażądania pobrania, a następnie otrzymania oddzwonienia, gdy pobieranie faktycznie się rozpocznie. Uważam to za pomocne, jeśli łącze uruchamia skrypt po stronie serwera, który zajmuje trochę czasu, aby skomponować plik przed wysłaniem. abyś mógł ostrzec ich o przetwarzaniu, a następnie, kiedy w końcu wyśle ​​plik, usuń powiadomienie o przetwarzaniu. dlatego chciałem spróbować załadować plik za pośrednictwem ajax, aby na początku zdarzyć się zdarzenie, gdy plik zostanie zażądany, a inne, gdy rozpocznie się pobieranie.

js na pierwszej stronie

function expdone()
{
    document.getElementById('exportdiv').style.display='none';
}
function expgo()
{
   document.getElementById('exportdiv').style.display='block';
   document.getElementById('exportif').src='test2.php?arguments=data';
}

iframe

<div id="exportdiv" style="display:none;">
<img src="loader.gif"><br><h1>Generating Report</h1>
<iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
</div>

następnie drugi plik:

<!DOCTYPE html>
<html>
<head>
<script>
function expdone()
{
    window.parent.expdone();
}
</script>
</head>
<body>
<iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
<script>document.getElementById('exportif').onload= expdone;</script>
</body></html>

Myślę, że istnieje sposób na odczytanie danych za pomocą js, więc nie byłoby potrzebne php. ale nie wiem tego od ręki, a serwer, którego używam, obsługuje PHP, więc to działa dla mnie. pomyślałem, że podzielę się tym na wypadek, gdyby to komukolwiek pomogło

Kit Ramos
źródło
0

Jeśli chcesz skorzystać z pobierania plików jQuery, zwróć uwagę na to dla IE. Musisz zresetować odpowiedź, aby nie została pobrana

    //The IE will only work if you reset response
    getServletResponse().reset();
    //The jquery.fileDownload needs a cookie be set
    getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
    //Do the reset of your action create InputStream and return

Twoje działanie można wdrożyć, ServletResponseAware aby uzyskać dostępgetServletResponse()

Alireza Fattahi
źródło
0

Pewne jest, że nie można tego zrobić za pośrednictwem połączenia Ajax.

Istnieje jednak obejście.

Kroki :

Jeśli używasz form.submit () do pobierania pliku, możesz:

  1. Utwórz wywołanie ajax od klienta do serwera i zapisz strumień plików w sesji.
  2. Po zwróceniu „sukcesu” z serwera, wywołaj form.submit (), aby po prostu przesłać strumień plików przechowywanych w sesji.

Jest to pomocne w przypadku, gdy chcesz zdecydować, czy plik ma zostać pobrany po utworzeniu form.submit (), np .: może wystąpić przypadek, w którym w formularzu form.submit () występuje wyjątek po stronie serwera, a zamiast tego awarii, może być konieczne wyświetlenie niestandardowego komunikatu po stronie klienta, w takim przypadku ta implementacja może pomóc.

Aman Srivastava
ź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
The HTML Code:-

'<button type="button" id="GetFile">Get File!</button>'


The jQuery Code:-

'$('#GetFile').on('click', function () {
    $.ajax({
        url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/172905/test.pdf',
        method: 'GET',
        xhrFields: {
            responseType: 'blob'
        },
        success: function (data) {
            var a = document.createElement('a');
            var url = window.URL.createObjectURL(data);
            a.href = url;
            a.download = 'myfile.pdf';
            document.body.append(a);
            a.click();
            a.remove();
            window.URL.revokeObjectURL(url);
        }
    });
});'
Shahrukh Alam
źródło
Tylko odpowiedzi na kod powinny zawierać przynajmniej minimalny opis wyjaśniający, jak działa kod i dlaczego odpowiada na pytanie.
Roberto Caboni
0

To działa tak dobrze w każdej przeglądarce (używam rdzenia asp.net)

            function onDownload() {

  const api = '@Url.Action("myaction", "mycontroller")'; 
  var form = new FormData(document.getElementById('form1'));

  fetch(api, { body: form, method: "POST"})
      .then(resp => resp.blob())
      .then(blob => {
          const url = window.URL.createObjectURL(blob);
        $('#linkdownload').attr('download', 'Attachement.zip');
          $('#linkdownload').attr("href", url);
          $('#linkdownload')
              .fadeIn(3000,
                  function() { });

      })
      .catch(() => alert('An error occurred'));



}
 
 <button type="button" onclick="onDownload()" class="btn btn-primary btn-sm">Click to Process Files</button>
 
 
 
 <a role="button" href="#" style="display: none" class="btn btn-sm btn-secondary" id="linkdownload">Click to download Attachments</a>
 
 
 <form asp-controller="mycontroller" asp-action="myaction" id="form1"></form>
 
 

        function onDownload() {
            const api = '@Url.Action("myaction", "mycontroller")'; 
            //form1 is your id form, and to get data content of form
            var form = new FormData(document.getElementById('form1'));

            fetch(api, { body: form, method: "POST"})
                .then(resp => resp.blob())
                .then(blob => {
                    const url = window.URL.createObjectURL(blob);
                    $('#linkdownload').attr('download', 'Attachments.zip');
                    $('#linkdownload').attr("href", url);
                    $('#linkdownload')
                        .fadeIn(3000,
                            function() {

                            });
                })
                .catch(() => alert('An error occurred'));                 

        }
Marinpietri
źródło
-1

Długo walczyłem z tym problemem. Wreszcie zaproponowana tutaj elegancka biblioteka zewnętrzna pomogła mi.

Eerik Sven Puudist
źródło