ExpressJS 和 PDFKit - 在内存中生成 PDF 并发送到客户端以供下载
ExpressJS and PDFKit - generate a PDF in memory and send to client for download
在我的api
路由器中,有一个叫做generatePDF
的函数,它的目的是使用PDFKit模块在内存中生成一个PDF文件并发送给客户端下载,而不是只显示。
在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;
这不起作用。错误信息是 (node:11444) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
.
我怎样才能解决这个问题并使其正常工作?
此外,一个相关的问题是如何将 PDF 生成的业务逻辑与路由器分离并将它们链接起来?
完整的解决方案。
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;
您可以像这样使用 blob 流。
参考: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;
});
将所有 pdf 数据通过管道传输到 blob,然后将其写入文件或 url。
或者你可以将 pdf 直接存储到像 firebase 存储这样的云存储中,然后将下载 link 发送给客户端。
如果您想动态生成 pdf,那么您也可以在 node 中试用 html-pdf 库,它允许您从 html 模板创建 pdf 并在其中添加动态数据。它也比 pdfkit 更可靠
https://www.npmjs.com/package/html-pdf
另请参阅此 link
Generate pdf file using pdfkit and send it to browser in nodejs-expressjs
首先,我建议为 PDF 工具包创建一个服务。然后一个控制器到你想要的路线。
我使用 get-stream
使这更容易。
它还回答了您对已接受答案的问题:
how I can separate the business logic of PDF generation from the
router and chain them up?
这是我的专业解决方案:
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;
}
}
}
然后是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);
}
最后是路线:
routes.get('/pdf', FileController.show);
对于那些不想在缓冲 PDF 上浪费 RAM 并立即将块发送给客户端的人:
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();
在我的api
路由器中,有一个叫做generatePDF
的函数,它的目的是使用PDFKit模块在内存中生成一个PDF文件并发送给客户端下载,而不是只显示。
在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;
这不起作用。错误信息是 (node:11444) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
.
我怎样才能解决这个问题并使其正常工作?
此外,一个相关的问题是如何将 PDF 生成的业务逻辑与路由器分离并将它们链接起来?
完整的解决方案。
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;
您可以像这样使用 blob 流。
参考: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;
});
将所有 pdf 数据通过管道传输到 blob,然后将其写入文件或 url。 或者你可以将 pdf 直接存储到像 firebase 存储这样的云存储中,然后将下载 link 发送给客户端。
如果您想动态生成 pdf,那么您也可以在 node 中试用 html-pdf 库,它允许您从 html 模板创建 pdf 并在其中添加动态数据。它也比 pdfkit 更可靠 https://www.npmjs.com/package/html-pdf 另请参阅此 link Generate pdf file using pdfkit and send it to browser in nodejs-expressjs
首先,我建议为 PDF 工具包创建一个服务。然后一个控制器到你想要的路线。
我使用 get-stream
使这更容易。
它还回答了您对已接受答案的问题:
how I can separate the business logic of PDF generation from the router and chain them up?
这是我的专业解决方案:
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;
}
}
}
然后是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);
}
最后是路线:
routes.get('/pdf', FileController.show);
对于那些不想在缓冲 PDF 上浪费 RAM 并立即将块发送给客户端的人:
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();