升级Expo SDK 49后,使用XHR上传图片至AWS S3报"Stream Closed"错误
Expo SDK 49(React Native 0.72.1)XHR上传图片至AWS S3报"Stream Closed"问题修复
问题背景
升级到Expo SDK 49(对应React Native v0.72.1)后,通过XHR向AWS S3存储桶上传图片时出现Stream Closed错误,该功能在SDK 48及更早版本可正常运行。上传流程为:客户端先向服务端获取预签名URL,再直接通过该URL完成图片上传。此前React Native 0.62/0.63曾出现类似问题,但旧方案均无效,推测为新版本兼容性问题。
问题代码
function makeRequest(method, url, uploadData) { return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); xhr.open(method, url); xhr.onload = function () { if (this.status >= 200 && this.status < 300) { resolve({ status: this.status, responseText: this.responseText, }); } else { console.warn( 'status -->', this.status, 'responseText -->', this.responseText ); reject( new Error({ status: this.status, responseText: this.responseText, }) ); } }; xhr.onerror = function () { console.log( 'status -->', this.status, '; responseText -->', this.responseText ); reject( new Error({ status: this.status, responseText: xhr.responseText, response: xhr.response, }) ); }; xhr.setRequestHeader('Content-Type', 'image/jpeg'); xhr.send({ uri: uploadData.uri, type: 'image/jpeg', name: uploadData.fileName, }); }); }
修复方案
React Native 0.72对XMLHttpRequest的send方法参数处理逻辑有调整,直接传入含uri的对象不再被正确解析为文件流,需改用标准格式传递数据,以下是两种可行方案:
方案1:用FormData包装上传内容
修改send部分代码,将图片数据放入FormData中:
function makeRequest(method, url, uploadData) { return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); xhr.open(method, url); xhr.onload = function () { if (this.status >= 200 && this.status < 300) { resolve({ status: this.status, responseText: this.responseText, }); } else { console.warn( 'status -->', this.status, 'responseText -->', this.responseText ); reject( new Error({ status: this.status, responseText: this.responseText, }) ); } }; xhr.onerror = function () { console.log( 'status -->', this.status, '; responseText -->', this.responseText ); reject( new Error({ status: this.status, responseText: xhr.responseText, response: xhr.response, }) ); }; // 移除手动设置的Content-Type,让浏览器自动生成带boundary的头部 // xhr.setRequestHeader('Content-Type', 'image/jpeg'); const formData = new FormData(); formData.append('file', { uri: uploadData.uri, type: 'image/jpeg', name: uploadData.fileName, }); xhr.send(formData); }); }
注意:若S3预签名URL要求严格匹配Content-Type: image/jpeg,需确保服务端生成预签名URL时允许multipart/form-data类型,或改用方案2。
方案2:读取图片二进制数据后发送
使用Expo的FileSystem模块读取图片二进制数据,以Blob格式发送:
import * as FileSystem from 'expo-file-system'; async function makeRequest(method, url, uploadData) { return new Promise(async function (resolve, reject) { const xhr = new XMLHttpRequest(); xhr.open(method, url); xhr.onload = function () { if (this.status >= 200 && this.status < 300) { resolve({ status: this.status, responseText: this.responseText, }); } else { console.warn( 'status -->', this.status, 'responseText -->', this.responseText ); reject( new Error({ status: this.status, responseText: this.responseText, }) ); } }; xhr.onerror = function () { console.log( 'status -->', this.status, '; responseText -->', this.responseText ); reject( new Error({ status: this.status, responseText: xhr.responseText, response: xhr.response, }) ); }; xhr.setRequestHeader('Content-Type', 'image/jpeg'); // 读取图片为二进制数据 const fileContent = await FileSystem.readAsStringAsync(uploadData.uri, { encoding: FileSystem.EncodingType.Base64, }); const blob = new Blob([Uint8Array.from(atob(fileContent), c => c.charCodeAt(0))], { type: 'image/jpeg', }); xhr.send(blob); }); }
该方案保持Content-Type为image/jpeg,匹配S3预签名URL要求,无需修改服务端配置。
原因分析
React Native 0.72基于Hermes引擎的更新,调整了XMLHttpRequest的文件上传逻辑,不再支持直接传入含uri的对象作为send参数,必须通过FormData或Blob/ArrayBuffer等标准格式传递,否则会导致流提前关闭,触发Stream Closed错误。
内容的提问来源于stack exchange,提问作者RdC1965
相关产品推荐
相关产品推荐

