hfs
Version:
HTTP File Server
112 lines (111 loc) • 5.08 kB
JavaScript
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.totalInSpeed = exports.totalOutSpeed = exports.totalGot = exports.totalSent = exports.throttler = void 0;
exports.roundSpeed = roundSpeed;
const stream_1 = require("stream");
const ThrottledStream_1 = require("./ThrottledStream");
const config_1 = require("./config");
const misc_1 = require("./misc");
const connections_1 = require("./connections");
const lodash_1 = __importDefault(require("lodash"));
const events_1 = __importDefault(require("./events"));
const persistence_1 = require("./persistence");
const mainThrottleGroup = new ThrottledStream_1.ThrottleGroup(Infinity);
(0, config_1.defineConfig)('max_kbps', Infinity).sub(v => mainThrottleGroup.updateLimit(v));
const ip2group = {};
const SymThrStr = Symbol('stream');
const SymTimeout = Symbol('timeout');
const maxKbpsPerIp = (0, config_1.defineConfig)('max_kbps_per_ip', Infinity);
maxKbpsPerIp.sub(v => {
for (const [ip, { group }] of Object.entries(ip2group))
if (ip) // empty-string = unlimited group
group.updateLimit(v);
});
const throttler = async (ctx, next) => {
var _a;
var _b;
await next();
let { body } = ctx;
const downloadTotal = ctx.response.length;
if (typeof body === 'string' || body && body instanceof Buffer)
ctx.body = body = stream_1.Readable.from(body);
if (!body || !(body instanceof stream_1.Readable))
return;
// we wrap the stream also for unlimited connections to get speed and other features
const noLimit = ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.ignore_limits) || (0, misc_1.isLocalHost)(ctx);
const ipGroup = ip2group[_b = noLimit ? '' : ctx.ip] || (ip2group[_b] = {
count: 0,
group: new ThrottledStream_1.ThrottleGroup(noLimit ? Infinity : maxKbpsPerIp.get(), noLimit ? undefined : mainThrottleGroup),
});
const conn = (0, connections_1.getConnection)(ctx);
if (!conn)
throw 'assert throttler connection';
const ts = conn[SymThrStr] = new ThrottledStream_1.ThrottledStream(ipGroup.group, conn[SymThrStr]);
const offset = ts.getBytesSent();
let closed = false;
const DELAY = 1000;
const update = lodash_1.default.debounce(() => {
const ts = conn[SymThrStr];
const outSpeed = roundSpeed(ts.getSpeed());
const { state } = ctx;
(0, connections_1.updateConnection)(conn, { outSpeed, sent: conn.socket.bytesWritten }, { opProgress: state.opTotal && ((state.opOffset || 0) + (ts.getBytesSent() - offset) / state.opTotal) });
/* in case this stream stands still for a while (before the end), we'll have neither 'sent' or 'close' events,
* so who will take care to updateConnection? This artificial next-call will ensure just that */
clearTimeout(conn[SymTimeout]);
if (outSpeed || !closed)
conn[SymTimeout] = setTimeout(update, DELAY);
}, DELAY, { leading: true, maxWait: DELAY });
ts.on('sent', (n) => {
exports.totalSent.set(x => x + n);
update();
});
++ipGroup.count;
ts.on('close', () => {
update.flush();
closed = true;
if (--ipGroup.count)
return; // any left?
delete ip2group[ctx.ip];
});
ctx.state.originalStream = body;
ctx.body = body.pipe(ts);
if (downloadTotal !== undefined) // undefined will break SSE
ctx.response.length = downloadTotal; // preserve this info
ts.once('close', () => // in case of compressed response, we offer calculation of real size
ctx.state.length = ts.getBytesSent() - offset);
};
exports.throttler = throttler;
function roundSpeed(n) {
return lodash_1.default.round(n, 1) || lodash_1.default.round(n, 3); // further precision if necessary
}
exports.totalSent = persistence_1.storedMap.singleSync('totalSent', 0);
exports.totalGot = persistence_1.storedMap.singleSync('totalGot', 0);
exports.totalOutSpeed = 0;
exports.totalInSpeed = 0;
let lastSent;
let lastGot;
let last = Date.now();
setInterval(() => {
const now = Date.now();
const past = now - last;
last = now;
{
const v = exports.totalSent.get();
exports.totalOutSpeed = roundSpeed((v - (lastSent !== null && lastSent !== void 0 ? lastSent : v)) / past);
lastSent = v;
}
{
const v = exports.totalGot.get();
exports.totalInSpeed = roundSpeed((v - (lastGot !== null && lastGot !== void 0 ? lastGot : v)) / past);
lastGot = v;
}
}, 1000);
events_1.default.on('connection', (c) => {
const count = (data) => exports.totalGot.set(x => x + data.length);
c.socket.on('data', count);
c.socket.on('secure', s => s.on('data', count)); // secure sockets won't forward 'data' events to the plain ones
});
;