From fde1c7269b13c09f924deea34e4de03848fcd542 Mon Sep 17 00:00:00 2001 From: Weijia Wang <381152119@qq.com> Date: Mon, 11 Feb 2019 13:30:15 +0800 Subject: [PATCH 1/4] http: improve performance for incoming headers --- benchmark/_http-benchmarkers.js | 7 +- benchmark/http/incoming_headers.js | 35 +++++ lib/_http_incoming.js | 199 ++++++++++++----------------- 3 files changed, 125 insertions(+), 116 deletions(-) create mode 100644 benchmark/http/incoming_headers.js diff --git a/benchmark/_http-benchmarkers.js b/benchmark/_http-benchmarkers.js index f4566284547ac4..d1de0df300f02e 100644 --- a/benchmark/_http-benchmarkers.js +++ b/benchmark/_http-benchmarkers.js @@ -24,9 +24,12 @@ class AutocannonBenchmarker { '-d', options.duration, '-c', options.connections, '-j', - '-n', - `http://127.0.0.1:${options.port}${options.path}`, + '-n' ]; + for (let field in options.headers) { + args.push('-H', `${field}=${options.headers[field]}`) + } + args.push(`http://127.0.0.1:${options.port}${options.path}`) const child = child_process.spawn(this.executable, args); return child; } diff --git a/benchmark/http/incoming_headers.js b/benchmark/http/incoming_headers.js new file mode 100644 index 00000000000000..a1ab57e23876bf --- /dev/null +++ b/benchmark/http/incoming_headers.js @@ -0,0 +1,35 @@ +'use strict'; +const common = require('../common.js'); +const http = require('http'); + +const bench = common.createBenchmark(main, { + // unicode confuses ab on os x. + c: [50, 500], + headerDuplicates: [0, 5, 20] +}); + +function main({ c, headerDuplicates }) { + const server = http.createServer((req, res) => { + res.end(); + }); + + server.listen(common.PORT, () => { + const headers = { + 'Content-Type': 'text/plain', + 'Accept': 'text/plain', + 'User-Agent': 'nodejs-benchmark', + 'Date': new Date().toString(), + 'Cache-Control': 'no-cache' + }; + for (let i = 0; i < headerDuplicates; i++) { + headers[`foo${i}`] = `some header value ${i}`; + } + bench.http({ + path: '/', + connections: c, + headers + }, () => { + server.close(); + }); + }); +} diff --git a/lib/_http_incoming.js b/lib/_http_incoming.js index 1e0c42f7bdb9af..d06388fb33cd95 100644 --- a/lib/_http_incoming.js +++ b/lib/_http_incoming.js @@ -137,130 +137,101 @@ function _addHeaderLines(headers, n) { // 'array' header list is taken from: // https://mxr.mozilla.org/mozilla/source/netwerk/protocol/http/src/nsHttpHeaderArray.cpp -function matchKnownFields(field) { - var low = false; - while (true) { - switch (field) { - case 'Content-Type': - case 'content-type': - return 'content-type'; - case 'Content-Length': - case 'content-length': - return 'content-length'; - case 'User-Agent': - case 'user-agent': - return 'user-agent'; - case 'Referer': - case 'referer': - return 'referer'; - case 'Host': - case 'host': - return 'host'; - case 'Authorization': - case 'authorization': - return 'authorization'; - case 'Proxy-Authorization': - case 'proxy-authorization': - return 'proxy-authorization'; - case 'If-Modified-Since': - case 'if-modified-since': - return 'if-modified-since'; - case 'If-Unmodified-Since': - case 'if-unmodified-since': - return 'if-unmodified-since'; - case 'From': - case 'from': - return 'from'; - case 'Location': - case 'location': +function matchKnownFields(field, lowercased) { + switch (field.length) { + case 3: + if (field === 'Age' || field === 'age') return 'age'; + break; + case 4: + if (field === 'Host' || field === 'host') return 'host'; + if (field === 'From' || field === 'from') return 'from'; + if (field === 'ETag' || field === 'etag') return 'etag'; + if (field === 'Date' || field === 'date') return '\u0000date'; + if (field === 'Vary' || field === 'vary') return '\u0000vary'; + break; + case 6: + if (field === 'Server' || field === 'server') return 'server'; + if (field === 'Cookie' || field === 'cookie') return '\u0002cookie'; + if (field === 'Origin' || field === 'origin') return '\u0000origin'; + if (field === 'Expect' || field === 'expect') return '\u0000expect'; + if (field === 'Accept' || field === 'accept') return '\u0000accept'; + break; + case 7: + if (field === 'Referer' || field === 'referer') return 'referer'; + if (field === 'Expires' || field === 'expires') return 'expires'; + if (field === 'Upgrade' || field === 'upgrade') return '\u0000upgrade'; + break; + case 8: + if (field === 'Location' || field === 'location') return 'location'; - case 'Max-Forwards': - case 'max-forwards': - return 'max-forwards'; - case 'Retry-After': - case 'retry-after': - return 'retry-after'; - case 'ETag': - case 'etag': - return 'etag'; - case 'Last-Modified': - case 'last-modified': - return 'last-modified'; - case 'Server': - case 'server': - return 'server'; - case 'Age': - case 'age': - return 'age'; - case 'Expires': - case 'expires': - return 'expires'; - case 'Set-Cookie': - case 'set-cookie': + if (field === 'If-Match' || field === 'if-Match') + return '\u0000if-match'; + break; + case 10: + if (field === 'User-Agent' || field === 'user-agent') + return 'user-agent'; + if (field === 'Set-Cookie' || field === 'set-cookie') return '\u0001'; - case 'Cookie': - case 'cookie': - return '\u0002cookie'; - // The fields below are not used in _addHeaderLine(), but they are common - // headers where we can avoid toLowerCase() if the mixed or lower case - // versions match the first time through. - case 'Transfer-Encoding': - case 'transfer-encoding': - return '\u0000transfer-encoding'; - case 'Date': - case 'date': - return '\u0000date'; - case 'Connection': - case 'connection': + if (field === 'Connection' || field === 'connection') return '\u0000connection'; - case 'Cache-Control': - case 'cache-control': + break; + case 11: + if (field === 'Retry-After' || field === 'retry-after') + return 'retry-after'; + break; + case 12: + if (field === 'Content-Type' || field === 'content-type') + return 'content-type'; + if (field === 'Max-Forwards' || field === 'max-forwards') + return 'max-forwards'; + break; + case 13: + if (field === 'Authorization' || field === 'authorization') + return 'authorization'; + if (field === 'Last-Modified' || field === 'last-modified') + return 'last-modified'; + if (field === 'Cache-Control' || field === 'cache-control') return '\u0000cache-control'; - case 'Vary': - case 'vary': - return '\u0000vary'; - case 'Content-Encoding': - case 'content-encoding': - return '\u0000content-encoding'; - case 'Origin': - case 'origin': - return '\u0000origin'; - case 'Upgrade': - case 'upgrade': - return '\u0000upgrade'; - case 'Expect': - case 'expect': - return '\u0000expect'; - case 'If-Match': - case 'if-match': - return '\u0000if-match'; - case 'If-None-Match': - case 'if-none-match': + if (field === 'If-None-Match' || field === 'if-none-match') return '\u0000if-none-match'; - case 'Accept': - case 'accept': - return '\u0000accept'; - case 'Accept-Encoding': - case 'accept-encoding': + break; + case 14: + if (field === 'Content-Length' || field === 'content-length') + return 'content-length'; + break; + case 15: + if (field === 'Accept-Encoding' || field === 'accept-encoding') return '\u0000accept-encoding'; - case 'Accept-Language': - case 'accept-language': + if (field === 'Accept-Language' || field === 'accept-language') return '\u0000accept-language'; - case 'X-Forwarded-For': - case 'x-forwarded-for': + if (field === 'X-Forwarded-For' || field === 'x-forwarded-for') return '\u0000x-forwarded-for'; - case 'X-Forwarded-Host': - case 'x-forwarded-host': + break; + case 16: + if (field === 'Content-Encoding' || field === 'content-encoding') + return '\u0000content-encoding'; + if (field === 'X-Forwarded-Host' || field === 'x-forwarded-host') return '\u0000x-forwarded-host'; - case 'X-Forwarded-Proto': - case 'x-forwarded-proto': + break; + case 17: + if (field === 'If-Modified-Since' || field === 'if-modified-since') + return 'if-modified-since'; + if (field === 'Transfer-Encoding' || field === 'transfer-encoding') + return '\u0000transfer-encoding'; + if (field === 'X-Forwarded-Proto' || field === 'x-forwarded-proto') return '\u0000x-forwarded-proto'; - default: - if (low) - return '\u0000' + field; - field = field.toLowerCase(); - low = true; - } + break; + case 19: + if (field === 'Proxy-Authorization' || field === 'proxy-authorization') + return 'proxy-authorization'; + if (field === 'If-Unmodified-Since' || field === 'if-unmodified-since') + return 'if-unmodified-since'; + break; + } + if (lowercased) { + return '\u0000' + field; + } else { + return matchKnownFields(field.toLowerCase(), true); } } // Add the given (field, value) pair to the message From 4be7aadb460d2a6f8f607a1b5098bd4d6c2b7ad2 Mon Sep 17 00:00:00 2001 From: Weijia Wang <381152119@qq.com> Date: Mon, 11 Feb 2019 14:24:38 +0800 Subject: [PATCH 2/4] [squash] eslint fix --- benchmark/_http-benchmarkers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmark/_http-benchmarkers.js b/benchmark/_http-benchmarkers.js index d1de0df300f02e..a4d623003947eb 100644 --- a/benchmark/_http-benchmarkers.js +++ b/benchmark/_http-benchmarkers.js @@ -24,12 +24,12 @@ class AutocannonBenchmarker { '-d', options.duration, '-c', options.connections, '-j', - '-n' + '-n', ]; - for (let field in options.headers) { - args.push('-H', `${field}=${options.headers[field]}`) + for (const field in options.headers) { + args.push('-H', `${field}=${options.headers[field]}`); } - args.push(`http://127.0.0.1:${options.port}${options.path}`) + args.push(`http://127.0.0.1:${options.port}${options.path}`); const child = child_process.spawn(this.executable, args); return child; } From ee03e7a2deca2d1b07cc444471972c2ea44a2f24 Mon Sep 17 00:00:00 2001 From: Weijia Wang <381152119@qq.com> Date: Mon, 11 Feb 2019 22:58:59 +0800 Subject: [PATCH 3/4] [squash] removing outdated url in comment --- lib/_http_incoming.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/_http_incoming.js b/lib/_http_incoming.js index d06388fb33cd95..799ebcc3e3d0ca 100644 --- a/lib/_http_incoming.js +++ b/lib/_http_incoming.js @@ -134,9 +134,6 @@ function _addHeaderLines(headers, n) { // TODO: perhaps http_parser could be returning both raw and lowercased versions // of known header names to avoid us having to call toLowerCase() for those // headers. - -// 'array' header list is taken from: -// https://mxr.mozilla.org/mozilla/source/netwerk/protocol/http/src/nsHttpHeaderArray.cpp function matchKnownFields(field, lowercased) { switch (field.length) { case 3: From da7b53d6ce5a4837ee724ce1431b1edff2fd809a Mon Sep 17 00:00:00 2001 From: Weijia Wang <381152119@qq.com> Date: Mon, 11 Feb 2019 22:59:20 +0800 Subject: [PATCH 4/4] [squash] fix typo --- lib/_http_incoming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_http_incoming.js b/lib/_http_incoming.js index 799ebcc3e3d0ca..7ba4d366b2147f 100644 --- a/lib/_http_incoming.js +++ b/lib/_http_incoming.js @@ -161,7 +161,7 @@ function matchKnownFields(field, lowercased) { case 8: if (field === 'Location' || field === 'location') return 'location'; - if (field === 'If-Match' || field === 'if-Match') + if (field === 'If-Match' || field === 'if-match') return '\u0000if-match'; break; case 10: