处理从ajax发布的文件

本文翻译自:Handle file download from ajax post

I have a javascript app that sends ajax POST requests to a certain URL. 我有一个JavaScript应用程序,可将ajax POST请求发送到某个URL。 Response might be a JSON string or it might be a file (as an attachment). 响应可能是JSON字符串,也可能是文件(作为附件)。 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? 我可以在ajax调用中轻松检测Content-Type和Content-Disposition,但是一旦检测到响应中包含文件,如何为客户端提供下载文件? 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. 拜托,拜托,请不要发布暗示我不应该为此使用ajax或重定向浏览器的答案,因为这都不是选项。 Using a plain HTML form is also not an option. 也不能使用纯HTML格式。 What I do need is to show a download dialog to the client. 我需要做的是向客户端显示一个下载对话框。 Can this be done and how? 这可以做到吗?


#1楼

参考:https://stackoom.com/question/15UkE/处理从ajax发布的文件


#2楼

Create a form, use the POST method, submit the form - there's no need for an iframe. 创建表单,使用POST方法,提交表单-不需要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. 当服务器页面响应该请求时,为文件的mime类型写一个响应标头,它将显示一个下载对话框-我已经做了很多次。

You want content-type of application/download - just search for how to provide a download for whatever language you're using. 您想要内容类型的应用程序/下载-只需搜索如何提供所用语言的下载即可。


#3楼

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: 在我的应用程序中,通过在PHP响应中设置正确的标头,我可以轻松地从AJAX调用下载文件:

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. 实际上,这会将浏览器“重定向”到该下载页面,但是正如@ahren在其评论中所说,它不会离开当前页面。

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. 一切都与设置正确的标头有关,因此,如果不是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. 假设您已经知道如何进行AJAX调用,则在客户端上向服务器执行AJAX请求。 The server then generates a link from where this file can be downloaded, eg the 'forward' URL where you want to point to. 然后,服务器生成一个链接,从该链接可以下载此文件,例如,您要指向的“转发” URL。 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): 在处理响应时,您将一个iframe注入您的体内,并将iframe的SRC设置为您刚收到的URL,如下所示(为简化本示例,使用jQuery):

$("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. 如果您如上所述设置了正确的标题,则iframe将强制执行一个下载对话框,而无需将浏览器导航到当前页面之外。

Note 注意

Extra addition in relation to your question; 与您的问题有关的额外内容; I think it's best to always return JSON when requesting stuff with AJAX technology. 我认为在使用AJAX技术请求内容时最好总是返回JSON。 After you've received the JSON response, you can then decide client-side what to do with it. 收到JSON响应后,您可以决定客户端如何处理它。 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. 例如,也许以后您希望用户单击指向URL的下载链接,而不是直接强制下载,在当前设置中,您将必须同时更新客户端和服务器端。


#4楼

Don't give up so quickly, because this can be done (in modern browsers) using parts of the FileAPI: 不要这么快就放弃,因为这可以使用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 = 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));

Here is the old version using jQuery.ajax. 这是使用jQuery.ajax的旧版本。 It might mangle binary data when the response is converted to a string of some charset. 当响应转换为某些字符集的字符串时,它可能会破坏二进制数据。

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

#5楼

I see you've already found out a solution, however I just wanted to add some information which may help someone trying to achieve the same thing with big POST requests. 我看到您已经找到了解决方案,但是我只想添加一些信息,这可以帮助某人尝试通过大型POST请求实现相同的目的。

I had the same issue a couple of weeks ago, indeed it isn't possible to achieve a "clean" download through AJAX, the Filament Group created a jQuery plugin which works exactly how you've already found out, it is called jQuery File Download however there is a downside to this technique. 几周前我遇到了同样的问题,实际上不可能通过AJAX实现“干净”的下载,Filament Group创建了一个jQuery插件,该插件的工作原理与您已经发现的完全一样,称为jQuery File下载但是此技术有一个缺点。

If you're sending big requests through AJAX (say files +1MB) it will negatively impact responsiveness. 如果您通过AJAX发送大请求(例如文件+ 1MB),则会对响应速度产生负面影响。 In slow Internet connections you'll have to wait a lot until the request is sent and also wait for the file to download. 在慢速的Internet连接中,您将不得不等待很多时间,直到发送请求,然后还要等待文件下载。 It isn't like an instant "click" => "popup" => "download start". 这不像即时的“点击” =>“弹出” =>“下载开始”。 It's more like "click" => "wait until data is sent" => "wait for response" => "download start" which makes it appear the file double its size because you'll have to wait for the request to be sent through AJAX and get it back as a downloadable file. 它更像是“单击” =>“等待数据发送完毕” =>“等待响应” =>“下载开始”,这使得文件看起来像是其大小的两倍,因为您必须等待请求的发送通过AJAX并以可下载文件的形式返回。

If you're working with small file sizes <1MB you won't notice this. 如果您使用的是小于1MB的小文件,则不会注意到这一点。 But as I discovered in my own app, for bigger file sizes it is almost unbearable. 但是,正如我在自己的应用程序中发现的那样,对于更大的文件大小,这几乎是难以忍受的。

My app allow users to export images dynamically generated, these images are sent through POST requests in base64 format to the server (it is the only possible way), then processed and sent back to users in form of .png, .jpg files, base64 strings for images +1MB are huge, this force users to wait more than necessary for the file to start downloading. 我的应用程序允许用户导出动态生成的图像,这些图像通过POST请求以base64格式发送到服务器(这是唯一可行的方式),然后进行处理并以.png,.jpg文件,base64的形式发送回用户+ 1MB的图像字符串很大,这迫使用户要花更多的时间等待文件开始下载。 In slow Internet connections it can be really annoying. 在慢速的Internet连接中,这确实很烦人。

My solution for this was to temporary write the file to the server, once it is ready, dynamically generate a link to the file in form of a button which changes between "Please wait..." and "Download" states and at the same time, print the base64 image in a preview popup window so users can "right-click" and save it. 为此,我的解决方案是将文件临时写入服务器,一旦准备好,就以按钮的形式动态生成指向文件的链接,该按钮在“请稍候...”和“下载”状态之间切换,并且保持不变然后,在预览弹出窗口中打印base64图像,以便用户可以“右键单击”并保存它。 This makes all the waiting time more bearable for users, and also speed things up. 这使得所有的等待时间对用户来说都可以承受,并且可以加快处理速度。

Update Sep 30, 2014: 2014年9月30日更新:

Months have passed since I posted this, finally I've found a better approach to speed things up when working with big base64 strings. 自从我发布这篇文章以来已经过去了几个月,终于找到了一种更好的方法来处理大型base64字符串。 I now store base64 strings into the database (using longtext or longblog fields), then I pass its record ID through the jQuery File Download, finally on the download script file I query the database using this ID to pull the base64 string and pass it through the download function. 现在,我将base64字符串存储到数据库中(使用longtext或longblog字段),然后通过jQuery File Download传递其记录ID,最后在下载脚本文件中,我使用该ID查询数据库以提取base64字符串并将其传递给数据库下载功能。

Download Script Example: 下载脚本示例:

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

I know this is way beyond what the OP asked, however I felt it would be good to update my answer with my findings. 我知道这超出了OP的要求,但是我觉得最好用我的发现来更新我的答案。 When I was searching for solutions to my problem, I read lots of "Download from AJAX POST data" threads which didn't give me the answer I was looking for, I hope this information helps someone looking to achieve something like this. 当我寻找问题的解决方案时,我读了很多“从AJAX POST数据下载”线程,这些线程没有给我我所需要的答案,我希望这些信息可以帮助想要实现此目标的人。


#6楼

As others have stated, you can create and submit a form to download via a POST request. 正如其他人所述,您可以创建并提交表单以通过POST请求下载。 However, you don't have to do this manually. 但是,您不必手动执行此操作。

One really simple library for doing exactly this is jquery.redirect . 一个用于执行此操作的非常简单的库是jquery.redirect It provides an API similar to the standard jQuery.post method: 它提供了类似于标准jQuery.post方法的API:

$.redirect(url, [values, [method, [target]]])
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值