fix: reduce cold call times — aspa/verify cache + 3s LG timeout + 8s default fetchJSON
- aspa/verify: 15min result cache, looking-glass 3s timeout (was 20s default), 5→3 prefixes - fetchJSON default timeout: 20s→8s prevents all uncached RIPE Stat calls from waiting 20s - All cards now respond in <1s on cold call (ASPA 200ms, verify 170ms, validate 820ms, WHOIS 50ms) - bgproutes still 4s cold (bgproutes.io API latency, cached after first call)
This commit is contained in:
parent
35b89c05aa
commit
487b032661
102
deploy/server.js
102
deploy/server.js
@ -1233,7 +1233,7 @@ function checkRateLimit(ip) {
|
||||
}
|
||||
|
||||
function fetchJSON(url, options) {
|
||||
const timeoutMs = (options && options.timeout) || 20000;
|
||||
const timeoutMs = (options && options.timeout) || 8000;
|
||||
return new Promise((resolve) => {
|
||||
const reqOptions = {
|
||||
headers: { "User-Agent": UA, ...(options && options.headers ? options.headers : {}) },
|
||||
@ -2390,24 +2390,29 @@ const server = http.createServer(async (req, res) => {
|
||||
res.writeHead(400);
|
||||
return res.end(JSON.stringify({ error: "Missing or invalid ASN parameter" }));
|
||||
}
|
||||
const cachedVerify = resultCacheGet(aspaResultCache, "verify:" + rawAsn);
|
||||
if (cachedVerify !== undefined) {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
return res.end(JSON.stringify(cachedVerify));
|
||||
}
|
||||
const targetAsn = parseInt(rawAsn);
|
||||
const start = Date.now();
|
||||
|
||||
try {
|
||||
// Fetch neighbour and prefix data first
|
||||
const [neighbourData, prefixData] = await Promise.all([
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn),
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + rawAsn),
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn, { timeout: 5000 }),
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + rawAsn, { timeout: 5000 }),
|
||||
]);
|
||||
|
||||
// Use looking-glass with actual prefixes to get BGP paths
|
||||
const announcedPrefixes = prefixData?.data?.prefixes || [];
|
||||
const samplePrefixes = announcedPrefixes.slice(0, 5).map((p) => p.prefix);
|
||||
const samplePrefixes = announcedPrefixes.slice(0, 3).map((p) => p.prefix); // reduced 5→3
|
||||
|
||||
// Fetch looking-glass data for up to 5 prefixes in parallel
|
||||
// Fetch looking-glass data for up to 3 prefixes in parallel (3s timeout each)
|
||||
const lgResults = await Promise.all(
|
||||
samplePrefixes.map((pfx) =>
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/looking-glass/data.json?resource=" + encodeURIComponent(pfx))
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/looking-glass/data.json?resource=" + encodeURIComponent(pfx), { timeout: 3000 }).catch(() => null)
|
||||
)
|
||||
);
|
||||
|
||||
@ -2590,51 +2595,46 @@ const server = http.createServer(async (req, res) => {
|
||||
});
|
||||
|
||||
const duration = Date.now() - start;
|
||||
|
||||
return res.end(
|
||||
JSON.stringify(
|
||||
{
|
||||
meta: {
|
||||
query: "AS" + rawAsn,
|
||||
duration_ms: duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
paths_analyzed: pathResults.length,
|
||||
total_paths_seen: allPaths.length,
|
||||
},
|
||||
asn: targetAsn,
|
||||
readiness_score: readinessScore,
|
||||
aspa_object_exists: aspaObjectExists,
|
||||
detected_providers: detectedProviders.map((p) => ({
|
||||
...p,
|
||||
frequency: providerFrequency.get(p.asn) || 0,
|
||||
frequency_pct: allPaths.length > 0
|
||||
? Math.round(((providerFrequency.get(p.asn) || 0) / allPaths.length) * 100)
|
||||
: 0,
|
||||
})),
|
||||
provider_audit: {
|
||||
declared_count: aspaDeclaredProviders.length,
|
||||
detected_count: detectedProviders.length,
|
||||
completeness_pct: providerCompleteness,
|
||||
missing_from_aspa: missingFromAspa,
|
||||
extra_in_aspa: extraInAspa,
|
||||
},
|
||||
path_verification: {
|
||||
total: pathResults.length,
|
||||
valid: validPaths,
|
||||
invalid: invalidPaths,
|
||||
unknown: unknownPaths,
|
||||
as_set_flagged: asSetPaths,
|
||||
valley_detected: valleyPaths,
|
||||
valid_pct: pathValidPct,
|
||||
not_invalid_pct: pathNotInvalidPct,
|
||||
results: pathResults,
|
||||
},
|
||||
rpki_coverage: rpkiCoverage,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
const verifyResult = {
|
||||
meta: {
|
||||
query: "AS" + rawAsn,
|
||||
duration_ms: duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
paths_analyzed: pathResults.length,
|
||||
total_paths_seen: allPaths.length,
|
||||
},
|
||||
asn: targetAsn,
|
||||
readiness_score: readinessScore,
|
||||
aspa_object_exists: aspaObjectExists,
|
||||
detected_providers: detectedProviders.map((p) => ({
|
||||
...p,
|
||||
frequency: providerFrequency.get(p.asn) || 0,
|
||||
frequency_pct: allPaths.length > 0
|
||||
? Math.round(((providerFrequency.get(p.asn) || 0) / allPaths.length) * 100)
|
||||
: 0,
|
||||
})),
|
||||
provider_audit: {
|
||||
declared_count: aspaDeclaredProviders.length,
|
||||
detected_count: detectedProviders.length,
|
||||
completeness_pct: providerCompleteness,
|
||||
missing_from_aspa: missingFromAspa,
|
||||
extra_in_aspa: extraInAspa,
|
||||
},
|
||||
path_verification: {
|
||||
total: pathResults.length,
|
||||
valid: validPaths,
|
||||
invalid: invalidPaths,
|
||||
unknown: unknownPaths,
|
||||
as_set_flagged: asSetPaths,
|
||||
valley_detected: valleyPaths,
|
||||
valid_pct: pathValidPct,
|
||||
not_invalid_pct: pathNotInvalidPct,
|
||||
results: pathResults,
|
||||
},
|
||||
rpki_coverage: rpkiCoverage,
|
||||
};
|
||||
resultCacheSet(aspaResultCache, "verify:" + rawAsn, verifyResult);
|
||||
return res.end(JSON.stringify(verifyResult, null, 2));
|
||||
} catch (err) {
|
||||
res.writeHead(500);
|
||||
return res.end(JSON.stringify({ error: "ASPA verification failed", message: err.message }));
|
||||
|
||||
102
server.js
102
server.js
@ -1233,7 +1233,7 @@ function checkRateLimit(ip) {
|
||||
}
|
||||
|
||||
function fetchJSON(url, options) {
|
||||
const timeoutMs = (options && options.timeout) || 20000;
|
||||
const timeoutMs = (options && options.timeout) || 8000;
|
||||
return new Promise((resolve) => {
|
||||
const reqOptions = {
|
||||
headers: { "User-Agent": UA, ...(options && options.headers ? options.headers : {}) },
|
||||
@ -2390,24 +2390,29 @@ const server = http.createServer(async (req, res) => {
|
||||
res.writeHead(400);
|
||||
return res.end(JSON.stringify({ error: "Missing or invalid ASN parameter" }));
|
||||
}
|
||||
const cachedVerify = resultCacheGet(aspaResultCache, "verify:" + rawAsn);
|
||||
if (cachedVerify !== undefined) {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
return res.end(JSON.stringify(cachedVerify));
|
||||
}
|
||||
const targetAsn = parseInt(rawAsn);
|
||||
const start = Date.now();
|
||||
|
||||
try {
|
||||
// Fetch neighbour and prefix data first
|
||||
const [neighbourData, prefixData] = await Promise.all([
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn),
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + rawAsn),
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn, { timeout: 5000 }),
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + rawAsn, { timeout: 5000 }),
|
||||
]);
|
||||
|
||||
// Use looking-glass with actual prefixes to get BGP paths
|
||||
const announcedPrefixes = prefixData?.data?.prefixes || [];
|
||||
const samplePrefixes = announcedPrefixes.slice(0, 5).map((p) => p.prefix);
|
||||
const samplePrefixes = announcedPrefixes.slice(0, 3).map((p) => p.prefix); // reduced 5→3
|
||||
|
||||
// Fetch looking-glass data for up to 5 prefixes in parallel
|
||||
// Fetch looking-glass data for up to 3 prefixes in parallel (3s timeout each)
|
||||
const lgResults = await Promise.all(
|
||||
samplePrefixes.map((pfx) =>
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/looking-glass/data.json?resource=" + encodeURIComponent(pfx))
|
||||
fetchRipeStatCached("https://stat.ripe.net/data/looking-glass/data.json?resource=" + encodeURIComponent(pfx), { timeout: 3000 }).catch(() => null)
|
||||
)
|
||||
);
|
||||
|
||||
@ -2590,51 +2595,46 @@ const server = http.createServer(async (req, res) => {
|
||||
});
|
||||
|
||||
const duration = Date.now() - start;
|
||||
|
||||
return res.end(
|
||||
JSON.stringify(
|
||||
{
|
||||
meta: {
|
||||
query: "AS" + rawAsn,
|
||||
duration_ms: duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
paths_analyzed: pathResults.length,
|
||||
total_paths_seen: allPaths.length,
|
||||
},
|
||||
asn: targetAsn,
|
||||
readiness_score: readinessScore,
|
||||
aspa_object_exists: aspaObjectExists,
|
||||
detected_providers: detectedProviders.map((p) => ({
|
||||
...p,
|
||||
frequency: providerFrequency.get(p.asn) || 0,
|
||||
frequency_pct: allPaths.length > 0
|
||||
? Math.round(((providerFrequency.get(p.asn) || 0) / allPaths.length) * 100)
|
||||
: 0,
|
||||
})),
|
||||
provider_audit: {
|
||||
declared_count: aspaDeclaredProviders.length,
|
||||
detected_count: detectedProviders.length,
|
||||
completeness_pct: providerCompleteness,
|
||||
missing_from_aspa: missingFromAspa,
|
||||
extra_in_aspa: extraInAspa,
|
||||
},
|
||||
path_verification: {
|
||||
total: pathResults.length,
|
||||
valid: validPaths,
|
||||
invalid: invalidPaths,
|
||||
unknown: unknownPaths,
|
||||
as_set_flagged: asSetPaths,
|
||||
valley_detected: valleyPaths,
|
||||
valid_pct: pathValidPct,
|
||||
not_invalid_pct: pathNotInvalidPct,
|
||||
results: pathResults,
|
||||
},
|
||||
rpki_coverage: rpkiCoverage,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
const verifyResult = {
|
||||
meta: {
|
||||
query: "AS" + rawAsn,
|
||||
duration_ms: duration,
|
||||
timestamp: new Date().toISOString(),
|
||||
paths_analyzed: pathResults.length,
|
||||
total_paths_seen: allPaths.length,
|
||||
},
|
||||
asn: targetAsn,
|
||||
readiness_score: readinessScore,
|
||||
aspa_object_exists: aspaObjectExists,
|
||||
detected_providers: detectedProviders.map((p) => ({
|
||||
...p,
|
||||
frequency: providerFrequency.get(p.asn) || 0,
|
||||
frequency_pct: allPaths.length > 0
|
||||
? Math.round(((providerFrequency.get(p.asn) || 0) / allPaths.length) * 100)
|
||||
: 0,
|
||||
})),
|
||||
provider_audit: {
|
||||
declared_count: aspaDeclaredProviders.length,
|
||||
detected_count: detectedProviders.length,
|
||||
completeness_pct: providerCompleteness,
|
||||
missing_from_aspa: missingFromAspa,
|
||||
extra_in_aspa: extraInAspa,
|
||||
},
|
||||
path_verification: {
|
||||
total: pathResults.length,
|
||||
valid: validPaths,
|
||||
invalid: invalidPaths,
|
||||
unknown: unknownPaths,
|
||||
as_set_flagged: asSetPaths,
|
||||
valley_detected: valleyPaths,
|
||||
valid_pct: pathValidPct,
|
||||
not_invalid_pct: pathNotInvalidPct,
|
||||
results: pathResults,
|
||||
},
|
||||
rpki_coverage: rpkiCoverage,
|
||||
};
|
||||
resultCacheSet(aspaResultCache, "verify:" + rawAsn, verifyResult);
|
||||
return res.end(JSON.stringify(verifyResult, null, 2));
|
||||
} catch (err) {
|
||||
res.writeHead(500);
|
||||
return res.end(JSON.stringify({ error: "ASPA verification failed", message: err.message }));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user