From 09ea79c00f8ce1b56750b849fac48cfdb9405c01 Mon Sep 17 00:00:00 2001 From: Guido Guidos Date: Thu, 20 Apr 2023 01:06:27 +0200 Subject: [PATCH 1/2] chunks: Merge HTTP chunks during chunked encoding --- lib/_http_outgoing.js | 20 ++++++++++++++-- test/own/test-http-outgoing-message.js | 33 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 test/own/test-http-outgoing-message.js diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index 6b1b8703f9f0de..0ce9db59e51af7 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -930,8 +930,6 @@ function write_(msg, chunk, encoding, callback, fromEnd) { let ret; if (msg.chunkedEncoding && chunk.length !== 0) { - len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength; - msg._send(NumberPrototypeToString(len, 16), 'latin1', null); msg._send(crlf_buf, null, null); msg._send(chunk, encoding, null, len); ret = msg._send(crlf_buf, null, callback); @@ -1057,6 +1055,24 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { } if (this.socket) { + let len = 0; + + for (let i = 0; i < this.socket._writableState.buffered.length; i++) { + const chunkData = this.socket._writableState.buffered[i]; + if ( + typeof chunkData.chunk === 'string' && + !chunkData.chunk.startsWith('HTTP/1.1') && + !chunkData.chunk.endsWith('\r\n') + ) { + len += chunkData.chunk.length; + } + } + const lenChunk = { + chunk: NumberPrototypeToString(len, 16), + encoding: 'latin1', + callback: nop, + }; + this.socket._writableState.buffered.splice(1, 0, lenChunk); // Fully uncork connection on end(). this.socket._writableState.corked = 1; this.socket.uncork(); diff --git a/test/own/test-http-outgoing-message.js b/test/own/test-http-outgoing-message.js new file mode 100644 index 00000000000000..58deb1d3c7fbaf --- /dev/null +++ b/test/own/test-http-outgoing-message.js @@ -0,0 +1,33 @@ +/* +In the first line of the test file you should +enable strict mode, unless you test something +that needs it disabled +*/ +'use strict'; + +/* +the common package gives you some commonly +used testing methods, like mustCall +*/ +const common = require('../common'); + +/* +a small description on what you are testing +*/ +// This test ensures that the http-parser can handle UTF-8 characters +// in the http header. + +const assert = require('assert'); +const http = require('http'); + +/* +the body of the actual test - tests should exit with code 0 on success +*/ +const server = http.createServer( + common.mustCall((request, response) => { + response.write('hello world'); + response.end(); + }) +); + +server.listen(3000, () => {}); From 8e9a0b5839759f95a5f2ea4da8301ed21096b763 Mon Sep 17 00:00:00 2001 From: Guido Guidos Date: Fri, 21 Apr 2023 01:36:34 +0200 Subject: [PATCH 2/2] chunks: Merge HTTP chunks during chunked encoding --- lib/_http_outgoing.js | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index 0ce9db59e51af7..0f1fe23749648c 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -866,6 +866,7 @@ function strictContentLength(msg) { ); } +let chunkBuffer = Buffer.alloc(0); function write_(msg, chunk, encoding, callback, fromEnd) { if (typeof callback !== 'function') callback = nop; @@ -930,9 +931,8 @@ function write_(msg, chunk, encoding, callback, fromEnd) { let ret; if (msg.chunkedEncoding && chunk.length !== 0) { - msg._send(crlf_buf, null, null); - msg._send(chunk, encoding, null, len); - ret = msg._send(crlf_buf, null, callback); + chunkBuffer = Buffer.concat([chunkBuffer, Buffer.from(chunk)]); + ret = true; } else { ret = msg._send(chunk, encoding, callback, len); } @@ -1044,6 +1044,14 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(this[kBytesWritten], this._contentLength); } + if (!this._headerSent && Buffer.byteLength(chunkBuffer)) { + this._send(NumberPrototypeToString(Buffer.byteLength(chunkBuffer), 16), 'latin1', null); + this._send(crlf_buf, null, null); + this._send(chunkBuffer, null, null); + this._send(crlf_buf, null, callback); + chunkBuffer = Buffer.alloc(0) + } + const finish = onFinish.bind(undefined, this); if (this._hasBody && this.chunkedEncoding) { @@ -1055,30 +1063,11 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { } if (this.socket) { - let len = 0; - - for (let i = 0; i < this.socket._writableState.buffered.length; i++) { - const chunkData = this.socket._writableState.buffered[i]; - if ( - typeof chunkData.chunk === 'string' && - !chunkData.chunk.startsWith('HTTP/1.1') && - !chunkData.chunk.endsWith('\r\n') - ) { - len += chunkData.chunk.length; - } - } - const lenChunk = { - chunk: NumberPrototypeToString(len, 16), - encoding: 'latin1', - callback: nop, - }; - this.socket._writableState.buffered.splice(1, 0, lenChunk); // Fully uncork connection on end(). this.socket._writableState.corked = 1; this.socket.uncork(); } this[kCorked] = 0; - this.finished = true; // There is the first message on the outgoing queue, and we've sent