openvibers/openvibe.media Commits
/limits.json lists the per-actor rate limits as rate_limits (roadmap WS-R task 4): every named limit the routes declared (object upload, parts, complete, delete, jobs, files, me), with its numbers per caller per minute and hour and 429 rate_limited past it, so Codes' limits page shows what is enforced. test/limits.test.js checks the upload and part numbers. npm test passes.
Changes
@@@ -36,13 +36,22 @@ function onLimited(e) {if (refused) refused.inc({ limit: e.name, window: e.window });}--const limits = createActorLimiter({++const limiter = createActorLimiter({limits: { minute: config.limits.minute, hour: config.limits.hour },actor,now: () => clock(),onLimited,});++/** Every named limit with its numbers, as the routes declare them (published in /limits.json). */++const registered = new Map();++function limits(name, own = {}) {++ registered.set(name, { minute: own.minute != null ? own.minute : config.limits.minute, hour: own.hour != null ? own.hour : config.limits.hour });++ return limiter(name, own);++}++limits.stats = () => limiter.stats();++limits.reset = () => limiter.reset();++/** Count refusals in /metrics (server/index.js, after observability.instrument). */function bindMetrics(registry) {if (refused || !registry) return;@@@ -51,6 +60,8 @@ function bindMetrics(registry) {module.exports = {limits,++ /** [{ id, minute, hour }] for every limit the routes have declared, sorted by id. */++ registered: () => [...registered].map(([id, n]) => ({ id, ...n })).sort((x, y) => x.id.localeCompare(y.id)),actor,bindMetrics,_setClockForTests(fn) { clock = fn || (() => Date.now()); },@@@ -39,9 +39,10 @@ function limitsOf(config) {}function mountLimits(app, config) {-- const body = limitsOf(config);app.get('/limits.json', (_req, res) => {-- res.set('Cache-Control', 'public, max-age=300').set('Access-Control-Allow-Origin', '*').json(body);++ // Per-actor rate limits (server/actor-limits.js), as the routes declared them: per caller, per minute and hour.++ const rate_limits = require('./actor-limits').registered().map((l) => ({ ...l, per: 'caller (principal, person, else address)', exceeded: '429 rate_limited' }));++ res.set('Cache-Control', 'public, max-age=300').set('Access-Control-Allow-Origin', '*').json({ ...limitsOf(config), rate_limits });});}@@@ -1,6 +1,7 @@'use strict';// GET /limits.json (WS-N task 7): the developer limits Media enforces, read from the running config--// (an env override is what it answers), public with CORS *; Codes' /docs/limits renders it.++// (an env override is what it answers), public with CORS *; Codes' /docs/limits renders it. rate_limits lists the++// per-actor rate limits the routes declare (WS-R task 4).process.env.MEDIA_APP_SANDBOX_QUOTA_MB = '50';const assert = require('assert');const http = require('http');@@@ -23,7 +24,15 @@ const { limitsOf, mountLimits } = require('../server/limits');const res = await fetch(`http://127.0.0.1:${server.address().port}/limits.json`);assert.strictEqual(res.status, 200);assert.strictEqual(res.headers.get('access-control-allow-origin'), '*');-- assert.strictEqual((await res.json()).service, 'media');++ const body = await res.json();++ assert.strictEqual(body.service, 'media');++ // The per-actor rate limits, as the routes declare them (loading a router declares its limits).++ require('../server/objects/routes');++ const again = await (await fetch(`http://127.0.0.1:${server.address().port}/limits.json`)).json();++ const rate = Object.fromEntries(again.rate_limits.map((l) => [l.id, l]));++ assert.deepStrictEqual([rate['media.object.upload'].minute, rate['media.object.upload'].hour], [30, 600]);++ assert.deepStrictEqual([rate['media.object.part'].minute, rate['media.object.part'].hour], [600, 20000]);++ assert.strictEqual(rate['media.object.upload'].exceeded, '429 rate_limited');server.close();console.log('limits: all checks passed');})().catch((e) => { console.error(e); process.exit(1); });
@@ -36,13 +36,22 @@ function onLimited(e) {
@@ -36,13 +36,22 @@ function onLimited(e) {
if (refused) refused.inc({ limit: e.name, window: e.window });
if (refused) refused.inc({ limit: e.name, window: e.window });
}
}
-const limits = createActorLimiter({
+const limiter = createActorLimiter({
limits: { minute: config.limits.minute, hour: config.limits.hour },
limits: { minute: config.limits.minute, hour: config.limits.hour },
actor,
actor,
now: () => clock(),
now: () => clock(),
onLimited,
onLimited,
});
});
+/** Every named limit with its numbers, as the routes declare them (published in /limits.json). */
+const registered = new Map();
+function limits(name, own = {}) {
+ registered.set(name, { minute: own.minute != null ? own.minute : config.limits.minute, hour: own.hour != null ? own.hour : config.limits.hour });
+ return limiter(name, own);
+}
+limits.stats = () => limiter.stats();
+limits.reset = () => limiter.reset();
+
/** Count refusals in /metrics (server/index.js, after observability.instrument). */
/** Count refusals in /metrics (server/index.js, after observability.instrument). */
function bindMetrics(registry) {
function bindMetrics(registry) {
if (refused || !registry) return;
if (refused || !registry) return;
@@ -51,6 +60,8 @@ function bindMetrics(registry) {
@@ -51,6 +60,8 @@ function bindMetrics(registry) {
module.exports = {
module.exports = {
limits,
limits,
+ /** [{ id, minute, hour }] for every limit the routes have declared, sorted by id. */
+ registered: () => [...registered].map(([id, n]) => ({ id, ...n })).sort((x, y) => x.id.localeCompare(y.id)),
actor,
actor,
bindMetrics,
bindMetrics,
_setClockForTests(fn) { clock = fn || (() => Date.now()); },
_setClockForTests(fn) { clock = fn || (() => Date.now()); },
@@ -39,9 +39,10 @@ function limitsOf(config) {
@@ -39,9 +39,10 @@ function limitsOf(config) {
}
}
function mountLimits(app, config) {
function mountLimits(app, config) {
- const body = limitsOf(config);
app.get('/limits.json', (_req, res) => {
app.get('/limits.json', (_req, res) => {
- res.set('Cache-Control', 'public, max-age=300').set('Access-Control-Allow-Origin', '*').json(body);
+ // Per-actor rate limits (server/actor-limits.js), as the routes declared them: per caller, per minute and hour.
+ const rate_limits = require('./actor-limits').registered().map((l) => ({ ...l, per: 'caller (principal, person, else address)', exceeded: '429 rate_limited' }));
+ res.set('Cache-Control', 'public, max-age=300').set('Access-Control-Allow-Origin', '*').json({ ...limitsOf(config), rate_limits });
});
});
}
}
@@ -1,6 +1,7 @@
@@ -1,6 +1,7 @@
'use strict';
'use strict';
// GET /limits.json (WS-N task 7): the developer limits Media enforces, read from the running config
// GET /limits.json (WS-N task 7): the developer limits Media enforces, read from the running config
-// (an env override is what it answers), public with CORS *; Codes' /docs/limits renders it.
+// (an env override is what it answers), public with CORS *; Codes' /docs/limits renders it. rate_limits lists the
+// per-actor rate limits the routes declare (WS-R task 4).
process.env.MEDIA_APP_SANDBOX_QUOTA_MB = '50';
process.env.MEDIA_APP_SANDBOX_QUOTA_MB = '50';
const assert = require('assert');
const assert = require('assert');
const http = require('http');
const http = require('http');
@@ -23,7 +24,15 @@ const { limitsOf, mountLimits } = require('../server/limits');
@@ -23,7 +24,15 @@ const { limitsOf, mountLimits } = require('../server/limits');
const res = await fetch(`http://127.0.0.1:${server.address().port}/limits.json`);
const res = await fetch(`http://127.0.0.1:${server.address().port}/limits.json`);
assert.strictEqual(res.status, 200);
assert.strictEqual(res.status, 200);
assert.strictEqual(res.headers.get('access-control-allow-origin'), '*');
assert.strictEqual(res.headers.get('access-control-allow-origin'), '*');
- assert.strictEqual((await res.json()).service, 'media');
+ const body = await res.json();
+ assert.strictEqual(body.service, 'media');
+ // The per-actor rate limits, as the routes declare them (loading a router declares its limits).
+ require('../server/objects/routes');
+ const again = await (await fetch(`http://127.0.0.1:${server.address().port}/limits.json`)).json();
+ const rate = Object.fromEntries(again.rate_limits.map((l) => [l.id, l]));
+ assert.deepStrictEqual([rate['media.object.upload'].minute, rate['media.object.upload'].hour], [30, 600]);
+ assert.deepStrictEqual([rate['media.object.part'].minute, rate['media.object.part'].hour], [600, 20000]);
+ assert.strictEqual(rate['media.object.upload'].exceeded, '429 rate_limited');
server.close();
server.close();
console.log('limits: all checks passed');
console.log('limits: all checks passed');
})().catch((e) => { console.error(e); process.exit(1); });
})().catch((e) => { console.error(e); process.exit(1); });