From d4ce0b19d86e6446d17897743d0ad1eda8079200 Mon Sep 17 00:00:00 2001 From: Aras Abbasi Date: Mon, 19 Feb 2024 07:42:22 +0100 Subject: [PATCH] perf: improve perf of parseRawHeaders (#2781) --- benchmarks/parseRawHeaders.mjs | 24 ++++++++++++++++++++++++ lib/core/util.js | 26 +++++++++++++++++--------- test/node-test/util.js | 1 + 3 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 benchmarks/parseRawHeaders.mjs diff --git a/benchmarks/parseRawHeaders.mjs b/benchmarks/parseRawHeaders.mjs new file mode 100644 index 00000000000..b7ac0f92586 --- /dev/null +++ b/benchmarks/parseRawHeaders.mjs @@ -0,0 +1,24 @@ +import { bench, group, run } from 'mitata' +import { parseRawHeaders } from '../lib/core/util.js' + +const rawHeadersMixed = ['key', 'value', Buffer.from('key'), Buffer.from('value')] +const rawHeadersOnlyStrings = ['key', 'value', 'key', 'value'] +const rawHeadersOnlyBuffers = [Buffer.from('key'), Buffer.from('value'), Buffer.from('key'), Buffer.from('value')] +const rawHeadersContent = ['content-length', 'value', 'content-disposition', 'form-data; name="fieldName"'] + +group('parseRawHeaders', () => { + bench('only strings', () => { + parseRawHeaders(rawHeadersOnlyStrings) + }) + bench('only buffers', () => { + parseRawHeaders(rawHeadersOnlyBuffers) + }) + bench('mixed', () => { + parseRawHeaders(rawHeadersMixed) + }) + bench('content-disposition special case', () => { + parseRawHeaders(rawHeadersContent) + }) +}) + +await run() diff --git a/lib/core/util.js b/lib/core/util.js index 1ad0eab89fd..96e76cc1355 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -279,22 +279,30 @@ function parseHeaders (headers, obj) { } function parseRawHeaders (headers) { - const ret = [] + const len = headers.length + const ret = new Array(len) + let hasContentLength = false let contentDispositionIdx = -1 + let key + let val + let kLen = 0 for (let n = 0; n < headers.length; n += 2) { - const key = headers[n + 0].toString() - const val = headers[n + 1].toString('utf8') + key = headers[n] + val = headers[n + 1] + + typeof key !== 'string' && (key = key.toString()) + typeof val !== 'string' && (val = val.toString('utf8')) - if (key.length === 14 && (key === 'content-length' || key.toLowerCase() === 'content-length')) { - ret.push(key, val) + kLen = key.length + if (kLen === 14 && key[7] === '-' && (key === 'content-length' || key.toLowerCase() === 'content-length')) { hasContentLength = true - } else if (key.length === 19 && (key === 'content-disposition' || key.toLowerCase() === 'content-disposition')) { - contentDispositionIdx = ret.push(key, val) - 1 - } else { - ret.push(key, val) + } else if (kLen === 19 && key[7] === '-' && (key === 'content-disposition' || key.toLowerCase() === 'content-disposition')) { + contentDispositionIdx = n + 1 } + ret[n] = key + ret[n + 1] = val } // See https://github.com/nodejs/node/pull/46528 diff --git a/test/node-test/util.js b/test/node-test/util.js index 9d18f98d596..fa1c6c50eb4 100644 --- a/test/node-test/util.js +++ b/test/node-test/util.js @@ -89,6 +89,7 @@ test('parseHeaders', () => { test('parseRawHeaders', () => { assert.deepEqual(util.parseRawHeaders(['key', 'value', Buffer.from('key'), Buffer.from('value')]), ['key', 'value', 'key', 'value']) + assert.deepEqual(util.parseRawHeaders(['content-length', 'value', 'content-disposition', 'form-data; name="fieldName"']), ['content-length', 'value', 'content-disposition', 'form-data; name="fieldName"']) }) test('buildURL', () => {