diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2023-02-12 15:51:07 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-12 20:51:07 +0000 |
commit | dc66fdc11e86ae060e43dbeb417738c1b1995fe6 (patch) | |
tree | 4c66594ef13faaa28a793467e57782d7939cfbf2 /ext/http/01_http.js | |
parent | fc843d035c0cb4936d9b87670e282667a9a8b265 (diff) |
perf(http): remove allocations checking upgrade and connection header values (#17727)
Diffstat (limited to 'ext/http/01_http.js')
-rw-r--r-- | ext/http/01_http.js | 92 |
1 files changed, 80 insertions, 12 deletions
diff --git a/ext/http/01_http.js b/ext/http/01_http.js index 1da371e8d..f9e15e7d5 100644 --- a/ext/http/01_http.js +++ b/ext/http/01_http.js @@ -1,5 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. const core = globalThis.Deno.core; +const internals = globalThis.__bootstrap.internals; const primordials = globalThis.__bootstrap.primordials; const { BadResourcePrototype, InterruptedPrototype, ops } = core; import * as webidl from "internal:deno_webidl/00_webidl.js"; @@ -40,18 +41,18 @@ import { } from "internal:deno_web/06_streams.js"; const { ArrayPrototypeIncludes, + ArrayPrototypeMap, ArrayPrototypePush, - ArrayPrototypeSome, Error, ObjectPrototypeIsPrototypeOf, SafeSetIterator, Set, SetPrototypeAdd, SetPrototypeDelete, + StringPrototypeCharCodeAt, StringPrototypeIncludes, StringPrototypeToLowerCase, StringPrototypeSplit, - StringPrototypeTrim, Symbol, SymbolAsyncIterator, TypeError, @@ -389,15 +390,13 @@ function createRespondWith( } const _ws = Symbol("[[associated_ws]]"); +const websocketCvf = buildCaseInsensitiveCommaValueFinder("websocket"); +const upgradeCvf = buildCaseInsensitiveCommaValueFinder("upgrade"); function upgradeWebSocket(request, options = {}) { const upgrade = request.headers.get("upgrade"); const upgradeHasWebSocketOption = upgrade !== null && - ArrayPrototypeSome( - StringPrototypeSplit(upgrade, ","), - (option) => - StringPrototypeToLowerCase(StringPrototypeTrim(option)) === "websocket", - ); + websocketCvf(upgrade); if (!upgradeHasWebSocketOption) { throw new TypeError( "Invalid Header: 'upgrade' header must contain 'websocket'", @@ -406,11 +405,7 @@ function upgradeWebSocket(request, options = {}) { const connection = request.headers.get("connection"); const connectionHasUpgradeOption = connection !== null && - ArrayPrototypeSome( - StringPrototypeSplit(connection, ","), - (option) => - StringPrototypeToLowerCase(StringPrototypeTrim(option)) === "upgrade", - ); + upgradeCvf(connection); if (!connectionHasUpgradeOption) { throw new TypeError( "Invalid Header: 'connection' header must contain 'Upgrade'", @@ -471,4 +466,77 @@ function upgradeHttp(req) { return req[_deferred].promise; } +const spaceCharCode = StringPrototypeCharCodeAt(" ", 0); +const tabCharCode = StringPrototypeCharCodeAt("\t", 0); +const commaCharCode = StringPrototypeCharCodeAt(",", 0); + +/** Builds a case function that can be used to find a case insensitive + * value in some text that's separated by commas. + * + * This is done because it doesn't require any allocations. + * @param checkText {string} - The text to find. (ex. "websocket") + */ +function buildCaseInsensitiveCommaValueFinder(checkText) { + const charCodes = ArrayPrototypeMap( + StringPrototypeSplit( + StringPrototypeToLowerCase(checkText), + "", + ), + (c) => [c.charCodeAt(0), c.toUpperCase().charCodeAt(0)], + ); + /** @type {number} */ + let i; + /** @type {number} */ + let char; + + /** @param value {string} */ + return function (value) { + for (i = 0; i < value.length; i++) { + char = value.charCodeAt(i); + skipWhitespace(value); + + if (hasWord(value)) { + skipWhitespace(value); + if (i === value.length || char === commaCharCode) { + return true; + } + } else { + skipUntilComma(value); + } + } + + return false; + }; + + /** @param value {string} */ + function hasWord(value) { + for (const [cLower, cUpper] of charCodes) { + if (cLower === char || cUpper === char) { + char = StringPrototypeCharCodeAt(value, ++i); + } else { + return false; + } + } + return true; + } + + /** @param value {string} */ + function skipWhitespace(value) { + while (char === spaceCharCode || char === tabCharCode) { + char = StringPrototypeCharCodeAt(value, ++i); + } + } + + /** @param value {string} */ + function skipUntilComma(value) { + while (char !== commaCharCode && i < value.length) { + char = StringPrototypeCharCodeAt(value, ++i); + } + } +} + +// Expose this function for unit tests +internals.buildCaseInsensitiveCommaValueFinder = + buildCaseInsensitiveCommaValueFinder; + export { _ws, HttpConn, upgradeHttp, upgradeWebSocket }; |