Skip to content
Permalink
Browse files

http: improve outgoing string write performance

PR-URL: #13013
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information...
mscdex authored and jasnell committed May 13, 2017
1 parent c0a7c8e commit 2264d9d4bada80345e63ef9c9c80577b164a50fa
Showing with 71 additions and 81 deletions.
  1. +65 −78 lib/_http_outgoing.js
  2. +3 −2 test/parallel/test-http-abort-client.js
  3. +3 −1 test/parallel/test-http-client-aborted-event.js
@@ -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;

if (this.connection && !this.connection.corked) {
this.connection.cork();
process.nextTick(connectionCorkNT, this.connection);
}
else
len = chunk.length;

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);
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 = data.length;
} else {
this._contentLength = 0;
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());
}));
}));

0 comments on commit 2264d9d

Please sign in to comment.
You can’t perform that action at this time.