为什么 nodeJs 不从磁盘读取整个二进制文件?

Why is nodeJs not reading entire binary file from disk?

我有一个 PDF 文件,我想使用 NodeJS 将其读入内存。理想情况下,我想使用 base64 对其进行编码以进行传输。但不知何故 read 函数似乎无法读取完整的 PDF 文件,这对我来说毫无意义。原始 PDF 是使用 pdfKit 生成的,可以使用 PDF reader 程序查看。

原始文件 test.pdf 在磁盘上有 90kB。但是,如果我读取它并将其写回磁盘,则只有 82kB 并且新的 PDF test-out.pdf 不正常。 pdf 查看器说:

Unable to open document. The pdf document is damaged.

因此 base64 编码也无法正常工作。我用这个 webservice 测试了它。有人知道为什么以及这里发生了什么吗?以及如何解决。

我已经找到 this post。

fs = require('fs');
let buf = fs.readFileSync('test.pdf'); // returns raw buffer binary data
// buf = fs.readFileSync('test.pdf', {encoding:'base64'}); // for the base64 encoded data
// ...transfer the base64 data...
fs.writeFileSync('test-out.pdf', buf); // should be pdf again

编辑 MCVE:

const fs = require('fs');
const PDFDocument = require('pdfkit');

let filepath = 'output.pdf';

class PDF {
  constructor() {
    this.doc = new PDFDocument();
    this.setupdocument();
    this.doc.pipe(fs.createWriteStream(filepath));
  }

  setupdocument() {
    var pageNumber = 1;
    this.doc.on('pageAdded', () => {
        this.doc.text(++pageNumber, 0.5 * (this.doc.page.width - 100), 40, {width: 100, align: 'center'});
      }
    );

    this.doc.moveDown();
    // draw some headline text
    this.doc.fontSize(25).text('Some Headline');
    this.doc.fontSize(15).text('Generated: ' + new Date().toUTCString());
    this.doc.moveDown();
    this.doc.font('Times-Roman', 11);
  }

  report(object) {

    this.doc.moveDown();
    this.doc
      .text(object.location+' '+object.table+' '+Date.now())
      .font('Times-Roman', 11)
      .moveDown()
      .text(object.name)
      .font('Times-Roman', 11);

    this.doc.end();
    let report = fs.readFileSync(filepath);
    return report;
  }
}

let pdf = new PDF();
let buf = pdf.report({location: 'athome', table:'wood', name:'Bob'});
fs.writeFileSync('outfile1.pdf', buf);

fs.readFileSync()encoding 选项用于告诉 readFile 函数文件已经是什么编码,以便读取文件的代码知道如何解释它读取的数据.它不会将其转换为该编码。

在这种情况下,您的 PDF 是二进制的 - 它不是 base64,所以您告诉它尝试将它从 base64 转换为二进制,这会导致它弄乱数据。

您根本不应该传递 encoding 选项,然后您将获得 RAW 二进制缓冲区(这就是 PDF 文件 - 原始二进制文件)。如果您出于某种原因想要将其转换为 base64,则可以对其执行 buf.toString('base64')。但是,这不是它的原始格式,如果您将转换后的数据写回到磁盘,它就不是合法的 PDF 文件。

要将同一个文件读写到不同的文件名,请完全取消编码选项:

const fs = require('fs');
let buf = fs.readFileSync('test.pdf'); // get raw buffer binary data
fs.writeFileSync('test-out.pdf', buf); // write out raw buffer binary data

经过大量搜索,我发现 this Github 问题。我的问题似乎是 doc.end() 的调用,由于某种原因它不等待流完成(finish 写流事件)。因此,正如 Github 问题中所建议的,以下方法有效:

  • 基于回调:
doc = new PDFDocument();
writeStream = fs.createWriteStream('filename.pdf');
doc.pipe(writeStream);
doc.end()
writeStream.on('finish', function () {
    // do stuff with the PDF file
});
  • 或基于承诺:
const stream = fs.createWriteStream(localFilePath);
doc.pipe(stream);
.....
doc.end();
await new Promise<void>(resolve => {
  stream.on("finish", function() {
    resolve();
  });
});
  • 甚至更好,而不是直接调用 doc.end(),而是调用下面的函数 savePdfToFile
function savePdfToFile(pdf : PDFKit.PDFDocument, fileName : string) : Promise<void> {
  return new Promise<void>((resolve, reject) => {

    //  To determine when the PDF has finished being written sucessfully 
    //  we need to confirm the following 2 conditions:
    //
    //  1. The write stream has been closed
    //  2. PDFDocument.end() was called syncronously without an error being thrown

    let pendingStepCount = 2;

    const stepFinished = () => {
      if (--pendingStepCount == 0) {
        resolve();
      }
    };

    const writeStream = fs.createWriteStream(fileName);
    writeStream.on('close', stepFinished);
    pdf.pipe(writeStream);

    pdf.end();

    stepFinished();
  }); 
}

此函数应正确处理以下情况:

  • PDF生成成功
  • 在写入流关闭之前 pdf.end() 内部抛出错误
  • 写入流关闭后 pdf.end() 内部抛出错误