我有一个React应用程序,它使用Python和Flask构建的REST后端。我正在从数据库下载数据并通过浏览器将其保存为CSV文件。 我有这个工作。然而,我不明白为什么我不得不超越我一直在阅读的资源并将其混合以使其工作。为什么我没有发现这个概述更好?
有人说我要做的就是用mimetype和Content-Disposition: attachment; filename=something.csv
设置响应头:
然而,仅此一点,仅使用普通链接,而不是fetch()
和身份验证,因此我不得不寻找将客户端数据保存到磁盘的方法,如下所示:
所以,我的问题是:
似乎第一个问题的答案是我无法修改请求标头(添加身份验证令牌),除非通过XHR类型的工作。这似乎在这里得到回答(没有回答,真的):
而且,由于某种原因,对Content-Disposition: attachment
的XHR的回应毫无意义。真的吗?在现代浏览器中管理这样的请求是否有更内在的方法?
我觉得我不明白这一点,这让我感到烦恼。
无论如何,这是我希望简化的工作代码,如果可能的话:
// https://stackoverflow.com/a/18197511/680464
download(filename, text) {
var pom = document.createElement('a');
pom.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(text));
pom.setAttribute('download', filename);
if (document.createEvent) {
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true);
pom.dispatchEvent(event);
}
else {
pom.click();
}
}
downloadReport(studyID) {
fetch(`/api/admin/studies/${studyID}/csv`
, {
headers: {
"Authorization": "Bearer " + this.props.authAPI.getToken()
, "Accept": "text/csv"
}
}
)
.then(this.checkStatus.bind(this))
.then((response) => response.text())
.then((responseText) => this.download(`study${studyID}.csv`, responseText))
.catch((error) => {
console.error(this.props.location.pathname, error)
})
}
@app.route("/api/admin/studies/<int:study_id>/csv", methods=["GET"])
@admin.login_required
def admin_get_csv(study_id):
test = [("1","2","3"),("4","5","6")]
def generate():
for row in test:
yield ",".join(row) + "\n"
return Response(
generate()
, mimetype="text/csv"
, headers={"Content-Disposition": "attachment; filename=study{0}.csv".format(study_id)}
)
答案 0 :(得分:1)
告诉浏览器如何使用DOM处理下载。使用Node.appendChild
注入锚标记,然后让用户单击链接。
<a href="/api/admin/studies/1/csv" download="study1.csv">Download CSV</a>
您正在做的是下载文件,将已完成的请求插入到锚标记中,并在代码中使用pom.click()
创建第二个请求。
编辑:我错过了授权标题。如果您喜欢这个建议,可以将令牌放在查询字符串中。
答案 1 :(得分:0)
参考this answer,您可以使用FileSaver或download.js个库。
示例:
var saveAs = require('file-saver');
fetch('/download/urf/file', {
headers: {
'Content-Type': 'application/pdf'
},
responseType: 'blob'
}).then(response => response.blob())
.then(blob => saveAs(blob, 'test.csv'));
答案 2 :(得分:0)
由于我们在Chrome浏览器中遇到了2MB的限制,因此我不得不重新考虑这一点。谁知道? (此人,其中一个:https://craignicol.wordpress.com/2016/07/19/excellent-export-and-the-chrome-url-limit/在我的问题出现后的几个月内发布了,我在下面实施了该解决方案。)
无论如何,我现在将尝试回答我自己的问题,因为我尚未看到有关所要求的身份验证要求的可接受答案。 (尽管,Conrado确实提供了一些有用的链接来满足该要求。)
关于以下问题:为什么我必须这样做,还有没有更内在的方法?答案分别是“仅因为”和“否”。我知道答案不是很好。我希望我能做更多的解释...但是我真的不能不把脚伸进嘴里的某个地方。我不是网络专家,当然不是解释它的合适人选。这就是我所读的内容。验证时,您只是无法将流伪造为文件下载。
至于:我所缺少的是什么?更简单的方法是什么?好吧,没有比这更简单的方法了。当然,我可以在代码库中添加更多类似FileSaver.js的库,并让它们为我隐藏一些代码。但是,我不喜欢比我真正需要的更大的工具集(我在看您,您可笑的50MB网站)。通过将我的download()函数隐藏在其他位置并将其导入,可以完成与这些库相同的操作。不,从我的角度来看,这并不容易。抢占一个预建函数的工作量可能较少,但就执行下载所需的代码量而言,却并不容易。抱歉。
但是...我 丢失了一些东西:导致Chrome达到2MB限制的那件事。当时,我还不太了解我所使用的URI数据黑客是如何工作的。我找到了一些有效的代码,并使用了它。我现在明白了—现在我有更多的时间来深入了解问题的这一部分。简而言之,我错过了blob选项和URI选项。当然,blob在各种浏览器中都有其自身的局限性,但是,考虑到我的用例在2016年不会受到任何使用情况的影响,blob选项从一开始就是一条更好的选择... hacky少(仅此而已,也许有点“容易”)。
这是我当前的解决方案,尝试先保存Blob,然后再退回URI数据黑客:
saveStreamCSV(filename, text) {
this.setState({downloadingCSV: false})
if(window.navigator.msSaveBlob) {
// IE 10 and later, and Edge.
var blobObject = new Blob([text], {type: 'text/csv'});
window.navigator.msSaveBlob(blobObject, filename);
} else {
// Everthing else (except old IE).
// Create a dummy anchor (with a download attribute) to click.
var anchor = document.createElement('a');
anchor.download = filename;
if(window.URL.createObjectURL) {
// Everything else new.
var blobObject = new Blob([text], {type: 'text/csv'});
anchor.href = window.URL.createObjectURL(blobObject);
} else {
// Fallback for older browsers (limited to 2MB on post-2010 Chrome).
// Load up the data into the URI for "download."
anchor.href = 'data:text/csv;charset=utf-8,' + encodeURIComponent(text);
}
// Now, click it.
if (document.createEvent) {
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true);
anchor.dispatchEvent(event);
}
else {
anchor.click();
}
}
}
handleDownloadClick(e) {
this.setState({downloadingCSV: true})
fetch(`/api/admin/studies/${this.props.study.id}/csv`
, {
headers: {
"Authorization": "Bearer " + this.props.authAPI.getToken()
, "Accept": "text/csv"
}
}
)
.then((response) => response.text())
.then((responseText) => this.saveStreamCSV(`study${this.props.study.id}.csv`, responseText))
.catch((error) => {
this.setState({downloadingCSV: false})
console.error("CSV handleDownloadClick:", error)
})
}
注意:之所以选择这条路线,是因为我不必担心FileSaver.js的所有使用案例(这仅是针对管理应用程序的功能,而不是面向公众的)。