使用 Express JS 在浏览器中显示 PDF

37

我正尝试通过Express提供PDF文件,以便在浏览器中显示:

app.post('/asset', function(request, response){
  var tempFile="/home/applmgr/Desktop/123456.pdf";
  fs.readFile(tempFile, function (err,data){
     response.contentType("application/pdf");
     response.send(data);
  });
});

然而,浏览器显示二进制内容。如何正确处理?


尝试使用response.end(data); Express的send方法可能会进行某些猜测。 - ebohlman
我已经添加了一个答案,说明如何正确设置浏览器对你提供的文件做出反应。你可以在头部设置这些内容,如果有任何问题,请告诉我。 - AlbertEngelB
7个回答

40
指定文件下载的处理方式都归结于 Content-disposition 头。您也可以在这里指定文件的名称。我们还设置了 Content-type,以确保浏览器知道如何处理给它的文件。
app.post('/url/to/hit', function(req, res, next) {
  var stream = fs.createReadStream('/location/of/pdf');
  var filename = "WhateverFilenameYouWant.pdf"; 
  // Be careful of special characters

  filename = encodeURIComponent(filename);
  // Ideally this should strip them

  res.setHeader('Content-disposition', 'inline; filename="' + filename + '"');
  res.setHeader('Content-type', 'application/pdf');

  stream.pipe(res);
});

现在,如果你更仔细地查看Content-disposition,你会注意到inline;字段是设置浏览器对文件反应的方式。如果你想强制下载,可以将inline;设置为attachment; 我也发现(通过几次被烧伤),如果你在文件名中设置特殊字符,它可能会出错。所以我使用encodeURIComponent()对文件名进行了编码,以确保不会发生这种情况。
希望这能帮助其他试图解决同样问题的人!
编辑
在我最初发布这篇文章和现在之间的时间里,我已经发现了如何正确编码content-disposition的文件名参数。根据规范,文件名应该是RFC5987编码。我最终在MDN上找到了一个示例代码片段,它正确处理了编码(encodeURIComponent()并不是这个字段的完全正确格式)。链接1 链接2

MDN片段

var fileName = 'my file(2).txt';
var header = "Content-Disposition: attachment; filename*=UTF-8''" 
             + encodeRFC5987ValueChars(fileName);

console.log(header); 
// logs "Content-Disposition: attachment; filename*=UTF-8''my%20file%282%29.txt"

function encodeRFC5987ValueChars (str) {
    return encodeURIComponent(str).
        // Note that although RFC3986 reserves "!", RFC5987 does not,
        // so we do not need to escape it
        replace(/['()]/g, escape). // i.e., %27 %28 %29
        replace(/\*/g, '%2A').
            // The following are not required for percent-encoding per RFC5987, 
            // so we can allow for a little better readability over the wire: |`^
            replace(/%(?:7C|60|5E)/g, unescape);
}

除此之外,需要注意的是,浏览器也没有完全遵守规范。有些字符在下载时仍然会出现错误(至少在我的测试中是这样的)。

您可以通过更新下载方式来解决这个问题。如果您的下载URL以文件名结尾(并且您没有在头部提供filename属性),它将从URL编码值中正确获取文件名。例如,在IE中:'http://subdomain.domain-url.com/some/path/to/downloads/' + encodeURIComponent("You're there, download this!.pdf")

哎呀,所有这些只是为了提供一个文件名给您的下载!


只是指出一个笔误,应该在content-disposition头中使用“attachment”,而不是“attatchment”。 - Marcus

13

我测试了你的代码,在Chrome中可以正常工作,但需要更改一个地方:
app.post 改为 app.get

编辑:由于你似乎认为只使用POST的服务器是个好主意,请看这篇文章:http://net.tutsplus.com/tutorials/other/a-beginners-introduction-to-http-and-rest/
向下滚动到HTTP动词这一部分,了解GET和POST之间的区别。 :)

一些快速的研究表明其他浏览器可能会遇到其他问题,例如IE可能会期望URL以.pdf结尾。由于我在Mac上,无法为您进行测试;)


1
嗯...不确定为什么您要配置服务器仅执行POST请求:浏览器会GET您发送的任何URL。这就是HTTP的工作方式。 :) POST通常用于表单提交,或者当您使用HTTP创建某些内容而不是获取它时使用。 - rdrey
1
公平地说,有一些使用情况下,您可能希望在POST请求后获得一个PDF文件作为响应。 - AlbertEngelB
1
我对Node还比较新。这种提供pdf文件的方法与我一直在使用的sendfile(some.html)有何不同?有人能解释一下它是如何工作的,以及为什么我不必从文件系统中读取html文件吗? - user137717
@user137717 很可能是"content-type"头部信息。 它为浏览器提供了打开传入文件的提示。 - AlbertEngelB
@rdrey 感谢您的信任投票!我还进一步扩展了我的答案,以涵盖设置文件名时遇到的各种问题。希望这能帮助其他试图提供 PDF 下载的人。 - AlbertEngelB
显示剩余5条评论

12

我的解决方案:直接将PDF发送给浏览器。

app.get('/my/pdf', function (req, res) {
    var doc = new Pdf();
    doc.text("Hello World", 50, 50);

    doc.output( function(pdf) {
        res.type('application/pdf');
        res.end(pdf, 'binary');
    });
});

在我的情况下,使用res.end()的第二个参数'binary'解决了问题。否则,express会将其解释为字符串。


4
方法 doc.output 已弃用。 - Tyler Eich
谢谢!“binary”参数真的救了我的一天! - Ming
非常感谢,这个答案用 jsPDF 真的救了我一天。 - ReZ
从数据库中读取PDF内容(通过变量),然后使用"res.type('application/pdf')",这个方法真是救了我一天。 - undefined

12

实际上,Express已经有了这个功能用于发送文件。你所需要的只是:

app.get('/sendMePDF', function(req, res) {
  res.sendFile(__dirname + "/static/pdf/Rabbi.pdf");
})

在这里,服务器将发送名为 "Rabbi.pdf" 的文件,并且它会在浏览器中打开,就像你在浏览器中打开 PDF 一样。我把文件放在了 "static" 文件夹中,但你可以把它放在任何地方,需要知道的是 sendFile() 函数接受绝对路径作为参数(而不是相对路径)。


这会在同一个窗口中打开PDF文件。有没有一种方法可以在新窗口中打开它? - Gangula
在某些浏览器中,它只会要求您下载它。 - Ivan Lopes

6
这很简单,只需遵循以下代码:
var express = require('express'),
    fs = require('fs'),
    app = express();

app.get('/', function (req, res) {
    var filePath = "/files/my_pdf_file.pdf";

    fs.readFile(__dirname + filePath , function (err,data){
        res.contentType("application/pdf");
        res.send(data);
    });
});

app.listen(3000, function(){
    console.log('Listening on 3000');
});

完整的repo::

克隆node-cheat pdf_browser,运行node app,然后跟着npm install express

祝您帮助愉快!


1
根据Express js文档,您可以使用下面所示的一个函数设置内容类型和内容配置。
 fs.readFile(filePath, (err, data) => {
res.set({
  "Content-Type": "application/pdf", //here you set the content type to pdf
  "Content-Disposition": "inline; filename=" + fileName, //if you change from inline to attachment if forces the file to download but inline displays the file on the browser
});
res.send(data); // here we send the pdf file to the browser
});

0
post('/fileToSend/', async (req, res) => {

  const documentPath = path.join(
    __dirname,
    '../assets/documents/document.pdf'
  );
    
  res.setHeader('Content-Type', 'application/pdf');
  res.setHeader('Content-Disposition', 'attachment; filename=document.pdf');

  return res.download(documentPath);
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接