node.jsstreamduplex

Remove NodeJs Stream padding


I'm writing an application where I need to strip the first X and last Y bytes from a stream. So what I need is basically a function I can pass to pipe that takes X and Y as parameters and removes the desired number of bytes from the stream as it comes through. My simplified setup is like this:

const rs = fs.createReadStream('some_file')
const ws = fs.createWriteStream('some_other_file')
rs.pipe(streamPadding(128, 512)).pipe(ws)

After that, some_other_fileshould contain all the contents of some_fileminus the first 128 Bytes and the last 512 bytes. I've read up on streams, but couldn't figure out how to properly do this, so that it also handles errors during the transfer and does backpressure correctly.

As far as I know, I'd need a duplex stream, that, whenever I read from it, reads from its input stream, keeps track of where in the stream we are and skips the first 128 bytes before emitting data. Some tips on how to implement that would be very helpful.

The second part seems more difficult, if not impossible to do, because how would I know whether I already reached the last 512 bytes or not, before the input stream actually closed. I suspect that might not be possible, but I'm sure there must be a way to solve this problem, so if you have any advice on that, I'd be very thankful!


Solution

  • You can create a new Transform Stream which does what you wish. As for losing the last x bytes, you can always keep the last x bytes buffered and just ignore them when the stream ends.

    Something like this (assuming you're working with buffers).

    
    const {Transform} = require('stream');
    
    
    const ignoreFirst = 128,
          ignoreLast = 512;
    
    let lastBuff,
        cnt = 0;
    
    const MyTrimmer = new Transform({
      transform(chunk,encoding,callback) {
        let len = Buffer.byteLength(chunk);
    
        // If we haven't ignored the first bit yet, make sure we do
        if(cnt <= ignoreFirst) {
          let diff = ignoreFirst - cnt;
    
          // If we have more than we want to ignore, adjust pointer
          if(len > diff)
            chunk = chunk.slice(diff,len);
          // Otherwise unset chunk for later
          else
            chunk = undefined;
        }
    
        // Keep track of how many bytes we've seen
        cnt += len;
    
        // If we have nothing to push after trimming, just get out
        if(!chunk)
          return callback();
    
        // If we already have a saved buff, concat it with the chunk
        if(lastBuff)
          chunk = Buffer.concat([lastBuff,chunk]);
        
        // Get the new chunk length
        len = Buffer.byteLength(chunk);
        
        // If the length is less than what we ignore at the end, save it and get out
        if(len < ignoreLast) {
          lastBuff = chunk;
          return callback();
        }
    
        // Otherwise save the piece we might want to ignore and push the rest through
        lastBuff = chunk.slice(len-ignoreLast,len);
        this.push(chunk.slice(0,len-ignoreLast));
        callback();
      }
    });
    
    
    

    Then you add that your pipeline, assuming you're reading from a file and writing to a file:

    const rs = fs.createReadStream('some_file')
    const ws = fs.createWriteStream('some_other_file')
    
    myTrimmer.pipe(ws);
    rs.pipe(myTrimmer);