
axios multipart/form-data 完全指南手动构造、自动序列化与 formSerializer 配置【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axiosaxios 对multipart/form-data格式的支持分三个层次直接把FormData对象传入请求data最基础的文件上传方式、在Content-Type设为multipart/form-data时自动把普通 JS 对象序列化为FormDatav0.27.0 引入、以及通过config.formSerializer精细控制数组展开、键名格式与嵌套深度等序列化行为。读完本文你能掌握浏览器/Node.js 两种环境下构造 multipart 请求的正确姿势、formDataHeaderPolicy请求头安全策略以及toFormData、formToJSON背后的序列化规则并能在服务端安全处理客户端上传的表单数据。手动创建 FormData 并发送请求以multipart/form-data格式发送请求的基本方式创建一个FormData对象向其追加数据然后传入 axios 请求配置的data属性const formData new FormData(); formData.append(foo, bar); axios.post(https://httpbin.org/post, formData);关键规则对于浏览器、Web Worker 或 React Native 的原生FormData不要手动设置Content-Type请求头——这些运行时会自行添加 multipart boundary。axios 在源码层面正是这样做的在 resolveConfig 中一旦检测到data是 FormData 且运行于标准浏览器、Web Worker 或 React Native 环境会执行headers.setContentType(undefined)把 Content-Type 的决定权完全交还给底层传输层XHR/fetch 会自动生成带 boundary 的正确头。在 Node.js 中可以使用form-data库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);注意 Node.js 侧与浏览器侧的处理差异当 Node.js 的FormData对象暴露getHeaders()方法form-data包即如此时axios 会调用它并合并返回的请求头见下文「Node.js FormData 的请求头策略」一节boundary 由form-data库自身提供。自动序列化为 FormData从 v0.27.0 起如果请求的Content-Type请求头设置为multipart/form-dataaxios 支持自动将对象序列化为FormData对象——可以直接把 JavaScript 对象传入data无需手动appendimport axios from axios; axios .post( https://httpbin.org/post, { x: 1 }, { headers: { Content-Type: multipart/form-data, }, } ) .then(({ data }) console.log(data));这条自动序列化链路的实现入口在默认transformRequest中lib/defaults/index.js当 payload 是普通对象、且 Content-Type 包含multipart/form-data或 data 是 FileList时axios 读取实例上的env.FormData和formSerializer配置调用toFormData(data, _FormData new _FormData(), formSerializer)完成转换// lib/defaults/index.js简化自源码 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 ); }两个值得注意的细节Node.js 构建默认使用form-data包作为 polyfill。你可以通过设置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));直接传 FileList 会被包装为files[]键。上面的源码可见isFileList为真时 data 被包成{ files[]: data }再序列化即所有文件以同名键files[]展开追加。Node.js FormData 的请求头策略formDataHeaderPolicy当你传入一个暴露getHeaders()的 Node.jsFormData对象例如form-data包时axios 默认会将它返回的所有请求头复制到请求上。这保留了 v1 的兼容性但如果FormData对象来自不可信来源可能会出问题——getHeaders()可能覆盖Authorization等请求头或注入任意请求头。对应实现是 setFormDataHeaders// lib/core/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); } }); }策略判断只在policy content-only时收窄为仅复制Content-Type与Content-Length其余情况全部合并legacy 行为。该函数在 resolveConfig 与 http 适配器 http.js 中被调用并读取请求自身的formDataHeaderPolicy配置通过own()只读自身属性避免原型污染干扰。设置formDataHeaderPolicy: content-only可只从getHeaders()复制Content-Type和Content-Length再通过请求的headers配置显式设置其他请求头await axios.post(https://example.com/upload, form, { formDataHeaderPolicy: content-only, headers: { Authorization: Bearer my-token, }, });默认值为legacy。该配置项的完整说明见请求配置参考 request-config 中的formDataHeaderPolicy章节Node.js 适配器侧的行为另有 http.test.js 与 resolveConfig.test.js 两类测试覆盖。支持的特殊结尾axios 的 FormData 序列化器支持两种特殊结尾用于在键名中声明该值应如何序列化{}— 使用JSON.stringify序列化该值[]— 将类数组对象展开为具有相同键的独立字段注意展开/扩展操作默认应用于数组和 FileList 对象即数组不需要[]结尾也会被展开。这两个行为的源码位置在 toFormData.js 的 defaultVisitorfunction defaultVisitor(value, key, path) { let arr value; if (value !path typeof value object) { if (utils.endsWith(key, {})) { // 顶层键以 {} 结尾整体 JSON.stringify key metaTokens ? key : key.slice(0, -2); value stringifyWithDepthLimit(value, 1); } else if ( (utils.isArray(value) isFlatArray(value)) || ((utils.isFileList(value) || utils.endsWith(key, [])) (arr utils.toArray(value))) ) { // 扁平数组 / FileList / 键以 [] 结尾逐项展开为同键多字段 key removeBrackets(key); arr.forEach(function each(el, index) { !(utils.isUndefined(el) || el null) formData.append( indexes true ? renderKey([key], index, dots) : indexes null ? key : key [], convertValue(el) ); }); return false; } } // ... }可以推断{}结尾仅在顶层键path为空上生效嵌套层级中的对象一律递归展开为父键[子键]形式。indexes选项则在展开扁平数组时决定键名形态见下一节。配置 FormData 序列化器FormData 序列化器通过config.formSerializer对象属性支持以下选项用于处理特殊情况选项默认值说明visitor: Function内置defaultVisitor用户自定义的访问者函数递归调用以按自定义规则将数据对象序列化为 FormDatadots: booleanfalse使用点号表示法代替方括号来序列化数组和对象metaTokens: booleantrue在 FormData 键中保留{}等特殊结尾如user{}: {name: John}。后端 body 解析器可利用此元信息自动将值解析为 JSONindexes: null \| false \| truefalse控制如何为扁平类数组的展开键添加索引详见下表maxDepth: number100序列化器递归的最大对象嵌套深度超出抛出code: ERR_FORM_DATA_DEPTH_EXCEEDED的AxiosError设为Infinity禁用限制Blob: typeof Blob运行时Blob在符合规范的FormData中转换类 ArrayBuffer 值时使用的 Blob 构造函数仅当运行时以其他标识符提供兼容的Blob时才需覆盖indexes的三种取值对应三种展开形态null— 不添加方括号arr: 1arr: 2arr: 3false默认— 添加空方括号arr[]: 1arr[]: 2arr[]: 3true— 添加带索引的方括号arr[0]: 1arr[1]: 2arr[2]: 3这些选项在 toFormData 中逐项读取并设置默认值const metaTokens option(metaTokens, true); const visitor option(visitor) || defaultVisitor; const dots option(dots, false); const indexes option(indexes, false); const _Blob option(Blob) || (typeof Blob ! undefined Blob); const maxDepth option(maxDepth, DEFAULT_FORM_DATA_MAX_DEPTH);其中DEFAULT_FORM_DATA_MAX_DEPTH 100定义在 toFormData.js并与反向转换formDataToJSON共用同一常量保证 FormData 与 JSON 往返转换的对称性。maxDepth 深度保护maxDepth限制是有意为之的安全机制。默认限制 100 保护服务端应用免受深层嵌套载荷的 DoS 攻击源码中build()递归入口每层都会throwIfMaxDepthExceeded(depth)而{}结尾的 JSON 序列化路径也通过stringifyWithDepthLimit在 replacer 中逐层检查深度。当 schema 确实需要超过 100 层嵌套时可提高限制// 当 schema 确实需要超过 100 层嵌套时可提高限制 axios.postForm(/api, data, { formSerializer: { maxDepth: 200 } });安全提示将客户端控制的 JSON 作为data转发给 axios 的服务端代码如果没有此保护容易发生调用栈溢出。除非你的 schema 确实需要否则不要提高maxDepth。对应的测试用例见 toFormData.test.jsmaxDepth: 200时 150 层嵌套可正常序列化、maxDepth: 5时 10 层嵌套被拒绝、maxDepth: Infinity时 500 层嵌套也不触发深度保护。该保护还延伸到AxiosURLSearchParams的 query 参数序列化路径见 AxiosURLSearchParams.js 同样复用toFormData。另外两点实现层面的行为值得了解从源码结构看循环引用检测build()用stack数组记录当前递归路径若发现某个值已在栈中则抛出Circular reference detected in path错误防止自引用对象导致死递归。值转换规则convertValue()把null转空字符串、Date 转 ISO 字符串、布尔转字符串ArrayBuffer/TypedArray 在规范合规的 FormData 中转为 Blob否则在 Node.js 中转为 Buffer两者都不可用时抛出Blob is not supported. Use a Buffer instead.。序列化过程示例对于以下对象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}]);对照前文规则可以逐条验证扁平数组arr默认展开为arr[]含嵌套数组的arr2不是扁平数组按对象路径展开为arr2[0]、arr2[1][0]、arr2[2]users数组的元素是对象递归展开为users[0][name]形式顶层键obj2{}保留{}结尾metaTokens默认true其值整体JSON.stringify。将 FormData 转回 JSONaxios.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 用正则/[^.[\]]|\[([^.[\]]*)\]/g把foo[x][y]、foo.x.y等字段名解析为路径段数组再递归buildPath构建嵌套结构——注释中明确说明user-name、user name这类键会被整体保留为字面键不会被拆分。同名键重复出现时会把已有值与新值合并为数组实现多值字段如arr[]展开后的多个同键字段到数组的还原。该函数同样受DEFAULT_FORM_DATA_MAX_DEPTH 100的深度保护并显式跳过__proto__键以防原型污染。axios.formToJSON的导出见 axios.jsaxios.formToJSON (thing) formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);注意它还接受原生 HTMLform元素内部先转换为FormData再解析。postForm / putForm / patchForm 快捷方法axios 支持以下快捷方法postForm、putForm、patchForm它们分别是相应 HTTP 方法的变体并预设Content-Type请求头为multipart/form-data——等价于手动在 config 中指定该 Content-Type 并触发自动序列化但更简洁。它们在 Axios.js 中与post/put/patch在同一循环中生成utils.forEach([post, put, patch, query], function forEachMethodWithData(method) { function generateHTTPMethod(isForm) { return function httpMethod(url, data, config) { return this.request( mergeConfig(config || {}, { method, headers: isForm ? { Content-Type: multipart/form-data, } : {}, url, data, }) ); }; } Axios.prototype[method] generateHTTPMethod(); // QUERY is a safe/idempotent read method; multipart form bodies dont fit // its semantics, so no queryForm shorthand is generated. if (method ! query) { Axios.prototype[method Form] generateHTTPMethod(true); } });从源码结构看有一个值得注意的细节query方法虽然也在此循环中但不会生成queryForm——源码注释说明 QUERY 是安全/幂等的读方法multipart 表单体的语义与之不符。小结与延伸阅读本文覆盖了 axios multipart/form-data 支持的完整链路手动FormData直传浏览器不手动设 Content-TypeNode.js 用form-data库、Content-Type: multipart/form-data触发的自动对象序列化、formDataHeaderPolicy请求头策略、{}/[]特殊结尾、formSerializer的六个配置项尤其maxDepth安全限制、formToJSON反向转换以及postForm/putForm/patchForm快捷方法。默认请求转换与 FormData 判定逻辑defaults/index.js序列化器核心实现与深度保护helpers/toFormData.js反向转换实现helpers/formDataToJSON.js请求头策略合并core/setFormDataHeaders.js、helpers/resolveConfig.js序列化器测试含 maxDepth 用例tests/unit/toFormData.test.js表单上传实战示例examples/postMultipartFormData、大文件上传示例 examples/uploadformDataHeaderPolicy完整配置说明docs/pages/advanced/request-config.md相关的 URL 编码表单格式复用同一序列化器x-www-form-urlencoded-format【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考