Skip to content

Instantly share code, notes, and snippets.

@krisselden
Last active April 22, 2020 23:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krisselden/3ebec94668d8f04bcd862e37a27320ff to your computer and use it in GitHub Desktop.
Save krisselden/3ebec94668d8f04bcd862e37a27320ff to your computer and use it in GitHub Desktop.
const tls = require("tls");
const https = require("https");
const { pipeline, Writable } = require("stream");
const zlib = require("zlib");
const { StringDecoder } = require("string_decoder");
/**
* @typedef {Object} Config
* @property {string} url
* @property {string} sessionCookie
* @property {string} controlCookie
* @property {string} enabledCookie
* @property {string=} overrideCookie
* @property {{host: string; port: number}=} connectTo
*/
run(require("./config"));
/**
* @param {Config} config
*/
async function run(config) {
const chunksControl = await runTest(
config.url,
config.sessionCookie,
config.controlCookie,
config.overrideCookie,
config.connectTo
);
const chunksEnabled = await runTest(
config.url,
config.sessionCookie,
config.enabledCookie,
config.overrideCookie,
config.connectTo
);
console.log("\nCONTROL %o", config.controlCookie);
await formatChunks(chunksControl);
console.log("\nENABLED %o", config.enabledCookie);
await formatChunks(chunksEnabled);
}
/**
* @param {{ts: number, chunk: Buffer}[]} chunks
*/
async function formatChunks(chunks) {
const gunzip = zlib.createGunzip({
chunkSize: 128 * 1024,
flush: zlib.constants.Z_SYNC_FLUSH,
});
const decoder = new StringDecoder("utf8");
let last = 0;
let total = 0;
/**
* @type {{ts: number; gap: number, total: number, chunk: number, inflated: number, head: string, text: number, tail: string}[]}
*/
const tabularData = [];
for (const { ts, chunk } of chunks) {
total += chunk.byteLength;
await new Promise((resolve) => gunzip.write(chunk, resolve));
const gap = ts - last;
let text = "";
let inflated = gunzip.read();
if (inflated) {
text = decoder.write(inflated);
}
const head = text.slice(0, 50);
const tail =
text.length > 50 ? text.slice(Math.max(text.length - 50, 50)) : "";
tabularData.push({
ts,
gap,
total,
chunk: chunk.byteLength,
inflated: inflated ? inflated.byteLength : 0,
text: text.length,
head,
tail,
});
last = ts;
}
console.table(tabularData);
}
/**
* @param {string} urlStr
* @param {string} sessionCookie
* @param {string} treatmentCookie
* @param {string=} overrideCookie
* @param {{ host: string; port: number }=} connectTo
*/
async function runTest(
urlStr,
sessionCookie,
treatmentCookie,
overrideCookie,
connectTo
) {
const url = new URL(urlStr);
const cookie = makeCookie(sessionCookie, treatmentCookie, overrideCookie);
/** @type {import('https').RequestOptions} */
const requestOptions = {
host: url.hostname,
port: url.port,
path: url.pathname + url.search,
headers: {
Host: url.host,
Cookie: cookie,
"Accept-Encoding": "gzip",
},
createConnection() {
const socket = tls.connect({
host: connectTo ? connectTo.host : url.hostname,
port: connectTo
? connectTo.port
: url.port
? parseInt(url.port, 10)
: 443,
rejectUnauthorized: false,
});
socket.setNoDelay(true);
return socket;
},
};
console.group("MEASURE");
console.log("GET %s %s", urlStr, treatmentCookie);
const response = await get(requestOptions);
console.log("RESPONSE");
const results = await readBody(response);
console.log("DONE");
console.groupEnd();
return results;
}
/**
* @param {import('https').RequestOptions} options
*/
async function get(options) {
return /** @type {import('http').IncomingMessage} */ (await new Promise(
(resolve) => https.get(options, resolve)
));
}
/**
* @typedef {(name: string, arg?: unknown) => void} Log
*/
/**
* @param {import('http').IncomingMessage} response
*/
async function readBody(response) {
const start = Date.now();
/** @type {{ts: number, chunk: Buffer}[]} */
const chunks = [];
let total = 0;
await new Promise((resolve) => {
pipeline(response, new Writable({ write, final, destroy }), resolve);
});
return chunks;
/**
* @param {Buffer} chunk
* @param {string} _encoding
* @param {() => void} callback
*/
function write(chunk, _encoding, callback) {
total += chunk.length;
chunks.push({ ts: Date.now() - start, chunk });
callback();
}
/**
* @param {() => void} callback
*/
function final(callback) {
callback();
}
/**
* @param {Error | null} error
* @param {(err: Error | null) => void} callback
*/
function destroy(error, callback) {
callback(error);
}
}
/**
* @param {string} sessionCookie
* @param {string} treatmentCookie
* @param {string=} overrideCookie
*/
function makeCookie(sessionCookie, treatmentCookie, overrideCookie) {
let cookie = `${sessionCookie}; ${treatmentCookie}`;
if (overrideCookie) {
cookie = `${cookie}; ${overrideCookie}`;
}
return cookie;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment