Skip to content

Commit 0bf57bd

Browse files
committed
initial version
1 parent 525b5cc commit 0bf57bd

5 files changed

Lines changed: 212 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# buffer-peek-stream changelog
2+
3+
## 0.1.0 (2014/11/05)
4+
5+
- initial version

README.md

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,55 @@
1-
node-buffer-peek-stream
2-
=======================
1+
# node-buffer-peek-stream
32

4-
Node Transform stream that lets you inspect the start of a ReadStream before deciding what to do with it
3+
A Transform stream which lets you take a peek at the first bytes before unpiping itself and unshifting the buffer back onto the upstream stream leaving the original stream ready to be
4+
piped again onto its final destination.
5+
6+
```
7+
npm install buffer-peek-stream
8+
```
9+
10+
Useful if you want to inspect the start of a stream before deciding what to do with it.
11+
12+
This works with buffers and does no string decoding. If you know you have a string and already
13+
know its encoding then checkout [peek-stream](https://github.com/mafintosh/peek-stream).
14+
15+
16+
## Usage
17+
As a function...
18+
```
19+
var peek = require('./buffer-peek-stream');
20+
var readstream = fs.createReadStream('package.json');
21+
22+
peek(readstream, 65536, function (err, data) {
23+
if (err) throw err;
24+
25+
// readstream is ready to be piped somewhere else
26+
readstream.pipe(somewhere_else);
27+
});
28+
```
29+
30+
As a stream...
31+
```
32+
var PeekStream = require('./buffer-peek-stream').Constructor;
33+
34+
var peek = new PeekStream(65536);
35+
var readstream = fs.createReadStream('package.json');
36+
37+
// peek will only emit the data event once
38+
peek.once('data', function (buf) {
39+
40+
// readstream is ready to be piped somewhere else
41+
readstream.pipe(somewhere_else);
42+
});
43+
44+
stream.pipe(peek);
45+
```
46+
47+
48+
## NOTICE
49+
This hasn't been run in production yet and hasn't gone through substantial testing. The approach
50+
taken to unshift data back onto the origin stream in particular could turn out to be a bad idea when
51+
you try to peek at more bytes than are in the stream.
52+
53+
54+
## Licence
55+
MIT

lib/buffer-peek-stream.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
var stream = require('stream');
2+
var util = require('util');
3+
4+
function BufferPeekStream(bytes) {
5+
stream.Transform.call(this);
6+
this.__buffer = [];
7+
this.__bufferLength = 0;
8+
this.__peekBytes = bytes || 65536;
9+
this.__peeking = true;
10+
this.__src = null;
11+
var self = this;
12+
13+
// when we get piped to store the parent so we can unshift what we've peeked
14+
this.once('pipe', function (src) {
15+
self.__src = src;
16+
17+
// inherit upstream encoding because sniffing seems to wipe out iconv-lite conversion
18+
// ideally would like this not to happen as guessing it's an overhead though small since we're
19+
// just peaking small amounts of data
20+
if (src._readableState.encoding) self.setEncoding(src._readableState.encoding);
21+
22+
self.once('pipe', function () {
23+
self.emit('error', new Error('BufferPeekStream can only be piped to once'));
24+
});
25+
});
26+
}
27+
28+
util.inherits(BufferPeekStream, stream.Transform);
29+
module.exports = BufferPeekStream;
30+
31+
32+
BufferPeekStream.prototype._transform = function _transform(chunk, enc, callback) {
33+
// buffer incoming chunks until we have enough for our peek
34+
this.__buffer.push(chunk);
35+
this.__bufferLength += chunk.length;
36+
37+
// buffered enough
38+
if (this.__bufferLength > this.__peekBytes) this.__stopPeeking();
39+
40+
callback();
41+
};
42+
43+
44+
BufferPeekStream.prototype._flush = function _flush(callback) {
45+
this.__stopPeeking();
46+
callback();
47+
};
48+
49+
50+
BufferPeekStream.prototype.__stopPeeking = function __stopPeeking() {
51+
// don't want to run this again if we get subsequent calls before drain
52+
if (!this.__peeking) return;
53+
54+
this.__peeking = false;
55+
56+
// unpipe from upstream
57+
this.__src.unpipe(this);
58+
59+
var buffer = Buffer.concat(this.__buffer);
60+
var source = this.__src;
61+
62+
// push exactly the number of bytes we wanted to peek
63+
this.push(buffer.slice(0, this.__peekBytes));
64+
this.push(null);
65+
66+
if (source._readableState.ended) {
67+
// if the source has ended then we need to modify its state so it'll start flowing again when we
68+
// unshift the data back on. these settings were naively obtained by creating a file read
69+
// stream of a tiny file then leaving it hang for a timeout of 5s and dumping the state
70+
source.readable = true;
71+
source._readableState.ended = true;
72+
source._readableState.endEmitted = false;
73+
source._readableState.ranOut = false;
74+
source._readableState.reading = false;
75+
source._readableState.calledRead = true;
76+
source._readableState.sync = false;
77+
source._readableState.needReadable = false;
78+
source._readableState.emittedReadable = true;
79+
source._readableState.readableListening = false;
80+
source._readableState.readingMore = false;
81+
82+
source.unshift(buffer);
83+
} else {
84+
// delay unshifting all chunks back onto parent until we're drained so we don't lose chunks that
85+
// have already been put onto the event stack before we unpiped.
86+
this.once('drain', function () {
87+
source.unshift(buffer);
88+
});
89+
}
90+
91+
// we don't need to keep the original buffers
92+
this.__buffer = null;
93+
};

lib/index.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
var BufferPeekStream = require('./buffer-peak-stream');
2+
3+
module.exports = peek;
4+
5+
peek.Constructor = BufferPeekStream;
6+
7+
function peek(input, bytes, cb) {
8+
if (typeof bytes === 'function') {
9+
cb = bytes;
10+
bytes = 65536; // 64K
11+
}
12+
13+
// make sure we don't callback more than once
14+
cb = once(cb);
15+
16+
var _ = new BufferPeekStream(bytes);
17+
18+
_.once('data', function (data) {
19+
cb(null, data);
20+
});
21+
22+
_.once('error', cb);
23+
24+
input.pipe(_);
25+
}
26+
27+
function once(fn) {
28+
var _fired = false;
29+
return function () {
30+
if (_fired) return;
31+
_fired = true;
32+
fn.apply(this, Array.prototype.splice.call(arguments, 0));
33+
};
34+
}

package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "buffer-peek-stream",
3+
"version": "0.1.0",
4+
"description": "Transform stream that lets you inspect the start of a ReadStream before deciding what to do with it",
5+
"main": "lib/index.js",
6+
"scripts": {
7+
"test": "mocha test/buffer-peek-stream.js"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/seangarner/node-buffer-peek-stream.git"
12+
},
13+
"keywords": [
14+
"stream",
15+
"peek",
16+
"parse",
17+
"buffer",
18+
"inspect"
19+
],
20+
"author": "Sean Garner",
21+
"license": "MIT",
22+
"bugs": {
23+
"url": "https://github.com/seangarner/node-buffer-peek-stream/issues"
24+
},
25+
"homepage": "https://github.com/seangarner/node-buffer-peek-stream"
26+
}

0 commit comments

Comments
 (0)