You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 07:54:55