【问题标题】:Prompt file download with XMLHttpRequest使用 XMLHttpRequest 提示下载文件
【发布时间】:2014-05-08 13:52:45
【问题描述】:

我知道 jQuery 的 ajax 方法无法处理下载,我不想添加 jQuery 插件来执行此操作。

我想知道如何使用 XMLHttpRequest 发送 POST 数据来下载文件。

这是我尝试过的:

var postData = new FormData();
postData.append('cells', JSON.stringify(output));

var xhr = new XMLHttpRequest();
xhr.open('POST', '/export/', true);
xhr.setRequestHeader("X-CSRFToken", csrftoken);
xhr.responseType = 'arraybuffer';
xhr.onload = function (e) {
    console.log(e);
    console.log(xhr);
}
xhr.send(postData);

我正在使用 Django,并且文件似乎已成功发送回客户端。在 Chrome 的网络选项卡中,我可以在预览选项卡中看到乱码(这是我所期望的)。但我想发回一个 zip 文件,而不是 zip 文件的文本表示。这是 Django 后端:

wrapper = FileWrapper(tmp_file)
response = HttpResponse(wrapper, content_type='application/zip')
response['Content-Disposition'] = "attachment; filename=export.zip"
response['Content-Length'] = tmp_file.tell()
return response

我已经搜索了好几个小时,但没有找到关于如何使用 XMLHttpRequests 执行此操作的适当示例。我不想使用 POST 操作创建正确的 html 表单,因为表单数据相当大,并且是动态创建的。

上面的代码有问题吗?我错过了什么?我只是不知道如何实际将数据作为下载发送到客户端。

【问题讨论】:

  • 我没有发现任何问题。如果您更改视图以响应 GET 请求返回文件并在浏览器中打开 URL,它是否按预期下载文件?
  • 是的,它会下载@Marat。我只需使用带有“/export/”操作的普通html表单就可以下载它,并且我得到了xhr的响应,但它只是不会触发下载。是不是xhr不能下载到客户端?
  • 你能不接受我的回答而接受史蒂文的回答吗?把过时的放在上面真的很令人困惑

标签: javascript django post xmlhttprequest


【解决方案1】:

如果您在发送请求之前将XMLHttpRequest.responseType 属性设置为'blob',那么当您返回响应时,它将被表示为一个blob。然后,您可以将 blob 保存到一个临时文件并导航到它。

var postData = new FormData();
postData.append('cells', JSON.stringify(output));

var xhr = new XMLHttpRequest();
xhr.open('POST', '/export/', true);
xhr.setRequestHeader('X-CSRFToken', csrftoken);
xhr.responseType = 'blob';
xhr.onload = function (e) {
    var blob = e.currentTarget.response;
    var contentDispo = e.currentTarget.getResponseHeader('Content-Disposition');
    // https://stackoverflow.com/a/23054920/
    var fileName = contentDispo.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)[1];
    saveOrOpenBlob(blob, fileName);
}
xhr.send(postData);

这是saveOrOpenBlob的示例实现:

function saveOrOpenBlob(blob, fileName) {
    window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
    window.requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fs) {
        fs.root.getFile(fileName, { create: true }, function (fileEntry) {
            fileEntry.createWriter(function (fileWriter) {
                fileWriter.addEventListener("writeend", function () {
                    window.location = fileEntry.toURL();
                }, false);
                fileWriter.write(blob, "_blank");
            }, function () { });
        }, function () { });
    }, function () { });
}

如果您不关心让浏览器在文件是可查看文件类型时导航到该文件,那么创建一个始终直接保存到文件的方法要简单得多:

function saveBlob(blob, fileName) {
    var a = document.createElement('a');
    a.href = window.URL.createObjectURL(blob);
    a.download = fileName;
    a.dispatchEvent(new MouseEvent('click'));
}

【讨论】:

  • 这拯救了我的一天 :) 谢谢。
  • 这是一个非常有趣的解决方案。我正在尝试类似的东西,但在 WebView 容器中 - 请参阅此处 stackoverflow.com/questions/49988202/…
  • 这对于小文件来说是一个很好的解决方案,但请注意,这会将整个文件放在内存中,然后再将其发送到磁盘,因此如果下载非常大的文件,请考虑这一点。此外,用户将无法看到实际下载的进度(如在 Chrome 的状态栏中),他们将看到的是从内存“下载”到磁盘的进度(一旦实际下载完成)。
  • 似乎“匹配”正在返回一个带下划线的文件名(当内容配置文件名包含周围的引号时)
  • 注意 saveOrOpenBlob 函数使用 requestFileSystem,不建议每个 MDN:developer.mozilla.org/en-US/docs/Web/API/Window/…
【解决方案2】:

更新:自引入Blob API 以来,此答案不再准确。详情请参考 Steven 的回答。


原始答案:

XHR 请求不会触发文件下载。我找不到明确的要求,但W3C doc on XMLHttpRequest 也没有描述对 content-disposition=attachment 响应的任何特殊反应

如果不是 POST 请求,您可以在单独的选项卡中通过 window.open() 下载文件。 Here 建议使用带有target=_blank 的隐藏表单

【讨论】:

  • 不确定你是否真的需要 window.open。在没有target="_blank" 的情况下,我已经完成了我打算做的事情。但我认为 XHR 不会触发下载是正确的。
  • 如果他使用的是POST请求但之后需要下载怎么办?
  • @gumuruh 我不确定与原始问题有什么区别。另外,请注意,这个答案实际上不再正确(请参阅更新部分)
  • 在新选项卡中打开会使客户端下载文件两次(除非缓存),这是大文件或移动连接的考虑因素。一次检查是否是下载,两次在新标签中加载时
  • Here is 一个有趣的 hack,效果很好:
【解决方案3】:

download: function(){
    var postData = new FormData();
		var xhr = new XMLHttpRequest();
		xhr.open('GET', downloadUrl, true);
		xhr.responseType = 'blob';
		xhr.onload = function (e) {
			var blob = xhr.response;
			this.saveOrOpenBlob(blob);
		}.bind(this)
		xhr.send(postData);
 }

saveOrOpenBlob: function(blob) {
		var assetRecord = this.getAssetRecord();
		var fileName = 'Test.mp4'
		var tempEl = document.createElement("a");
    	document.body.appendChild(tempEl);
    	tempEl.style = "display: none";
        url = window.URL.createObjectURL(blob);
        tempEl.href = url;
        tempEl.download = fileName;
        tempEl.click();
		window.URL.revokeObjectURL(url);
	},

试试这个对我有用。

【讨论】:

  • 怀疑您需要将 tempEl 附加到 document.body,但这应该可以正常工作。我也喜欢revokeObjectURL
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-25
  • 2012-07-04
  • 2015-04-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多