jm-tech-power-official/Flash Download
A desktop download manager that opens up to 32 connections against one file and can send each of them out a different proxy route.
Public
Uncategorized
Clone
jm-tech-power-official/flash-download.jmproj
test/helpers.js
'use strict';
const http = require('http');
const { URL } = require('url');
/** Deterministic body so every test can check the bytes landed in the right order. */
function makeBody(size) {
const buf = Buffer.alloc(size);
for (let i = 0; i < size; i++) buf[i] = (i * 2654435761) & 0xff;
return buf;
}
/**
* Origin server. Options:
* ranges:false ignore Range entirely (forces a single lane)
* flaky:N destroy the socket mid-body for the first N requests
* slowRange:fn delay(ms) before the body, by start offset
* throttle:fn ms between each of 16 body chunks, by start offset (a truly slow lane)
* hang:fn by start offset: send a little, then go silent forever (a dead connection)
* connRate:fn KB/s for the Nth body request — models a server that throttles
* per connection, so one unlucky connection is slow for its whole life
*/
function originServer(body, opts = {}) {
const { ranges = true, flaky = 0, slowRange = null, throttle = null, connRate = null, hang = null } = opts;
let bodyRequests = 0;
let flakesLeft = flaky;
const stats = { requests: 0, ranged: 0 };
const server = http.createServer((req, res) => {
stats.requests += 1;
const range = ranges ? req.headers.range : null;
let start = 0, end = body.length - 1, status = 200;
if (range) {
const m = /bytes=(\d+)-(\d*)/.exec(range);
if (m) {
start = Number(m[1]);
end = m[2] ? Number(m[2]) : body.length - 1;
status = 206;
stats.ranged += 1;
}
}
if (start >= body.length) { res.writeHead(416); return res.end(); }
end = Math.min(end, body.length - 1);
const slice = body.subarray(start, end + 1);
const headers = {
'content-length': String(slice.length),
'content-type': 'application/octet-stream',
etag: '"fixed-etag"',
};
if (ranges) headers['accept-ranges'] = 'bytes';
if (status === 206) headers['content-range'] = `bytes ${start}-${end}/${body.length}`;
res.writeHead(status, headers);
if (slice.length === 0) return res.end();
// Send a token amount and then never speak again, like a wedged proxy.
if (hang && hang(start) && slice.length > 65536) {
return res.write(slice.subarray(0, 65536));
}
if (flakesLeft > 0 && slice.length > 4096) {
flakesLeft -= 1;
res.write(slice.subarray(0, 2048));
return setTimeout(() => res.socket && res.socket.destroy(), 10);
}
// A per-connection rate limit: whichever connection is unlucky stays slow,
// and a replacement connection for the same bytes gets a fresh draw.
if (connRate && slice.length > 1) {
bodyRequests += 1;
const kbps = connRate(bodyRequests);
if (kbps) {
const per = Math.max(2048, Math.round((kbps * 1024) / 20));
let sent = 0;
const next = () => {
if (res.writableEnded || !res.socket || res.socket.destroyed) return;
const n = Math.min(per, slice.length - sent);
res.write(slice.subarray(sent, sent + n));
sent += n;
if (sent >= slice.length) return res.end();
setTimeout(next, 50);
};
return next();
}
}
// Dribble the body out so this range stays slow for its whole life, rather
// than being merely late. That is what makes a lane worth stealing from.
const perChunk = throttle ? throttle(start) : 0;
if (perChunk > 0) {
const parts = 16;
const step = Math.ceil(slice.length / parts);
let i = 0;
const next = () => {
if (res.writableEnded || !res.socket) return;
if (i >= slice.length) return res.end();
res.write(slice.subarray(i, Math.min(i + step, slice.length)));
i += step;
setTimeout(next, perChunk);
};
return next();
}
const send = () => res.end(slice);
const ms = slowRange ? slowRange(start) : 0;
ms > 0 ? setTimeout(send, ms) : send();
});
return { server, stats };
}
/** Minimal forward proxy so the route path is exercised for real. */
function proxyServer() {
const stats = { forwarded: 0 };
const server = http.createServer((req, res) => {
let target;
try { target = new URL(req.url); } catch { res.writeHead(400); return res.end(); }
stats.forwarded += 1;
const up = http.request({
host: target.hostname, port: target.port || 80,
path: target.pathname + target.search, method: req.method, headers: req.headers,
}, (pr) => { res.writeHead(pr.statusCode, pr.headers); pr.pipe(res); });
up.on('error', () => { try { res.writeHead(502); res.end(); } catch {} });
req.pipe(up);
});
return { server, stats };
}
const listen = (server) => new Promise((resolve) => server.listen(0, '127.0.0.1', () => resolve(server.address().port)));
const close = (server) => new Promise((resolve) => server.close(resolve));
module.exports = { makeBody, originServer, proxyServer, listen, close };