Handle file download from ajax post

2018-12-31 00:46发布

I have a javascript app that sends ajax POST requests to a certain URL. Response might be a JSON string or it might be a file (as an attachment). I can easily detect Content-Type and Content-Disposition in my ajax call, but once I detect that the response contains a file, how do I offer the client to download it? I've read a number of similar threads here but none of them provide the answer I'm looking for.

Please, please, please do not post answers suggesting that I shouldn't use ajax for this or that I should redirect the browser, because none of this is an option. Using a plain HTML form is also not an option. What I do need is to show a download dialog to the client. Can this be done and how?

EDIT:

Apparently, this cannot be done, but there is a simple workaround, as suggested by the accepted answer. For anyone who comes across this issue in the future, here's how I solved it:

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

So basically, just generate a HTML form with the same params that were used in AJAX request and submit it.

14条回答
步步皆殇っ
2楼-- · 2018-12-31 01:42

I used this FileSaver.js. In my case with csv files, i did this (in coffescript):

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

I think for most complicated case, the data must be processed properly. Under the hood FileSaver.js implement the same approach of the answer of Jonathan Amend.

查看更多
孤独寂梦人
3楼-- · 2018-12-31 01:43

For those looking for a solution from an Angular perspective, this worked for me:

$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();
});
查看更多
公子世无双
4楼-- · 2018-12-31 01:43

Here is my solution using a temporary hidden form.

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

Note that I massively use JQuery but you can do the same with native JS.

查看更多
看淡一切
5楼-- · 2018-12-31 01:43

see: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ it'll return a blob as a response, which can then be put into filesaver

查看更多
流年柔荑漫光年
6楼-- · 2018-12-31 01:45

What server-side language are you using? In my app I can easily download a file from an AJAX call by setting the correct headers in PHP's response:

Setting headers server-side

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;

This will in fact 'redirect' the browser to this download page, but as @ahren alread said in his comment, it won't navigate away from the current page.

It's all about setting the correct headers so I'm sure you'll find a suitable solution for the server-side language you're using if it's not PHP.

Handling the response client side

Assuming you already know how to make an AJAX call, on the client side you execute an AJAX request to the server. The server then generates a link from where this file can be downloaded, e.g. the 'forward' URL where you want to point to. For example, the server responds with:

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

When processing the response, you inject an iframe in your body and set the iframe's SRC to the URL you just received like this (using jQuery for the ease of this example):

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

If you've set the correct headers as shown above, the iframe will force a download dialog without navigating the browser away from the current page.

Note

Extra addition in relation to your question; I think it's best to always return JSON when requesting stuff with AJAX technology. After you've received the JSON response, you can then decide client-side what to do with it. Maybe, for example, later on you want the user to click a download link to the URL instead of forcing the download directly, in your current setup you would have to update both client and server-side to do so.

查看更多
皆成旧梦
7楼-- · 2018-12-31 01:46

Create a form, use the POST method, submit the form - there's no need for an iframe. When the server page responds to the request, write a response header for the mime type of the file, and it will present a download dialog - I've done this a number of times.

You want content-type of application/download - just search for how to provide a download for whatever language you're using.

查看更多
登录 后发表回答