Skip to content

Commit 2264d9d

Browse files
mscdexjasnell
authored andcommitted
http: improve outgoing string write performance
PR-URL: #13013 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent c0a7c8e commit 2264d9d

File tree

3 files changed

+71
-81
lines changed

3 files changed

+71
-81
lines changed

lib/_http_outgoing.js

+65-78
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ utcDate._onTimeout = function _onTimeout() {
7878
};
7979

8080

81+
function noopPendingOutput(amount) {}
82+
83+
8184
function OutgoingMessage() {
8285
Stream.call(this);
8386

@@ -117,7 +120,7 @@ function OutgoingMessage() {
117120
this._header = null;
118121
this[outHeadersKey] = null;
119122

120-
this._onPendingData = null;
123+
this._onPendingData = noopPendingOutput;
121124
}
122125
util.inherits(OutgoingMessage, Stream);
123126

@@ -234,12 +237,18 @@ OutgoingMessage.prototype._send = function _send(data, encoding, callback) {
234237
(encoding === 'utf8' || encoding === 'latin1' || !encoding)) {
235238
data = this._header + data;
236239
} else {
237-
this.output.unshift(this._header);
238-
this.outputEncodings.unshift('latin1');
239-
this.outputCallbacks.unshift(null);
240-
this.outputSize += this._header.length;
241-
if (typeof this._onPendingData === 'function')
242-
this._onPendingData(this._header.length);
240+
var header = this._header;
241+
if (this.output.length === 0) {
242+
this.output = [header];
243+
this.outputEncodings = ['latin1'];
244+
this.outputCallbacks = [null];
245+
} else {
246+
this.output.unshift(header);
247+
this.outputEncodings.unshift('latin1');
248+
this.outputCallbacks.unshift(null);
249+
}
250+
this.outputSize += header.length;
251+
this._onPendingData(header.length);
243252
}
244253
this._headerSent = true;
245254
}
@@ -275,19 +284,13 @@ function _writeRaw(data, encoding, callback) {
275284
return conn.write(data, encoding, callback);
276285
}
277286
// Buffer, as long as we're not destroyed.
278-
return this._buffer(data, encoding, callback);
279-
}
280-
281-
282-
OutgoingMessage.prototype._buffer = function _buffer(data, encoding, callback) {
283287
this.output.push(data);
284288
this.outputEncodings.push(encoding);
285289
this.outputCallbacks.push(callback);
286290
this.outputSize += data.length;
287-
if (typeof this._onPendingData === 'function')
288-
this._onPendingData(data.length);
291+
this._onPendingData(data.length);
289292
return false;
290-
};
293+
}
291294

292295

293296
OutgoingMessage.prototype._storeHeader = _storeHeader;
@@ -624,27 +627,31 @@ Object.defineProperty(OutgoingMessage.prototype, 'headersSent', {
624627

625628
const crlf_buf = Buffer.from('\r\n');
626629
OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
627-
if (this.finished) {
630+
return write_(this, chunk, encoding, callback, false);
631+
};
632+
633+
function write_(msg, chunk, encoding, callback, fromEnd) {
634+
if (msg.finished) {
628635
var err = new Error('write after end');
629-
nextTick(this.socket[async_id_symbol],
630-
writeAfterEndNT.bind(this),
636+
nextTick(msg.socket[async_id_symbol],
637+
writeAfterEndNT.bind(msg),
631638
err,
632639
callback);
633640

634641
return true;
635642
}
636643

637-
if (!this._header) {
638-
this._implicitHeader();
644+
if (!msg._header) {
645+
msg._implicitHeader();
639646
}
640647

641-
if (!this._hasBody) {
648+
if (!msg._hasBody) {
642649
debug('This type of response MUST NOT have a body. ' +
643650
'Ignoring write() calls.');
644651
return true;
645652
}
646653

647-
if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
654+
if (!fromEnd && typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
648655
throw new TypeError('First argument must be a string or Buffer');
649656
}
650657

@@ -654,38 +661,28 @@ OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
654661
if (chunk.length === 0) return true;
655662

656663
var len, ret;
657-
if (this.chunkedEncoding) {
658-
if (typeof chunk === 'string' &&
659-
encoding !== 'hex' &&
660-
encoding !== 'base64' &&
661-
encoding !== 'latin1') {
664+
if (msg.chunkedEncoding) {
665+
if (typeof chunk === 'string')
662666
len = Buffer.byteLength(chunk, encoding);
663-
chunk = len.toString(16) + CRLF + chunk + CRLF;
664-
ret = this._send(chunk, encoding, callback);
665-
} else {
666-
// buffer, or a non-toString-friendly encoding
667-
if (typeof chunk === 'string')
668-
len = Buffer.byteLength(chunk, encoding);
669-
else
670-
len = chunk.length;
671-
672-
if (this.connection && !this.connection.corked) {
673-
this.connection.cork();
674-
process.nextTick(connectionCorkNT, this.connection);
675-
}
667+
else
668+
len = chunk.length;
676669

677-
this._send(len.toString(16), 'latin1', null);
678-
this._send(crlf_buf, null, null);
679-
this._send(chunk, encoding, null);
680-
ret = this._send(crlf_buf, null, callback);
670+
if (msg.connection && !msg.connection.corked) {
671+
msg.connection.cork();
672+
process.nextTick(connectionCorkNT, msg.connection);
681673
}
674+
675+
msg._send(len.toString(16), 'latin1', null);
676+
msg._send(crlf_buf, null, null);
677+
msg._send(chunk, encoding, null);
678+
ret = msg._send(crlf_buf, null, callback);
682679
} else {
683-
ret = this._send(chunk, encoding, callback);
680+
ret = msg._send(chunk, encoding, callback);
684681
}
685682

686683
debug('write ret = ' + ret);
687684
return ret;
688-
};
685+
}
689686

690687

691688
function writeAfterEndNT(err, callback) {
@@ -736,49 +733,40 @@ function onFinish(outmsg) {
736733
outmsg.emit('finish');
737734
}
738735

739-
OutgoingMessage.prototype.end = function end(data, encoding, callback) {
740-
if (typeof data === 'function') {
741-
callback = data;
742-
data = null;
736+
OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
737+
if (typeof chunk === 'function') {
738+
callback = chunk;
739+
chunk = null;
743740
} else if (typeof encoding === 'function') {
744741
callback = encoding;
745742
encoding = null;
746743
}
747744

748-
if (data && typeof data !== 'string' && !(data instanceof Buffer)) {
749-
throw new TypeError('First argument must be a string or Buffer');
750-
}
751-
752745
if (this.finished) {
753746
return false;
754747
}
755748

756-
if (!this._header) {
757-
if (data) {
758-
if (typeof data === 'string')
759-
this._contentLength = Buffer.byteLength(data, encoding);
749+
var uncork;
750+
if (chunk) {
751+
if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
752+
throw new TypeError('First argument must be a string or Buffer');
753+
}
754+
if (!this._header) {
755+
if (typeof chunk === 'string')
756+
this._contentLength = Buffer.byteLength(chunk, encoding);
760757
else
761-
this._contentLength = data.length;
762-
} else {
763-
this._contentLength = 0;
758+
this._contentLength = chunk.length;
759+
}
760+
if (this.connection) {
761+
this.connection.cork();
762+
uncork = true;
764763
}
764+
write_(this, chunk, encoding, null, true);
765+
} else if (!this._header) {
766+
this._contentLength = 0;
765767
this._implicitHeader();
766768
}
767769

768-
if (data && !this._hasBody) {
769-
debug('This type of response MUST NOT have a body. ' +
770-
'Ignoring data passed to end().');
771-
data = null;
772-
}
773-
774-
if (this.connection && data)
775-
this.connection.cork();
776-
777-
if (data) {
778-
// Normal body write.
779-
this.write(data, encoding);
780-
}
781-
782770
if (typeof callback === 'function')
783771
this.once('finish', callback);
784772

@@ -792,7 +780,7 @@ OutgoingMessage.prototype.end = function end(data, encoding, callback) {
792780
ret = this._send('', 'latin1', finish);
793781
}
794782

795-
if (this.connection && data)
783+
if (uncork)
796784
this.connection.uncork();
797785

798786
this.finished = true;
@@ -871,8 +859,7 @@ OutgoingMessage.prototype._flushOutput = function _flushOutput(socket) {
871859
this.output = [];
872860
this.outputEncodings = [];
873861
this.outputCallbacks = [];
874-
if (typeof this._onPendingData === 'function')
875-
this._onPendingData(-this.outputSize);
862+
this._onPendingData(-this.outputSize);
876863
this.outputSize = 0;
877864

878865
return ret;

test/parallel/test-http-abort-client.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323
const common = require('../common');
2424
const http = require('http');
2525

26+
let serverRes;
2627
const server = http.Server(function(req, res) {
2728
console.log('Server accepted request.');
29+
serverRes = res;
2830
res.writeHead(200);
2931
res.write('Part of my res.');
30-
31-
res.destroy();
3232
});
3333

3434
server.listen(0, common.mustCall(function() {
@@ -37,6 +37,7 @@ server.listen(0, common.mustCall(function() {
3737
headers: { connection: 'keep-alive' }
3838
}, common.mustCall(function(res) {
3939
server.close();
40+
serverRes.destroy();
4041

4142
console.log(`Got res: ${res.statusCode}`);
4243
console.dir(res.headers);

test/parallel/test-http-client-aborted-event.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
const common = require('../common');
33
const http = require('http');
44

5+
let serverRes;
56
const server = http.Server(function(req, res) {
67
res.write('Part of my res.');
7-
res.destroy();
8+
serverRes = res;
89
});
910

1011
server.listen(0, common.mustCall(function() {
@@ -13,6 +14,7 @@ server.listen(0, common.mustCall(function() {
1314
headers: { connection: 'keep-alive' }
1415
}, common.mustCall(function(res) {
1516
server.close();
17+
serverRes.destroy();
1618
res.on('aborted', common.mustCall());
1719
}));
1820
}));

0 commit comments

Comments
 (0)