[英]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.