Node.js的http.get请求Overpass API频繁超时失败,但PHP file_get_contents可正常工作,求排查脚本问题
我来帮你排查这个问题!从你给出的错误日志和代码对比来看,Node.js脚本遇到的超时/连接问题,主要和原生http模块的连接策略、超时设置细节以及IPv6优先级有关,而PHP的file_get_contents在这些方面有更宽松的默认行为。下面是具体的排查和修复方案:
1. 先解决IPv6连接失败的问题
错误日志里出现了ENETUNREACH的IPv6地址错误,说明你的网络环境可能不支持IPv6,但Node.js默认会优先尝试IPv6连接,导致连接失败后才回退到IPv4,这个过程耗时过长直接触发了超时。而PHP的file_get_contents可能默认优先用IPv4,或者回退更快。
修复方案:在http.get的选项里强制指定使用IPv4(family: 4):
http.get(url, { timeout: 220000, family: 4 }, (res) => { // 原有的响应处理逻辑 })
2. 区分「连接超时」和「响应超时」
你设置的timeout: 220000是响应超时(即连接建立后,等待服务器响应的超时),但错误里的connect ETIMEDOUT是连接阶段的超时(即还没和服务器建立TCP连接就超时了),原生http模块默认没有单独设置连接超时,需要手动通过socket事件来设置:
修复方案:给请求添加连接超时监听:
const req = http.get(url, { timeout: 220000, family: 4 }, (res) => { // 原有的res处理逻辑 }); // 新增:设置连接阶段的超时(比如30秒) req.on('socket', (socket) => { socket.setTimeout(30000); socket.on('timeout', () => { req.abort(); // 超时就中断请求 }); });
3. 完善非200状态码的重试逻辑
你的脚本目前只在ETIMEDOUT错误时重试,但Overpass API可能返回503(服务繁忙)等临时状态码,这些情况也应该触发重试,而PHP的file_get_contents可能在遇到这类状态码时自动重试(或者你的PHP环境有相关配置)。
修复方案:在非200状态码的分支里添加重试逻辑:
if (res.statusCode !== 200) { console.error(`Failed to fetch data: ${res.statusCode} ${res.statusMessage}`); res.resume(); // 消费响应释放内存 // 还有重试次数的话就重试 if (retries < maxRetries - 1) { console.error(`Retrying due to non-200 status... (${retries + 1}/${maxRetries})`); setTimeout(() => fetchData(retries + 1), 5000); } return; }
4. (可选)改用更成熟的HTTP客户端库
原生http模块比较底层,处理超时、重试、连接策略等边缘情况不够省心。如果不想折腾原生代码,可以用axios或者node-fetch这类第三方库,它们默认处理了很多细节,比如自动重试、连接/响应超时分离、IPv4/IPv6回退等。
比如用axios的简化示例:
const fs = require('fs'); const path = require('path'); const axios = require('axios'); const overpassUrl = 'http://overpass-api.de/api/interpreter'; const query = '[out:xml][timeout:25];area(3600382313)->.searchArea;(nwr["landuse"="winter_sports"](area.searchArea););out geom;'; const xmlFilePath = path.join(__dirname, '../writable/ski/sknowed_calcs/aa_winter_sports_polygons.xml'); const maxRetries = 3; async function fetchData(retries = 0) { console.log(`Fetching data from Overpass API... (Attempt ${retries + 1}/${maxRetries})`); try { const response = await axios.get(overpassUrl, { params: { data: query }, timeout: 220000, // 总超时 family: 4, // 强制IPv4 }); // 保存文件 fs.mkdirSync(path.dirname(xmlFilePath), { recursive: true }); fs.writeFileSync(xmlFilePath, response.data, 'utf8'); console.log(`XML file saved at ${xmlFilePath}`); } catch (error) { if (retries < maxRetries - 1) { console.error(`Request failed, retrying in 5s... (${retries + 1}/${maxRetries})`, error.message); setTimeout(() => fetchData(retries + 1), 5000); } else { console.error('Request failed after all retries:', error); } } } fetchData();
总结
优先尝试强制IPv4和添加连接超时这两个修改,应该就能解决大部分问题。如果还是不行,再考虑改用第三方HTTP库,或者检查你的Node.js版本(旧版本的http模块可能有连接策略的bug)。
备注:内容来源于stack exchange,提问作者spreaderman

