JM Tech Power

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 };