1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

perf(http): remove allocations checking upgrade and connection header values (#17727)

This commit is contained in:
David Sherret 2023-02-12 15:51:07 -05:00 committed by GitHub
parent fc843d035c
commit dc66fdc11e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 109 additions and 12 deletions

View file

@ -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,

View file

@ -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 };