mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Both disk caches fed by OVERPASS_UPSTREAMS key only on the query — the Overpass proxy on the sanitized request body, the installations proxy on the bbox — so an entry written against one instance is a hit for the same query against another. After an operator repoints OVERPASS_UPSTREAMS, the previous upstream's answers keep being served for 7 days, 30 for boundary-class queries, and indefinitely through the serve-stale path. An install that predates #742 has a cache written from the public instances that were the default then, and configuring your own instance does not displace it. Cached answers now carry a tag identifying the configured upstream set, and a disk read refuses an entry that does not match it, on the stale path too. With nothing configured any entry is still accepted: there is no instance to contradict, and serving last-good data at any age is what the not-configured path exists for. The tag is a hash of the resolved endpoint list, never the URLs; it is written to disk and compared on read, and is not sent to clients or surfaced in errors. One line is logged once per process when a foreign entry is ignored, naming the variable and what will happen, as local-receivers.js already does for a rejected feed. Cost worth naming: on upgrade every existing entry is untagged, so the first outage after upgrading serves nothing where it previously served stale data, until each query is fetched once from the configured instance.
256 lines
9.4 KiB
JavaScript
256 lines
9.4 KiB
JavaScript
import { makeRateLimiter, clientKey } from './common/rate-limit.js';
|
|
import {
|
|
MILITARY_INSTALLATION_ELEMENT_CAP,
|
|
MILITARY_INSTALLATION_MAX_RESPONSE_BYTES,
|
|
MILITARY_INSTALLATION_DISK_TTL_MS,
|
|
MILITARY_INSTALLATION_STALE_MS,
|
|
} from './military-installations/constants.js';
|
|
import {
|
|
fetchOverpassPayload,
|
|
overpassNotConfigured,
|
|
} from './overpass/transport.js';
|
|
import {
|
|
resolveOverpassUpstreams,
|
|
resolveOverpassUpstreamTag,
|
|
} from './overpass/constants.js';
|
|
import {
|
|
_militaryInstallationCache,
|
|
trimMilitaryInstallationCache,
|
|
writeMilitaryInstallationDisk,
|
|
resolveMilitaryInstallationTier,
|
|
readMilitaryInstallationDisk,
|
|
} from './military-installations/cache.js';
|
|
import {
|
|
validMilitaryInstallationBox,
|
|
quantizeMilitaryInstallationBox,
|
|
militaryInstallationCacheKey,
|
|
militaryInstallationFailureReason,
|
|
} from './military-installations/query.js';
|
|
import { coalesceProxyRequest } from './common/http.js';
|
|
|
|
const _militaryInstallationsRateLimiter = makeRateLimiter({
|
|
windowMs: 60_000,
|
|
max: 90,
|
|
globalMax: 300,
|
|
});
|
|
|
|
const _militaryInstallationInFlight = new Map();
|
|
|
|
function militaryInstallationsProxy() {
|
|
async function refresh(box, key) {
|
|
const bbox = `${box.south},${box.west},${box.north},${box.east}`;
|
|
const ql = `[out:json][timeout:20];(nwr["military"~"^(airfield|naval_base|range|barracks|base)$"](${bbox});nwr["landuse"="military"](${bbox}););out center tags geom ${MILITARY_INSTALLATION_ELEMENT_CAP};`;
|
|
const upstream = await fetchOverpassPayload(
|
|
`data=${encodeURIComponent(ql)}`,
|
|
MILITARY_INSTALLATION_MAX_RESPONSE_BYTES,
|
|
);
|
|
if (
|
|
upstream.status >= 400 ||
|
|
upstream.rateLimited ||
|
|
upstream.runtimeError
|
|
) {
|
|
throw Object.assign(
|
|
new Error('Mapped installation upstream unavailable'),
|
|
{
|
|
retryAfterMs: upstream.retryAfterMs,
|
|
installationReason: upstream.rateLimited
|
|
? 'rate_limited'
|
|
: upstream.status === 504
|
|
? 'timeout'
|
|
: upstream.runtimeError
|
|
? 'query_failed'
|
|
: 'unavailable',
|
|
},
|
|
);
|
|
}
|
|
const parsed = JSON.parse(upstream.body);
|
|
const elements = Array.isArray(parsed?.elements)
|
|
? parsed.elements.slice(0, MILITARY_INSTALLATION_ELEMENT_CAP)
|
|
: [];
|
|
const payload = {
|
|
elements,
|
|
// Honest truncation flag — the client re-asks for its exact viewport so
|
|
// off-view features can never starve in-view ones. The cap travels with
|
|
// the payload so the client never has to hard-code it.
|
|
saturated: elements.length >= MILITARY_INSTALLATION_ELEMENT_CAP,
|
|
elementCap: MILITARY_INSTALLATION_ELEMENT_CAP,
|
|
retrievedAt: new Date().toISOString(),
|
|
status: 'ready',
|
|
};
|
|
// Tagged with the configured upstream set so the disk layer does not serve
|
|
// this answer back after the operator repoints (see overpass/cache.js).
|
|
const entry = {
|
|
payload,
|
|
cachedAt: Date.now(),
|
|
upstreams: resolveOverpassUpstreamTag(),
|
|
};
|
|
_militaryInstallationCache.set(key, entry);
|
|
trimMilitaryInstallationCache();
|
|
writeMilitaryInstallationDisk(key, entry);
|
|
return payload;
|
|
}
|
|
|
|
function install(middlewares) {
|
|
middlewares.use('/api/military-installations', async (req, res) => {
|
|
if (req.method !== 'GET') {
|
|
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ error: 'Method Not Allowed' }));
|
|
return;
|
|
}
|
|
const url = new URL(req.url, 'http://localhost');
|
|
const requested = validMilitaryInstallationBox(url.searchParams);
|
|
if (!requested) {
|
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
res.end(
|
|
JSON.stringify({
|
|
error: 'A non-dateline bbox no larger than 10 degrees is required',
|
|
}),
|
|
);
|
|
return;
|
|
}
|
|
// Query the SNAPPED box, not the raw viewport: neighbouring views then
|
|
// share one cache entry, and an outward snap always covers what was asked.
|
|
// `exact=1` opts out — the client sends it after a SATURATED snapped
|
|
// response, so a truncated tile can never starve the actual viewport. It
|
|
// is keyed separately so exact and snapped answers never collide.
|
|
const exact = url.searchParams.get('exact') === '1';
|
|
const box = exact
|
|
? requested
|
|
: quantizeMilitaryInstallationBox(requested);
|
|
// Key at the precision the query actually uses (see militaryInstallationCacheKey).
|
|
const key = exact
|
|
? `exact:${militaryInstallationCacheKey(box, 5)}`
|
|
: militaryInstallationCacheKey(box);
|
|
const now = Date.now();
|
|
const cached = _militaryInstallationCache.get(key);
|
|
if (!resolveOverpassUpstreams().length) {
|
|
const stale =
|
|
cached || (await readMilitaryInstallationDisk(key, Infinity));
|
|
const disabled = overpassNotConfigured();
|
|
res.writeHead(200, {
|
|
'Content-Type': 'application/json',
|
|
'Cache-Control': 'no-store',
|
|
'X-Military-Installations': stale ? 'STALE' : 'DISABLED',
|
|
});
|
|
res.end(
|
|
stale
|
|
? JSON.stringify({
|
|
...stale.payload,
|
|
status: 'stale',
|
|
retrievedAt:
|
|
stale.payload.retrievedAt ||
|
|
new Date(stale.cachedAt).toISOString(),
|
|
})
|
|
: disabled.body,
|
|
);
|
|
return;
|
|
}
|
|
if (!_militaryInstallationsRateLimiter(clientKey(req))) {
|
|
res.writeHead(429, {
|
|
'Content-Type': 'application/json',
|
|
'Retry-After': '5',
|
|
});
|
|
res.end(JSON.stringify({ error: 'Rate limit exceeded' }));
|
|
return;
|
|
}
|
|
const preflight = await resolveMilitaryInstallationTier({
|
|
cacheKey: key,
|
|
memoryCache: _militaryInstallationCache,
|
|
inFlight: _militaryInstallationInFlight,
|
|
readDisk: () =>
|
|
readMilitaryInstallationDisk(key, MILITARY_INSTALLATION_DISK_TTL_MS),
|
|
now,
|
|
});
|
|
if (preflight.source !== 'UPSTREAM') {
|
|
if (preflight.source === 'DISK') {
|
|
_militaryInstallationCache.set(key, preflight.entry);
|
|
trimMilitaryInstallationCache();
|
|
}
|
|
res.writeHead(200, {
|
|
'Content-Type': 'application/json',
|
|
'Cache-Control': 'public, max-age=60',
|
|
'X-Military-Installations': preflight.source,
|
|
});
|
|
res.end(
|
|
JSON.stringify({ ...preflight.entry.payload, status: 'cached' }),
|
|
);
|
|
return;
|
|
}
|
|
const request = coalesceProxyRequest(
|
|
_militaryInstallationInFlight,
|
|
key,
|
|
() => refresh(box, key),
|
|
);
|
|
try {
|
|
const payload = await request.promise;
|
|
res.writeHead(200, {
|
|
'Content-Type': 'application/json',
|
|
'Cache-Control': 'public, max-age=60',
|
|
'X-Military-Installations': request.shared ? 'INFLIGHT' : 'MISS',
|
|
});
|
|
res.end(JSON.stringify(payload));
|
|
} catch (error) {
|
|
if (cached && now - cached.cachedAt <= MILITARY_INSTALLATION_STALE_MS) {
|
|
res.writeHead(200, {
|
|
'Content-Type': 'application/json',
|
|
'Cache-Control': 'no-store',
|
|
'X-Military-Installations': 'STALE',
|
|
});
|
|
res.end(JSON.stringify({ ...cached.payload, status: 'stale' }));
|
|
return;
|
|
}
|
|
// Overpass is down: last-good mapped context at ANY age beats an empty
|
|
// layer (the same serve-stale rule the Overpass proxy applies).
|
|
const stale = await readMilitaryInstallationDisk(key, Infinity);
|
|
if (stale) {
|
|
res.writeHead(200, {
|
|
'Content-Type': 'application/json',
|
|
'Cache-Control': 'no-store',
|
|
'X-Military-Installations': 'STALE-DISK',
|
|
});
|
|
res.end(JSON.stringify({ ...stale.payload, status: 'stale' }));
|
|
return;
|
|
}
|
|
res.writeHead(503, {
|
|
...(error?.retryAfterMs
|
|
? { 'Retry-After': String(Math.ceil(error.retryAfterMs / 1000)) }
|
|
: {}),
|
|
'Content-Type': 'application/json',
|
|
'Cache-Control': 'no-store',
|
|
});
|
|
res.end(
|
|
JSON.stringify({
|
|
error: 'Mapped installation context is temporarily unavailable',
|
|
reason: militaryInstallationFailureReason(error),
|
|
retryAfterMs: error?.retryAfterMs,
|
|
}),
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
return {
|
|
name: 'military-installations-proxy',
|
|
configureServer(server) {
|
|
install(server.middlewares);
|
|
},
|
|
configurePreviewServer(server) {
|
|
install(server.middlewares);
|
|
},
|
|
};
|
|
}
|
|
|
|
export { militaryInstallationsProxy };
|
|
|
|
export { MILITARY_INSTALLATION_ELEMENT_CAP } from './military-installations/constants.js';
|
|
export { quantizeMilitaryInstallationBox } from './military-installations/query.js';
|
|
export { militaryInstallationCacheKey } from './military-installations/query.js';
|
|
export { resolveMilitaryInstallationTier } from './military-installations/cache.js';
|
|
export { migrateMilitaryInstallationEntry } from './military-installations/cache.js';
|
|
export { militaryInstallationDiskFresh } from './military-installations/cache.js';
|
|
export { militaryInstallationDiskPath } from './military-installations/cache.js';
|
|
export { readMilitaryInstallationDisk } from './military-installations/cache.js';
|
|
export { writeMilitaryInstallationDisk } from './military-installations/cache.js';
|
|
export { validMilitaryInstallationBox } from './military-installations/query.js';
|
|
export { militaryInstallationFailureReason } from './military-installations/query.js';
|