pagespeed-quest
Version:
A framework for efficient web front-end speed improvement
110 lines • 11.5 kB
JavaScript
import { Proxy } from './proxy.js';
const ChunkSize = 1024 * 16;
export class PlaybackProxy extends Proxy {
transactions = [];
transactionsMap = new Map();
async loadTransactions(inventory) {
this.transactions = await this.inventoryRepository.loadTransactions(inventory.resources);
for (const transaction of this.transactions) {
const playbackTransaction = {
method: transaction.method,
url: transaction.url,
ttfbMs: transaction.ttfbMs,
statusCode: transaction.statusCode,
err: transaction.errorMessage ? new Error(transaction.errorMessage) : undefined,
rawHeaders: transaction.rawHeaders || {},
contentChunks: [],
contentLength: 0,
durationMs: transaction.durationMs || 0,
};
if (transaction.content) {
const maxChunks = 10;
const minInterval = 10;
const chunks = Math.min(maxChunks, Math.floor(playbackTransaction.durationMs / minInterval));
playbackTransaction.contentChunks = [];
const chunkSize = Math.max(ChunkSize, Math.ceil(transaction.content.length / chunks));
for (let i = 0; i <= transaction.content.length; i += chunkSize) {
playbackTransaction.contentChunks.push(transaction.content.subarray(i, i + chunkSize));
}
}
if (!this.transactionsMap.has(transaction.method)) {
this.transactionsMap.set(transaction.method, new Map());
}
this.transactionsMap.get(transaction.method).set(transaction.url, playbackTransaction);
}
}
async setup() {
const inventory = await this.inventoryRepository.loadInventory();
await this.loadTransactions(inventory);
if (inventory.entryUrl)
this.entryUrl = inventory.entryUrl;
let requestNumber = 1;
this.proxy.onRequest((ctx, onRequestComplete) => {
const number = requestNumber++;
// Skip websocket
if (ctx.clientToProxyRequest.headers?.upgrade === 'websocket') {
this.dependency.logger?.warn({ number }, `Request #${number} ${ctx.clientToProxyRequest.url} skipped (websocket)`);
return;
}
const identifier = Proxy.contextRequest(ctx);
const transaction = this.transactionsMap.get(identifier.method)?.get(identifier.url);
if (!transaction) {
this.dependency.logger?.warn({ number, identifier }, `Request #${number} ${identifier.url} (${identifier.method}) not found in inventory`);
return;
}
const contentStream = this.createThrottlingTransform() || ctx.proxyToClientResponse;
if (contentStream !== ctx.proxyToClientResponse) {
contentStream.pipe(ctx.proxyToClientResponse);
}
this.dependency.logger?.debug({ number, identifier }, `Request #${number} ${transaction.url} started`);
ctx.onError((_, err) => {
this.dependency.logger?.warn({ number, identifier, err }, `Request #${number} ${transaction.url} failed: ${err.message}`);
});
setTimeout(() => {
// Error
if (transaction.err) {
return onRequestComplete(transaction.err);
}
// Status code
ctx.proxyToClientResponse.statusCode = transaction.statusCode || 500;
// Headers
if (transaction.rawHeaders) {
for (const [key, value] of Object.entries(transaction.rawHeaders)) {
if (ctx.proxyToClientResponse.headersSent)
break;
ctx.proxyToClientResponse.setHeader(key, value);
}
}
// Empty content body
if (!transaction.contentChunks || transaction.contentChunks.length === 0) {
contentStream.end();
return;
}
// Content body
const chunks = [...transaction.contentChunks];
const intervalMs = transaction.durationMs / transaction.contentChunks.length;
const interval = setInterval(() => {
const chunk = chunks.shift();
if (chunk) {
contentStream.write(chunk);
}
if (chunks.length === 0) {
clearInterval(interval);
contentStream.end();
this.dependency.logger?.debug({ number, identifier }, `Request #${number} ${transaction.url} completed`);
}
}, intervalMs);
}, transaction.ttfbMs);
});
}
async shutdown() {
// nothing to do
}
}
export async function withPlaybackProxy(options, dependency, cb) {
const proxy = new PlaybackProxy(options, dependency);
await proxy.start();
await cb(proxy);
await proxy.stop();
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGxheWJhY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvcGxheWJhY2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUFFLEtBQUssRUFBaUMsTUFBTSxZQUFZLENBQUE7QUFFakUsTUFBTSxTQUFTLEdBQUcsSUFBSSxHQUFHLEVBQUUsQ0FBQTtBQWMzQixNQUFNLE9BQU8sYUFBYyxTQUFRLEtBQUs7SUFDdEMsWUFBWSxHQUFrQixFQUFFLENBQUE7SUFDaEMsZUFBZSxHQUFrRCxJQUFJLEdBQUcsRUFBRSxDQUFBO0lBRTFFLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFvQjtRQUN6QyxJQUFJLENBQUMsWUFBWSxHQUFHLE1BQU0sSUFBSSxDQUFDLG1CQUFtQixDQUFDLGdCQUFnQixDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQTtRQUV4RixLQUFLLE1BQU0sV0FBVyxJQUFJLElBQUksQ0FBQyxZQUFZLEVBQUU7WUFDM0MsTUFBTSxtQkFBbUIsR0FBd0I7Z0JBQy9DLE1BQU0sRUFBRSxXQUFXLENBQUMsTUFBTTtnQkFDMUIsR0FBRyxFQUFFLFdBQVcsQ0FBQyxHQUFHO2dCQUNwQixNQUFNLEVBQUUsV0FBVyxDQUFDLE1BQU07Z0JBQzFCLFVBQVUsRUFBRSxXQUFXLENBQUMsVUFBVTtnQkFDbEMsR0FBRyxFQUFFLFdBQVcsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxDQUFDLFdBQVcsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUztnQkFDL0UsVUFBVSxFQUFFLFdBQVcsQ0FBQyxVQUFVLElBQUksRUFBRTtnQkFDeEMsYUFBYSxFQUFFLEVBQUU7Z0JBQ2pCLGFBQWEsRUFBRSxDQUFDO2dCQUNoQixVQUFVLEVBQUUsV0FBVyxDQUFDLFVBQVUsSUFBSSxDQUFDO2FBQ3hDLENBQUE7WUFFRCxJQUFJLFdBQVcsQ0FBQyxPQUFPLEVBQUU7Z0JBQ3ZCLE1BQU0sU0FBUyxHQUFHLEVB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