Skip to content

Commit 220186c

Browse files
addaleaxjasnell
authored andcommitted
stream: support Uint8Array input to methods
PR-URL: #11608 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 9d922c6 commit 220186c

File tree

6 files changed

+205
-23
lines changed

6 files changed

+205
-23
lines changed

doc/api/stream.md

+49-20
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ There are four fundamental stream types within Node.js:
4747
### Object Mode
4848

4949
All streams created by Node.js APIs operate exclusively on strings and `Buffer`
50-
objects. It is possible, however, for stream implementations to work with other
51-
types of JavaScript values (with the exception of `null`, which serves a special
52-
purpose within streams). Such streams are considered to operate in "object
53-
mode".
50+
(or `Uint8Array`) objects. It is possible, however, for stream implementations
51+
to work with other types of JavaScript values (with the exception of `null`,
52+
which serves a special purpose within streams). Such streams are considered to
53+
operate in "object mode".
5454

5555
Stream instances are switched into object mode using the `objectMode` option
5656
when the stream is created. Attempting to switch an existing stream into
@@ -352,12 +352,17 @@ See also: [`writable.uncork()`][].
352352
##### writable.end([chunk][, encoding][, callback])
353353
<!-- YAML
354354
added: v0.9.4
355+
changes:
356+
- version: REPLACEME
357+
pr-url: https://github.com/nodejs/node/pull/11608
358+
description: The `chunk` argument can now be a `Uint8Array` instance.
355359
-->
356360

357-
* `chunk` {string|Buffer|any} Optional data to write. For streams not operating
358-
in object mode, `chunk` must be a string or a `Buffer`. For object mode
359-
streams, `chunk` may be any JavaScript value other than `null`.
360-
* `encoding` {string} The encoding, if `chunk` is a String
361+
* `chunk` {string|Buffer|Uint8Array|any} Optional data to write. For streams
362+
not operating in object mode, `chunk` must be a string, `Buffer` or
363+
`Uint8Array`. For object mode streams, `chunk` may be any JavaScript value
364+
other than `null`.
365+
* `encoding` {string} The encoding, if `chunk` is a string
361366
* `callback` {Function} Optional callback for when the stream is finished
362367

363368
Calling the `writable.end()` method signals that no more data will be written
@@ -434,14 +439,20 @@ See also: [`writable.cork()`][].
434439
<!-- YAML
435440
added: v0.9.4
436441
changes:
442+
- version: REPLACEME
443+
pr-url: https://github.com/nodejs/node/pull/11608
444+
description: The `chunk` argument can now be a `Uint8Array` instance.
437445
- version: v6.0.0
438446
pr-url: https://github.com/nodejs/node/pull/6170
439447
description: Passing `null` as the `chunk` parameter will always be
440448
considered invalid now, even in object mode.
441449
-->
442450

443-
* `chunk` {string|Buffer} The data to write
444-
* `encoding` {string} The encoding, if `chunk` is a String
451+
* `chunk` {string|Buffer|Uint8Array|any} Optional data to write. For streams
452+
not operating in object mode, `chunk` must be a string, `Buffer` or
453+
`Uint8Array`. For object mode streams, `chunk` may be any JavaScript value
454+
other than `null`.
455+
* `encoding` {string} The encoding, if `chunk` is a string
445456
* `callback` {Function} Callback for when this chunk of data is flushed
446457
* Returns: {boolean} `false` if the stream wishes for the calling code to
447458
wait for the `'drain'` event to be emitted before continuing to write
@@ -985,9 +996,16 @@ setTimeout(() => {
985996
##### readable.unshift(chunk)
986997
<!-- YAML
987998
added: v0.9.11
999+
changes:
1000+
- version: REPLACEME
1001+
pr-url: https://github.com/nodejs/node/pull/11608
1002+
description: The `chunk` argument can now be a `Uint8Array` instance.
9881003
-->
9891004

990-
* `chunk` {Buffer|string|any} Chunk of data to unshift onto the read queue
1005+
* `chunk` {Buffer|Uint8Array|string|any} Chunk of data to unshift onto the
1006+
read queue. For streams not operating in object mode, `chunk` must be a
1007+
string, `Buffer` or `Uint8Array`. For object mode streams, `chunk` may be
1008+
any JavaScript value other than `null`.
9911009

9921010
The `readable.unshift()` method pushes a chunk of data back into the internal
9931011
buffer. This is useful in certain situations where a stream is being consumed by
@@ -1274,8 +1292,9 @@ constructor and implement the `writable._write()` method. The
12741292
Defaults to `true`
12751293
* `objectMode` {boolean} Whether or not the
12761294
[`stream.write(anyObj)`][stream-write] is a valid operation. When set,
1277-
it becomes possible to write JavaScript values other than string or
1278-
`Buffer` if supported by the stream implementation. Defaults to `false`
1295+
it becomes possible to write JavaScript values other than string,
1296+
`Buffer` or `Uint8Array` if supported by the stream implementation.
1297+
Defaults to `false`
12791298
* `write` {Function} Implementation for the
12801299
[`stream._write()`][stream-_write] method.
12811300
* `writev` {Function} Implementation for the
@@ -1564,16 +1583,26 @@ internal to the class that defines it, and should never be called directly by
15641583
user programs.
15651584

15661585
#### readable.push(chunk[, encoding])
1586+
<!-- YAML
1587+
changes:
1588+
- version: REPLACEME
1589+
pr-url: https://github.com/nodejs/node/pull/11608
1590+
description: The `chunk` argument can now be a `Uint8Array` instance.
1591+
-->
15671592

1568-
* `chunk` {Buffer|null|string|any} Chunk of data to push into the read queue
1569-
* `encoding` {string} Encoding of String chunks. Must be a valid
1593+
* `chunk` {Buffer|Uint8Array|string|null|any} Chunk of data to push into the
1594+
read queue. For streams not operating in object mode, `chunk` must be a
1595+
string, `Buffer` or `Uint8Array`. For object mode streams, `chunk` may be
1596+
any JavaScript value.
1597+
* `encoding` {string} Encoding of string chunks. Must be a valid
15701598
Buffer encoding, such as `'utf8'` or `'ascii'`
15711599
* Returns {boolean} `true` if additional chunks of data may continued to be
15721600
pushed; `false` otherwise.
15731601

1574-
When `chunk` is not `null`, the `chunk` of data will be added to the
1575-
internal queue for users of the stream to consume. Passing `chunk` as `null`
1576-
signals the end of the stream (EOF), after which no more data can be written.
1602+
When `chunk` is a `Buffer`, `Uint8Array` or `string`, the `chunk` of data will
1603+
be added to the internal queue for users of the stream to consume.
1604+
Passing `chunk` as `null` signals the end of the stream (EOF), after which no
1605+
more data can be written.
15771606

15781607
When the Readable is operating in paused mode, the data added with
15791608
`readable.push()` can be read out by calling the
@@ -2088,8 +2117,8 @@ Readable stream class internals.
20882117

20892118
Use of `readable.push('')` is not recommended.
20902119

2091-
Pushing a zero-byte string or `Buffer` to a stream that is not in object mode
2092-
has an interesting side effect. Because it *is* a call to
2120+
Pushing a zero-byte string, `Buffer` or `Uint8Array` to a stream that is not in
2121+
object mode has an interesting side effect. Because it *is* a call to
20932122
[`readable.push()`][stream-push], the call will end the reading process.
20942123
However, because the argument is an empty string, no data is added to the
20952124
readable buffer so there is nothing for a user to consume.

lib/_stream_readable.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,12 @@ function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) {
212212
if (er) {
213213
stream.emit('error', er);
214214
} else if (state.objectMode || chunk && chunk.length > 0) {
215+
if (typeof chunk !== 'string' &&
216+
Object.getPrototypeOf(chunk) !== Buffer.prototype &&
217+
!state.objectMode) {
218+
chunk = Stream._uint8ArrayToBuffer(chunk);
219+
}
220+
215221
if (addToFront) {
216222
if (state.endEmitted)
217223
stream.emit('error', new Error('stream.unshift() after end event'));
@@ -259,7 +265,7 @@ function addChunk(stream, state, chunk, addToFront) {
259265

260266
function chunkInvalid(state, chunk) {
261267
var er;
262-
if (!(chunk instanceof Buffer) &&
268+
if (!Stream._isUint8Array(chunk) &&
263269
typeof chunk !== 'string' &&
264270
chunk !== undefined &&
265271
!state.objectMode) {

lib/_stream_writable.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,11 @@ function validChunk(stream, state, chunk, cb) {
248248
Writable.prototype.write = function(chunk, encoding, cb) {
249249
var state = this._writableState;
250250
var ret = false;
251-
var isBuf = (chunk instanceof Buffer);
251+
var isBuf = Stream._isUint8Array(chunk) && !state.objectMode;
252+
253+
if (isBuf && Object.getPrototypeOf(chunk) !== Buffer.prototype) {
254+
chunk = Stream._uint8ArrayToBuffer(chunk);
255+
}
252256

253257
if (typeof encoding === 'function') {
254258
cb = encoding;

lib/internal/streams/BufferList.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
const Buffer = require('buffer').Buffer;
44

5+
function copyBuffer(src, target, offset) {
6+
Buffer.prototype.copy.call(src, target, offset);
7+
}
8+
59
module.exports = class BufferList {
610
constructor() {
711
this.head = null;
@@ -63,7 +67,7 @@ module.exports = class BufferList {
6367
var p = this.head;
6468
var i = 0;
6569
while (p) {
66-
p.data.copy(ret, i);
70+
copyBuffer(p.data, ret, i);
6771
i += p.data.length;
6872
p = p.next;
6973
}

lib/stream.js

+37
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
'use strict';
2323

24+
const Buffer = require('buffer').Buffer;
25+
2426
// Note: export Stream before Readable/Writable/Duplex/...
2527
// to avoid a cross-reference(require) issues
2628
const Stream = module.exports = require('internal/streams/legacy');
@@ -33,3 +35,38 @@ Stream.PassThrough = require('_stream_passthrough');
3335

3436
// Backwards-compat with node 0.4.x
3537
Stream.Stream = Stream;
38+
39+
// Internal utilities
40+
try {
41+
Stream._isUint8Array = process.binding('util').isUint8Array;
42+
} catch (e) {
43+
// This throws for Node < 4.2.0 because there’s no util binding and
44+
// returns undefined for Node < 7.4.0.
45+
}
46+
47+
if (!Stream._isUint8Array) {
48+
Stream._isUint8Array = function _isUint8Array(obj) {
49+
return Object.prototype.toString.call(obj) === '[object Uint8Array]';
50+
};
51+
}
52+
53+
const version = process.version.substr(1).split('.');
54+
if (version[0] === 0 && version[1] < 12) {
55+
Stream._uint8ArrayToBuffer = Buffer;
56+
} else {
57+
try {
58+
const internalBuffer = require('internal/buffer');
59+
Stream._uint8ArrayToBuffer = function _uint8ArrayToBuffer(chunk) {
60+
return new internalBuffer.FastBuffer(chunk.buffer,
61+
chunk.byteOffset,
62+
chunk.byteLength);
63+
};
64+
} catch (e) {
65+
}
66+
67+
if (!Stream._uint8ArrayToBuffer) {
68+
Stream._uint8ArrayToBuffer = function _uint8ArrayToBuffer(chunk) {
69+
return Buffer.prototype.slice.call(chunk);
70+
};
71+
}
72+
}
+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const Buffer = require('buffer').Buffer;
5+
6+
const { Readable, Writable } = require('stream');
7+
8+
const ABC = new Uint8Array([0x41, 0x42, 0x43]);
9+
const DEF = new Uint8Array([0x44, 0x45, 0x46]);
10+
const GHI = new Uint8Array([0x47, 0x48, 0x49]);
11+
12+
{
13+
// Simple Writable test.
14+
15+
let n = 0;
16+
const writable = new Writable({
17+
write: common.mustCall((chunk, encoding, cb) => {
18+
assert(chunk instanceof Buffer);
19+
if (n++ === 0) {
20+
assert.strictEqual(String(chunk), 'ABC');
21+
} else {
22+
assert.strictEqual(String(chunk), 'DEF');
23+
}
24+
25+
cb();
26+
}, 2)
27+
});
28+
29+
writable.write(ABC);
30+
writable.end(DEF);
31+
}
32+
33+
{
34+
// Writable test, pass in Uint8Array in object mode.
35+
36+
const writable = new Writable({
37+
objectMode: true,
38+
write: common.mustCall((chunk, encoding, cb) => {
39+
assert(!(chunk instanceof Buffer));
40+
assert(chunk instanceof Uint8Array);
41+
assert.strictEqual(chunk, ABC);
42+
assert.strictEqual(encoding, 'utf8');
43+
cb();
44+
})
45+
});
46+
47+
writable.end(ABC);
48+
}
49+
50+
{
51+
// Writable test, multiple writes carried out via writev.
52+
let callback;
53+
54+
const writable = new Writable({
55+
write: common.mustCall((chunk, encoding, cb) => {
56+
assert(chunk instanceof Buffer);
57+
assert.strictEqual(encoding, 'buffer');
58+
assert.strictEqual(String(chunk), 'ABC');
59+
callback = cb;
60+
}),
61+
writev: common.mustCall((chunks, cb) => {
62+
assert.strictEqual(chunks.length, 2);
63+
assert.strictEqual(chunks[0].encoding, 'buffer');
64+
assert.strictEqual(chunks[1].encoding, 'buffer');
65+
assert.strictEqual(chunks[0].chunk + chunks[1].chunk, 'DEFGHI');
66+
})
67+
});
68+
69+
writable.write(ABC);
70+
writable.write(DEF);
71+
writable.end(GHI);
72+
callback();
73+
}
74+
75+
{
76+
// Simple Readable test.
77+
const readable = new Readable({
78+
read() {}
79+
});
80+
81+
readable.push(DEF);
82+
readable.unshift(ABC);
83+
84+
const buf = readable.read();
85+
assert(buf instanceof Buffer);
86+
assert.deepStrictEqual([...buf], [...ABC, ...DEF]);
87+
}
88+
89+
{
90+
// Readable test, setEncoding.
91+
const readable = new Readable({
92+
read() {}
93+
});
94+
95+
readable.setEncoding('utf8');
96+
97+
readable.push(DEF);
98+
readable.unshift(ABC);
99+
100+
const out = readable.read();
101+
assert.strictEqual(out, 'ABCDEF');
102+
}

0 commit comments

Comments
 (0)