Axios 上传大文件崩溃:鸿蒙 RNOH 下 XHR 返回空响应头引发的"假失败"

React Native + 鸿蒙适配项目中,上传小文件正常、上传大文件崩溃------而且后端其实已经收到了文件。一次诡异的偶发问题,最终靠 9 行 patch 解决。

问题

项目里有个文件上传功能,小图片上传一切正常,换成稍大的文件(~3MB)就崩了。错误信息很明确:

vbnet 复制代码
Error: header name must be a non-empty string
  at AxiosHeaders.set (axios/lib/core/AxiosHeaders.js:...)
  at parseHeaders (axios/lib/helpers/parseHeaders.js:...)
  at AxiosHeaders.from (axios/lib/core/AxiosHeaders.js:...)
  at xhrAdapter.onloadend (axios/lib/adapters/xhr.js:97)

注意几个关键点:

  • 栈打在 onloadend------响应已经回来了才报错
  • 后台日志显示文件已经写入成功
  • 同一套代码在 Android 上没问题,只有鸿蒙设备复现
  • 登录等普通 JSON 接口完全正常,只在上传场景出现

一句话总结:HTTP 请求成功了,但客户端在解析响应头时崩了,业务拿到的是"假失败"。

复现

HarmonyOS 6.1.0 (API 23) 模拟器,同一份上传代码:

上传文件 getAllResponseHeaders() 返回值 结果
~99 KB JPG "content-type: application/json\r\n..." 上传成功
~3 MB PNG ""(空字符串) 崩溃

小文件返回正常 header 字符串,大文件返回空字符串。跟图片格式无关,跟文件大小有关------更像是鸿蒙 XHR 在处理较大 multipart 响应时的时序问题。

根因

axios 的 xhr.js 在拿到响应后这样组装 headers:

js 复制代码
const responseHeaders = AxiosHeaders.from(
  'getAllResponseHeaders' in request && request.getAllResponseHeaders(),
);

正常情况下 getAllResponseHeaders() 返回类似 "content-type: application/json\n..." 的字符串,AxiosHeaders.from() 解析成功,一切正常。

但鸿蒙在大 multipart 响应下返回了 ''。追踪 AxiosHeaders.from('') 的调用链:

scss 复制代码
AxiosHeaders.from('')
  → new AxiosHeaders('')
    → this.set('')
      → 判断是字符串 → parseHeaders('')
        → ''.split('\n') → ['']
          → 循环处理: line = ''
            → i = ''.indexOf(':') → -1
            → key = ''.substring(0, -1).trim() → ''
              → headers.set('', value)
                → normalizeHeader('') → ''
                  → throw 'header name must be a non-empty string'

空字符串按 \n 切割后得到一个空元素,解析出空 key,触发校验异常。

更要命的是这个异常的位置:它发生在 onloadend 内部,axios 的 promise 还没 settle。所以 拦截器也救不了 ------promise 链断了,interceptors.response 根本不会触发:

arduino 复制代码
正常流程: onloadend → 解析 headers → settle(resolve) → 拦截器 → 业务拿到 response
这次异常: onloadend → 解析 headers → throw → promise 未 settle → 拦截器不触发
                                                               → RN XHR onerror 兜底成错误

修复

patches/axios+1.6.7.patch

diff 复制代码
       // Prepare the response
-      const responseHeaders = AxiosHeaders.from(
-        'getAllResponseHeaders' in request && request.getAllResponseHeaders()
-      );
+      let responseHeaders;
+      try {
+        const rawHeaders =
+          'getAllResponseHeaders' in request && request.getAllResponseHeaders();
+        responseHeaders =
+          rawHeaders && String(rawHeaders).trim()
+            ? AxiosHeaders.from(rawHeaders)
+            : new AxiosHeaders();
+      } catch (_err) {
+        responseHeaders = new AxiosHeaders();
+      }

做了两件事:

  1. 空值守卫rawHeaders && String(rawHeaders).trim() 确保空字符串、null、纯空白都不进 parseHeaders,直接返回空的 AxiosHeaders
  2. try-catch 兜底 :万一遇到畸形字符串(比如 '\r\n\r\n')走到 parseHeaders 抛错,也不会阻断响应体解析

responseDatastatus 等主路径完全不动,补丁只管 headers。

为什么不用其他方案

方案 为什么不选
axios.interceptors.response 兜底 promise 在 onloadend 里就断了,拦截器作为 promise 链上的节点不会触发
切换 adapter(http adapter) 行为差异大,且项目依赖 XHR 特有的 onUploadProgress
升级 axios 查了 1.7.x 源码,同样没有防御;这是跨端环境的边界问题,axios 不太可能为此做适配
等 RNOH 修复 治本但周期不可控,业务不能等

业务侧配套

patch 解决了崩溃,但业务侧还有两件事需要处理:

  • FormData 的 Content-Type :axios 默认会把 FormData 序列化成 JSON,需要在拦截器里对 FormData 请求设置 AxiosHeaders.setContentType(false),让浏览器/RN 自动处理 boundary
  • 更大文件的"假成功" :patch 后不再崩了,但更大文件可能出现"流中断 + 后端返回 200 包裹业务错误码"的情况,业务侧需要主动判断 res.success,不能只看 HTTP status

经验

  1. 不要信任 RN / 鸿蒙的 XHR 实现完全符合 Web 规范 ------getAllResponseHeaders() 在规范里要求返回空字符串或合法 header 列表,但"空字符串"在 axios 的 parseHeaders 里恰好是个会抛错的值
  2. 偶发崩溃优先查平台差异------同一个 bug 只在特定平台出现,大概率是平台实现与预期不符
  3. patch 是务实的选择------精准改几行代码,最小化维护负担,等上游修复后直接删 patch 文件
相关推荐
一拳一个娘娘腔20 小时前
【第七期】漏洞攻防-前端篇:XSS 与 CSRF —— 当浏览器成为攻击者的“肉鸡”
前端·xss·csrf
不吃鱼的羊20 小时前
DaVinci配置NVM模块
前端·javascript·网络
excel20 小时前
为什么需要构建工具(Webpack / Vite 的本质)
前端
lang2015092820 小时前
Java SAX 流式解析全解:从原理到 EasyExcel 实战
java·前端·javascript
Rain50920 小时前
2.4. PostgreSQL 数据库连接与实战指南
前端·数据库·人工智能·后端·postgresql·数据分析
console.log('npc')20 小时前
Codex 桌面端接入 Headroom 压缩代理完整教程
前端·vscode
独泪了无痕21 小时前
Vue集成uuid生成唯一标识实践指南
前端·vue.js
yuanyxh1 天前
Mac 软件推荐
前端·javascript·程序员
万少1 天前
AtomCode开发微信小程序《谁去呀》 全流程
前端·javascript·后端
某人辛木1 天前
Web自动化测试
前端·python·pycharm·pytest