【问题标题】:Why is nodeJs not reading entire binary file from disk?为什么 nodeJs 不从磁盘读取整个二进制文件?
【发布时间】:2021-04-29 20:19:57
【问题描述】:

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

原始文件test.pdf 的磁盘空间为 90kB。但是,如果我将其读取并写入磁盘,则只有 82kB 并且新的 PDF test-out.pdf 不行。 pdf查看器说:

无法打开文档。 pdf文档已损坏。

base64 编码因此也不能正常工作。我使用这个webservice 对其进行了测试。有人知道为什么以及这里发生了什么吗?以及如何解决。

我已经找到this 的帖子了。

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);

【问题讨论】:

    标签: javascript node.js pdf file-io node-pdfkit


    【解决方案1】:

    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
    

    【讨论】:

    • 在读/写操作中默认编码是'utf8',所以我们在处理pdf、图像等二进制文件时是否需要使用'binary'?
    • @Viney - 根据fs.readFileSync() documentation,默认为null,这意味着不管它是什么——不要试图解释它是你想要的二进制。
    • 感谢您的确认和解释。主要问题是readFileSync 不知何故不读取(整个)文件。在我刚刚添加到我的问题的 MCVE 中,没有读取任何内容,并且在我的真实文档中只有相同的 82kB 读取,无论文件实际有多大(没有错误)。你知道为什么吗?它与其他 pdf 文件(例如 MS Word 生成的文件)按预期工作,但不适用于pdfkit 的。但正如问题中所述,从pdfkitoutput.pdf 生成的文件可以像正常一样读取,并且似乎完全没问题。 output1.pdf 失败。
    【解决方案2】:

    经过大量搜索,我发现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() 内部抛出错误

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-08
      • 2013-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-24
      相关资源
      最近更新 更多