你的浏览器无法正常显示内容,请更换或升级浏览器!

Node.js 原生 https 直连做接口自动化的五个坑

tenfei
tenfei
发布于2026-10-09 13:59 阅读27次
Node.js 原生 https 直连做接口自动化的五个坑
接口自动化脚本写着写着就卡住?本文用 Node.js 原生 https 模块手写一套可复用的请求骨架,逐条讲清 Content-Length 字节长度、cookie 跨请求保持、gzip 乱码、charset 与超时这五个高频坑,并附一份排查顺序清单。
## 为什么用原生 https 而不用 axios 在给老服务器写接口自动化脚本时,我一度是 axios 的重度用户。直到有一次在只有内网环境、没有 npm 源的机器上跑脚本,装依赖直接卡死;再后来遇到目标服务器对请求头指纹敏感,axios 默认带的一堆头反被拦截。从那以后,凡是简单的接口调用,我都改用 Node.js 原生 `https` 模块直连——零依赖、可完全控制请求头、行为可预测。 但原生写法有几个坑,几乎每个人都踩过。 ## 坑一:Content-Length 用错长度 最常见的一行错误代码: ```js headers['Content-Length'] = data.length; // 错! ``` `String.length` 返回的是 UTF-16 码元个数,而 `Content-Length` 要求的是**字节数**。只要请求体里有中文,两者就不相等——比如"你好"两个字符,length 是 2,字节是 6。长度声明偏小,服务端会截断请求体,于是你收到一个莫名其妙的 400,或者保存进去的内容只剩半截。 正确写法: ```js const payload = JSON.stringify(body); headers['Content-Length'] = Buffer.byteLength(payload, 'utf8'); ``` ## 坑二:Cookie 没有跨请求保持 服务器在登录接口会下发 `token` cookie,后续所有请求都必须带上。原生模块**不会自动帮你维护 cookie jar**,必须自己实现: ```js const cookies = new Map(); function storeCookies(setCookie) { if (!setCookie) return; for (const line of setCookie) { const first = line.split(';')[0]; const i = first.indexOf('='); if (i > 0) cookies.set(first.slice(0, i).trim(), first.slice(i + 1).trim()); } } ``` 注意 `set-cookie` 响应头在 Node 里是**数组**,不是字符串,`split` 之前要先确认类型,否则拿到的是逗号拼接的整体,解析必错。 ## 坑三:响应体被 gzip 成一堆乱码 默认请求头不带 `Accept-Encoding`,很多服务器仍会返回 gzip 压缩内容。如果你没解压,拿到的 buffer 转成字符串就是乱码。 两种处理:一是显式声明 `'Accept-Encoding': 'identity'` 告诉服务端不要压缩;二是用 `zlib.gunzipSync` 解压。做接口自动化我更推荐前者,简单直接。 ## 坑四:中文乱码与 charset Node 的 `res.setEncoding('utf8')` 只负责按 UTF-8 解码缓冲区,**不会改变服务端返回的内容编码**。如果对方还是 GBK 老站点,照样乱码。判断方法:读响应头 `content-type` 里有没有 `charset=`。没有时默认按 UTF-8 处理。 另外 Windows 终端输出中文前,记得设置 `PYTHONIOENCODING` 类似的思路——Node 里可以用 `process.stdout.write` 配合 `Buffer.from(str, 'utf8')` 保证管道输出不乱码。 ## 坑五:没有超时,脚本静默挂死 原生 `https.request` 默认**没有超时**。对方服务器不响应时,脚本会一直挂着,你以为在跑,其实早就死了。一定要加: ```js req.setTimeout(30000, () => req.destroy(new Error('timeout'))); ``` 并且把 `req.on('error')` 接上,否则异常会被静默吞掉。 ## 一份可复用的最小骨架 把上面五点串起来,核心就这么多: ```js function req(host, path, method, body) { return new Promise((resolve, reject) => { const payload = body === undefined ? null : JSON.stringify(body); const headers = { 'Accept-Encoding': 'identity' }; if (payload !== null) { headers['Content-Type'] = 'application/json;charset=UTF-8'; headers['Content-Length'] = Buffer.byteLength(payload); } const ch = cookieHeader(); if (ch) headers['Cookie'] = ch; const r = https.request({ hostname: host, path, method, headers }, res => { storeCookies(res.headers['set-cookie']); const chunks = []; res.on('data', c => chunks.push(c)); res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); }); r.on('error', reject); r.setTimeout(30000, () => r.destroy(new Error('timeout'))); if (payload !== null) r.write(payload); r.end(); }); } ``` ## 排查清单 遇到问题时按这个顺序过一遍:先确认 `Content-Length` 是不是按字节算的;再打印 `res.headers['set-cookie']` 看 cookie 有没有拿到;然后看响应头有没有 `content-encoding`;最后确认超时回调有没有生效。九成问题都在这四步里。 原生模块的好处是透明——每一个字节、每一个头都在你手里。代价是这些细节都得自己管。把这五个坑填上,它比任何第三方库都可靠。

1

0

文章点评
赞助商广告位
Copyright © from 2021 by namoer.com
458815@qq.com QQ:458815
蜀ICP备2022020274号-2