lib: refactor wrap_js_stream for ES6/readability

PR-URL: https://github.com/nodejs/node/pull/16158
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
Anna Henningsen 2017-10-12 07:57:45 +02:00
parent 542e94cdce
commit 33b4320cf9
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
3 changed files with 196 additions and 199 deletions

View File

@ -8,119 +8,114 @@ const uv = process.binding('uv');
const debug = util.debuglog('stream_wrap'); const debug = util.debuglog('stream_wrap');
const errors = require('internal/errors'); const errors = require('internal/errors');
function StreamWrap(stream) { /* This class serves as a wrapper for when the C++ side of Node wants access
* to a standard JS stream. For example, TLS or HTTP do not operate on network
* resources conceptually, although that is the common case and what we are
* optimizing for; in theory, they are completely composable and can work with
* any stream resource they see.
*
* For the common case, i.e. a TLS socket wrapping around a net.Socket, we
* can skip going through the JS layer and let TLS access the raw C++ handle
* of a net.Socket. The flipside of this is that, to maintain composability,
* we need a way to create "fake" net.Socket instances that call back into a
* "real" JavaScript stream. JSStreamWrap is exactly this.
*/
class JSStreamWrap extends Socket {
constructor(stream) {
const handle = new JSStream(); const handle = new JSStream();
handle.close = (cb) => {
this.stream = stream;
this._list = null;
const self = this;
handle.close = function(cb) {
debug('close'); debug('close');
self.doClose(cb); this.doClose(cb);
};
handle.isAlive = function() {
return self.isAlive();
};
handle.isClosing = function() {
return self.isClosing();
};
handle.onreadstart = function() {
return self.readStart();
};
handle.onreadstop = function() {
return self.readStop();
};
handle.onshutdown = function(req) {
return self.doShutdown(req);
};
handle.onwrite = function(req, bufs) {
return self.doWrite(req, bufs);
}; };
handle.isAlive = () => this.isAlive();
handle.isClosing = () => this.isClosing();
handle.onreadstart = () => this.readStart();
handle.onreadstop = () => this.readStop();
handle.onshutdown = (req) => this.doShutdown(req);
handle.onwrite = (req, bufs) => this.doWrite(req, bufs);
this.stream.pause(); stream.pause();
this.stream.on('error', function onerror(err) { stream.on('error', (err) => this.emit('error', err));
self.emit('error', err); const ondata = (chunk) => {
}); if (typeof chunk === 'string' ||
this.stream.on('data', function ondata(chunk) { stream._readableState.objectMode === true) {
if (typeof chunk === 'string' || this._readableState.objectMode === true) { // Make sure that no further `data` events will happen.
// Make sure that no further `data` events will happen stream.pause();
this.pause(); stream.removeListener('data', ondata);
this.removeListener('data', ondata);
self.emit('error', new errors.Error('ERR_STREAM_WRAP')); this.emit('error', new errors.Error('ERR_STREAM_WRAP'));
return; return;
} }
debug('data', chunk.length); debug('data', chunk.length);
if (self._handle) if (this._handle)
self._handle.readBuffer(chunk); this._handle.readBuffer(chunk);
}); };
this.stream.once('end', function onend() { stream.on('data', ondata);
stream.once('end', () => {
debug('end'); debug('end');
if (self._handle) if (this._handle)
self._handle.emitEOF(); this._handle.emitEOF();
}); });
Socket.call(this, { super({ handle, manualStart: true });
handle: handle this.stream = stream;
}); this._list = null;
this.read(0);
} }
util.inherits(StreamWrap, Socket);
module.exports = StreamWrap;
// require('_stream_wrap').StreamWrap // Legacy
StreamWrap.StreamWrap = StreamWrap; static get StreamWrap() {
return JSStreamWrap;
}
StreamWrap.prototype.isAlive = function isAlive() { isAlive() {
return true; return true;
}; }
StreamWrap.prototype.isClosing = function isClosing() { isClosing() {
return !this.readable || !this.writable; return !this.readable || !this.writable;
}; }
StreamWrap.prototype.readStart = function readStart() { readStart() {
this.stream.resume(); this.stream.resume();
return 0; return 0;
}; }
StreamWrap.prototype.readStop = function readStop() { readStop() {
this.stream.pause(); this.stream.pause();
return 0; return 0;
}; }
StreamWrap.prototype.doShutdown = function doShutdown(req) { doShutdown(req) {
const self = this;
const handle = this._handle; const handle = this._handle;
const item = this._enqueue('shutdown', req); const item = this._enqueue('shutdown', req);
this.stream.end(function() { this.stream.end(() => {
// Ensure that write was dispatched // Ensure that write was dispatched
setImmediate(function() { setImmediate(() => {
if (!self._dequeue(item)) if (!this._dequeue(item))
return; return;
handle.finishShutdown(req, 0); handle.finishShutdown(req, 0);
}); });
}); });
return 0; return 0;
}; }
StreamWrap.prototype.doWrite = function doWrite(req, bufs) { doWrite(req, bufs) {
const self = this; const self = this;
const handle = self._handle; const handle = this._handle;
var pending = bufs.length; var pending = bufs.length;
// Queue the request to be able to cancel it // Queue the request to be able to cancel it
const item = self._enqueue('write', req); const item = this._enqueue('write', req);
self.stream.cork(); this.stream.cork();
for (var n = 0; n < bufs.length; n++) for (var n = 0; n < bufs.length; n++)
self.stream.write(bufs[n], done); this.stream.write(bufs[n], done);
self.stream.uncork(); this.stream.uncork();
function done(err) { function done(err) {
if (!err && --pending !== 0) if (!err && --pending !== 0)
@ -147,16 +142,9 @@ StreamWrap.prototype.doWrite = function doWrite(req, bufs) {
} }
return 0; return 0;
};
function QueueItem(type, req) {
this.type = type;
this.req = req;
this.prev = this;
this.next = this;
} }
StreamWrap.prototype._enqueue = function _enqueue(type, req) { _enqueue(type, req) {
const item = new QueueItem(type, req); const item = new QueueItem(type, req);
if (this._list === null) { if (this._list === null) {
this._list = item; this._list = item;
@ -169,9 +157,9 @@ StreamWrap.prototype._enqueue = function _enqueue(type, req) {
item.prev.next = item; item.prev.next = item;
return item; return item;
}; }
StreamWrap.prototype._dequeue = function _dequeue(item) { _dequeue(item) {
assert(item instanceof QueueItem); assert(item instanceof QueueItem);
var next = item.next; var next = item.next;
@ -195,17 +183,16 @@ StreamWrap.prototype._dequeue = function _dequeue(item) {
this._list = next; this._list = next;
return true; return true;
}; }
StreamWrap.prototype.doClose = function doClose(cb) { doClose(cb) {
const self = this; const handle = this._handle;
const handle = self._handle;
setImmediate(function() { setImmediate(() => {
while (self._list !== null) { while (this._list !== null) {
const item = self._list; const item = this._list;
const req = item.req; const req = item.req;
self._dequeue(item); this._dequeue(item);
const errCode = uv.UV_ECANCELED; const errCode = uv.UV_ECANCELED;
if (item.type === 'write') { if (item.type === 'write') {
@ -217,7 +204,17 @@ StreamWrap.prototype.doClose = function doClose(cb) {
} }
// Should be already set by net.js // Should be already set by net.js
assert(self._handle === null); assert.strictEqual(this._handle, null);
cb(); cb();
}); });
}; }
}
function QueueItem(type, req) {
this.type = type;
this.req = req;
this.prev = this;
this.next = this;
}
module.exports = JSStreamWrap;

View File

@ -245,7 +245,7 @@ function Socket(options) {
this._handle.reading = false; this._handle.reading = false;
this._handle.readStop(); this._handle.readStop();
this._readableState.flowing = false; this._readableState.flowing = false;
} else { } else if (!options.manualStart) {
this.read(0); this.read(0);
} }
} }

View File

@ -15,5 +15,5 @@ const TlsSocket = require('tls').TLSSocket;
const EventEmitter = require('events').EventEmitter; const EventEmitter = require('events').EventEmitter;
assert.throws( assert.throws(
() => { new TlsSocket(new EventEmitter()); }, () => { new TlsSocket(new EventEmitter()); },
/^TypeError: this\.stream\.pause is not a function/ /^TypeError: (.+) is not a function$/
); );