perf: accelerate production request hot paths
This commit is contained in:
@@ -2073,6 +2073,23 @@ const COMPRESSIBLE_TYPE =
|
||||
/^(?:text\/|application\/(?:json|xml|javascript|manifest\+json)|image\/svg\+xml)/i;
|
||||
/** Below this size gzip's overhead isn't worth it. */
|
||||
const COMPRESS_MIN_BYTES = 1024;
|
||||
const COMPRESSED_RESPONSE_CACHE_MAX = 256;
|
||||
const compressedResponseCache = new Map<
|
||||
string,
|
||||
{ body: Uint8Array; headers: [string, string][]; status: number; statusText: string }
|
||||
>();
|
||||
|
||||
function rememberCompressedResponse(
|
||||
key: string,
|
||||
value: { body: Uint8Array; headers: [string, string][]; status: number; statusText: string },
|
||||
) {
|
||||
compressedResponseCache.delete(key);
|
||||
compressedResponseCache.set(key, value);
|
||||
if (compressedResponseCache.size > COMPRESSED_RESPONSE_CACHE_MAX) {
|
||||
const oldest = compressedResponseCache.keys().next().value;
|
||||
if (oldest) compressedResponseCache.delete(oldest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gzip a response when the client accepts it and the body is a compressible,
|
||||
@@ -2080,6 +2097,7 @@ const COMPRESS_MIN_BYTES = 1024;
|
||||
* `Cache-Control: no-transform`, so they are never buffered here.
|
||||
*/
|
||||
async function compressResponse(req: Request, res: Response): Promise<Response> {
|
||||
if (req.method.toUpperCase() === "HEAD") return res;
|
||||
const accept = (req.headers.get("accept-encoding") ?? "").toLowerCase();
|
||||
const acceptsBrotli = /(?:^|,)\s*br(?:\s*;|\s*,|$)/.test(accept);
|
||||
const acceptsGzip = /(?:^|,)\s*gzip(?:\s*;|\s*,|$)/.test(accept);
|
||||
@@ -2089,6 +2107,25 @@ async function compressResponse(req: Request, res: Response): Promise<Response>
|
||||
if (!COMPRESSIBLE_TYPE.test(res.headers.get("content-type") ?? "")) return res;
|
||||
if ((res.headers.get("cache-control") ?? "").includes("no-transform")) return res;
|
||||
|
||||
const immutable = (res.headers.get("cache-control") ?? "").includes("immutable");
|
||||
// Brotli is ideal for immutable assets because the result is cached. Prefer
|
||||
// substantially faster gzip for per-request HTML/JSON when the client allows
|
||||
// both, avoiding synchronous Brotli work on the request hot path.
|
||||
const preferredEncoding = acceptsBrotli && (immutable || !acceptsGzip) ? "br" : "gzip";
|
||||
const cacheKey = immutable ? `${req.url}\n${preferredEncoding}` : "";
|
||||
if (cacheKey) {
|
||||
const cached = compressedResponseCache.get(cacheKey);
|
||||
if (cached) {
|
||||
compressedResponseCache.delete(cacheKey);
|
||||
compressedResponseCache.set(cacheKey, cached);
|
||||
return new Response(cached.body.slice(), {
|
||||
status: cached.status,
|
||||
statusText: cached.statusText,
|
||||
headers: cached.headers,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const body = new Uint8Array(await res.arrayBuffer());
|
||||
if (body.length < COMPRESS_MIN_BYTES) {
|
||||
return new Response(body, {
|
||||
@@ -2100,7 +2137,7 @@ async function compressResponse(req: Request, res: Response): Promise<Response>
|
||||
|
||||
let encoded: Uint8Array;
|
||||
let encoding: "br" | "gzip";
|
||||
if (acceptsBrotli) {
|
||||
if (preferredEncoding === "br") {
|
||||
encoded = new Uint8Array(
|
||||
brotliCompressSync(body, {
|
||||
params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 4 },
|
||||
@@ -2120,6 +2157,14 @@ async function compressResponse(req: Request, res: Response): Promise<Response>
|
||||
else if (!/\baccept-encoding\b/i.test(vary)) headers.set("Vary", `${vary}, Accept-Encoding`);
|
||||
const responseBody = new ArrayBuffer(encoded.byteLength);
|
||||
new Uint8Array(responseBody).set(encoded);
|
||||
if (cacheKey) {
|
||||
rememberCompressedResponse(cacheKey, {
|
||||
body: encoded.slice(),
|
||||
headers: [...headers.entries()],
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
});
|
||||
}
|
||||
return new Response(responseBody, {
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
|
||||
Reference in New Issue
Block a user