鸿蒙 HTTP 文件上传:用 multiFormDataList 正确发送 multipart
·
鸿蒙 HTTP 文件上传:用 multiFormDataList 正确发送 multipart
前言
在鸿蒙应用里上传用户头像、附件是再常见不过的需求,但社区里关于“服务端收不到 multipart”“请求一直 pending”的提问非常集中。根因几乎都出在两点:extraData 不能直接传文件对象,以及忘记调用 destroy() 释放连接。本文给出 API 11+ 的标准写法,并补充 rcp 更简洁的替代方案。
问题描述
开发者常踩的两个坑:
- 服务端
req.file是 undefined:照着 Web 前端的习惯,把文件塞进extraData: { file: fileObj }。但extraData只接受string / Object / ArrayBuffer类型——传普通对象会被 JSON 序列化,服务端自然解析不出文件流。 - 请求永久 pending、连接泄漏:手动拼接
multipart字符串时,一旦boundary分隔符或Content-Length算错,底层 TCP 会一直等待剩余数据,导致请求卡死;再加上http.createHttp()用完不释放,连接池被占满后后续请求全部挂起。
细节解析
官方推荐的正确做法(API 11+)是使用 multiFormDataList 字段(类型 Array<MultiFormData>)。系统会自动处理 boundary 分隔符、Content-Length 和请求体组装,禁止手填这些头。要点:
- 一个数组元素里,
filePath(文件沙箱路径)和data(文本)互斥,文件字段用filePath,文本字段用data,分别放在数组的不同对象中。 name必须与后端接收字段名一致(如后端用file,你就写name: 'file')。filePath必须是应用沙箱绝对路径(context.filesDir下的路径)。相册返回的 URI 要先复制到沙箱;不能直接传file://前缀或 fd。http.createHttp()每次返回的HttpRequest不可复用,用完必须destroy(),推荐try/finally保证执行。module.json5需要声明ohos.permission.INTERNET。
如果上传逻辑复杂(断点续传、重试、后台托管),可以用 @kit.NetworkKit 的 request.agent()(API 10+)任务托管,或 @kit.RemoteCommunicationKit 的 rcp 模块——后者对 multipart 封装更友好。
示例代码
标准写法(http + multiFormDataList,Promise 风格)
import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
async function uploadAvatar(ctx: common.UIAbilityContext, serverUrl: string, filePath: string) {
const req = http.createHttp();
try {
const res = await req.request(serverUrl, {
method: http.RequestMethod.POST,
header: { 'Content-Type': 'multipart/form-data' },
multiFormDataList: [
{
name: 'file', // 与后端字段一致
contentType: 'image/jpeg',
remoteFileName: 'avatar.jpg', // 服务端保存的文件名
filePath: filePath // 沙箱绝对路径,非 file:// URI
},
{
name: 'name', // 文本字段与文件字段分开
contentType: 'text/plain',
data: '用户昵称'
}
],
connectTimeout: 15000,
readTimeout: 30000
});
if (res.responseCode < 200 || res.responseCode >= 300) {
throw new Error(`上传失败:${res.responseCode}`);
}
return res.result;
} finally {
req.destroy(); // 无论成功失败都释放连接
}
}
更简洁的 rcp 写法
import { rcp } from '@kit.RemoteCommunicationKit';
function uploadWithRcp(serverUrl: string, filePath: string): void {
const session = rcp.createSession();
const form = new rcp.MultipartForm({
name: '用户昵称',
file: { remoteFileName: 'avatar.jpg', contentType: 'image/jpeg', contentOrPath: filePath }
});
const req = new rcp.Request(serverUrl);
req.method = 'POST';
req.content = form;
session.fetch(req)
.then((resp) => console.info(`upload ok: ${resp.statusCode}`))
.catch((err: BusinessError) => console.error(`fail: ${err.message}`))
.finally(() => session.close());
}
相册 URI 转沙箱路径(上传前必备)
import { fileIo as fs } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
function copyToSandbox(ctx: common.UIAbilityContext, srcUri: string): string {
const dest = `${ctx.filesDir}/upload_${Date.now()}.jpg`;
fs.copyFileSync(srcUri, dest); // 相册 URI 必须先复制进沙箱再传 filePath
return dest;
}
总结
文件上传的正确姿势:用 multiFormDataList 让系统自动组装 multipart,不手拼字符串;name 对齐后端字段;filePath 用沙箱绝对路径;destroy() 放进 finally。想要断点续传或后台托管用 request.agent(),想要代码简洁用 rcp。记住:extraData 传文件对象是行不通的,连接不释放才是 pending 的真凶。
更多推荐
所有评论(0)