Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: allow typed arrays in .write() #22428

Closed
wants to merge 9 commits into from
Closed
14 changes: 12 additions & 2 deletions doc/api/net.md
Original file line number Diff line number Diff line change
Expand Up @@ -689,9 +689,14 @@ listeners for that event will receive `exception` as an argument.
### socket.end([data][, encoding])
<!-- YAML
added: v0.1.90
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/22428
description: The `data` parameter can now be any `TypedArray` or a
`DataView`.
-->

* `data` {string|Buffer|Uint8Array}
* `data` {string|Buffer|TypedArray|DataView}
* `encoding` {string} Only used when data is `string`. **Default:** `'utf8'`.
* Returns: {net.Socket} The socket itself.

Expand Down Expand Up @@ -850,9 +855,14 @@ active socket in the event system. If the socket is already `unref`ed calling
### socket.write(data[, encoding][, callback])
<!-- YAML
added: v0.1.90
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/22428
description: The `data` parameter can now by any `TypedArray` or a
`DataView`.
-->

* `data` {string|Buffer|Uint8Array}
* `data` {string|Buffer|TypedArray|DataView}
* `encoding` {string} Only used when data is `string`. **Default:** `utf8`.
* `callback` {Function}
* Returns: {boolean}
Expand Down
36 changes: 26 additions & 10 deletions doc/api/stream.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,10 @@ but instead implement [`writable._destroy()`][writable-_destroy].
<!-- YAML
added: v0.9.4
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/22427
description: The `chunk` argument can now be any `TypedArray` or a
`DataView`.
- version: v10.0.0
pr-url: https://github.com/nodejs/node/pull/18780
description: This method now returns a reference to `writable`.
Expand All @@ -384,8 +388,8 @@ changes:
description: The `chunk` argument can now be a `Uint8Array` instance.
-->

* `chunk` {string|Buffer|Uint8Array|any} Optional data to write. For streams
not operating in object mode, `chunk` must be a string, `Buffer` or
* `chunk` {string|Buffer|TypedArray|DataView|any} Optional data to write. For
streams not operating in object mode, `chunk` must be a string, `Buffer` or
`Uint8Array`. For object mode streams, `chunk` may be any JavaScript value
other than `null`.
* `encoding` {string} The encoding, if `chunk` is a string
Expand Down Expand Up @@ -486,6 +490,10 @@ the status of the `highWaterMark`.
<!-- YAML
added: v0.9.4
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/22427
description: The `chunk` argument can now be any `TypedArray` or a
`DataView`.
- version: v8.0.0
pr-url: https://github.com/nodejs/node/pull/11608
description: The `chunk` argument can now be a `Uint8Array` instance.
Expand All @@ -495,8 +503,8 @@ changes:
considered invalid now, even in object mode.
-->

* `chunk` {string|Buffer|Uint8Array|any} Optional data to write. For streams
not operating in object mode, `chunk` must be a string, `Buffer` or
* `chunk` {string|Buffer|TypedArray|DataView|any} Optional data to write. For
streams not operating in object mode, `chunk` must be a string, `Buffer` or
`Uint8Array`. For object mode streams, `chunk` may be any JavaScript value
other than `null`.
* `encoding` {string} The encoding, if `chunk` is a string
Expand Down Expand Up @@ -1129,13 +1137,17 @@ setTimeout(() => {
<!-- YAML
added: v0.9.11
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/22427
description: The `chunk` argument can now be any `TypedArray` or a
`DataView`.
- version: v8.0.0
pr-url: https://github.com/nodejs/node/pull/11608
description: The `chunk` argument can now be a `Uint8Array` instance.
-->

* `chunk` {Buffer|Uint8Array|string|any} Chunk of data to unshift onto the
read queue. For streams not operating in object mode, `chunk` must be a
* `chunk` {Buffer|TypedArray|DataView|string|any} Chunk of data to unshift onto
the read queue. For streams not operating in object mode, `chunk` must be a
string, `Buffer` or `Uint8Array`. For object mode streams, `chunk` may be
any JavaScript value other than `null`.

Expand Down Expand Up @@ -1860,15 +1872,19 @@ It can be overridden by child classes but it **must not** be called directly.
#### readable.push(chunk[, encoding])
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/22427
description: The `chunk` argument can now be any `TypedArray` or a
`DataView`.
- version: v8.0.0
pr-url: https://github.com/nodejs/node/pull/11608
description: The `chunk` argument can now be a `Uint8Array` instance.
-->

* `chunk` {Buffer|Uint8Array|string|null|any} Chunk of data to push into the
read queue. For streams not operating in object mode, `chunk` must be a
string, `Buffer` or `Uint8Array`. For object mode streams, `chunk` may be
any JavaScript value.
* `chunk` {Buffer|TypedArray|DataView|string|null|any} Chunk of data to push
into the read queue. For streams not operating in object mode, `chunk` must
be a string, `Buffer` or `Uint8Array`. For object mode streams, `chunk` may
be any JavaScript value.
* `encoding` {string} Encoding of string chunks. Must be a valid
`Buffer` encoding, such as `'utf8'` or `'ascii'`.
* Returns: {boolean} `true` if additional chunks of data may continued to be
Expand Down
11 changes: 8 additions & 3 deletions lib/_stream_readable.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) {
if (typeof chunk !== 'string' &&
!state.objectMode &&
Object.getPrototypeOf(chunk) !== Buffer.prototype) {
chunk = Stream._uint8ArrayToBuffer(chunk);
chunk = Stream._typedArrayToBuffer(chunk);
}

if (addToFront) {
Expand Down Expand Up @@ -297,12 +297,17 @@ function addChunk(stream, state, chunk, addToFront) {

function chunkInvalid(state, chunk) {
var er;
if (!Stream._isUint8Array(chunk) &&
if (!Stream._isArrayBufferView(chunk) &&
typeof chunk !== 'string' &&
chunk !== undefined &&
!state.objectMode) {
er = new ERR_INVALID_ARG_TYPE(
'chunk', ['string', 'Buffer', 'Uint8Array'], chunk);
'chunk',
['string',
'Buffer',
'TypedArray',
'DataView'],
chunk);
}
return er;
}
Expand Down
4 changes: 2 additions & 2 deletions lib/_stream_writable.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,10 @@ function validChunk(stream, state, chunk, cb) {
Writable.prototype.write = function(chunk, encoding, cb) {
var state = this._writableState;
var ret = false;
var isBuf = !state.objectMode && Stream._isUint8Array(chunk);
var isBuf = !state.objectMode && Stream._isArrayBufferView(chunk);

if (isBuf && Object.getPrototypeOf(chunk) !== Buffer.prototype) {
chunk = Stream._uint8ArrayToBuffer(chunk);
chunk = Stream._typedArrayToBuffer(chunk);
}

if (typeof encoding === 'function') {
Expand Down
19 changes: 10 additions & 9 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const {
UV_EOF
} = internalBinding('uv');

const { Buffer } = require('buffer');
const { byteLength } = require('buffer').Buffer;
const TTYWrap = process.binding('tty_wrap');
const { ShutdownWrap } = internalBinding('stream_wrap');
const {
Expand Down Expand Up @@ -78,6 +78,7 @@ const {
} = errors.codes;
const { validateInt32, validateString } = require('internal/validators');
const kLastWriteQueueSize = Symbol('lastWriteQueueSize');
const { isArrayBufferView } = require('internal/util/types');

// Lazy loaded to improve startup performance.
let cluster;
Expand Down Expand Up @@ -791,28 +792,28 @@ protoGetter('bytesWritten', function bytesWritten() {
return undefined;

this.writableBuffer.forEach(function(el) {
if (el.chunk instanceof Buffer)
bytes += el.chunk.length;
if (isArrayBufferView(el.chunk))
bytes += el.chunk.byteLength;
else
bytes += Buffer.byteLength(el.chunk, el.encoding);
bytes += byteLength(el.chunk, el.encoding);
});

if (Array.isArray(data)) {
// was a writev, iterate over chunks to get total length
for (var i = 0; i < data.length; i++) {
const chunk = data[i];

if (data.allBuffers || chunk instanceof Buffer)
bytes += chunk.length;
if (data.allBuffers || isArrayBufferView(chunk))
bytes += chunk.byteLength;
else
bytes += Buffer.byteLength(chunk.chunk, chunk.encoding);
bytes += byteLength(chunk.chunk, chunk.encoding);
}
} else if (data) {
// Writes are either a string or a Buffer.
if (typeof data !== 'string')
bytes += data.length;
bytes += data.byteLength;
else
bytes += Buffer.byteLength(data, encoding);
bytes += byteLength(data, encoding);
}

return bytes;
Expand Down
22 changes: 10 additions & 12 deletions lib/stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,39 +44,37 @@ Stream.Stream = Stream;
// Internal utilities
try {
const types = require('util').types;
if (types && typeof types.isUint8Array === 'function') {
Stream._isUint8Array = types.isUint8Array;
if (types && typeof types.isArrayBufferView === 'function') {
Stream._isArrayBufferView = types.isArrayBufferView;
} else {
// This throws for Node < 4.2.0 because there's no util binding and
// returns undefined for Node < 7.4.0.
Stream._isUint8Array = process.binding('util').isUint8Array;
Stream._isArrayBufferView = process.binding('util').isArrayBufferView;
}
} catch (e) {
}

if (!Stream._isUint8Array) {
Stream._isUint8Array = function _isUint8Array(obj) {
return Object.prototype.toString.call(obj) === '[object Uint8Array]';
};
if (!Stream._isArrayBufferView) {
Stream._isArrayBufferView = ArrayBuffer.isView;
}

const version = process.version.substr(1).split('.');
if (version[0] === 0 && version[1] < 12) {
Stream._uint8ArrayToBuffer = Buffer;
Stream._typedArrayToBuffer = Buffer;
} else {
try {
const internalBuffer = require('internal/buffer');
Stream._uint8ArrayToBuffer = function _uint8ArrayToBuffer(chunk) {
Stream._typedArrayToBuffer = function _typedArrayToBuffer(chunk) {
return new internalBuffer.FastBuffer(chunk.buffer,
chunk.byteOffset,
chunk.byteLength);
};
} catch (e) {
}

if (!Stream._uint8ArrayToBuffer) {
Stream._uint8ArrayToBuffer = function _uint8ArrayToBuffer(chunk) {
return Buffer.prototype.slice.call(chunk);
if (!Stream._typedArrayToBuffer) {
Stream._typedArrayToBuffer = function _typedArrayToBuffer(chunk) {
return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
};
}
}
18 changes: 12 additions & 6 deletions test/parallel/test-net-bytes-stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,20 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.

'use strict';
require('../common');
const common = require('../common');
const assert = require('assert');
const net = require('net');

let bytesRead = 0;
let bytesWritten = 0;
let count = 0;

const writeFoo = ['foo'];
writeFoo.push(...common.getArrayBufferViews(Buffer.from(writeFoo[0])));
const writeBar = ['bar'];
writeBar.push(...common.getArrayBufferViews(Buffer.from(writeBar[0])));
const maxReconnects = writeFoo.length - 1;

const tcp = net.Server(function(s) {
console.log('tcp server connection');

Expand All @@ -48,9 +54,9 @@ tcp.listen(0, function doTest() {
count++;
console.error('CLIENT connect #%d', count);

socket.write('foo', function() {
socket.write(writeFoo[count - 1], function() {
console.error('CLIENT: write cb');
socket.end('bar');
socket.end(writeBar[count - 1]);
});
});

Expand All @@ -63,7 +69,7 @@ tcp.listen(0, function doTest() {
console.error('CLIENT close event #%d', count);
console.log(`Bytes read: ${bytesRead}`);
console.log(`Bytes written: ${bytesWritten}`);
if (count < 2) {
if (count < maxReconnects) {
console.error('RECONNECTING');
socket.connect(tcp.address().port);
} else {
Expand All @@ -73,6 +79,6 @@ tcp.listen(0, function doTest() {
});

process.on('exit', function() {
assert.strictEqual(bytesRead, 12);
assert.strictEqual(bytesWritten, 12);
assert.strictEqual(bytesRead, 24);
assert.strictEqual(bytesWritten, 24);
});
Loading