1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-24 08:09:08 -05:00

perf(http): cache verified headers (#19465)

Use `Map` to cache validated HTTP headers. Cache
has a capacity of 4096 elements and it's cleared
once that capacity is reached.

In `preactssr` benchmark it lowers the time spent
when adding headers from 180ms to 2.5ms.
This commit is contained in:
Bartek Iwańczuk 2023-06-13 21:13:34 +02:00
parent d14b0f0564
commit e4920f4a28
No known key found for this signature in database
GPG key ID: 0C6BCDDC3B3AD750

View file

@ -32,11 +32,16 @@ const {
ObjectHasOwn,
RegExpPrototypeExec,
SafeArrayIterator,
SafeRegExp,
SafeMap,
MapPrototypeGet,
MapPrototypeHas,
MapPrototypeSet,
MapPrototypeClear,
Symbol,
SymbolFor,
SymbolIterator,
StringPrototypeReplaceAll,
StringPrototypeCharCodeAt,
TypeError,
} = primordials;
@ -87,9 +92,33 @@ function fillHeaders(headers, object) {
}
}
// Regex matching illegal chars in a header value
// deno-lint-ignore no-control-regex
const ILLEGAL_VALUE_CHARS = new SafeRegExp(/[\x00\x0A\x0D]/);
function checkForInvalidValueChars(value) {
for (let i = 0; i < value.length; i++) {
const c = StringPrototypeCharCodeAt(value, i);
if (c === 0x0a || c === 0x0d || c === 0x00) {
return false;
}
}
return true;
}
const HEADER_NAME_CACHE = new SafeMap();
function checkHeaderNameForHttpTokenCodePoint(name) {
if (MapPrototypeHas(HEADER_NAME_CACHE, name)) {
return MapPrototypeGet(HEADER_NAME_CACHE, name);
}
const valid = RegExpPrototypeExec(HTTP_TOKEN_CODE_POINT_RE, name) !== null;
if (HEADER_NAME_CACHE.size > 4096) {
MapPrototypeClear(HEADER_NAME_CACHE);
}
MapPrototypeSet(HEADER_NAME_CACHE, name, valid);
return valid;
}
/**
* https://fetch.spec.whatwg.org/#concept-headers-append
@ -102,10 +131,10 @@ function appendHeader(headers, name, value) {
value = normalizeHeaderValue(value);
// 2.
if (RegExpPrototypeExec(HTTP_TOKEN_CODE_POINT_RE, name) === null) {
if (!checkHeaderNameForHttpTokenCodePoint(name)) {
throw new TypeError("Header name is not valid.");
}
if (RegExpPrototypeExec(ILLEGAL_VALUE_CHARS, value) !== null) {
if (!checkForInvalidValueChars(value)) {
throw new TypeError("Header value is not valid.");
}
@ -282,7 +311,7 @@ class Headers {
webidl.requiredArguments(arguments.length, 1, prefix);
name = webidl.converters["ByteString"](name, prefix, "Argument 1");
if (RegExpPrototypeExec(HTTP_TOKEN_CODE_POINT_RE, name) === null) {
if (!checkHeaderNameForHttpTokenCodePoint(name)) {
throw new TypeError("Header name is not valid.");
}
if (this[_guard] == "immutable") {
@ -307,7 +336,7 @@ class Headers {
webidl.requiredArguments(arguments.length, 1, prefix);
name = webidl.converters["ByteString"](name, prefix, "Argument 1");
if (RegExpPrototypeExec(HTTP_TOKEN_CODE_POINT_RE, name) === null) {
if (!checkHeaderNameForHttpTokenCodePoint(name)) {
throw new TypeError("Header name is not valid.");
}
@ -323,7 +352,7 @@ class Headers {
webidl.requiredArguments(arguments.length, 1, prefix);
name = webidl.converters["ByteString"](name, prefix, "Argument 1");
if (RegExpPrototypeExec(HTTP_TOKEN_CODE_POINT_RE, name) === null) {
if (!checkHeaderNameForHttpTokenCodePoint(name)) {
throw new TypeError("Header name is not valid.");
}
@ -351,10 +380,10 @@ class Headers {
value = normalizeHeaderValue(value);
// 2.
if (RegExpPrototypeExec(HTTP_TOKEN_CODE_POINT_RE, name) === null) {
if (!checkHeaderNameForHttpTokenCodePoint(name)) {
throw new TypeError("Header name is not valid.");
}
if (RegExpPrototypeExec(ILLEGAL_VALUE_CHARS, value) !== null) {
if (!checkForInvalidValueChars(value)) {
throw new TypeError("Header value is not valid.");
}