mirror of
https://github.com/denoland/deno.git
synced 2024-11-28 16:20:57 -05:00
perf(http): remove allocations checking upgrade and connection header values (#17727)
This commit is contained in:
parent
fc843d035c
commit
dc66fdc11e
2 changed files with 109 additions and 12 deletions
|
@ -14,6 +14,11 @@ import {
|
|||
} from "./test_util.ts";
|
||||
import { join } from "../../../test_util/std/path/mod.ts";
|
||||
|
||||
const {
|
||||
buildCaseInsensitiveCommaValueFinder,
|
||||
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
||||
} = Deno[Deno.internal];
|
||||
|
||||
async function writeRequestAndReadResponse(conn: Deno.Conn): Promise<string> {
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
@ -2612,6 +2617,30 @@ Deno.test({
|
|||
},
|
||||
});
|
||||
|
||||
Deno.test("case insensitive comma value finder", async (t) => {
|
||||
const cases = /** @type {[string, boolean][]} */ ([
|
||||
["websocket", true],
|
||||
["wEbSOcKET", true],
|
||||
[",wEbSOcKET", true],
|
||||
[",wEbSOcKET,", true],
|
||||
[", wEbSOcKET ,", true],
|
||||
["test, wEbSOcKET ,", true],
|
||||
["test ,\twEbSOcKET\t\t ,", true],
|
||||
["test , wEbSOcKET", true],
|
||||
["test, asdf,web,wEbSOcKET", true],
|
||||
["test, asdf,web,wEbSOcKETs", false],
|
||||
["test, asdf,awebsocket,wEbSOcKETs", false],
|
||||
]);
|
||||
|
||||
const findValue = buildCaseInsensitiveCommaValueFinder("websocket");
|
||||
for (const [input, expected] of cases) {
|
||||
await t.step(input.toString(), () => {
|
||||
const actual = findValue(input);
|
||||
assertEquals(actual, expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function httpServerWithErrorBody(
|
||||
listener: Deno.Listener,
|
||||
compression: boolean,
|
||||
|
|
|
@ -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 };
|
||||
|
|
Loading…
Reference in a new issue