【问题标题】:How can I avoid zlib "unexpected end of file" when GUnzipping partial files?GUnzipping部分文件时如何避免zlib“文件意外结束”?
【发布时间】:2016-02-28 20:39:10
【问题描述】:

我正在尝试在解压缩 gzip 文件时读取它的一部分,这样我就可以在不读取不必要字节的情况下解析标头内容。我之前使用 fs.read() 进行了这项工作,同时传递选项以仅读取前 500 个字节,然后使用 zlib.gunzip() 在从二进制数据中解析标头之前解压缩内容。

在 node v5.0.0 修补了一个错误以确保 zlib 在截断的输入 (https://github.com/nodejs/node/pull/2595) 上引发错误之前,这一直运行良好。

现在我从 zlib 收到以下错误。

Error: unexpected end of file

如果知道我正在截断输入而不会引发错误,我该如何解压缩这个部分文件。我想用流可能会更容易,所以我写了以下内容。

var readStream = fs.createReadStream(file.path, {start: 0, end: 500});
var gunzip = zlib.createGunzip();

readStream.pipe(gunzip)
    .on('data', function(chunk) {
        console.log(parseBinaryHeader(chunk));
        console.log('got %d bytes of data', chunk.length);
    })
    .on('error', function (err) {
        console.log(err);
    })
    .on('end', function() {
        console.log('end');
    });

我的parseBinaryHeader() 函数正在返回正确的标头内容,所以我知道它正在解压缩,但是当它到达输入末尾时仍然会抛出错误。我可以添加错误侦听器来处理错误并且什么都不做,但这似乎并不理想。

有什么想法吗?

【问题讨论】:

  • 不要输入截断的文件?它应该警告您输入基本上已损坏。
  • 那么我如何才能解决需要有效读取一堆文件的标题而不关心其余内容的更大问题。文件数量和大小各不相同,但我通常会阅读 30 多个文件,总大小为 2-5GB。我只需要每个文件的前 500 个字节。我尝试完整阅读每个文件,但性能差异非常显着。
  • 尝试/捕捉错误?除非 zlib 有一个可以启用的“忽略错误”标志,否则你会被它会吐出的东西所困扰,或者可以只编写自己的解压缩库。
  • 可以不用流直接使用zlib.createGunzipzlib.createGunzipSync吗?您应该能够为其提供部分压缩数据和可用的未压缩数据。一旦你得到了你想要的东西,就杀死这些物体。
  • 感谢@MarkAdler。我做了与此非常相似的事情。我将一个块写入 Gunzip 流,然后立即暂停流以避免文件意外结束。这使我能够继续处理所有其他 gunzip 错误。

标签: node.js zlib


【解决方案1】:

感谢所有建议。我还向节点存储库提交了一个问题问题,并得到了一些很好的反馈。这就是最终对我有用的东西。

  • 将块大小设置为完整的标头大小。
  • 将单个块写入解压缩流并立即暂停流。
  • 处理解压缩的块。

例子

var bytesRead = 500;
var decompressStream = zlib.createGunzip()
    .on('data', function (chunk) {
        parseHeader(chunk);
        decompressStream.pause();
    }).on('error', function(err) {
        handleGunzipError(err, file, chunk);
    });

fs.createReadStream(file.path, {start: 0, end: bytesRead, chunkSize: bytesRead + 1})
    .on('data', function (chunk) {
        decompressStream.write(chunk);
    });

到目前为止,这一直有效,并且还允许我继续处理所有其他 gunzip 错误,因为 pause() 可防止解压缩流抛出“文件意外结束”错误。

【讨论】:

  • parseHeader(chunk) 是从哪里来的?就我而言,它说 parseHeader 未定义
  • 不幸的是,这个答案不完整。 parseHeader 未定义,handleGunzipError 也未定义。那么您能否解释一下如何解析 gzip 块而不会引发“Unexpected End Of File”错误?
  • @h0r53 对于这个问题的上下文,parseHeaderhandleGunzipError 的细节并不重要。您可以将它们替换为 console.log 并根据需要处理每个内容。 chunk 只是文件的解压缩部分。您如何解析它取决于它是什么类型的文件以及您的特定需求。如果进一步,您甚至可能不需要解析。
  • @Constellates 我以为chunk的目的是发送数据片段,而gzip + chunk的情况下,数据首先通过gzip压缩,然后是跨一个或多个数据包分块。不是这样吗?
  • @h0r53 差不多了。在这个例子中,我打开一个流来读取一个文件。该文件已被 gzip 压缩。每次文件流从该 gzip 压缩文件中读取新数据块时,它都会调用decompressStream.write。反过来 zlib gunzip 对象 (decompressStream) 解压缩每个块并将解压缩的文件块传递给 parseHeader。因此,如果您将其替换为 console.log,您应该会看到解压缩的文件。
【解决方案2】:

我在尝试结束处理 NodeJS Gzip 流时遇到了同样的问题。我使用“buffer-peek-stream”来检查 gzip 流的标头 - 确定它实际上是一个 gzip 流。然后我解开流的前几兆字节 - 查看该文件并确定 gzip 内容的 mime 类型。

这需要两次调用 zlib.createGunzip()

我发现,即使我创建了看似两个独立的 gunzip 转换实例,破坏第二个实例也会导致第一个实例抛出此“文件意外结束”错误。即使第一个实例处于完全不同的上下文中。

在我的情况下,解决方法是在创建第二个实例之前调用第一个实例上的 .destroy() 来清理它。

【讨论】:

    【解决方案3】:

    使用节点 v10.13.0 时出现此错误。我升级到 v10.19.0 并且已修复。

    【讨论】:

      【解决方案4】:

      Constellates 的回答效果很好,但前提是可提取块小于 zlib 的处理块大小(默认为 16 KB)。对于较大的数量,您需要组合块,例如通过连接它们。这是一个使用 Promises 的 TS 示例:

      const gunzipped: Buffer = await new Promise((resolve, reject) => {
          const buffer_builder: Buffer[] = []
          const decompress_stream = zlib.createGunzip()
              .on('data', (chunk: Buffer) => {
                  buffer_builder.push(chunk)
              }).on('close', () => {
                  resolve(Buffer.concat(buffer_builder))
              }).on('error', (err) => {
                  if(err.errno !== -5) // EOF: expected
                      reject(err)
              });
          decompress_stream.write(/* ... your gzipped input buffer */)
          decompress_stream.end()
      })
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-06-10
        • 2021-04-22
        • 2014-06-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多