簡體   English   中英

我在這里缺少什么來從這個生成的 Node.js 子進程中獲取數據?

[英]What am I missing here to get data out of this spawned Node.js child process?

我正在嘗試使用生成的命令行lzip進程來擴展 lzip 數據 stream,因為我還沒有找到任何好的本機 JavaScript 工具來完成這項工作。

我可以使用文件和文件描述符讓它工作,但是必須寫出並讀回一堆臨時暫存文件似乎很愚蠢。 我想在 memory 中盡我所能。

所以這是我嘗試使用的代碼:

import { requestBinary } from 'by-request';
import { spawn } from 'child_process';
import { min } from '@tubular/math';

export async function tarLzToZip(url: string): Promise<void> {
  const lzData = await requestBinary(url, { headers: { 'User-Agent': 'curl/7.64.1' } });
  const lzipProc = spawn('lzip', ['-d'], { stdio: ['pipe', 'pipe', process.stderr] });
  let tarContent = Buffer.alloc(0);

  lzipProc.stdout.on('data', data => {
    tarContent = Buffer.concat([tarContent, data], tarContent.length + data.length);
  });

  for (let offset = 0; offset < lzData.length; offset += 4096) {
    await new Promise<void>((resolve, reject) => {
      lzipProc.stdin.write(lzData.slice(offset, min(offset + 4096, lzData.length)), err => {
        if (err)
          reject(err);
        else
          resolve();
      });
    });
  }

  await new Promise<void>((resolve, reject) => {
    lzipProc.stdin.end((err: any) => {
      if (err)
        reject(err);
      else
        resolve();
    });
  });

  console.log('data length:', tarContent.length);
}

當我逐步使用調試器時,將數據發送到lzipProc.stdin似乎一切順利。 (我試過像這樣做兩個塊,所有數據都在一個 go 中。) lzipProc.stdout.on('data', data => ,但是,永遠不會被調用。當我走到盡頭時, tarContent是空的。

這里缺少什么? 我需要不同的stdio配置嗎? 我應該使用不同的 stream 對象嗎? 我需要更多的山羊在滿月的光芒下犧牲嗎?

更新

我的解決方案基於下面發布的 Matt 的出色回答,以及我的用例的所有細節:

import archiver from 'archiver';
import fs, { ReadStream } from 'fs';
import fsp from 'fs/promises';
import needle from 'needle';
import path from 'path';
import { spawn } from 'child_process';
import tar from 'tar-stream';

const baseUrl = 'https://data.iana.org/time-zones/releases/';

export async function codeAndDataToZip(version: string): Promise<ReadStream> {
  return compressedTarToZip(`${baseUrl}tzdb-${version}.tar.lz`);
}

export async function codeToZip(version: string): Promise<ReadStream> {
  return compressedTarToZip(`${baseUrl}tzcode${version}.tar.gz`);
}

export async function dataToZip(version: string): Promise<ReadStream> {
  return compressedTarToZip(`${baseUrl}tzdata${version}.tar.gz`);
}

async function compressedTarToZip(url: string): Promise<ReadStream> {
  const fileName = /([-a-z0-9]+)\.tar\.[lg]z$/i.exec(url)[1] + '.zip';
  const filePath = path.join(process.env.TZE_ZIP_DIR || path.join(__dirname, 'tz-zip-cache'), fileName);

  if (await fsp.stat(filePath).catch(() => false))
    return fs.createReadStream(filePath);

  const [command, args] = url.endsWith('.lz') ? ['lzip', ['-d']] : ['gzip', ['-dc']];
  const originalArchive = needle.get(url, { headers: { 'User-Agent': 'curl/7.64.1' } });
  const tarExtract = tar.extract({ allowUnknownFormat: true });
  const zipPack = archiver('zip');
  const writeFile = fs.createWriteStream(filePath);
  const commandProc = spawn(command, args);

  commandProc.stderr.on('data', msg => { throw new Error(`${command} error: ${msg}`); });
  commandProc.stderr.on('error', err => { throw err; });

  originalArchive.pipe(commandProc.stdin);
  commandProc.stdout.pipe(tarExtract);

  tarExtract.on('entry', (header, stream, next) => {
    zipPack.append(stream, { name: header.name, date: header.mtime });
    stream.on('end', next);
  });

  tarExtract.on('finish', () => zipPack.finalize());
  zipPack.pipe(writeFile);

  return new Promise<ReadStream>((resolve, reject) => {
    const rejectWithError = (err: any): void =>
      reject(err instanceof Error ? err : new Error(err.message || err.toString()));

    writeFile.on('error', rejectWithError);
    writeFile.on('finish', () => resolve(fs.createReadStream(filePath)));
    tarExtract.on('error', err => {
      // tar-stream has a problem with the format of a few of the tar files
      // dealt with here, which nevertheless are valid archives.
      if (/unexpected end of data|invalid tar header/i.test(err.message))
        console.error('Archive %s: %s', url, err.message);
      else
        reject(err);
    });
    zipPack.on('error', rejectWithError);
    zipPack.on('warning', rejectWithError);
    commandProc.on('error', rejectWithError);
    commandProc.on('exit', err => err && reject(new Error(`${command} error: ${err}`)));
    originalArchive.on('error', rejectWithError);
  });
}

我會將流媒體留給節點或包,除非您有需要完成的特定處理。 只需將整個 stream 設置包裝在 promise 中。

如果你也 stream 請求/響應,它可以通過管道傳輸到解壓縮器中。 然后來自解壓縮器的stdout可以通過管道傳輸到存檔 stream 處理程序。

import fs from 'fs'
import { spawn } from 'child_process'
import needle from 'needle'
import tar from 'tar-stream'
import archiver from 'archiver'

export function tarLzToZip(url) {
  return new Promise((resolve, reject) => {
    // Setup streams
    const res = needle.get(url)
    const lzipProc = spawn('lzip', ['-dc'], { stdio: ['pipe','pipe',process.stderr] })
    const tarExtract = tar.extract()
    const zipPack = archiver('zip')
    const writeFile = fs.createWriteStream('tardir.zip')

    // Pipelines and processing
    res.pipe(gzipProc.stdin)
    lzipProc.stdout.pipe(tarExtract)
    // tar -> zip (simple file name)
    tarExtract.on('entry', function(header, stream, next) {
      console.log('entry', header)
      zipPack.append(stream, { name: header.name })
      stream.on('end', () => next())
    })
    tarExtract.on('finish', function() {
      zipPack.finalize()
    })
    zipPack.pipe(writeFile)

    // Handle the things
    writeFile.on('error', reject)
    writeFile.on('close', () => console.log('write close'))
    writeFile.on('finish', resolve(true))
    tarExtract.on('error', reject)
    zipPack.on('error', reject)
    zipPack.on('warning', reject)
    lzipProc.on('error', reject)
    lzipProc.on('exit', code => {if (code !== 0) reject(new Error(`lzip ${code}`))})
    res.on('error', reject)
    res.on('done', ()=> console.log('request done', res.request.statusCode))
  })
}

您可能希望更詳細地記錄錯誤和 stderr,因為單數 promise reject可以輕松隱藏多個流中實際發生的事情。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM