如何将pdfmake与fastify结合实现PDF流式输出?
如何结合PDFMake与Fastify实现流式PDF输出?
PDFMake 提供了流式生成PDF文档的方法:
const pdfDoc = printer.createPdfKitDocument(docDefinition, options); pdfDoc.pipe(fs.createWriteStream('document.pdf')); // 发送到输出流 pdfDoc.end();
Fastify 的流式响应实现方式如下:
fastify.get('/streams', function (request, reply) { const fs = require('fs') const stream = fs.createReadStream('some-file', 'utf8') reply.header('Content-Type', 'application/octet-stream') reply.send(stream) // 从输入流发送 })
想把两者结合,让PDFMake直接通过Fastify流式输出,不需要先缓冲整个PDF文件。
参考Koa(或Express)的实现方式:
app.use(async (ctx, next) => { const pdfDoc = printer.createPdfKitDocument(docDefinition, options); res.setHeader('content-type', 'application/pdf'); res.setHeader('content-disposition', req.headers['content-disposition'] || 'inline'); res.status(200); pdfDoc.pipe(res); pdfDoc.end(); });
但在Fastify中用reply.send(pdfDoc)无法实现相同效果。
更新内容
因为PDFKit的PDFKit.PDFDocument属于可读流,尝试了以下写法:
fastify.get('/streams', function (request, reply) { const pdfDoc = printer.createPdfKitDocument(docDefinition, options); reply.header('Content-Type', 'application/pdf'); reply.header('content-disposition', 'inline'); reply.send(pdfDoc); // 浏览器提示“Failed to load PDF document” })
还试了PDFKit推荐的blob-stream方案:
fastify.get('/streams', function (request, reply) { const pdfDoc = printer.createPdfKitDocument(docDefinition, options); const stream = pdfDoc.pipe(blobStream()); reply.header('Content-Type', 'application/pdf'); reply.header('content-disposition', 'inline'); reply.send(stream); // 浏览器提示“Failed to load PDF document” pdfDoc.end(); })
除了浏览器提示无法加载PDF外,没有其他错误信息。
解决方案
问题出在PDFKit的流是对象模式的,每个chunk是PDF对象而非Buffer,而Fastify默认只处理Buffer/字符串模式的流,所以直接发送会导致PDF解析失败。下面提供两种可行的解决方法:
方法一:直接使用Fastify的原始响应对象
这是最直接高效的方式,和Koa/Express的实现逻辑一致:
fastify.get('/pdf', function (request, reply) { const pdfDoc = printer.createPdfKitDocument(docDefinition, options); // 设置响应头 reply.header('Content-Type', 'application/pdf'); reply.header('Content-Disposition', 'inline'); // 将PDF流直接pipe到原始响应对象 pdfDoc.pipe(reply.raw); pdfDoc.end(); // 告诉Fastify已经手动处理了响应,避免重复发送 reply.sent = true; });
方法二:转换流模式
通过转换流将PDFKit的对象流转为Buffer流,兼容Fastify的处理逻辑:
const { pipeline } = require('stream'); const { promisify } = require('util'); const pipelineAsync = promisify(pipeline); fastify.get('/pdf', async function (request, reply) { const pdfDoc = printer.createPdfKitDocument(docDefinition, options); reply.header('Content-Type', 'application/pdf'); reply.header('Content-Disposition', 'inline'); // 创建转换流,将对象转为Buffer const bufferStream = new require('stream').Transform({ objectMode: true, transform(chunk, encoding, callback) { callback(null, chunk); } }); // 处理流并发送 await pipelineAsync(pdfDoc, bufferStream); reply.send(bufferStream); });
关键说明
- PDFKit的
PDFDocument默认是对象模式流,Fastify无法直接解析,所以要么直接对接原始响应,要么转换流模式。 - 方法一不需要额外转换,性能更优,推荐优先使用。
内容的提问来源于stack exchange,提问作者Yanick Rochon
相关产品推荐
相关产品推荐

