Also drops checkRateLimit/rateLimitMap -- confirmed dead code, never called by the request handler. Verified via smoke-test harness (28/28 match).
132 lines
4.6 KiB
JavaScript
132 lines
4.6 KiB
JavaScript
const https = require("https");
|
|
const { UA } = require("../data/constants");
|
|
|
|
const BGPROUTES_API_KEY = process.env.BGPROUTES_API_KEY || "";
|
|
const BGPROUTES_API_URL = process.env.BGPROUTES_API_URL || "https://api.bgproutes.io/v1";
|
|
|
|
function fetchJSON(url, options) {
|
|
const timeoutMs = (options && options.timeout) || 8000;
|
|
return new Promise((resolve) => {
|
|
const reqOptions = {
|
|
headers: { "User-Agent": UA, ...(options && options.headers ? options.headers : {}) },
|
|
timeout: timeoutMs,
|
|
};
|
|
const timer = setTimeout(() => resolve(null), timeoutMs + 500);
|
|
https
|
|
.get(url, reqOptions, (res) => {
|
|
let data = "";
|
|
res.on("data", (chunk) => (data += chunk));
|
|
res.on("end", () => {
|
|
clearTimeout(timer);
|
|
if (res.statusCode === 429) {
|
|
console.warn("[PDB] Rate limited (429):", url.substring(0, 80));
|
|
return resolve(null);
|
|
}
|
|
try {
|
|
resolve(JSON.parse(data));
|
|
} catch (_e) {
|
|
resolve(null);
|
|
}
|
|
});
|
|
})
|
|
.on("timeout", () => { clearTimeout(timer); resolve(null); })
|
|
.on("error", () => { clearTimeout(timer); resolve(null); });
|
|
});
|
|
}
|
|
|
|
// Generic JSON fetch with one retry — for sources that occasionally fail under load (RIPE Stat, Atlas)
|
|
async function fetchJSONWithRetry(url, options) {
|
|
const result = await fetchJSON(url, options);
|
|
if (result !== null) return result;
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
return fetchJSON(url, options);
|
|
}
|
|
|
|
function fetchHTML(url, options) {
|
|
return new Promise((resolve) => {
|
|
const reqOptions = {
|
|
headers: {
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
...(options && options.headers ? options.headers : {}),
|
|
},
|
|
};
|
|
const lib = url.startsWith("https") ? require("https") : require("http");
|
|
lib
|
|
.get(url, reqOptions, (res) => {
|
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
return fetchHTML(res.headers.location, options).then(resolve);
|
|
}
|
|
let data = "";
|
|
res.on("data", (chunk) => (data += chunk));
|
|
res.on("end", () => resolve(data));
|
|
})
|
|
.on("error", () => resolve(null));
|
|
});
|
|
}
|
|
|
|
function postJSON(url, body, options) {
|
|
return new Promise((resolve) => {
|
|
const data = JSON.stringify(body);
|
|
const parsed = new URL(url);
|
|
const timeout = (options && options.timeout) || 10000;
|
|
const reqOptions = {
|
|
hostname: parsed.hostname,
|
|
port: parsed.port || 443,
|
|
path: parsed.pathname + parsed.search,
|
|
method: "POST",
|
|
headers: {
|
|
"User-Agent": UA,
|
|
"Content-Type": "application/json",
|
|
"Content-Length": Buffer.byteLength(data),
|
|
...(options && options.headers ? options.headers : {}),
|
|
},
|
|
};
|
|
let done = false;
|
|
const timer = setTimeout(() => { if (!done) { done = true; req.destroy(); resolve(null); } }, timeout);
|
|
const req = https.request(reqOptions, (res) => {
|
|
let chunks = "";
|
|
res.on("data", (chunk) => (chunks += chunk));
|
|
res.on("end", () => {
|
|
if (done) return;
|
|
done = true;
|
|
clearTimeout(timer);
|
|
try {
|
|
resolve(JSON.parse(chunks));
|
|
} catch (_e) {
|
|
resolve(null);
|
|
}
|
|
});
|
|
});
|
|
req.on("error", () => { if (!done) { done = true; clearTimeout(timer); resolve(null); } });
|
|
req.write(data);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
// bgproutes.io visibility fallback helper
|
|
// Queries the RIB endpoint to estimate prefix visibility across vantage points
|
|
function fetchBgproutesVisibility(prefix) {
|
|
if (!BGPROUTES_API_KEY) return Promise.resolve(null);
|
|
const url = BGPROUTES_API_URL + "/rib?prefix=" + encodeURIComponent(prefix) + "&prefix_match=exact";
|
|
return fetchJSON(url, {
|
|
timeout: 15000,
|
|
headers: {
|
|
"Authorization": "Bearer " + BGPROUTES_API_KEY,
|
|
"User-Agent": UA,
|
|
},
|
|
}).then(function(data) {
|
|
if (!data || !data.data) return null;
|
|
// data.data should be an array of RIB entries from different vantage points
|
|
var entries = Array.isArray(data.data) ? data.data : (data.data.entries || data.data.routes || []);
|
|
var vpSet = new Set();
|
|
entries.forEach(function(e) {
|
|
if (e.vantage_point || e.vp || e.collector || e.peer_asn) {
|
|
vpSet.add(e.vantage_point || e.vp || e.collector || e.peer_asn);
|
|
}
|
|
});
|
|
return { vps_seeing: vpSet.size, total_entries: entries.length, source: "bgproutes.io" };
|
|
}).catch(function() { return null; });
|
|
}
|
|
|
|
module.exports = { fetchJSON, fetchJSONWithRetry, fetchHTML, postJSON, fetchBgproutesVisibility };
|