简体   繁体   English

ExpressJS 和 PDFKit - 在 memory 中生成 PDF 并发送到客户端以供下载

[英]ExpressJS and PDFKit - generate a PDF in memory and send to client for download

In my api router, there is a function called generatePDF which aims to use PDFKit module to generate a PDF file in memory and send to client for download instead of displaying only. In my api router, there is a function called generatePDF which aims to use PDFKit module to generate a PDF file in memory and send to client for download instead of displaying only.

In api.js :api.js

var express = require('express');
var router = express.Router();

const PDFDocument = require('pdfkit');

router.get('/generatePDF', async function(req, res, next) {
    var myDoc = new PDFDocument({bufferPages: true});
    myDoc.pipe(res);
    myDoc.font('Times-Roman')
         .fontSize(12)
         .text(`this is a test text`);
    myDoc.end();
    res.writeHead(200, {
        'Content-Type': 'application/pdf',
        'Content-disposition': 'attachment;filename=test.pdf',
        'Content-Length': 1111
    });
    res.send( myDoc.toString('base64'));
});

module.exports = router;

This does not work.这不起作用。 The error message is (node:11444) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client .错误消息是(node:11444) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

How can I go about fixing the issue and getting it work?我该如何解决问题并使其正常工作?

Also, a relevant question would be how I can separate the business logic of PDF generation from the router and chain them up?另外,一个相关的问题是如何将 PDF 生成的业务逻辑与路由器分离并将它们链接起来?

Complete solution.完整的解决方案。

var express = require('express');
var router = express.Router();

const PDFDocument =  require('pdfkit');

router.get('/generatePDF', async function(req, res, next) {
var myDoc = new PDFDocument({bufferPages: true});

let buffers = [];
myDoc.on('data', buffers.push.bind(buffers));
myDoc.on('end', () => {

    let pdfData = Buffer.concat(buffers);
    res.writeHead(200, {
    'Content-Length': Buffer.byteLength(pdfData),
    'Content-Type': 'application/pdf',
    'Content-disposition': 'attachment;filename=test.pdf',})
    .end(pdfData);

});

myDoc.font('Times-Roman')
     .fontSize(12)
     .text(`this is a test text`);
myDoc.end();
});

module.exports = router;

First I recommend to create a service for the PDF kit.首先,我建议为 PDF 套件创建服务。 And then a Controller to the route that you want.然后是 Controller 到您想要的路线。

I used get-stream to make this easier.我使用get-stream使这更容易。

It also answers your question to the accepted answer:它还回答了您对已接受答案的问题:

how I can separate the business logic of PDF generation from the router and chain them up?如何将 PDF 一代的业务逻辑与路由器分离并将它们链接起来?

This is my professional solution:这是我的专业解决方案:

import PDFDocument from 'pdfkit';
import getStream from 'get-stream';
import fs from 'fs';


export default class PdfKitService {
  /**
   * Generate a PDF of the letter
   *
   * @returns {Buffer}
   */
  async generatePdf() {
    try {
      const doc = new PDFDocument();

      doc.fontSize(25).text('Some text with an embedded font!', 100, 100);

      if (process.env.NODE_ENV === 'development') {
        doc.pipe(fs.createWriteStream(`${__dirname}/../file.pdf`));
      }

      doc.end();

      const pdfStream = await getStream.buffer(doc);

      return pdfStream;
    } catch (error) {
      return null;
    }
  }
}

And then the method of the Controller:然后是Controller的方法:

(...) 

  async show(req, res) {
    const pdfKitService = new PdfKitService();
    const pdfStream = await pdfKitService.generatePdf();

    res
      .writeHead(200, {
        'Content-Length': Buffer.byteLength(pdfStream),
        'Content-Type': 'application/pdf',
        'Content-disposition': 'attachment;filename=test.pdf',
      })
      .end(pdfStream);

 
  }

And finally the route:最后是路线:

routes.get('/pdf', FileController.show);

For those how don't want to waste RAM on buffering PDFs and send chunks right away to the client:对于那些不想浪费内存来缓冲 PDF 并立即将块发送给客户端的人:

    const filename = `Receipt_${invoice.number}.pdf`;
    const doc = new PDFDocument({ bufferPages: true });
    const stream = res.writeHead(200, {
      'Content-Type': 'application/pdf',
      'Content-disposition': `attachment;filename=${filename}.pdf`,
    });
    doc.on('data', (chunk) => stream.write(chunk));
    doc.on('end', () => stream.end());

    doc.font('Times-Roman')
      .fontSize(12)
      .text(`this is a test text`);
    doc.end();

You can use blob stream like this.您可以像这样使用blob stream。

reference: https://pdfkit.org/index.html参考: https://pdfkit.org/index.html

const PDFDocument = require('pdfkit');

const blobStream  = require('blob-stream');

// create a document the same way as above

const doc = new PDFDocument;

// pipe the document to a blob

const stream = doc.pipe(blobStream());

// add your content to the document here, as usual

doc.font('fonts/PalatinoBold.ttf')
   .fontSize(25)
   .text('Some text with an embedded font!', 100, 100);

// get a blob when you're done
doc.end();
stream.on('finish', function() {
  // get a blob you can do whatever you like with
  const blob = stream.toBlob('application/pdf');

  // or get a blob URL for display in the browser
  const url = stream.toBlobURL('application/pdf');
  iframe.src = url;
});

pipe all your pdf data to your blob and then write it to a file or url. pipe 所有 pdf 数据到您的 blob,然后将其写入文件或 url。 or u can store the pdf directly into cloud storage like firebase storage and send download link to client.或者您可以将 pdf 直接存储到云存储中,如 firebase 存储并将下载链接发送给客户端。

If you want to generate pdfs dynamically then you can also try out html-pdf library in node which allows you to create a pdf from html template and add dynamic data in it.如果您想动态生成 pdf,那么您还可以在节点中尝试 html-pdf 库,它允许您从 html 模板创建 pdf 并在其中添加动态数据。 Also it is more reliable than pdfkit https://www.npmjs.com/package/html-pdf Also refer this link Generate pdf file using pdfkit and send it to browser in nodejs-expressjs它也比 pdfkit https://www.npmjs.com/package/html-pdf更可靠 另请参阅此链接Generate pdf file using pdfkit and send it to browser in nodejs-expressjs

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用 pdfkit 生成 pdf 文件并将其发送到 nodejs-expressjs 中的浏览器 - Generate pdf file using pdfkit and send it to browser in nodejs-expressjs 使用Weasyprint生成pdf文件,保存在zip文件中,然后将该zip文件发送给客户端,然后提交下载 - Generate pdf files with Weasyprint, save in zip file, send that zip file to client and present it for download 如何使用NodeJS下载pdf并将其发送到客户端? - how to download pdf using NodeJS and send it to the client? 通过Express将pdf发送到js客户端并下载 - Send pdf via express to js client and download 使用PDFKIT使用Node.js生成PDF - Using PDFKIT to generate PDF using nodejs 生成pdfkit/node webkit后打开pdf - open pdf after generate pdfkit/node webkit ExpressJS-在服务器端生成CSV文件,并允许从客户端下载 - ExpressJS - Generate a CSV file on the server side and enable download from the client side 使用 pdfkit python 在生产中下载为 PDF 时,PDF 图像模糊 - PDF images blurry when download as PDF in production with pdfkit python Node Js NPM PDFkit 和 Voilab pdf 表,生成表后 pdfkit 段落错误 - Node Js NPM PDFkit and Voilab pdf table, pdfkit paragraph error afer generate table 从服务器发送消息(Expressjs路由)到客户端 - Send messages from server (Expressjs routing) to client
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM