Axios 文件上传实战postForm、FileList 与 Node.js 流式传输的源码级解析【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axiosAxios 通过postForm系列快捷方法、File/FileList自动识别以及 Node.js 下的form-data流式封装让multipart/form-data文件上传在浏览器与 Node.js 两个环境都可以保持简洁写法。本文以 Axios 仓库的官方文件上传文档为主线结合 lib/core/Axios.js、lib/defaults/index.js、lib/helpers/toFormData.js 与 lib/adapters/http.js 的源码实现完整覆盖单文件、多文件、进度回调、Node 流式上传、Buffer 上传的写法并给出maxRedirects、formDataHeaderPolicy等关键参数的底层依据与注意事项。一、两种上传入口postForm 与 FormDataAxios 文件上传文档docs/fr/pages/advanced/file-posting.md开篇即给出结论当需要multipart/form-data上传时使用postForm或手工构造FormData。这两条路径在源码中的差异非常清晰。post/put/patch及对应的Form变体方法在 lib/core/Axios.js 中由同一个工厂函数generateHTTPMethod(isForm)生成// lib/core/Axios.js节选 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(); if (method ! query) { Axios.prototype[method Form] generateHTTPMethod(true); }从源码结构看postForm与普通post的唯一区别是postForm会预先写入Content-Type: multipart/form-data请求头且该头不带 boundary之后交给默认的transformRequest决定是否把对象转换为FormData。这一转换逻辑位于 lib/defaults/index.js// lib/defaults/index.js节选transformRequest 默认实现 if (isObjectPayload) { const formSerializer own(this, formSerializer); if (contentType.indexOf(application/x-www-form-urlencoded) -1) { return toURLEncodedForm(data, formSerializer).toString(); } 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 ); } }这里有两处直接对应文档行为的关键分支Content-Type 已含multipart/form-data即postForm或手动设置该头的post→ 调用 lib/helpers/toFormData.js 把普通对象转成FormDatadata 本身就是一个FileList→ 先包一层{ files[]: data }再转换这正是文档中“多文件默认字段名”的来源。此外若data已经是FormData实例transformRequest会原样放行除非 Content-Type 是 JSON则经formDataToJSON序列化所以手工FormData路径不经过任何转换。仓库中的可运行示例 examples/postMultipartFormData/index.html 同时演示了两种入口既可以new FormData()后 append 文件再 POST也可以传普通对象并让 Axios 自动转换配合 examples/postMultipartFormData/server.js 可本地验证请求体内容。二、浏览器上传单个文件文档“Single file (browser)”一节的写法是把File对象直接作为字段值传入postFormawait axios.postForm(https://httpbin.org/post, { description: My profile photo, file: document.querySelector(#fileInput).files[0], });Axios 会检测该字段并自动使用正确的内容类型。其原理链条是postForm设置Content-Type: multipart/form-data见上一节默认transformRequest发现 Content-Type 匹配后调用toFormData(data)lib/helpers/toFormData.js 的convertValue负责值级转换null变空字符串、Date转 ISO 字符串、布尔转字符串在 Node 环境中ArrayBuffer/TypedArray会被转成Blob若目标FormData是规范兼容实现或Buffer平台支持 Buffer 时见 lib/platform/node/classes/Buffer.js否则抛出AxiosError(Blob is not supported. Use a Buffer instead.)。浏览器中的File是规范兼容的Blob会被原样 append 到FormData浏览器在发送时自动为其生成filename与Content-Type取File.type。这也是为什么文档强调“不需要手动设置文件字段的 Content-Type”——真正需要 boundary 的 multipart 头由浏览器或 Node 适配器见下文在发送阶段补齐。三、浏览器上传多个文件文档“Multiple files (browser)”给出三种多文件策略均可由 lib/helpers/toFormData.js 的defaultVisitor解释。1. 直接传 FileList默认字段名 files[]await axios.postForm( https://httpbin.org/post, document.querySelector(#fileInput).files );FileList在transformRequest中被识别后包装为{ files[]: data }lib/defaults/index.js随后defaultVisitor对[]结尾的键执行removeBrackets把每个元素 append 为files[]。结果所有文件共享同一字段名files[]服务端按同名数组接收。[]后缀的识别逻辑在 lib/helpers/toFormData.jsfunction removeBrackets(key) { return utils.endsWith(key, []) ? key.slice(0, -2) : key; }以及 lib/helpers/toFormData.js 的数组分支indexes选项控制字段名形态// 默认 indexes falsekey []indexes truekey[index]indexes null直接用 key indexes true ? renderKey([key], index, dots) : indexes null ? key : key []2. 自定义字段名键名加 []await axios.postForm(https://httpbin.org/post, { files[]: document.querySelector(#fileInput).files, });即把FileList或File对象数组显式挂在自定义键下只要键以[]结尾就会触发逐个 append不写[]的话数组整体不是“可访问对象”会被convertValue处理行为与文档承诺的“custom field name”一致的前提就是加上[]后缀。3. 每个文件使用不同字段名当服务端要求每个文件对应独立字段时文档建议手工构造FormData并使用普通postconst formData new FormData(); formData.append(avatar, avatarFile); formData.append(cover, coverFile); await axios.post(https://httpbin.org/post, formData);由于data已是FormDatatransformRequest直接放行字段名完全由你控制。仓库测试 tests/unit/toFormData.test.js 对toFormData的键渲染dots、indexes、metaTokens选项有成体系的用例可作行为参照。四、浏览器跟踪上传进度文档“Tracking upload progress (browser)”一节await axios.postForm(https://httpbin.org/post, { file: document.querySelector(#fileInput).files[0], }, { onUploadProgress: (progressEvent) { const percent Math.round( (progressEvent.loaded * 100) / progressEvent.total ); console.log(Upload progress: ${percent}%); }, });浏览器侧的实现位于 XHR 适配器 lib/adapters/xhr.jsif (onUploadProgress request.upload) { [uploadThrottled, flushUpload] progressEventReducer(onUploadProgress); }request.upload绑定的是 XMLHttpRequest 的Upload对象progress事件经 lib/helpers/progressEventReducer.js 节流/防抖后回调用户函数。完整的事件字段列表loaded、total、percent、lengthComputable等见文档 docs/pages/advanced/progress-capturing.md。五、Node.js用文件流上传文档“Files in Node.js”一节的推荐做法是用fs.createReadStream避免整文件驻留内存import fs from fs; import FormData from form-data; import axios from axios; const form new FormData(); form.append(file, fs.createReadStream(/path/to/file.jpg)); form.append(description, My uploaded file); await axios.post(https://httpbin.org/post, form);文档提示tipNode.js 环境中创建FormData需要 npm 包form-dataNode.js v18 已原生提供全局FormData。仓库源码印证了这两条路径在 HTTP 适配器中分别处理见 lib/adapters/http.js// support for spec compliant FormData objects if (utils.isSpecCompliantForm(data)) { const userBoundary headers.getContentType(/boundary([-_\w\d]{10,70})/i); data formDataToStream( data, (formHeaders) { headers.set(formHeaders); }, { tag: axios-${VERSION}-boundary, boundary: (userBoundary userBoundary[1]) || undefined, } ); // support for https://www.npmjs.com/package/form-data api } else if ( utils.isFormData(data) utils.isFunction(data.getHeaders) data.getHeaders ! Object.prototype.getHeaders ) { setFormDataHeaders(headers, data.getHeaders(), own(formDataHeaderPolicy)); // ...若未带 Content-Length尝试 data.getLength() 补齐 }两条路径的要点规范兼容 FormDataNode 18 全局实现由 lib/helpers/formDataToStream.js 把FormData转成可流式消费的 Readable Streamboundary 默认自动生成也可从用户提供的Content-Type头中解析复用npmform-data包Axios 通过data.getHeaders()获取该包生成的头含带 boundary 的Content-Type并经 lib/core/setFormDataHeaders.js 合并到请求头若请求未声明Content-Length还会异步调用data.getLength()尝试补齐。form-data包的引入位置即 lib/platform/node/classes/FormData.js它只是import FormData from form-data的一行再导出被toFormData在 Node 平台下用作默认FormData构造器见 lib/helpers/toFormData.js 的PlatformFormData导入与new (PlatformFormData || FormData)()。六、Node.js直接上传 Buffer文档“Uploading a Buffer (Node.js)”一节展示了内存Buffer的上传方式const buffer Buffer.from(Hello, world!); const form new FormData(); form.append(file, buffer, { filename: hello.txt, contentType: text/plain, knownLength: buffer.length, }); await axios.post(https://httpbin.org/post, form);第三个参数是 npmform-data包的append选项filename决定 multipart 中的文件名contentType决定该 part 的 MIME 类型knownLength用于让form-data能计算精确的Content-Length。在 Node 侧toFormData的convertValue也会把ArrayBuffer/TypedArray转成Buffer当目标是form-data这类非规范实现时见 lib/helpers/toFormData.js因此对putForm/postForm直接传包含 Buffer 字段的对象同样是可行的。七、两条关键注意事项含源码依据文档以 warning 与 danger 形式给出两条 Node.js 限制均有源码支撑1. Node.js 下 FormData 上传暂不支持进度捕获Node 的 HTTP 适配器虽然读取了onUploadProgresslib/adapters/http.js 解构出该配置但对 FormData 流式数据spec-compliant 转formDataToStream或form-data包流并未把上传字节数回传给回调文档明确“CapturingFormDataupload progress is not currently supported in Node.js environments”实际项目中应只在浏览器侧依赖onUploadProgress。2. 上传可读流时建议设置 maxRedirects: 0文档原文danger 块When uploading a readable stream in Node.js, setmaxRedirects: 0to prevent thefollow-redirectspackage from buffering the entire stream in RAM.源码依据在 lib/adapters/http.js 的传输层选择逻辑} else if (maxRedirects 0) { transport isHttpsRequest ? https : http; isNativeTransport true; } else { // 默认走 follow-redirects 封装的 transport if (maxRedirects) { options.maxRedirects maxRedirects; } ... }maxRedirects: 0时 Axios 直接使用原生http/https模块流被原样 pipe 出去否则follow-redirects为了支持重定向回放会把整个请求体缓冲到内存/临时状态中大文件场景下这是显性的内存风险。代价是请求不再自动跟随重定向若接口有 3xx 跳转需自行处理。补充formDataHeaderPolicy合并 npmform-data头部时还可通过formDataHeaderPolicy控制策略实现在 lib/core/setFormDataHeaders.js取值content-only时仅复制content-type/content-length两个头其余策略下整包合并data.getHeaders()的结果。默认策略为合并全部头一般无需配置。八、toFormData 的高级行为速览postForm的对象转换由 lib/helpers/toFormData.js 完成除上文提及的indexes/[]规则外还有几个默认行为值得知道{}元标记键以{}结尾且值为对象时值会被JSON.stringify成字符串字段metaTokens默认true时保留{}后缀嵌套深度限制DEFAULT_FORM_DATA_MAX_DEPTH 100lib/helpers/toFormData.js超过会抛出ERR_FORM_DATA_DEPTH_EXCEEDED对象中存在循环引用会直接抛Circular reference detected错误formSerializer配置项可通过formSerializer传入visitor/dots/metaTokens/indexes等选项定制序列化见 lib/defaults/index.js 中own(this, formSerializer)的传递React Native 特判isReactNative(formData) isReactNativeBlob(value)时直接 append 原始 Bloblib/helpers/toFormData.js。九、适用前提与小结适用前提以上结论基于当前仓库的 lib/ 源码与 docs/pages/advanced/file-posting.md、docs/fr/pages/advanced/file-posting.md 文档Node 侧示例要求能安装 npm 包form-data或运行在 Node v18原生全局FormData环境行为速查postFormpost 预置 multipart 头FileList裸传 → 字段名files[]键名带[]→ 同名多值手工FormData→ 字段名完全自定义进阶控制onUploadProgress仅限浏览器 XHR 路径Node 流式上传务必考虑maxRedirects: 0formDataHeaderPolicy、formSerializer分别控制表单头合并与字段序列化细节。参考实现与测试入口examples/postMultipartFormData/index.html、examples/postMultipartFormData/server.js、tests/unit/toFormData.test.js、tests/unit/axiosHeaders.test.js、lib/adapters/xhr.js、lib/adapters/http.js。【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考