http: improve outgoing string write performance

PR-URL: https://github.com/nodejs/node/pull/13013
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Brian White 2017-05-13 10:07:44 -04:00
Родитель 329b6e908c
Коммит a10bdb51b1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 606D7358F94DA209
3 изменённых файлов: 72 добавлений и 82 удалений

Просмотреть файл

@ -78,6 +78,9 @@ utcDate._onTimeout = function _onTimeout() {
};
function noopPendingOutput(amount) {}
function OutgoingMessage() {
Stream.call(this);
@ -117,7 +120,7 @@ function OutgoingMessage() {
this._header = null;
this[outHeadersKey] = null;
this._onPendingData = null;
this._onPendingData = noopPendingOutput;
}
util.inherits(OutgoingMessage, Stream);
@ -234,12 +237,18 @@ OutgoingMessage.prototype._send = function _send(data, encoding, callback) {
(encoding === 'utf8' || encoding === 'latin1' || !encoding)) {
data = this._header + data;
} else {
this.output.unshift(this._header);
this.outputEncodings.unshift('latin1');
this.outputCallbacks.unshift(null);
this.outputSize += this._header.length;
if (typeof this._onPendingData === 'function')
this._onPendingData(this._header.length);
var header = this._header;
if (this.output.length === 0) {
this.output = [header];
this.outputEncodings = ['latin1'];
this.outputCallbacks = [null];
} else {
this.output.unshift(header);
this.outputEncodings.unshift('latin1');
this.outputCallbacks.unshift(null);
}
this.outputSize += header.length;
this._onPendingData(header.length);
}
this._headerSent = true;
}
@ -275,19 +284,13 @@ function _writeRaw(data, encoding, callback) {
return conn.write(data, encoding, callback);
}
// Buffer, as long as we're not destroyed.
return this._buffer(data, encoding, callback);
}
OutgoingMessage.prototype._buffer = function _buffer(data, encoding, callback) {
this.output.push(data);
this.outputEncodings.push(encoding);
this.outputCallbacks.push(callback);
this.outputSize += data.length;
if (typeof this._onPendingData === 'function')
this._onPendingData(data.length);
this._onPendingData(data.length);
return false;
};
}
OutgoingMessage.prototype._storeHeader = _storeHeader;
@ -624,27 +627,31 @@ Object.defineProperty(OutgoingMessage.prototype, 'headersSent', {
const crlf_buf = Buffer.from('\r\n');
OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
if (this.finished) {
return write_(this, chunk, encoding, callback, false);
};
function write_(msg, chunk, encoding, callback, fromEnd) {
if (msg.finished) {
var err = new Error('write after end');
nextTick(this.socket[async_id_symbol],
writeAfterEndNT.bind(this),
nextTick(msg.socket[async_id_symbol],
writeAfterEndNT.bind(msg),
err,
callback);
return true;
}
if (!this._header) {
this._implicitHeader();
if (!msg._header) {
msg._implicitHeader();
}
if (!this._hasBody) {
if (!msg._hasBody) {
debug('This type of response MUST NOT have a body. ' +
'Ignoring write() calls.');
return true;
}
if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
if (!fromEnd && typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
throw new TypeError('First argument must be a string or Buffer');
}
@ -654,38 +661,28 @@ OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
if (chunk.length === 0) return true;
var len, ret;
if (this.chunkedEncoding) {
if (typeof chunk === 'string' &&
encoding !== 'hex' &&
encoding !== 'base64' &&
encoding !== 'latin1') {
if (msg.chunkedEncoding) {
if (typeof chunk === 'string')
len = Buffer.byteLength(chunk, encoding);
chunk = len.toString(16) + CRLF + chunk + CRLF;
ret = this._send(chunk, encoding, callback);
} else {
// buffer, or a non-toString-friendly encoding
if (typeof chunk === 'string')
len = Buffer.byteLength(chunk, encoding);
else
len = chunk.length;
else
len = chunk.length;
if (this.connection && !this.connection.corked) {
this.connection.cork();
process.nextTick(connectionCorkNT, this.connection);
}
this._send(len.toString(16), 'latin1', null);
this._send(crlf_buf, null, null);
this._send(chunk, encoding, null);
ret = this._send(crlf_buf, null, callback);
if (msg.connection && !msg.connection.corked) {
msg.connection.cork();
process.nextTick(connectionCorkNT, msg.connection);
}
msg._send(len.toString(16), 'latin1', null);
msg._send(crlf_buf, null, null);
msg._send(chunk, encoding, null);
ret = msg._send(crlf_buf, null, callback);
} else {
ret = this._send(chunk, encoding, callback);
ret = msg._send(chunk, encoding, callback);
}
debug('write ret = ' + ret);
return ret;
};
}
function writeAfterEndNT(err, callback) {
@ -736,49 +733,40 @@ function onFinish(outmsg) {
outmsg.emit('finish');
}
OutgoingMessage.prototype.end = function end(data, encoding, callback) {
if (typeof data === 'function') {
callback = data;
data = null;
OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
if (typeof chunk === 'function') {
callback = chunk;
chunk = null;
} else if (typeof encoding === 'function') {
callback = encoding;
encoding = null;
}
if (data && typeof data !== 'string' && !(data instanceof Buffer)) {
throw new TypeError('First argument must be a string or Buffer');
}
if (this.finished) {
return false;
}
if (!this._header) {
if (data) {
if (typeof data === 'string')
this._contentLength = Buffer.byteLength(data, encoding);
else
this._contentLength = data.length;
} else {
this._contentLength = 0;
var uncork;
if (chunk) {
if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
throw new TypeError('First argument must be a string or Buffer');
}
if (!this._header) {
if (typeof chunk === 'string')
this._contentLength = Buffer.byteLength(chunk, encoding);
else
this._contentLength = chunk.length;
}
if (this.connection) {
this.connection.cork();
uncork = true;
}
write_(this, chunk, encoding, null, true);
} else if (!this._header) {
this._contentLength = 0;
this._implicitHeader();
}
if (data && !this._hasBody) {
debug('This type of response MUST NOT have a body. ' +
'Ignoring data passed to end().');
data = null;
}
if (this.connection && data)
this.connection.cork();
if (data) {
// Normal body write.
this.write(data, encoding);
}
if (typeof callback === 'function')
this.once('finish', callback);
@ -792,7 +780,7 @@ OutgoingMessage.prototype.end = function end(data, encoding, callback) {
ret = this._send('', 'latin1', finish);
}
if (this.connection && data)
if (uncork)
this.connection.uncork();
this.finished = true;
@ -871,8 +859,7 @@ OutgoingMessage.prototype._flushOutput = function _flushOutput(socket) {
this.output = [];
this.outputEncodings = [];
this.outputCallbacks = [];
if (typeof this._onPendingData === 'function')
this._onPendingData(-this.outputSize);
this._onPendingData(-this.outputSize);
this.outputSize = 0;
return ret;

Просмотреть файл

@ -23,12 +23,12 @@
const common = require('../common');
const http = require('http');
let serverRes;
const server = http.Server(function(req, res) {
console.log('Server accepted request.');
serverRes = res;
res.writeHead(200);
res.write('Part of my res.');
res.destroy();
});
server.listen(0, common.mustCall(function() {
@ -37,6 +37,7 @@ server.listen(0, common.mustCall(function() {
headers: { connection: 'keep-alive' }
}, common.mustCall(function(res) {
server.close();
serverRes.destroy();
console.log(`Got res: ${res.statusCode}`);
console.dir(res.headers);

Просмотреть файл

@ -2,9 +2,10 @@
const common = require('../common');
const http = require('http');
let serverRes;
const server = http.Server(function(req, res) {
res.write('Part of my res.');
res.destroy();
serverRes = res;
});
server.listen(0, common.mustCall(function() {
@ -13,6 +14,7 @@ server.listen(0, common.mustCall(function() {
headers: { connection: 'keep-alive' }
}, common.mustCall(function(res) {
server.close();
serverRes.destroy();
res.on('aborted', common.mustCall());
}));
}));