首页
/ axios 表单请求实战:x-www-form-urlencoded 的 URLSearchParams、自动序列化与深度限制

axios 表单请求实战:x-www-form-urlencoded 的 URLSearchParams、自动序列化与深度限制

2026-09-04 22:02:49作者:冯爽妲Honey

axios 默认将 JavaScript 对象序列化为 JSON 发送。当后端要求以 application/x-www-form-urlencoded(传统 HTML 表单)格式提交数据时,需要理解本文覆盖的三条路径:直接使用 URLSearchParams、使用 qs/querystring 手动序列化、以及 axios v0.21.0 引入的「对象自动序列化」机制。读完本文,你将掌握表单编码请求的完整写法、嵌套对象的键名规则、底层序列化器的源码实现,以及 maxDepth 深度保护参数的原理与安全意义。

一、默认行为:对象被序列化为 JSON

axios 的请求体转换发生在默认 transformRequest 中。从 lib/defaults/index.js 可以看到默认逻辑:

transformRequest: [
  function transformRequest(data, headers) {
    const contentType = headers.getContentType() || '';
    const hasJSONContentType = contentType.indexOf('application/json') > -1;
    const isObjectPayload = utils.isObject(data);
    // ...
    if (isObjectPayload || hasJSONContentType) {
      headers.setContentType('application/json', false);
      return stringifySafely(data); // JSON.stringify
    }
    return data;
  },
],

也就是说:只要你传入的是一个普通对象且没有声明表单类型的 Content-Type,axios 就会 JSON.stringify 它并把请求头设为 application/json。要走表单编码,必须显式声明 Content-Type: application/x-www-form-urlencoded,或直接把数据写成 URLSearchParams 实例。

二、方案一:URLSearchParams(推荐)

URLSearchParams 是标准 Web API,在绝大多数浏览器中得到支持,Node.js 从 v10(2018 年)开始内置。它是发送表单编码数据的首选方式:

const params = new URLSearchParams({ foo: 'bar' });
params.append('extraparam', 'value');
axios.post('/foo', params);

axios 对 URLSearchParams 有专门的处理分支。在 lib/defaults/index.js 中:

if (utils.isURLSearchParams(data)) {
  headers.setContentType('application/x-www-form-urlencoded;charset=utf-8', false);
  return data.toString();
}

即:当 dataURLSearchParams 实例时,axios 会自动把请求头设置为 application/x-www-form-urlencoded;charset=utf-8,并调用其 toString() 得到 foo=bar&extraparam=value 形式的请求体。你甚至不需要手动设置 Content-Type

三、方案二:qs / querystring 手动序列化(兼容老环境)

针对较旧的浏览器或没有 URLSearchParams 的环境,可以用 qs 库把对象序列化为表单编码字符串:

const qs = require('qs');
axios.post('/foo', qs.stringify({ bar: 123 }));

此时 data 是一个字符串,transformRequest 会跳过所有对象分支直接透传。但字符串不会自动携带表单 Content-Type,如果需要完全控制请求头和请求方法,应显式设置 Content-Type,并像普通配置一样调用 axios:

import qs from 'qs';

const data = { bar: 123 };
const options = {
  method: 'POST',
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  data: qs.stringify(data),
  url: '/foo',
};
axios(options);

Node.js 内置 querystring 模块(不推荐用于新代码)

在非常老的 Node.js 版本中,还可以使用内置 querystring 模块:

const querystring = require('querystring');
axios.post('https://something.com/', querystring.stringify({ foo: 'bar' }));

需要注意两点:

  1. 该模块自 Node.js v16 起已被标记为废弃(deprecated),新代码应优先选择 URLSearchParamsqs
  2. querystring 对嵌套对象存在已知问题,需要序列化嵌套对象时请优先使用 qs

四、v0.21.0 起:对象自动序列化为 URLSearchParams

从 v0.21.0 开始,axios 具备了一个非常实用的能力:Content-Type 被设置为 application/x-www-form-urlencoded 时,直接传 JavaScript 对象,axios 会自动将其序列化为表单编码格式。例如向 POST 请求传数据:

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

await axios.postForm('https://postman-echo.com/post', data, {
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
});

这里有一个值得注意的细节:axios.postFormlib/core/Axios.js 中的 generateHTTPMethod(true) 生成,默认会注入 'Content-Type': 'multipart/form-data' 头:

Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
// 其中 isForm 为 true 时:
// headers: { 'Content-Type': 'multipart/form-data' }

但请求配置中的 headers 会在 mergeConfig 阶段覆盖实例默认头,因此上面示例中显式传入的 application/x-www-form-urlencoded 会生效,最终进入 transformRequest 的这条分支(lib/defaults/index.js):

if (isObjectPayload) {
  const formSerializer = own(this, 'formSerializer');
  if (contentType.indexOf('application/x-www-form-urlencoded') > -1) {
    return toURLEncodedForm(data, formSerializer).toString();
  }
  // ...multipart 分支
}

服务器实际收到的键名

上述 data 对象会被自动序列化为 URLSearchParams 并发送,服务器(如 postman-echo)收到的数据为:

{
  "x": "1",
  "arr[]": ["1", "2", "3"],
  "arr2[0]": "1",
  "arr2[1][0]": "2",
  "arr2[2]": "3",
  "users[0][name]": "Peter",
  "users[0][surname]": "Griffin",
  "users[1][name]": "Thomas",
  "users[1][surname]": "Anderson"
}

键名规则与序列化器的遍历逻辑一一对应(见下文第五节的 defaultVisitor):

  • 扁平数组(元素全部非可遍历)→ 重复的 arr[]=1&arr[]=2&... 形式;
  • 混合数组/嵌套结构 → 带下标的 arr2[0]arr2[1][0] 形式;
  • 对象数组 → users[0][name] 这类深层方括号键名。

如果你的后端 body 解析器支持嵌套对象解码(如 express 的 body-parser 配合 extended: true),服务端可以自动还原出与原始对象相同的结构:

var app = express();

app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies

app.post('/', function (req, res, next) {
  // echo body as JSON
  res.send(JSON.stringify(req.body));
});

server = app.listen(3000);

五、源码深入:toURLEncodedForm 与共享递归遍历器

自动序列化的入口是 lib/helpers/toURLEncodedForm.js,实现非常薄——它复用了 FormData 序列化器的同一个递归遍历器 toFormData,只是目标容器换成了 URLSearchParams

export default function toURLEncodedForm(data, options) {
  return toFormData(data, new platform.classes.URLSearchParams(), {
    visitor: function (value, key, path, helpers) {
      if (platform.isNode && utils.isBuffer(value)) {
        this.append(key, value.toString('base64'));
        return false;
      }
      return helpers.defaultVisitor.apply(this, arguments);
    },
    ...options,
  });
}

两个值得注意的实现事实:

  1. 容器按平台选择platform.classes.URLSearchParams 分别来自 lib/platform/browser/classes/lib/platform/node/classes/,保证浏览器与 Node 环境行为一致;
  2. Node 下 Buffer 特判Buffer 值会被 toString('base64') 后追加,避免被当作普通对象展开。

核心的递归遍历器位于 lib/helpers/toFormData.js,其 defaultVisitor 决定了上文看到的键名规则:

function defaultVisitor(value, key, path) {
  // 扁平数组或 key 以 '[]' 结尾 → 去掉括号后对每个元素 append(key + '[]')
  // 普通对象/数组 → 返回 true,交给外层 build() 以 path.concat(key) 递归下钻
  // 叶子值 → formData.append(renderKey(path, key, dots), convertValue(value))
}

其中 renderKeylib/helpers/toFormData.js)负责把路径数组 ['arr2', '1', '0'] 渲染为 arr2[1][0](默认方括号风格;开启 dots 选项时则为点号风格)。对 nullDateBoolean 等值的归一化由 convertValue 完成(null → 空串,Date → ISO 字符串)。

另外,共享的 Blob 序列化选项(SerializerOptions.Blob)只作用于符合规范的 FormData:在 lib/helpers/toFormData.js 中,useBlob = _Blob && utils.isSpecCompliantForm(formData)URLSearchParams 容器不满足该条件,因此该选项对表单编码序列化没有效果

六、params 序列化:GET 查询串的 maxDepth 深度保护

maxDepth 不只作用于请求体。当 axios 用 AxiosURLSearchParams 序列化 params(GET 查询参数)时,调用的是同一个递归遍历器。调用链为:lib/helpers/buildURL.jslib/helpers/AxiosURLSearchParams.jstoFormData(params, this, options)

function AxiosURLSearchParams(params, options) {
  this._pairs = [];
  params && toFormData(params, this, options); // options 即 paramsSerializer
}

lib/helpers/toFormData.js 中定义了默认深度上限:

// Default nesting limit shared with the inverse transform (formDataToJSON) so
// the FormData <-> JSON round-trip stays symmetric.
export const DEFAULT_FORM_DATA_MAX_DEPTH = 100;

递归构建过程中每一层都会检查深度(lib/helpers/toFormData.jsL246-L249):

function throwIfMaxDepthExceeded(depth) {
  if (depth > maxDepth) {
    throw new AxiosError(
      'Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth,
      AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED
    );
  }
}

超出限制时抛出 codeERR_FORM_DATA_DEPTH_EXCEEDEDAxiosError(常量定义见 lib/core/AxiosError.js),而不是任由调用栈溢出导致进程崩溃。

如果你的 params 对象合理地嵌套超过 100 层,可以显式调高上限:

// Raise the limit if your params object legitimately nests deeper than 100 levels:
axios.get('/api', { params: deepObject, paramsSerializer: { maxDepth: 200 } });

安全提示:仅在数据结构确实需要时才调高 maxDepth。默认值 100 能保护「把客户端可控数据转发为 axios params」的服务端代码,抵御通过深度嵌套对象发起的拒绝服务(DoS)攻击。同样的机制也通过 build() 中的循环引用检测(stack.indexOf(value) !== -1 时抛出 Circular reference detected)覆盖自引用对象。

请求体一侧对应的是 formSerializer 选项:transformRequesttoURLEncodedForm(data, formSerializer) 会把 formSerializer 原样透传给 toFormData(见 lib/defaults/index.js),因此需要为深层嵌套的请求体调高上限时,写法为:

axios.post(url, deepData, {
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  formSerializer: { maxDepth: 200 },
});

此外,toFormData 支持 metaTokens(默认 true)、dots(默认 false)、indexes(默认 false)等选项;{} 后缀的键会触发带深度限制的 JSON.stringifydots: true 时键名改用点号风格,indexes 可控制数组下标的渲染方式。这些选项同样可通过 formSerializer / paramsSerializer 透传。

七、三种方案选型小结

场景 推荐方案 依据
现代浏览器 + Node v10+ 直接传 URLSearchParams 实例 标准 API,axios 自动设置 Content-Typelib/defaults/index.js
旧浏览器/无 URLSearchParams 环境 qs.stringify + 显式 Content-Type 字符串数据原样透传
直接传 JS 对象(含嵌套) postForm/post + content-type: application/x-www-form-urlencoded v0.21.0 起自动序列化,支持 maxDepth 等选项
Node 老版本 querystring 已废弃(Node v16 起),仅存量代码使用

完整文档另见英文原文 docs/pages/advanced/x-www-form-urlencoded-format.md 及中文版本 docs/zh/pages/advanced/x-www-form-urlencoded-format.md。相关测试可参考 tests/unit/axios.test.jstests/unit/helpers/AxiosURLSearchParams.test.jstests/unit/toFormData.test.js,其中覆盖了数组、嵌套对象序列化及深度上限的行为验证。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
docsdocs
暂无描述
Markdown
889
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341