gateway: fix ip-record fetches (Host header) so coinspectrum.bch resolves
Symptom: pasting https://navigate.st/search?q=coinspectrum.bch (or the new default-search-engine redirect from any browser) returned 404 from nginx. Same for any BCNR name whose on-chain record uses ip (h / s3 / p were fine). Cause: the gateway's ip branch used fetch() with { headers: { host: name } }. WHATWG fetch defines Host as a forbidden header, so undici silently drops it and sends Host: <ip>. nginx has no server_name for a bare IP, falls to the default server, 404s. The whole point of the ip record is to reach a server_name that matches the BCNR name, so Host must be set. Fix: use http.request from node:http (no forbidden-header list) with an explicit Host header. Same behavior for every ip record; nothing changes for h / s3 / p / u branches. Live-patched on the VPS gateway and restarted the bns-gateway systemd service. coinspectrum.bch now returns 200 (116 KB) via navigate.st/bns/coinspectrum.bch/ and via the /search?q= redirect chain. Argus/src/gateway/public-gateway.mjs mirrors the on-disk change on the VPS at /opt/bns-gateway/public-gateway.mjs so future redeploys keep it.
This commit is contained in:
parent
b4a550ef41
commit
c2578d4017
1 changed files with 22 additions and 5 deletions
|
|
@ -77,6 +77,19 @@ const nx = (h) => `<div style="font:16px system-ui;padding:2rem;color:#333"><h2>
|
|||
// apex, current priority stands (h → s3 → ip → u). This does not break
|
||||
// Sia-per-subdomain owners: if they omit `ip` from the parent, the fallback
|
||||
// still tries `s3` first as before.
|
||||
function ipFetch(ip, reqPath, hostHeader) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request({ host: ip, port: 80, path: reqPath, method: "GET", headers: { Host: hostHeader } }, (r) => {
|
||||
const chunks = [];
|
||||
r.on("data", (c) => chunks.push(c));
|
||||
r.on("end", () => resolve({ status: r.statusCode, headers: r.headers, body: Buffer.concat(chunks) }));
|
||||
});
|
||||
req.on("error", reject);
|
||||
req.setTimeout(15000, () => req.destroy(new Error("upstream timeout")));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function serve(res, name, reqPath, baseHref) {
|
||||
let entry;
|
||||
try { entry = await resolve(name); } catch (e) { res.writeHead(502); return res.end("resolve error: " + e.message); }
|
||||
|
|
@ -102,11 +115,15 @@ async function serve(res, name, reqPath, baseHref) {
|
|||
res.writeHead(up.status, { "content-type": ct }); return res.end(buf);
|
||||
}
|
||||
case "ip": {
|
||||
const up = await fetch(`http://${r.ip}${reqPath}`, { headers: { host: name } });
|
||||
const buf = Buffer.from(await up.arrayBuffer());
|
||||
let ct = up.headers.get("content-type") || guess(reqPath);
|
||||
if (ct.includes("text/html")) { res.writeHead(up.status, { "content-type": ct }); return res.end(injectBase(buf.toString("utf8"), baseHref)); }
|
||||
res.writeHead(up.status, { "content-type": ct }); return res.end(buf);
|
||||
// http.request instead of fetch(): WHATWG fetch treats Host as a
|
||||
// forbidden header, so undici silently drops { headers: { host: name } }
|
||||
// and sends Host: <ip>. nginx then falls to default_server and 404s.
|
||||
// The whole point of an ip record is to reach a server_name matching the
|
||||
// BCNR name, so Host must be right.
|
||||
const up = await ipFetch(r.ip, reqPath, name);
|
||||
let ct = up.headers["content-type"] || guess(reqPath);
|
||||
if (ct.includes("text/html")) { res.writeHead(up.status, { "content-type": ct }); return res.end(injectBase(up.body.toString("utf8"), baseHref)); }
|
||||
res.writeHead(up.status, { "content-type": ct }); return res.end(up.body);
|
||||
}
|
||||
case "u":
|
||||
res.writeHead(302, { location: r.u }); return res.end();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue