向服务器发出 JSON POST 请求,接收二进制响应(Excel 文件),如何下载?

Posted

技术标签:

【中文标题】向服务器发出 JSON POST 请求,接收二进制响应(Excel 文件),如何下载?【英文标题】:Make a JSON POST request to server, receive a binary response (an Excel file), how to download it? 【发布时间】:2014-03-07 08:03:08 【问题描述】:

我正在尝试对向其发送 JSON 数据的服务器进行 POST 调用。服务器获取 JSON 数据,进行一些处理,然后发回 Excel .xlsx 作为响应。我希望浏览器打开“将文件另存为”对话框供用户保存。我一直在寻找一个干净的解决方案来做到这一点。但是这个问题javascript/jQuery to download file via POST with JSON data 的一个可能解决方案建议将 Excel 文件保存在服务器上,然后发回 URL 链接,然后打开 iframe 供用户下载。这对我来说是不行的,因为用户可以在服务器上创建数千个 Excel 文件,而服务器的保存空间有限。我希望解决方案是即时的。我见过的另一个解决方案建议将数据转换为表单,然后使用表单提交。同样,这是不行的,因为我的数据在数百个甚至数千个 Excel 行的范围内。

我的 jQuery POST 调用:

$.ajax( 类型:'POST', url: '/服务器/路径', 数据:JSON.stringify(dataSent), 处理数据:假, 成功:函数(数据,文本状态,jqXHR) , 错误:函数(结果,状态,错误) , 内容类型:'应用程序/json', 数据类型:“应用程序/vnd.ms-excel” );

在后台我设置了这个:

Response.header("Content-Type", "application/vnd.ms-excel")

Response.header("Content-Disposition", "attachment; filename=\"export.xlsx\"")

强制浏览器打开“将文件另存为...”对话框的最佳方法是什么?

谢谢,

【问题讨论】:

使用“在服务器上另存为 Excel”方法,您不应该将 Excel 文件留在服务器上。要么将文件作为进程的一部分删除,要么定期清理一些后台进程。没有人希望您永远在服务器上留下 1000 多个 excel 文件 为什么你认为使用表单的 POST(而不是 GET)在某种程度上比 Jquery POST 在可以发送的数据量上更受限制?在下面,它们是相同的过程。 【参考方案1】:

我不确定有没有办法通过 JS 接收二进制数据然后开始下载。

如果我有这个任务,我会将方法更改为 GET 并生成文件(作为流)并使用适当的标头(Content-Disposition、Content-Length、Content-Type)返回它

【讨论】:

GET 在许多浏览器上被限制为特定的 URL 大小:如果 OP 有很多数据,它可能无法全部通过。 是的,就像 Tim 说的那样,我也不想将我的数据作为 GET 调用的查询参数,那简直是丑陋的。【参考方案2】:

我想办法解决这个问题。我不会进行 POST 调用来强制浏览器打开保存对话框,而是进行 POST 调用来生成文件,然后将文件临时存储在服务器上,返回文件名。然后使用“Content-Disposition: attachment; filename=filename1”对该文件使用 GET 调用。带有该标头的 GET 调用将强制浏览器始终打开“保存此文件”对话框。

【讨论】:

【参考方案3】:

使用Blob URLs 这实际上非常简单。

首先,下载文件。我将在 TypeScript 中使用 fetchasync/await(您始终可以使用承诺链而不是 async/await 和/或 XHR 而不是 fetch):

(async () => 
   let response = await fetch("/post/path", 
       body: JSON.stringify(data), // must match 'Content-Type' header
       headers: 
           'content-type': 'application/json'
       ,
       method: 'POST',
   );

   let blob = await response.blob();
   let filename = "file.txt";

   saveBlobAsFile(filename, blob); // This function is defined below
)();

现在你有了一个 blob,你可以通过创建一个 Blob URL 和一个隐藏链接将它传递给一个函数来下载它:

/**
 * Downloads a blob as a file.
 * 
 * TODO: Support ios Safari, which doesn't support the "download" attribute.
 * 
 * @param name The name of the downloaded file
 * @param blob The blob to download
 */
export function saveBlobAsFile(name: string, blob: Blob) 
    // IE10 & IE11 Support, since they don't support the "download"
    // attribute on anchor tags.
    if (navigator.msSaveBlob) 
        navigator.msSaveBlob(blob, name);
        return;
    

    // Create the URL and hidden anchor tag
    let $hiddenAnchorTag = $('<a style="display: none;"/>');
    let url = URL.createObjectURL(blob);

    // Set the attributes for the download
    $hiddenAnchorTag.attr('href', url);
    $hiddenAnchorTag.attr('download', name);

    // Insert the link and click to download
    $(document.body).append($hiddenAnchorTag);
    $hiddenAnchorTag[0].click();

    // Clean up after ourselves
    $hiddenAnchorTag.remove();
    URL.revokeObjectURL(url);

其他说明

fetch 响应对象包含标头,因此您可以解析Content-Disposition 以获取服务器预期的文件名。我在网上找到了几个很好的正则表达式,它们对此非常有效。里程可能会有所不同,但我建议为此创建一个函数并使用一些不错的单元测试对其进行限制。 这比尝试将当前位置设置为文件的位置要好得多,因为它允许您在 POST 中包含更多详细信息,包括 API 密钥或类似的安全信息,此外它还允许您处理错误/exceptions 干净,并知道操作何时完成(例如在尝试离开用户仍在等待下载的页面时发出警告)。 Blob 甚至支持对数据进行切片,因此您可以通过获取单个切片(耶 Content-Range!)并将它们组合成单个 Blob 并下载最终的切片来扩展它以下载大文件blob,同时为用户提供了一个不错的加载进度指示器! 您可以像使用任何其他 URL 一样使用 Blob URL。它们指向资源,因此您可以将该 URL 传递给 img 标签、其他请求 URL 的库、src 标签等。 @phamductri 在服务器上提供临时文件可能非常危险!如果您需要使用该模式,您需要使用表或查找来抽象文件名,因此用户无法控制实际的文件名或路径(在指定目录中使用 UUID),并确保用户可以只下载他们生成的文件。您需要确保以下几点(这不是一个完整的列表): 用户不能指定任意路径保存到 他们可以保存您的数据库配置文件等。 用户不能指定任意读取路径 他们可以读取您的数据库配置文件等。 文件名不能冲突。 用户 A 生成一个名为“accounts_to_pay.csv”的文件。用户 B 同时生成了一个同名文件(无论是恶意的还是意外的),现在用户 A 正在向用户 B 想要的任何人付款。

【讨论】:

以上是关于向服务器发出 JSON POST 请求,接收二进制响应(Excel 文件),如何下载?的主要内容,如果未能解决你的问题,请参考以下文章

向 Fastify 发出 POST 请求时,JSON 未被解析以进行验证

POST 后无法映射接收到的对象

尝试在 https://api.thetvdb.com/ 向 TVDB REST API 发出 JSON POST 请求,但出现 CORS 错误

发出 JSON RPC API 的 GET 请求而不是 POST 请求

Laravel 4 使用数据从控制器向外部 url 发出 post 请求

角度不会向节点js rest API发出发布请求