news 2026/9/5 16:52:09

Axios multipart/form-data 完整指南:FormData 自动序列化、formSerializer 配置与 formToJSON 反向转换

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Axios multipart/form-data 完整指南:FormData 自动序列化、formSerializer 配置与 formToJSON 反向转换

Axios multipart/form-data 完整指南:FormData 自动序列化、formSerializer 配置与 formToJSON 反向转换

【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios

本文围绕 Axios 的multipart/form-data发送能力展开:从浏览器与 Node.js 环境下的基础提交,到 v0.27.0 引入的对象自动序列化机制、formSerializer各配置项(dotsmetaTokensindexesmaxDepth等)的取值与行为,再到 Node.js 下formDataHeaderPolicy头安全策略与formToJSON()的反向解析。读完你可以完整掌握 Axios 表单序列化的规则细节,并结合 toFormData.js、setFormDataHeaders.js、formDataToJSON.js 三处源码理解其底层实现。

基础提交:浏览器与 Node.js 两种写法

Axios 支持发送multipart/form-data格式的请求,该格式常用于文件上传场景。基本思路是:构造一个FormData对象并append数据,再将其作为data传给 axios 请求配置。

浏览器端直接使用原生FormData

const formData = new FormData(); formData.append('foo', 'bar'); axios.post('https://httpbin.org/post', formData);

不要手动设置Content-Type。对于浏览器、Web Worker 或 React Native 环境中的FormData对象,环境本身会自动添加 multipart boundary(边界串),手动设置反而会破坏请求体格式。

在 Node.js 中通常使用社区form-data包(Axios 自身的 polyfill 也基于它):

const FormData = require('form-data'); const form = new FormData(); form.append('my_field', 'my value'); form.append('my_buffer', Buffer.alloc(10)); form.append('my_file', fs.createReadStream('/foo/bar.jpg')); axios.post('https://example.com', form);

自动序列化为 FormData(v0.27.0+)

自 v0.27.0 起,当请求的Content-Type头被设置为multipart/form-data时,Axios 会自动把普通 JavaScript 对象序列化为FormData对象——你无需手工 append 任何字段:

import axios from 'axios'; axios .post( 'https://httpbin.org/post', { x: 1 }, { headers: { 'Content-Type': 'multipart/form-data', }, } ) .then(({ data }) => console.log(data));

在 Node.js 版本中默认使用form-datapolyfill;你也可以通过配置项env.FormData替换所用的 FormData 类,尽管大多数情况下没有必要:

const axios = require('axios'); var FormData = require('form-data'); axios .post( 'https://httpbin.org/post', { x: 1, buf: Buffer.alloc(10) }, { headers: { 'Content-Type': 'multipart/form-data', }, } ) .then(({ data }) => console.log(data));

从源码看,这条“自动序列化”链路位于默认请求转换器 defaults/index.js:当data是对象且Content-Type包含multipart/form-data(或data本身是 FileList)时,会调用toFormData(),并从this.env.FormData中取出自定义类实例作为容器:

// lib/defaults/index.js const formSerializer = own(this, 'formSerializer'); // ... if ( (isFileList = utils.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1 ) { const env = own(this, 'env'); const _FormData = env && env.FormData; return toFormData( isFileList ? { 'files[]': data } : data, _FormData && new _FormData(), formSerializer ); }

两个值得注意的实现细节:其一,纯 FileList 会被包装成{ 'files[]': data }后再序列化,因此文件批量上传默认使用files[]字段名;其二,transformRequest里还有反向判断——如果data已经是FormData而 Content-Type 却是 JSON,会走formDataToJSON(data)JSON.stringify(见 defaults/index.js),即“FormData + JSON 头”会被自动转成 JSON 发送。

Node.js FormData 的头拷贝策略 formDataHeaderPolicy(仅 Node.js)

当传入一个暴露了getHeaders()方法的 Node.jsFormData对象(如form-data包)时,Axios 默认会把它返回的全部头拷贝到请求上。这一行为保持了与 v1 的兼容性,但当FormData来自不可信来源时存在隐患:getHeaders()可能覆盖Authorization等关键头,或注入任意自定义头。

设置formDataHeaderPolicy: 'content-only'后,Axios 只从getHeaders()拷贝Content-TypeContent-Length两个头,其余请求头必须通过headers配置显式声明:

await axios.post('https://example.com/upload', form, { formDataHeaderPolicy: 'content-only', headers: { Authorization: 'Bearer my-token', }, });

该配置的默认值是'legacy'(全部拷贝)。参数解析逻辑非常直白,见 setFormDataHeaders.js:

const FORM_DATA_CONTENT_HEADERS = ['content-type', 'content-length']; export default function setFormDataHeaders(headers, formHeaders, policy) { if (policy !== 'content-only') { headers.set(formHeaders); return; } Object.entries(formHeaders || {}).forEach(([key, val]) => { if (FORM_DATA_CONTENT_HEADERS.includes(key.toLowerCase())) { headers.set(key, val); } }); }

调用点有两处:统一在 resolveConfig.js 中基于getHeaders()应用一次,Node 的 http 适配器 adapters/http.js 中再次应用以覆盖重定向等场景。更完整的配置说明可参考请求配置文档中的formDataHeaderPolicy小节(docs/fr/pages/advanced/request-config.md)。

特殊终结符:{}[]

Axios 的 FormData 序列化器支持两种字段名终结符,用于表达特殊序列化操作:

  • {}— 将该值用JSON.stringify序列化后作为单个字符串字段发送;
  • []— 把数组类型的对象拆解为多个同名字段发送。

注意:对数组和FileList类型,拆解(展开为同名字段)是默认行为,无需加[]

配置序列化器:config.formSerializer

序列化器支持通过config.formSerializer配置对象传入额外选项,用于处理各种特殊需求:

选项类型与默认值说明
visitorFunction用户自定义的访问器函数,会被递归调用以按自定义规则将数据对象序列化为 FormData
dotsboolean = false序列化数组和对象时使用点号记法(user.name)替代方括号记法(user[name]
metaTokensboolean = true在字段名中保留特殊终结符(如user{}: '{"name": "John"}')。后端 body-parser 可利用这些元信息自动按 JSON 解析该值
indexesnull \| false \| true = false控制为展开后的数组字段追加何种索引:null— 不加括号(arr: 1arr: 2arr: 3);false(默认)— 加空括号(arr[]: 1…);true— 加真实下标(arr[0]: 1arr[1]: 2arr[2]: 3
maxDepthnumber = 100序列化递归的最大对象嵌套深度。超限抛出 code 为ERR_FORM_DATA_DEPTH_EXCEEDEDAxiosError,用于防御服务端通过深层嵌套负载发起的 DoS 攻击;设为Infinity可关闭限制
Blobtypeof BlobArrayBuffer值转换为符合规范的FormData时使用的 Blob 构造函数。仅在你的运行时以其他标识符提供了兼容 Blob 构造器时才需要替换

例如,对确实会超过 100 层嵌套的数据结构放宽限制:

// 允许更深的嵌套,用于确实超过 100 层的数据结构 axios.postForm('/api', data, { formSerializer: { maxDepth: 200 } });

安全说明:默认 100 层上限是有意为之。服务端把客户端可控的 JSON 透传给 axios 作为data的代码,缺少该保护时会面临调用栈溢出风险;仅在你的数据结构确实需要时才调高maxDepth

源码视角:序列化器内部如何工作

核心实现位于 toFormData.js。默认上限与反向转换共享同一个常量,保证 FormData 与 JSON 互转的深度语义对称(toFormData.js):

// lib/helpers/toFormData.js export const DEFAULT_FORM_DATA_MAX_DEPTH = 100;

几个关键机制:

  • 深度保护build()在每次递归前调用throwIfMaxDepthExceeded(depth)检查层级,超限即抛出带ERR_FORM_DATA_DEPTH_EXCEEDEDcode 的AxiosError(toFormData.js);对{}终结符触发的JSON.stringify也内置了等价的深度限制stringifyWithDepthLimit()
  • 循环引用检测build()维护一个stack,遇到重复对象立即抛出Circular reference detected错误,避免死递归(toFormData.js)。
  • indexes三分支:平铺数组展开时的字段名生成就写死在defaultVisitor中——indexes === truerenderKey([key], index, dots)生成arr[0]indexes === null直接用key,其余情况(默认false)拼key + '[]'(toFormData.js)。
  • metaTokens{}终结符在metaTokenstrue时原样保留在字段名里,否则被key.slice(0, -2)剥掉(toFormData.js)。
  • dots:字段路径渲染由renderKey(path, key, dots)完成,dots为真时用.连接,否则用方括号包裹(toFormData.js)。
  • 值类型转换Date转为 ISO 字符串、布尔转字符串、ArrayBuffer/TypedArray 在有规范兼容Blob时转 Blob、在 Node 有 Buffer 时转 Buffer(convertValue,toFormData.js)。
  • visitor扩展点:自定义 visitor 以 FormData 为this被调用,返回true表示继续递归遍历该值;同时可访问defaultVisitorconvertValueisVisitable及一组is*类型判断辅助函数(exposedHelpers,toFormData.js)。

序列化过程完整示例

以一个典型的嵌套对象为例:

const obj = { x: 1, arr: [1, 2, 3], arr2: [1, [2], 3], users: [ { name: 'Peter', surname: 'Griffin' }, { name: 'Thomas', surname: 'Anderson' }, ], 'obj2{}': [{ x: 1 }], };

Axios 序列化器内部等价于执行了以下步骤:

const formData = new FormData(); formData.append('x', '1'); formData.append('arr[]', '1'); formData.append('arr[]', '2'); formData.append('arr[]', '3'); formData.append('arr2[0]', '1'); formData.append('arr2[1][0]', '2'); formData.append('arr2[2]', '3'); formData.append('users[0][name]', 'Peter'); formData.append('users[0][surname]', 'Griffin'); formData.append('users[1][name]', 'Thomas'); formData.append('users[1][surname]', 'Anderson'); formData.append('obj2{}', '[{"x":1}]');

规则可以归纳为:纯标量直接追加;无嵌套的平铺数组默认展开为key[]同名多值;混合数组按真实下标生成路径(arr2[1][0]);对象数组递归展开为users[0][name]这类路径;{}终结符的值整体JSON.stringify成单字段。

反向转换:axios.formToJSON()

axios.formToJSON()能把字段名中的点号与方括号记法还原为嵌套的对象和数组结构。只有.[]是结构性分隔符,其余字符(如-、空格、+*&)都会原样保留在字面量键中:

const form = new FormData(); form.append('user-name', 'johndoe'); form.append('user.name', 'john'); console.log(axios.formToJSON(form)); // { // 'user-name': 'johndoe', // user: { name: 'john' } // }

user[name]同样会产生嵌套对象路径,items[]则产生数组。

实现见 formDataToJSON.js,几个实现要点:

  • 路径解析使用正则/[^.[\]]+|\[([^.[\]]*)]/g,把foo[x][y][z]拆为['foo','x','y','z']foo.x.y.z同理;段内不含[的设计让解析保持线性复杂度(formDataToJSON.js)。
  • __proto__段被直接跳过,杜绝原型污染;同名重复字段会合并为数组(formDataToJSON.js)。
  • 数字键(01…)在目标位置自动形成数组,非数字键还原为对象属性;解析同样受 100 层深度上限保护,超限抛ERR_FORM_DATA_DEPTH_EXCEEDED
  • 该函数挂在 axios 实例上,且支持直接传 HTML<form>元素——内部会先new FormData(thing)再解析(axios.js)。对应的单元测试见 tests/unit/helpers/formDataToJSON.test.js。

toFormData本身也作为axios.toFormData暴露(axios.js),可以脱离请求流程手动序列化对象,测试用例见 tests/unit/toFormData.test.js。

便捷方法:postForm / putForm / patchForm

Axios 还提供三个快捷方法:postFormputFormpatchForm。它们与对应的 HTTP 方法完全等价,唯一区别是预先将Content-Type头设置为multipart/form-data,因此可以直接传普通对象:

// 等价于 axios.post('/api', data, { headers: { 'Content-Type': 'multipart/form-data' } }) axios.postForm('/api', { name: 'john', file: new Blob(['...']) });

这也意味着postForm系列方法天然触发前文描述的自动序列化流程,并受同一套formSerializer选项约束。

小结

  • 浏览器 / Web Worker / React Native:用原生FormData直接传data不要手动设置Content-Type(boundary 由环境生成)。
  • Node.js:使用form-data包或依赖自动序列化;对不可信来源的 FormData,用formDataHeaderPolicy: 'content-only'收敛头拷贝范围。
  • 对象自动序列化要求Content-Typemultipart/form-dataformSerializervisitor/dots/metaTokens/indexes/maxDepth/Blob覆盖几乎全部特殊场景,maxDepth默认 100 层是服务端 DoS 防护线。
  • 终结符{}(JSON 化)与[](字段展开)是字段级的序列化开关;formToJSON().[]三个结构性分隔符做无损逆向解析,并与序列化器共享同一深度上限。

【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 16:48:21

树莓派4B开源语音控制机器人:openduckmini部署与调试全解析

openduckmini 是一个开源机器人项目&#xff0c;圈子里一般叫它“开源机器鸭”。树莓派4B版本最值得关注的能力&#xff0c;就是让桌面级机器人支持语音控制&#xff1a;你喊一句“小鸭抬头”或者“介绍一下自己”&#xff0c;它能先录音、识别&#xff0c;再根据指令执行动作或…

作者头像 李华
网站建设 2026/9/5 16:47:06

macOS取证实战:从MacBook磁盘镜像到日志分析提取证据

如果你关注苹果和 OpenAI 这场诉讼&#xff0c;会发现一个容易被忽略但技术上很有意思的细节&#xff1a;苹果提交的部分证据&#xff0c;是从一名前员工的 MacBook 上提取出来的。这件事真正值得技术人关注的&#xff0c;不是两家的法律纠纷&#xff0c;而是“一台 MacBook 到…

作者头像 李华
网站建设 2026/9/5 16:44:45

多代理智能体编排实战:Fable调度GPT-5.6 Terra的部署与安全边界

Perplexity 推出的 AI 计算机&#xff0c;Fable 作为核心调度系统&#xff0c;GPT-5.6 Terra 作为子代理&#xff0c;这套架构最近讨论热度很高。我看了不少相关讨论&#xff0c;其中“大模型 GPT-5.6 SOL 失控出逃”这个话题更是把多代理系统的安全边界问题推到了台前。 先说…

作者头像 李华
网站建设 2026/9/5 16:32:59

IOPaint 图像修复实战指南:5分钟上手去除物体与水印

IOPaint 图像修复实战指南&#xff1a;5分钟上手去除物体与水印 【免费下载链接】IOPaint Image inpainting tool powered by SOTA AI Model. Remove any unwanted object, defect, people from your pictures or erase and replace(powered by stable diffusion) any thing on…

作者头像 李华