使用经过身份验证的REST中的fetch()下载和保存数据

jua*_*gan 9 javascript ajax xmlhttprequest flask reactjs

我有一个React应用程序,使用Python和Flask构建的REST后端.我正在从数据库下载数据并通过浏览器将其保存为CSV文件. 我有这个工作. 然而,我不明白的原因是为什么我必须超越我一直在阅读的资源并将其混合以使其发挥作用.为什么我没有发现这个概述更好?

有人说我要做的就是用mimetype设置响应头,并且Content-Disposition: attachment; filename=something.csv:

然而,仅此一点,仅使用普通链接,而不是fetch()和身份验证,所以我不得不寻找将客户端数据保存到磁盘的方法,例如:

所以,我的问题是:

  • 为什么我必须这样做?, 要么
  • 我错过了什么 - 更简单的方法是什么?

似乎第一个问题的答案是我不能修改请求头(添加身份验证令牌),除非通过XHR类型的工作.这似乎在这里得到了回答(没有回答,真的):

并且,由于某种原因,对XHR的反应Content-Disposition: attachment毫无意义.真的吗?在现代浏览器中管理这样的请求是否有更内在的方法?

我觉得我不明白这一点,这让我感到烦恼.

无论如何,这是我希望简化的工作代码,如果可能的话:

JS(React):

// 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)
    })
}
Run Code Online (Sandbox Code Playgroud)

Python(Flask):

@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)}
            )
Run Code Online (Sandbox Code Playgroud)

jua*_*gan 6

我被迫回到这个话题,因为我们刚刚遇到了 Chrome 中 2MB 的限制。谁知道?(这个人,例如: https: //craignicol.wordpress.com/2016/07/19/excellent-export-and-the-chrome-url-limit/在我提出问题几个月后发布,我的解决方案是下面实施。)

无论如何,我现在将尝试回答我自己的问题,因为我还没有看到关于所请求的身份验证要求的可接受的答案。(尽管如此,Conrado 确实提供了一些针对该要求的有用链接。)

至于以下问题:为什么我必须这样做,是否没有更内在的方式?答案似乎分别是“只是因为”和“不”。我知道,这不是很好的答案。我希望我能解释得更多……但我真的不能不把脚放在嘴里。我不是网络专家,当然也不是解释它的合适人选。这就是我读到的内容。您无法在身份验证时将流伪造为文件下载。

至于:我缺少什么以及更简单的方法是什么?好吧,没有更简单的方法了。当然,我可以在代码库中添加更多像 FileSaver.js 这样的库,并让它们为我隐藏一些代码。但是,我不喜欢比我真正需要的更大的工具集(我正在看着你,你可笑的 50MB 网站)。我可以通过将 download() 函数隐藏在其他地方并导入它来完成与这些库相同的事情。不,从我的角度来看,这并不容易。获取预构建函数的工作量可能会减少,但就执行下载所需的代码量而言,这并不容易。对不起。

但是……我错过了一些东西:导致 Chrome 中 2MB 限制的东西。当时,我并没有真正理解我正在使用的这个 URI 数据 hack 是如何工作的。我找到了一些有效的代码,并使用了它。我现在明白了——现在我有更多的时间来更深入地阅读这部分问题。简而言之,我缺少 blob 选项与 URI 选项。当然,Blob 在各种浏览器中都有其自身的局限性,但是,考虑到我的用例不会受到 2016 年任何浏览器的影响,Blob 选项从一开始就是一个更好的路线......而且感觉不那么老套(也许仅仅因为这一点就有点“容易”)。

这是我当前的解决方案,在回退到 URI 数据 hack 之前尝试进行 blob 保存:

JS(反应):

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)
    })
}
Run Code Online (Sandbox Code Playgroud)

注意:我走这条路只是因为我不需要担心 FileSaver.js 构建的所有用例(这仅适用于管理应用程序上的功能,而不是面向公众的功能)。