You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 18:45:42