Last active
April 22, 2020 23:01
-
-
Save krisselden/3ebec94668d8f04bcd862e37a27320ff to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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