【问题标题】:Express.JS: Force download of PDF file by cross-origin URLExpress.JS:通过跨域 URL 强制下载 PDF 文件
【发布时间】:2021-09-28 11:21:29
【问题描述】:

编辑:

标题已更改,以便任何人都可以提出替代解决方案,以使用类似的技术和平台实现相同的结果。不必一定是res.attachment

我试图实现通过跨域 URL 强制下载 PDF 文件。代码似乎按预期工作,但下载的文件是 ZERO BYTES,为什么?

在服务器上:

app.get("/download", (req, res) => {
    res.type("application/octet-stream");
    // Note that the PDF URL is Cross-Origin.
    res.attachment("https://cross-origin-domain-name.com/downloads/fiename.pdf");
    res.send();
});

在 HTML 上:

<a class="a-very-big-button" href="/download">Download PDF</a>

我错过了什么吗?我确实尝试了许多其他选项,例如 res.download()readStream.pipe(res) 方法,但大多数都要求文件在同一台服务器上。对于我的应用,我需要帮助我的客户根据他们提交的 URL 提供一个下载 PDF 按钮,该 URL可能在他们的 Web 服务器上。任何意见,将不胜感激!谢谢。

【问题讨论】:

    标签: javascript node.js express


    【解决方案1】:

    res.attachment 确实将字符串作为其唯一参数,但该字符串用作向浏览器提示用户决定保存文件时文件名应该是什么。它不允许您指定要获取的 URL 或文件名

    因为你没有发送任何数据(res.send() 没有缓冲区或.write() 调用),只是关于文件名应该是什么的建议,下载是 0 字节。

    你可以做的是pipe a HTTP request to res,它会让你的服务器下载并转发文件。该文件不会缓存在您的服务器上,并且会“消耗”上传和下载容量(但没有存储空间)。


    关于如何将 HTTPS 请求通过管道传输到响应的示例。

    您可以使用许多其他库来代替 Node 的内置 https.request。它们中的大多数都支持流文件。这些库可以更轻松地处理错误。

    const express = require('express');
    const https = require('https');
    
    const app = express();
    const url = 'https://full-url-to-your/remote-file.pdf';
    const headerAllowList = [
      'content-type', 'content-length', 'last-modified', 'etag'
    ];
    
    app.use('/', async (req, res, next) => {
      // Create a HTTPS request
      const externalRequest = https.request(url, {
        headers: {
          // You can add headers like authorization or user agent strings here.
          // Accept: '*/*',
          // 'User-Agent': '',
        },
      }, (externalResponse) => {
        // This callback won't start until `.end()` is called.
    
        // To make life easier on ourselves, we can copy some of the headers
        // that the server sent your Node app and pass them along to the user.
        headerAllowList
          .filter(header => header in externalResponse.headers)
          .forEach(header => res.set(header, externalResponse.headers[header]));
    
        // If we didn't have content-type in the above headerAllowList, 
        // you could manually tell browser to expect a PDF file.
        // res.set('Content-Type', 'application/pdf');
    
        // Suggest a filename
        res.attachment('some-file.pdf');
    
        // Start piping the ReadableStream to Express' res.
        externalResponse.pipe(res);
      });
    
      externalRequest.on('error', (err) => {
        next(err);
      });
    
      // Start executing the HTTPS request
      externalRequest.end();
    });
    app.listen(8000);
    

    如果您访问 localhost:8000,您将收到一个 PDF,其中包含一个带有建议文件名的保存文件对话框,从指定的 URL 提供。

    【讨论】:

    • 感谢您的回复!但不幸的是,我无法将所有客户的 PDF 文件下载到我的服务器上,因为这可能会在 Google Cloud Platform (GCP) 上花费我很多。
    • 它将花费出口和入口带宽成本,但不会产生任何存储费用(因为如前所述,没有缓存)。您唯一的其他选择是简单地使用 &lt;a download="some-filename.pdf" href="https://not-your-website-but-the-remote-system/file.pdf"&gt; 链接到文件或执行 301 重定向。
    • download 属性仅适用于same-origin。顺便说一句,我无法让the suggested link 工作。由于request 已弃用,我改用fetch 答案,但仍然无法正常工作。您可以将完整代码添加到现有答案中吗?老实说,我在ExpressJS 方面不是很好。我真的需要Save as 对话框来弹出并下载PDF 文件。谢谢!
    • 我添加了一个不使用 Express 以外的外部库的示例。这将提供一个带有“另存为”对话框的 PDF 文件,并带有建议的文件名,并且作为奖励,它将保留像 Content-Length 这样的标题。
    • 有传出(请求:您的 Node 应用程序到远程服务器)和传入(响应:远程服务器到您的 Node 应用程序)标头。您引用的内容是从您的节点应用程序发送到其他服务器的内容。远程服务器可能需要Authorization 标头、User-Agent 或其他内容。如果是,您可以在此处添加它们,如果不是,则将其留空。 (编辑:当然,还有您的 Node 应用程序发送给用户的标头。我希望 cmets 能够清楚地说明什么是什么!)
    猜你喜欢
    • 1970-01-01
    • 2017-08-13
    • 2023-03-28
    • 2020-12-13
    • 2013-03-31
    • 1970-01-01
    • 1970-01-01
    • 2014-11-19
    • 2020-04-28
    相关资源
    最近更新 更多