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
parent 329b6e908c
commit a10bdb51b1
No known key found for this signature in database
GPG Key ID: 606D7358F94DA209
3 changed files with 72 additions and 82 deletions

View File

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

View File

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

View File

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