Also drops confirmed-dead code found during extraction: unused TIER1_ASNS require (its only consumer, computeRouteLeakDetection, already moved to server/route-leak.js with its own require) and bgproutesVpCache/ bgproutesVpCacheTs/BGPROUTES_VP_TTL (declared, never read anywhere). Verified via smoke-test harness (28/28 match). server.js: 6302 -> 5907 lines.
113 lines
3.9 KiB
JavaScript
113 lines
3.9 KiB
JavaScript
// Task 6: In-memory cache with TTL + Rate Limiting
|
|
const responseCache = new Map();
|
|
|
|
function cacheGet(key) {
|
|
const entry = responseCache.get(key);
|
|
if (!entry) return null;
|
|
if (Date.now() > entry.expires) {
|
|
responseCache.delete(key);
|
|
return null;
|
|
}
|
|
return entry.data;
|
|
}
|
|
|
|
function cacheSet(key, data, ttlMs) {
|
|
responseCache.set(key, { data, expires: Date.now() + ttlMs });
|
|
// Evict old entries periodically (keep cache under 500 entries)
|
|
if (responseCache.size > 500) {
|
|
const now = Date.now();
|
|
for (const [k, v] of responseCache) {
|
|
if (now > v.expires) responseCache.delete(k);
|
|
}
|
|
}
|
|
}
|
|
|
|
const CACHE_TTL_LOOKUP = 5 * 60 * 1000; // 5 minutes
|
|
const CACHE_TTL_LOOKUP_DEGRADED = 90 * 1000; // matches the existing 90s guard for suspicious /api/validate zero-counts
|
|
const CACHE_TTL_DEFAULT = 5 * 60 * 1000; // 5 minutes
|
|
|
|
// RDAP Cache — prevents 429 flooding on LACNIC/AFRINIC/APNIC/ARIN
|
|
const rdapCache = new Map(); // key: asn string, value: { data, ts }
|
|
const RDAP_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
|
|
|
function rdapCacheGet(asn) {
|
|
const e = rdapCache.get(String(asn));
|
|
if (e && (Date.now() - e.ts) < RDAP_CACHE_TTL) return e.data;
|
|
return undefined; // undefined = not cached, null = cached miss
|
|
}
|
|
function rdapCacheSet(asn, data) {
|
|
if (rdapCache.size > 5000) {
|
|
rdapCache.delete(rdapCache.keys().next().value);
|
|
}
|
|
rdapCache.set(String(asn), { data, ts: Date.now() });
|
|
}
|
|
|
|
// WHOIS Cache — 24h TTL, prevents repeated RDAP hammering
|
|
const whoisCache = new Map(); // key: asn string, value: { data, ts, ttl }
|
|
const WHOIS_CACHE_TTL = 24 * 60 * 60 * 1000;
|
|
// A "not found" result after all 5 RIR sources failed is far more often a shared
|
|
// network blip (DNS, outbound connectivity, simultaneous rate-limit) than a real
|
|
// ASN existing nowhere across RIPE+APNIC+ARIN+LACNIC+AFRINIC -- fetchJSON can't
|
|
// tell timeout/network-error apart from a genuine 404, so cache that specific
|
|
// conclusion much more briefly than a confirmed, parsed WHOIS record.
|
|
const WHOIS_NOT_FOUND_CACHE_TTL = 10 * 60 * 1000;
|
|
function whoisCacheGet(asn) {
|
|
const e = whoisCache.get(String(asn));
|
|
if (e && (Date.now() - e.ts) < (e.ttl || WHOIS_CACHE_TTL)) return e.data;
|
|
return undefined;
|
|
}
|
|
function whoisCacheSet(asn, data, ttl) {
|
|
if (whoisCache.size > 5000) whoisCache.delete(whoisCache.keys().next().value);
|
|
whoisCache.set(String(asn), { data, ts: Date.now(), ttl: ttl || WHOIS_CACHE_TTL });
|
|
}
|
|
|
|
// Quick-IX Cache — 1h TTL, for Peering Recommendations
|
|
const quickIxCache = new Map(); // key: asn string, value: { data, ts }
|
|
const QUICK_IX_CACHE_TTL = 60 * 60 * 1000;
|
|
function quickIxCacheGet(asn) {
|
|
const e = quickIxCache.get(String(asn));
|
|
if (e && (Date.now() - e.ts) < QUICK_IX_CACHE_TTL) return e.data;
|
|
return undefined;
|
|
}
|
|
function quickIxCacheSet(asn, data) {
|
|
if (quickIxCache.size > 2000) quickIxCache.delete(quickIxCache.keys().next().value);
|
|
quickIxCache.set(String(asn), { data, ts: Date.now() });
|
|
}
|
|
|
|
// bgproutes + ASPA result caches — 15min TTL, prevent re-hitting slow APIs
|
|
const bgproutesResultCache = new Map();
|
|
const aspaResultCache = new Map();
|
|
const validateResultCache = new Map();
|
|
const RESULT_CACHE_TTL = 15 * 60 * 1000; // 15 minutes
|
|
function resultCacheGet(map, key) {
|
|
const e = map.get(String(key));
|
|
const ttl = e && e.ttl ? e.ttl : RESULT_CACHE_TTL;
|
|
if (e && (Date.now() - e.ts) < ttl) return e.data;
|
|
return undefined;
|
|
}
|
|
function resultCacheSet(map, key, data, ttlMs) {
|
|
if (map.size > 2000) map.delete(map.keys().next().value);
|
|
map.set(String(key), { data, ts: Date.now(), ttl: ttlMs || RESULT_CACHE_TTL });
|
|
}
|
|
|
|
module.exports = {
|
|
responseCache,
|
|
cacheGet,
|
|
cacheSet,
|
|
CACHE_TTL_LOOKUP,
|
|
CACHE_TTL_LOOKUP_DEGRADED,
|
|
CACHE_TTL_DEFAULT,
|
|
rdapCacheGet,
|
|
rdapCacheSet,
|
|
whoisCacheGet,
|
|
whoisCacheSet,
|
|
WHOIS_NOT_FOUND_CACHE_TTL,
|
|
quickIxCacheGet,
|
|
quickIxCacheSet,
|
|
bgproutesResultCache,
|
|
aspaResultCache,
|
|
validateResultCache,
|
|
resultCacheGet,
|
|
resultCacheSet,
|
|
};
|