2
0
mirror of https://github.com/tenrok/axios.git synced 2026-06-17 19:21:29 +03:00

fix: form data recursion (#10726)

* docs: update docs with new change

* feat: added tests for the form data change

* feat: update readme with latest changes to form data

* chore: added new error code

* feat: implement gaurd in form data helper

* fix: failing test and correctly throw

* chore: fix docs issues

* fix: types
This commit is contained in:
Jay
2026-04-15 20:28:33 +02:00
committed by GitHub
parent 42b215406e
commit 85132ffba1
19 changed files with 627 additions and 361 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ on:
push:
branches: [v1.x]
pull_request:
branches: ["**"]
branches: ['**']
permissions: {}
+197 -197
View File
@@ -154,36 +154,36 @@ $ bun add axios
Once the package is installed, you can import the library using `import` or `require` approach:
```js
import axios, { isCancel, AxiosError } from "axios";
import axios, { isCancel, AxiosError } from 'axios';
```
You can also use the default export, since the named export is just a re-export from the Axios factory:
```js
import axios from "axios";
import axios from 'axios';
console.log(axios.isCancel("something"));
console.log(axios.isCancel('something'));
```
If you use `require` for importing, **only the default export is available**:
```js
const axios = require("axios");
const axios = require('axios');
console.log(axios.isCancel("something"));
console.log(axios.isCancel('something'));
```
For some bundlers and some ES6 linters you may need to do the following:
```js
import { default as axios } from "axios";
import { default as axios } from 'axios';
```
For cases where something went wrong when trying to import a module into a custom or legacy environment,
you can try importing the module package directly:
```js
const axios = require("axios/dist/browser/axios.cjs"); // browser commonJS bundle (ES2017)
const axios = require('axios/dist/browser/axios.cjs'); // browser commonJS bundle (ES2017)
// const axios = require('axios/dist/node/axios.cjs'); // node commonJS bundle (ES2017)
```
@@ -204,11 +204,11 @@ Using unpkg CDN:
## Example
```js
import axios from "axios";
import axios from 'axios';
//const axios = require('axios'); // legacy way
try {
const response = await axios.get("/user?ID=12345");
const response = await axios.get('/user?ID=12345');
console.log(response);
} catch (error) {
console.error(error);
@@ -216,7 +216,7 @@ try {
// Optionally the request above could also be done as
axios
.get("/user", {
.get('/user', {
params: {
ID: 12345,
},
@@ -234,7 +234,7 @@ axios
// Want to use async/await? Add the `async` keyword to your outer function/method.
async function getUser() {
try {
const response = await axios.get("/user?ID=12345");
const response = await axios.get('/user?ID=12345');
console.log(response);
} catch (error) {
console.error(error);
@@ -248,9 +248,9 @@ async function getUser() {
Performing a `POST` request
```js
const response = await axios.post("/user", {
firstName: "Fred",
lastName: "Flintstone",
const response = await axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone',
});
console.log(response);
```
@@ -259,11 +259,11 @@ Performing multiple concurrent requests
```js
function getUserAccount() {
return axios.get("/user/12345");
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get("/user/12345/permissions");
return axios.get('/user/12345/permissions');
}
Promise.all([getUserAccount(), getUserPermissions()]).then(function (results) {
@@ -281,11 +281,11 @@ Requests can be made by passing the relevant config to `axios`.
```js
// Send a POST request
axios({
method: "post",
url: "/user/12345",
method: 'post',
url: '/user/12345',
data: {
firstName: "Fred",
lastName: "Flintstone",
firstName: 'Fred',
lastName: 'Flintstone',
},
});
```
@@ -293,18 +293,18 @@ axios({
```js
// GET request for remote image in node.js
const response = await axios({
method: "get",
url: "https://bit.ly/2mTM3nY",
responseType: "stream",
method: 'get',
url: 'https://bit.ly/2mTM3nY',
responseType: 'stream',
});
response.data.pipe(fs.createWriteStream("ada_lovelace.jpg"));
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'));
```
##### axios(url[, config])
```js
// Send a GET request (default method)
axios("/user/12345");
axios('/user/12345');
```
### Request method aliases
@@ -348,9 +348,9 @@ You can create a new instance of axios with a custom config.
```js
const instance = axios.create({
baseURL: "https://some-domain.com/api/",
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: { "X-Custom-Header": "foobar" },
headers: { 'X-Custom-Header': 'foobar' },
});
```
@@ -436,7 +436,12 @@ These are the available config options for making requests. Only the `url` is re
serialize?: (params: Record<string, any>, options?: ParamsSerializerOptions ),
// Configuration for formatting array indexes in the params.
indexes: false // Three available options: (1) indexes: null (leads to no brackets), (2) (default) indexes: false (leads to empty brackets), (3) indexes: true (leads to brackets with indexes).
indexes: false, // Three available options: (1) indexes: null (leads to no brackets), (2) (default) indexes: false (leads to empty brackets), (3) indexes: true (leads to brackets with indexes).
// Maximum object nesting depth when serializing params. Payloads deeper than this throw an
// AxiosError with code ERR_FORM_DATA_DEPTH_EXCEEDED. Default: 100. Set to Infinity to disable.
maxDepth: 100
},
// `data` is the data to be sent as the request body
@@ -679,6 +684,7 @@ These are the available config options for making requests. Only the `url` is re
dots: boolean; // use dots instead of brackets format
metaTokens: boolean; // keep special endings like {} in parameter key
indexes: boolean; // array indexes format null - no brackets, false - empty brackets, true - brackets with indexes
maxDepth: 100; // maximum object nesting depth; throws AxiosError (ERR_FORM_DATA_DEPTH_EXCEEDED) if exceeded. Set to Infinity to disable.
},
// http adapter only (node.js)
@@ -688,6 +694,7 @@ These are the available config options for making requests. Only the `url` is re
]
}
```
## 🔥 HTTP/2 Support
Axios has experimental HTTP/2 support available via the Node.js HTTP adapter.
@@ -731,7 +738,7 @@ The response to a request contains the following information.
When using `then`, you will receive the response as follows:
```js
const response = await axios.get("/user/12345");
const response = await axios.get('/user/12345');
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
@@ -748,14 +755,13 @@ You can specify config defaults that will be applied to every request.
### Global axios defaults
```js
axios.defaults.baseURL = "https://api.example.com";
axios.defaults.baseURL = 'https://api.example.com';
// Important: If axios is used with multiple domains, the AUTH_TOKEN will be sent to all of them.
// See below for an example using Custom instance defaults instead.
axios.defaults.headers.common["Authorization"] = AUTH_TOKEN;
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post["Content-Type"] =
"application/x-www-form-urlencoded";
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
```
### Custom instance defaults
@@ -763,11 +769,11 @@ axios.defaults.headers.post["Content-Type"] =
```js
// Set config defaults when creating the instance
const instance = axios.create({
baseURL: "https://api.example.com",
baseURL: 'https://api.example.com',
});
// Alter defaults after instance has been created
instance.defaults.headers.common["Authorization"] = AUTH_TOKEN;
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
```
### Config order of precedence
@@ -784,7 +790,7 @@ const instance = axios.create();
instance.defaults.timeout = 2500;
// Override timeout for this request as it's known to take a long time
instance.get("/longRequest", {
instance.get('/longRequest', {
timeout: 5000,
});
```
@@ -806,7 +812,7 @@ instance.interceptors.request.use(
function (error) {
// Do something with the request error
return Promise.reject(error);
},
}
);
// Add a response interceptor
@@ -820,7 +826,7 @@ instance.interceptors.response.use(
// Any status codes that fall outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
},
}
);
```
@@ -865,11 +871,11 @@ to the options object that will tell axios to run the code synchronously and avo
```js
axios.interceptors.request.use(
function (config) {
config.headers.test = "I am only a header!";
config.headers.test = 'I am only a header!';
return config;
},
null,
{ synchronous: true },
{ synchronous: true }
);
```
@@ -881,15 +887,15 @@ asynchronous request interceptor that only needs to run at certain times.
```js
function onGetCall(config) {
return config.method === "get";
return config.method === 'get';
}
axios.interceptors.request.use(
function (config) {
config.headers.test = "special get headers";
config.headers.test = 'special get headers';
return config;
},
null,
{ runWhen: onGetCall },
{ runWhen: onGetCall }
);
```
@@ -913,12 +919,12 @@ const interceptor = (id) => (base) => {
return base;
};
instance.interceptors.request.use(interceptor("Request Interceptor 1"));
instance.interceptors.request.use(interceptor("Request Interceptor 2"));
instance.interceptors.request.use(interceptor("Request Interceptor 3"));
instance.interceptors.response.use(interceptor("Response Interceptor 1"));
instance.interceptors.response.use(interceptor("Response Interceptor 2"));
instance.interceptors.response.use(interceptor("Response Interceptor 3"));
instance.interceptors.request.use(interceptor('Request Interceptor 1'));
instance.interceptors.request.use(interceptor('Request Interceptor 2'));
instance.interceptors.request.use(interceptor('Request Interceptor 3'));
instance.interceptors.response.use(interceptor('Response Interceptor 1'));
instance.interceptors.response.use(interceptor('Response Interceptor 2'));
instance.interceptors.response.use(interceptor('Response Interceptor 3'));
// Console output:
// Request Interceptor 3
@@ -982,7 +988,7 @@ Below is a list of potential axios identified error:
The default behavior is to reject every response that returns with a status code that falls out of the range of 2xx and treat it as an error.
```js
axios.get("/user/12345").catch(function (error) {
axios.get('/user/12345').catch(function (error) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
@@ -996,7 +1002,7 @@ axios.get("/user/12345").catch(function (error) {
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log("Error", error.message);
console.log('Error', error.message);
}
console.log(error.config);
});
@@ -1005,7 +1011,7 @@ axios.get("/user/12345").catch(function (error) {
Using the `validateStatus` config option, you can override the default condition (status >= 200 && status < 300) and define HTTP code(s) that should throw an error.
```js
axios.get("/user/12345", {
axios.get('/user/12345', {
validateStatus: function (status) {
return status < 500; // Resolve only if the status code is less than 500
},
@@ -1015,7 +1021,7 @@ axios.get("/user/12345", {
Using `toJSON` you get an object with more information about the HTTP error.
```js
axios.get("/user/12345").catch(function (error) {
axios.get('/user/12345').catch(function (error) {
console.log(error.toJSON());
});
```
@@ -1025,7 +1031,7 @@ axios.get("/user/12345").catch(function (error) {
```js
async function fetchWithTimeout() {
try {
const response = await axios.get("https://example.com/data", {
const response = await axios.get('https://example.com/data', {
timeout: 5000, // 5 seconds
transitional: {
// set to true if you prefer ETIMEDOUT over ECONNABORTED
@@ -1033,19 +1039,19 @@ async function fetchWithTimeout() {
},
});
console.log("Response:", response.data);
console.log('Response:', response.data);
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
console.error("Request timed out. Please try again.");
if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
console.error('Request timed out. Please try again.');
return;
}
console.error("Axios error:", error.message);
console.error('Axios error:', error.message);
return;
}
console.error("Unexpected error:", error);
console.error('Unexpected error:', error);
}
}
```
@@ -1060,7 +1066,7 @@ Starting from `v0.22.0` Axios supports AbortController to cancel requests in a f
const controller = new AbortController();
axios
.get("/foo/bar", {
.get('/foo/bar', {
signal: controller.signal,
})
.then(function (response) {
@@ -1085,29 +1091,29 @@ const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios
.get("/user/12345", {
.get('/user/12345', {
cancelToken: source.token,
})
.catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log("Request canceled", thrown.message);
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});
axios.post(
"/user/12345",
'/user/12345',
{
name: "new name",
name: 'new name',
},
{
cancelToken: source.token,
},
}
);
// cancel the request (the message parameter is optional)
source.cancel("Operation canceled by the user.");
source.cancel('Operation canceled by the user.');
```
You can also create a cancel token by passing an executor function to the `CancelToken` constructor:
@@ -1116,7 +1122,7 @@ You can also create a cancel token by passing an executor function to the `Cance
const CancelToken = axios.CancelToken;
let cancel;
axios.get("/user/12345", {
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
@@ -1139,9 +1145,9 @@ cancel();
By default, axios serializes JavaScript objects to `JSON`. To send data in the [`application/x-www-form-urlencoded`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) format instead, you can use the [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) API, which is [supported](http://www.caniuse.com/#feat=urlsearchparams) in the vast majority of browsers, and [Node](https://nodejs.org/api/url.html#url_class_urlsearchparams) starting with v10 (released in 2018).
```js
const params = new URLSearchParams({ foo: "bar" });
params.append("extraparam", "value");
axios.post("/foo", params);
const params = new URLSearchParams({ foo: 'bar' });
params.append('extraparam', 'value');
axios.post('/foo', params);
```
### Query string (Older browsers)
@@ -1151,18 +1157,18 @@ For compatibility with very old browsers, there is a [polyfill](https://github.c
Alternatively, you can encode data using the [`qs`](https://github.com/ljharb/qs) library:
```js
const qs = require("qs");
axios.post("/foo", qs.stringify({ bar: 123 }));
const qs = require('qs');
axios.post('/foo', qs.stringify({ bar: 123 }));
```
Or in another way (ES6),
```js
import qs from "qs";
import qs from 'qs';
const data = { bar: 123 };
const options = {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify(data),
url,
};
@@ -1174,8 +1180,8 @@ axios(options);
For older Node.js engines, you can use the [`querystring`](https://nodejs.org/api/querystring.html) module as follows:
```js
const querystring = require("querystring");
axios.post("https://something.com/", querystring.stringify({ foo: "bar" }));
const querystring = require('querystring');
axios.post('https://something.com/', querystring.stringify({ foo: 'bar' }));
```
You can also use the [`qs`](https://github.com/ljharb/qs) library.
@@ -1192,13 +1198,13 @@ const data = {
arr: [1, 2, 3],
arr2: [1, [2], 3],
users: [
{ name: "Peter", surname: "Griffin" },
{ name: "Thomas", surname: "Anderson" },
{ 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" },
await axios.postForm('https://postman-echo.com/post', data, {
headers: { 'content-type': 'application/x-www-form-urlencoded' },
});
```
@@ -1226,7 +1232,7 @@ const app = express();
app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
app.post("/", function (req, res, next) {
app.post('/', function (req, res, next) {
// echo body as JSON
res.send(JSON.stringify(req.body));
});
@@ -1243,22 +1249,22 @@ Setting the `Content-Type` header is not required as Axios guesses it based on t
```js
const formData = new FormData();
formData.append("foo", "bar");
formData.append('foo', 'bar');
axios.post("https://httpbin.org/post", formData);
axios.post('https://httpbin.org/post', formData);
```
In node.js, you can use the [`form-data`](https://github.com/form-data/form-data) library as follows:
```js
const FormData = require("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"));
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);
axios.post('https://example.com', form);
```
### 🆕 Automatic serialization to FormData
@@ -1269,17 +1275,17 @@ header is set to `multipart/form-data`.
The following request will submit the data in a FormData format (Browser & Node.js):
```js
import axios from "axios";
import axios from 'axios';
axios
.post(
"https://httpbin.org/post",
'https://httpbin.org/post',
{ x: 1 },
{
headers: {
"Content-Type": "multipart/form-data",
'Content-Type': 'multipart/form-data',
},
},
}
)
.then(({ data }) => console.log(data));
```
@@ -1290,18 +1296,18 @@ You can overload the FormData class by setting the `env.FormData` config variabl
but you probably won't need it in most cases:
```js
const axios = require("axios");
var FormData = require("form-data");
const axios = require('axios');
var FormData = require('form-data');
axios
.post(
"https://httpbin.org/post",
'https://httpbin.org/post',
{ x: 1, buf: Buffer.alloc(10) },
{
headers: {
"Content-Type": "multipart/form-data",
'Content-Type': 'multipart/form-data',
},
},
}
)
.then(({ data }) => console.log(data));
```
@@ -1327,6 +1333,19 @@ FormData serializer supports additional options via `config.formSerializer: obje
- `null` - don't add brackets (`arr: 1`, `arr: 2`, `arr: 3`)
- `false`(default) - add empty brackets (`arr[]: 1`, `arr[]: 2`, `arr[]: 3`)
- `true` - add brackets with indexes (`arr[0]: 1`, `arr[1]: 2`, `arr[2]: 3`)
- `maxDepth: number = 100` - maximum object nesting depth the serializer will recurse into. If the
input object exceeds this depth, an `AxiosError` with `code: 'ERR_FORM_DATA_DEPTH_EXCEEDED'` is
thrown instead of overflowing the call stack. This protects server-side applications from DoS
attacks via deeply nested payloads ([GHSA-62hf-57xw-28j9](https://github.com/axios/axios/security/advisories/GHSA-62hf-57xw-28j9)).
Set to `Infinity` to disable the limit and restore pre-fix behaviour.
```js
// Raise the limit for a schema that genuinely nests deeper than 100 levels:
axios.post('/api', data, { formSerializer: { maxDepth: 200 } });
// Same protection applies to params serialization:
axios.get('/api', { params: data, paramsSerializer: { maxDepth: 200 } });
```
Let's say we have an object like this one:
@@ -1336,10 +1355,10 @@ const obj = {
arr: [1, 2, 3],
arr2: [1, [2], 3],
users: [
{ name: "Peter", surname: "Griffin" },
{ name: "Thomas", surname: "Anderson" },
{ name: 'Peter', surname: 'Griffin' },
{ name: 'Thomas', surname: 'Anderson' },
],
"obj2{}": [{ x: 1 }],
'obj2{}': [{ x: 1 }],
};
```
@@ -1347,18 +1366,18 @@ The following steps will be executed by the Axios serializer internally:
```js
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}]');
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}]');
```
Axios supports the following shortcut methods: `postForm`, `putForm`, `patchForm`
@@ -1369,27 +1388,24 @@ which are just the corresponding http methods with the `Content-Type` header pre
You can easily submit a single file:
```js
await axios.postForm("https://httpbin.org/post", {
myVar: "foo",
file: document.querySelector("#fileInput").files[0],
await axios.postForm('https://httpbin.org/post', {
myVar: 'foo',
file: document.querySelector('#fileInput').files[0],
});
```
or multiple files as `multipart/form-data`:
```js
await axios.postForm("https://httpbin.org/post", {
"files[]": document.querySelector("#fileInput").files,
await axios.postForm('https://httpbin.org/post', {
'files[]': document.querySelector('#fileInput').files,
});
```
`FileList` object can be passed directly:
```js
await axios.postForm(
"https://httpbin.org/post",
document.querySelector("#fileInput").files,
);
await axios.postForm('https://httpbin.org/post', document.querySelector('#fileInput').files);
```
All files will be sent with the same field names: `files[]`.
@@ -1399,24 +1415,17 @@ All files will be sent with the same field names: `files[]`.
Pass an HTML Form element as a payload to submit it as `multipart/form-data` content.
```js
await axios.postForm(
"https://httpbin.org/post",
document.querySelector("#htmlForm"),
);
await axios.postForm('https://httpbin.org/post', document.querySelector('#htmlForm'));
```
`FormData` and `HTMLForm` objects can also be posted as `JSON` by explicitly setting the `Content-Type` header to `application/json`:
```js
await axios.post(
"https://httpbin.org/post",
document.querySelector("#htmlForm"),
{
headers: {
"Content-Type": "application/json",
},
await axios.post('https://httpbin.org/post', document.querySelector('#htmlForm'), {
headers: {
'Content-Type': 'application/json',
},
);
});
```
For example, the Form
@@ -1503,7 +1512,7 @@ const { data } = await axios.post(SERVER_URL, readableStream, {
},
headers: {
"Content-Length": contentLength,
'Content-Length': contentLength,
},
maxRedirects: 0, // avoid buffering the entire stream
@@ -1524,9 +1533,7 @@ Download and upload rate limits can only be set for the http adapter (node.js):
```js
const { data } = await axios.post(LOCAL_SERVER_URL, myBuffer, {
onUploadProgress: ({ progress, rate }) => {
console.log(
`Upload [${(progress * 100).toFixed(2)}%]: ${(rate / 1024).toFixed(2)}KB/s`,
);
console.log(`Upload [${(progress * 100).toFixed(2)}%]: ${(rate / 1024).toFixed(2)}KB/s`);
},
maxRate: [100 * 1024], // 100KB/s limit
@@ -1561,18 +1568,18 @@ The headers object is always initialized inside interceptors and transformers:
```ts
axios.interceptors.request.use((request: InternalAxiosRequestConfig) => {
request.headers.set("My-header", "value");
request.headers.set('My-header', 'value');
request.headers.set({
"My-set-header1": "my-set-value1",
"My-set-header2": "my-set-value2",
'My-set-header1': 'my-set-value1',
'My-set-header2': 'my-set-value2',
});
request.headers.set("User-Agent", false); // disable subsequent setting the header by Axios
request.headers.set('User-Agent', false); // disable subsequent setting the header by Axios
request.headers.setContentType("text/plain");
request.headers.setContentType('text/plain');
request.headers["My-set-header2"] = "newValue"; // direct access is deprecated
request.headers['My-set-header2'] = 'newValue'; // direct access is deprecated
return request;
});
@@ -1582,9 +1589,9 @@ You can iterate over an `AxiosHeaders` instance using a `for...of` statement:
```js
const headers = new AxiosHeaders({
foo: "1",
bar: "2",
baz: "3",
foo: '1',
bar: '2',
baz: '3',
});
for (const [header, value] of headers) {
@@ -1683,26 +1690,26 @@ matcher function or internal key-value parser.
```ts
const headers = new AxiosHeaders({
"Content-Type": "multipart/form-data; boundary=Asrf456BGe4h",
'Content-Type': 'multipart/form-data; boundary=Asrf456BGe4h',
});
console.log(headers.get("Content-Type"));
console.log(headers.get('Content-Type'));
// multipart/form-data; boundary=Asrf456BGe4h
console.log(headers.get("Content-Type", true)); // parse key-value pairs from a string separated with \s,;= delimiters:
console.log(headers.get('Content-Type', true)); // parse key-value pairs from a string separated with \s,;= delimiters:
// [Object: null prototype] {
// 'multipart/form-data': undefined,
// boundary: 'Asrf456BGe4h'
// }
console.log(
headers.get("Content-Type", (value, name, headers) => {
return String(value).replace(/a/g, "ZZZ");
}),
headers.get('Content-Type', (value, name, headers) => {
return String(value).replace(/a/g, 'ZZZ');
})
);
// multipZZZrt/form-dZZZtZZZ; boundZZZry=Asrf456BGe4h
console.log(headers.get("Content-Type", /boundary=(\w+)/)?.[0]);
console.log(headers.get('Content-Type', /boundary=(\w+)/)?.[0]);
// boundary=Asrf456BGe4h
```
@@ -1735,9 +1742,9 @@ Unlike the `delete` method matcher, this optional matcher will be used to match
```ts
const headers = new AxiosHeaders({
foo: "1",
"x-foo": "2",
"x-bar": "3",
foo: '1',
'x-foo': '2',
'x-bar': '3',
});
console.log(headers.clear(/^x-/)); // true
@@ -1756,11 +1763,11 @@ Set `format` to true for converting header names to lowercase and capitalizing t
```js
const headers = new AxiosHeaders({
foo: "1",
foo: '1',
});
headers.Foo = "2";
headers.FOO = "3";
headers.Foo = '2';
headers.FOO = '3';
console.log(headers.toJSON()); // [Object: null prototype] { foo: '1', Foo: '2', FOO: '3' }
console.log(headers.normalize().toJSON()); // [Object: null prototype] { foo: '3' }
@@ -1827,7 +1834,7 @@ To use it by default, it must be selected explicitly:
```js
const { data } = axios.get(url, {
adapter: "fetch", // by default ['xhr', 'http', 'fetch']
adapter: 'fetch', // by default ['xhr', 'http', 'fetch']
});
```
@@ -1835,7 +1842,7 @@ You can create a separate instance for this:
```js
const fetchAxios = axios.create({
adapter: "fetch",
adapter: 'fetch',
});
const { data } = fetchAxios.get(url);
@@ -1859,12 +1866,12 @@ you must disable their use inside the fetch adapter by passing null.
Basic example:
```js
import customFetchFunction from "customFetchModule";
import customFetchFunction from 'customFetchModule';
const instance = axios.create({
adapter: "fetch",
adapter: 'fetch',
onDownloadProgress(e) {
console.log("downloadProgress", e);
console.log('downloadProgress', e);
},
env: {
fetch: customFetchFunction,
@@ -1879,20 +1886,20 @@ const instance = axios.create({
A minimal example of setting up Axios for use in a [Tauri](https://tauri.app/plugin/http-client/) app with a platform fetch function that ignores CORS policy for requests.
```js
import { fetch } from "@tauri-apps/plugin-http";
import axios from "axios";
import { fetch } from '@tauri-apps/plugin-http';
import axios from 'axios';
const instance = axios.create({
adapter: "fetch",
adapter: 'fetch',
onDownloadProgress(e) {
console.log("downloadProgress", e);
console.log('downloadProgress', e);
},
env: {
fetch,
},
});
const { data } = await instance.get("https://google.com");
const { data } = await instance.get('https://google.com');
```
#### 🔥 Using with SvelteKit
@@ -1902,17 +1909,14 @@ which makes it incompatible with the standard URL API. So, Axios must be configu
```js
export async function load({ fetch }) {
const { data: post } = await axios.get(
"https://jsonplaceholder.typicode.com/posts/1",
{
adapter: "fetch",
env: {
fetch,
Request: null,
Response: null,
},
const { data: post } = await axios.get('https://jsonplaceholder.typicode.com/posts/1', {
adapter: 'fetch',
env: {
fetch,
Request: null,
Response: null,
},
);
});
return { post };
}
@@ -1931,21 +1935,17 @@ Note: HTTP/2 redirects are currently not supported by the HTTP/2 adapter.
```js
const form = new FormData();
form.append("foo", "123");
form.append('foo', '123');
const { data, headers, status } = await axios.post(
"https://httpbin.org/post",
form,
{
onUploadProgress(e) {
console.log("upload progress", e);
},
onDownloadProgress(e) {
console.log("download progress", e);
},
responseType: "arraybuffer",
}
);
const { data, headers, status } = await axios.post('https://httpbin.org/post', form, {
onUploadProgress(e) {
console.log('upload progress', e);
},
onDownloadProgress(e) {
console.log('download progress', e);
},
responseType: 'arraybuffer',
});
```
## Semver
@@ -1964,7 +1964,7 @@ axios includes [TypeScript](https://typescriptlang.org) definitions and a type g
```typescript
let user: User = null;
try {
const { data } = await axios.get("/user?ID=12345");
const { data } = await axios.get('/user?ID=12345');
user = data.userDetails;
} catch (error) {
if (axios.isAxiosError(error)) {
@@ -1984,10 +1984,10 @@ If you use TypeScript to type check CJS JavaScript code, your only option is to
You can also create a custom instance with typed interceptors:
```typescript
import axios, { AxiosInstance, InternalAxiosRequestConfig } from "axios";
import axios, { AxiosInstance, InternalAxiosRequestConfig } from 'axios';
const apiClient: AxiosInstance = axios.create({
baseURL: "https://api.example.com",
baseURL: 'https://api.example.com',
timeout: 10000,
});
@@ -4,22 +4,22 @@ axios puede enviar solicitudes en el formato `multipart/form-data`. Este formato
```js
const formData = new FormData();
formData.append("foo", "bar");
formData.append('foo', 'bar');
axios.post("https://httpbin.org/post", formData);
axios.post('https://httpbin.org/post', formData);
```
En Node.js, puedes usar la librería `form-data` de la siguiente manera:
```js
const FormData = require("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"));
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);
axios.post('https://example.com', form);
```
## Serialización automática a FormData <Badge type="tip" text="Nuevo" />
@@ -27,15 +27,15 @@ axios.post("https://example.com", form);
A partir de la versión v0.27.0, Axios admite la serialización automática de objetos a un objeto FormData si el encabezado `Content-Type` de la solicitud está establecido en `multipart/form-data`. Esto significa que puedes pasar un objeto JavaScript directamente a la propiedad `data` de la configuración de solicitud de axios. Por ejemplo, al pasar datos a una solicitud POST:
```js
import axios from "axios";
import axios from 'axios';
axios
.post(
"https://httpbin.org/post",
'https://httpbin.org/post',
{ x: 1 },
{
headers: {
"Content-Type": "multipart/form-data",
'Content-Type': 'multipart/form-data',
},
}
)
@@ -45,16 +45,16 @@ axios
En el entorno de Node.js, el polyfill ([`form-data`](https://github.com/form-data/form-data)) se usa de forma predeterminada. Puedes sobrescribir la clase FormData estableciendo la variable de configuración `env.FormData`, aunque en la mayoría de los casos no lo necesitarás:
```js
const axios = require("axios");
var FormData = require("form-data");
const axios = require('axios');
var FormData = require('form-data');
axios
.post(
"https://httpbin.org/post",
'https://httpbin.org/post',
{ x: 1, buf: Buffer.alloc(10) },
{
headers: {
"Content-Type": "multipart/form-data",
'Content-Type': 'multipart/form-data',
},
}
)
@@ -83,6 +83,16 @@ El serializador de FormData admite opciones adicionales a través de la propieda
- `null` - no añadir corchetes (`arr: 1`, `arr: 2`, `arr: 3`)
- `false` (predeterminado) - añadir corchetes vacíos (`arr[]: 1`, `arr[]: 2`, `arr[]: 3`)
- `true` - añadir corchetes con índices (`arr[0]: 1`, `arr[1]: 2`, `arr[2]: 3`)
- `maxDepth: number = 100` - profundidad máxima de anidación de objetos en la que el serializador recursará. Si la entrada excede esta profundidad, se lanza un `AxiosError` con `code: 'ERR_FORM_DATA_DEPTH_EXCEEDED'`. Esto protege las aplicaciones del lado del servidor contra ataques DoS mediante cargas útiles profundamente anidadas. Establece en `Infinity` para desactivar el límite.
```js
// Aumentar el límite para esquemas que legítimamente exceden 100 niveles:
axios.post('/api', data, { formSerializer: { maxDepth: 200 } });
```
::: warning Nota de seguridad
El límite predeterminado de 100 es intencional. El código del lado del servidor que reenvía JSON controlado por el cliente a axios como `data` es vulnerable a un desbordamiento de pila de llamadas sin esta protección. Solo aumenta `maxDepth` si tu esquema realmente lo requiere.
:::
Por ejemplo, si tenemos un objeto como este:
@@ -92,10 +102,10 @@ const obj = {
arr: [1, 2, 3],
arr2: [1, [2], 3],
users: [
{ name: "Peter", surname: "Griffin" },
{ name: "Thomas", surname: "Anderson" },
{ name: 'Peter', surname: 'Griffin' },
{ name: 'Thomas', surname: 'Anderson' },
],
"obj2{}": [{ x: 1 }],
'obj2{}': [{ x: 1 }],
};
```
@@ -103,18 +113,18 @@ El serializador de Axios ejecutará internamente los siguientes pasos:
```js
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}]');
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}]');
```
Axios admite los siguientes métodos abreviados: `postForm`, `putForm`, `patchForm`, que son simplemente los métodos HTTP correspondientes con el encabezado `Content-Type` preestablecido en `multipart/form-data`.
+22 -6
View File
@@ -221,7 +221,15 @@ La propiedad `env` te permite establecer algunas opciones de configuración. Por
### `formSerializer`
La función `formSerializer` te permite serializar el objeto `data` antes de enviarlo al servidor. Hay varias opciones disponibles para esta función; consulta el ejemplo completo de configuración de solicitud al final de esta página.
La opción `formSerializer` te permite configurar cómo se serializan los objetos planos a `multipart/form-data` cuando se usan como `data` de solicitud. Opciones disponibles:
- `visitor` — función visitante personalizada llamada recursivamente para cada valor
- `dots` — usar notación de punto en lugar de notación de corchetes
- `metaTokens` — preservar terminaciones especiales de clave como `{}`
- `indexes` — controlar el formato de corchetes para claves de arreglo (`null` / `false` / `true`)
- `maxDepth` _(predeterminado: `100`)_ — profundidad máxima de anidación antes de lanzar un `AxiosError` con código `ERR_FORM_DATA_DEPTH_EXCEEDED`. Establece en `Infinity` para desactivar.
Consulta la página [multipart/form-data](/pages/advanced/multipart-form-data-format) para todos los detalles, y el ejemplo completo de configuración de solicitud al final de esta página.
### `maxRate` <Badge type="warning" text="Solo en Node.js" />
@@ -257,7 +265,11 @@ La propiedad `maxRate` define el **ancho de banda** máximo (en bytes por segund
// (1) indexes: null (leads to no brackets)
// (2) (default) indexes: false (leads to empty brackets)
// (3) indexes: true (leads to brackets with indexes).
indexes: false
indexes: false,
// Profundidad máxima de anidación de objetos al serializar params. Lanza AxiosError
// (ERR_FORM_DATA_DEPTH_EXCEEDED) si se excede. Predeterminado: 100. Establecer en Infinity para desactivar.
maxDepth: 100
},
data: {
@@ -336,11 +348,15 @@ La propiedad `maxRate` define el **ancho de banda** máximo (en bytes por segund
// Keep special endings like {} in parameter key
metaTokens: boolean;
// Use array indexes format:
// null - no brackets
// false - empty brackets
// true - brackets with indexes
// Usar formato de índices de arreglo:
// null - sin corchetes
// false - corchetes vacíos
// true - corchetes con índices
indexes: boolean;
// Profundidad máxima de anidación de objetos. Lanza AxiosError (ERR_FORM_DATA_DEPTH_EXCEEDED)
// si se excede. Predeterminado: 100. Establecer en Infinity para desactivar.
maxDepth: 100;
},
maxRate: [
100 * 1024, // 100KB/s upload limit,
@@ -64,6 +64,19 @@ El objeto `data` será serializado automáticamente a `URLSearchParams` y enviad
Si el analizador de cuerpo de tu backend (como `body-parser` de `express.js`) admite la decodificación de objetos anidados, recibirás el mismo objeto en el lado del servidor automáticamente.
## Límite de profundidad para la serialización de parámetros
Cuando axios serializa un objeto `params` mediante `AxiosURLSearchParams`, se llama al mismo recorrido recursivo utilizado por el serializador de FormData. Una opción `maxDepth` (predeterminado `100`) limita la profundidad de recursión. Las cargas útiles que exceden el límite lanzan un `AxiosError` con `code: 'ERR_FORM_DATA_DEPTH_EXCEEDED'` en lugar de desbordar la pila de llamadas.
```js
// Aumentar el límite si tu objeto params legítimamente anida más de 100 niveles:
axios.get('/api', { params: deepObject, paramsSerializer: { maxDepth: 200 } });
```
::: warning Nota de seguridad
Solo aumenta `maxDepth` si tu esquema realmente lo requiere. El valor predeterminado de 100 protege el código del lado del servidor que reenvía datos controlados por el cliente a axios como `params` contra ataques DoS mediante objetos profundamente anidados.
:::
```js
var app = express();
@@ -4,22 +4,22 @@ axios peut envoyer des requêtes au format `multipart/form-data`. Ce format est
```js
const formData = new FormData();
formData.append("foo", "bar");
formData.append('foo', 'bar');
axios.post("https://httpbin.org/post", formData);
axios.post('https://httpbin.org/post', formData);
```
Dans Node.js, vous pouvez utiliser la bibliothèque `form-data` comme suit :
```js
const FormData = require("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"));
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);
axios.post('https://example.com', form);
```
## Sérialisation automatique vers FormData <Badge type="tip" text="Nouveau" />
@@ -27,15 +27,15 @@ axios.post("https://example.com", form);
À partir de la version v0.27.0, Axios prend en charge la sérialisation automatique d'objets en objet FormData si l'en-tête Content-Type de la requête est défini à multipart/form-data. Cela signifie que vous pouvez passer directement un objet JavaScript à la propriété data de la configuration de requête axios. Par exemple lors de l'envoi de données vers une requête POST :
```js
import axios from "axios";
import axios from 'axios';
axios
.post(
"https://httpbin.org/post",
'https://httpbin.org/post',
{ x: 1 },
{
headers: {
"Content-Type": "multipart/form-data",
'Content-Type': 'multipart/form-data',
},
}
)
@@ -45,16 +45,16 @@ axios
Dans la version Node.js, le polyfill ([`form-data`](https://github.com/form-data/form-data)) est utilisé par défaut. Vous pouvez remplacer la classe FormData en définissant la variable de configuration env.FormData, mais vous n'en aurez probablement pas besoin dans la plupart des cas :
```js
const axios = require("axios");
var FormData = require("form-data");
const axios = require('axios');
var FormData = require('form-data');
axios
.post(
"https://httpbin.org/post",
'https://httpbin.org/post',
{ x: 1, buf: Buffer.alloc(10) },
{
headers: {
"Content-Type": "multipart/form-data",
'Content-Type': 'multipart/form-data',
},
}
)
@@ -83,6 +83,16 @@ Le sérialiseur FormData supporte des options supplémentaires via la propriét
- `null` - ne pas ajouter de crochets (`arr: 1`, `arr: 2`, `arr: 3`)
- `false` (défaut) - ajouter des crochets vides (`arr[]: 1`, `arr[]: 2`, `arr[]: 3`)
- `true` - ajouter des crochets avec index (`arr[0]: 1`, `arr[1]: 2`, `arr[2]: 3`)
- `maxDepth: number = 100` - profondeur maximale d'imbrication des objets dans laquelle le sérialiseur va récurser. Si l'entrée dépasse cette profondeur, une `AxiosError` avec `code: 'ERR_FORM_DATA_DEPTH_EXCEEDED'` est levée. Cela protège les applications côté serveur contre les attaques DoS via des charges utiles profondément imbriquées. Définir à `Infinity` pour désactiver la limite.
```js
// Autoriser une imbrication plus profonde pour les schémas qui dépassent légitimement 100 niveaux :
axios.post('/api', data, { formSerializer: { maxDepth: 200 } });
```
::: warning Note de sécurité
La limite par défaut de 100 est intentionnelle. Le code côté serveur qui transfère du JSON contrôlé par le client vers axios en tant que `data` est vulnérable à un débordement de pile d'appels sans cette protection. N'augmentez `maxDepth` que si votre schéma le nécessite réellement.
:::
Par exemple, si nous avons un objet comme celui-ci :
@@ -92,10 +102,10 @@ const obj = {
arr: [1, 2, 3],
arr2: [1, [2], 3],
users: [
{ name: "Peter", surname: "Griffin" },
{ name: "Thomas", surname: "Anderson" },
{ name: 'Peter', surname: 'Griffin' },
{ name: 'Thomas', surname: 'Anderson' },
],
"obj2{}": [{ x: 1 }],
'obj2{}': [{ x: 1 }],
};
```
@@ -103,18 +113,18 @@ Les étapes suivantes seront exécutées en interne par le sérialiseur Axios :
```js
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}]');
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}]');
```
Axios supporte les méthodes raccourcies suivantes : `postForm`, `putForm`, `patchForm` qui sont simplement les méthodes HTTP correspondantes avec l'en-tête `Content-Type` prédéfini à `multipart/form-data`.
+18 -2
View File
@@ -221,7 +221,15 @@ La propriété `env` vous permet de définir certaines options de configuration.
### `formSerializer`
La fonction `formSerializer` vous permet de sérialiser l'objet `data` avant son envoi au serveur. Plusieurs options sont disponibles pour cette fonction ; veuillez vous référer à l'exemple de configuration complète en bas de cette page.
L'option `formSerializer` vous permet de configurer comment les objets simples sont sérialisés en `multipart/form-data` lorsqu'ils sont utilisés comme `data` de requête. Options disponibles :
- `visitor` — fonction visiteur personnalisée appelée récursivement pour chaque valeur
- `dots` — utiliser la notation pointée au lieu de la notation entre crochets
- `metaTokens` — conserver les terminaisons spéciales de clé telles que `{}`
- `indexes` — contrôler le format des crochets pour les clés de tableau (`null` / `false` / `true`)
- `maxDepth` _(par défaut : `100`)_ — profondeur maximale d'imbrication avant de lever une `AxiosError` avec le code `ERR_FORM_DATA_DEPTH_EXCEEDED`. Définir à `Infinity` pour désactiver.
Consultez la page [multipart/form-data](/pages/advanced/multipart-form-data-format) pour tous les détails, et l'exemple de configuration complète en bas de cette page.
### `maxRate` <Badge type="warning" text="Node.js uniquement" />
@@ -257,7 +265,11 @@ La propriété `maxRate` définit la **bande passante** maximale (en octets par
// (1) indexes: null (pas de crochets)
// (2) (défaut) indexes: false (crochets vides)
// (3) indexes: true (crochets avec index).
indexes: false
indexes: false,
// Profondeur maximale d'imbrication des objets lors de la sérialisation des params. Lève une AxiosError
// (ERR_FORM_DATA_DEPTH_EXCEEDED) si dépassée. Par défaut : 100. Définir à Infinity pour désactiver.
maxDepth: 100
},
data: {
@@ -341,6 +353,10 @@ La propriété `maxRate` définit la **bande passante** maximale (en octets par
// false - crochets vides
// true - crochets avec index
indexes: boolean;
// Profondeur maximale d'imbrication des objets. Lève une AxiosError (ERR_FORM_DATA_DEPTH_EXCEEDED)
// si dépassée. Par défaut : 100. Définir à Infinity pour désactiver.
maxDepth: 100;
},
maxRate: [
100 * 1024, // Limite d'envoi de 100Ko/s,
@@ -64,6 +64,19 @@ L'objet `data` sera automatiquement sérialisé en `URLSearchParams` et envoyé
Si le body-parser de votre backend (comme `body-parser` d'`express.js`) prend en charge le décodage des objets imbriqués, vous obtiendrez automatiquement le même objet côté serveur.
## Limite de profondeur pour la sérialisation des paramètres
Lorsqu'axios sérialise un objet `params` via `AxiosURLSearchParams`, le même parcours récursif utilisé par le sérialiseur FormData est appelé. Une option `maxDepth` (par défaut `100`) limite la profondeur de récursion. Les charges utiles dépassant la limite lèvent une `AxiosError` avec `code: 'ERR_FORM_DATA_DEPTH_EXCEEDED'` au lieu de provoquer un débordement de pile d'appels.
```js
// Augmenter la limite si votre objet params nécessite légitimement plus de 100 niveaux d'imbrication :
axios.get('/api', { params: deepObject, paramsSerializer: { maxDepth: 200 } });
```
::: warning Note de sécurité
N'augmentez `maxDepth` que si votre schéma le nécessite réellement. La valeur par défaut de 100 protège le code côté serveur qui transfère des données contrôlées par le client vers axios en tant que `params` contre les attaques DoS via des objets profondément imbriqués.
:::
```js
var app = express();
@@ -4,22 +4,22 @@ axios can send requests in the `multipart/form-data` format. This format is comm
```js
const formData = new FormData();
formData.append("foo", "bar");
formData.append('foo', 'bar');
axios.post("https://httpbin.org/post", formData);
axios.post('https://httpbin.org/post', formData);
```
In node.js, you can use the `form-data` library as follows:
```js
const FormData = require("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"));
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);
axios.post('https://example.com', form);
```
## Automatic serialization to FormData <Badge type="tip" text="New" />
@@ -27,15 +27,15 @@ axios.post("https://example.com", form);
Starting from v0.27.0, Axios supports automatic object serialization to a FormData object if the request Content-Type header is set to multipart/form-data. This means that you can pass a JavaScript object directly to the data property of the axios request config. For example when passing data to a POST request:
```js
import axios from "axios";
import axios from 'axios';
axios
.post(
"https://httpbin.org/post",
'https://httpbin.org/post',
{ x: 1 },
{
headers: {
"Content-Type": "multipart/form-data",
'Content-Type': 'multipart/form-data',
},
}
)
@@ -45,16 +45,16 @@ axios
In the node.js build, the ([`form-data`](https://github.com/form-data/form-data)) polyfill is used by default. You can overload the FormData class by setting the env.FormData config variable, but you probably won't need it in most cases:
```js
const axios = require("axios");
var FormData = require("form-data");
const axios = require('axios');
var FormData = require('form-data');
axios
.post(
"https://httpbin.org/post",
'https://httpbin.org/post',
{ x: 1, buf: Buffer.alloc(10) },
{
headers: {
"Content-Type": "multipart/form-data",
'Content-Type': 'multipart/form-data',
},
}
)
@@ -83,6 +83,16 @@ FormData serializer supports additional options via config.formSerializer: objec
- `null` - don't add brackets (`arr: 1`, `arr: 2`, `arr: 3`)
- `false` (default) - add empty brackets (`arr[]: 1`, `arr[]: 2`, `arr[]: 3`)
- `true` - add brackets with indexes (`arr[0]: 1`, `arr[1]: 2`, `arr[2]: 3`)
- `maxDepth: number = 100` - maximum object nesting depth the serializer will recurse into. If the input exceeds this depth, an `AxiosError` with `code: 'ERR_FORM_DATA_DEPTH_EXCEEDED'` is thrown. This protects server-side applications from DoS attacks via deeply nested payloads. Set to `Infinity` to disable the limit.
```js
// Allow deeper nesting for schemas that legitimately exceed 100 levels:
axios.post('/api', data, { formSerializer: { maxDepth: 200 } });
```
::: warning Security note
The default limit of 100 is intentional. Server-side code that forwards client-controlled JSON to axios as `data` is vulnerable to a call-stack overflow without this guard. Only raise `maxDepth` if your schema genuinely requires it.
:::
For example, if we have an object like this:
@@ -92,10 +102,10 @@ const obj = {
arr: [1, 2, 3],
arr2: [1, [2], 3],
users: [
{ name: "Peter", surname: "Griffin" },
{ name: "Thomas", surname: "Anderson" },
{ name: 'Peter', surname: 'Griffin' },
{ name: 'Thomas', surname: 'Anderson' },
],
"obj2{}": [{ x: 1 }],
'obj2{}': [{ x: 1 }],
};
```
@@ -103,18 +113,18 @@ The following steps will be executed by the Axios serializer internally:
```js
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}]');
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}]');
```
Axios supports the following shortcut methods: `postForm`, `putForm`, `patchForm` which are just the corresponding http methods with the `Content-Type` header preset to `multipart/form-data`.
+18 -2
View File
@@ -221,7 +221,15 @@ The `env` property allows you to set some configuration options. For example the
### `formSerializer`
The `formSerializer` function allows you to serialize the `data` object before it is sent to the server. There are a few options available for this function, so please refer to the full request config example at the end of this page.
The `formSerializer` option allows you to configure how plain objects are serialized to `multipart/form-data` when used as request `data`. Available options:
- `visitor` — custom visitor function called recursively for each value
- `dots` — use dot notation instead of bracket notation
- `metaTokens` — preserve special key endings such as `{}`
- `indexes` — control bracket format for array keys (`null` / `false` / `true`)
- `maxDepth` _(default: `100`)_ — maximum nesting depth before throwing `AxiosError` with code `ERR_FORM_DATA_DEPTH_EXCEEDED`. Set to `Infinity` to disable.
See the [multipart/form-data](/pages/advanced/multipart-form-data-format) page for full details, and the full request config example at the end of this page.
### `maxRate` <Badge type="warning" text="Node.js only" />
@@ -257,7 +265,11 @@ The `maxRate` property defines the maximum **bandwidth** (in bytes per second) f
// (1) indexes: null (leads to no brackets)
// (2) (default) indexes: false (leads to empty brackets)
// (3) indexes: true (leads to brackets with indexes).
indexes: false
indexes: false,
// Maximum object nesting depth when serializing params. Throws AxiosError
// (ERR_FORM_DATA_DEPTH_EXCEEDED) if exceeded. Default: 100. Set to Infinity to disable.
maxDepth: 100
},
data: {
@@ -341,6 +353,10 @@ The `maxRate` property defines the maximum **bandwidth** (in bytes per second) f
// false - empty brackets
// true - brackets with indexes
indexes: boolean;
// Maximum object nesting depth. Throws AxiosError (ERR_FORM_DATA_DEPTH_EXCEEDED)
// if exceeded. Default: 100. Set to Infinity to disable.
maxDepth: 100;
},
maxRate: [
100 * 1024, // 100KB/s upload limit,
@@ -64,6 +64,19 @@ The `data` object will be automatically serialized to `URLSearchParams` and sent
If your backend body-parser (like `body-parser` of `express.js`) supports nested objects decoding, you will get the same object on the server-side automatically
## Depth limit for params serialization
When axios serializes a `params` object via `AxiosURLSearchParams`, the same recursive walker used by the FormData serializer is called. A `maxDepth` option (default `100`) limits how deeply it will recurse. Payloads exceeding the limit throw an `AxiosError` with `code: 'ERR_FORM_DATA_DEPTH_EXCEEDED'` instead of overflowing the call stack.
```js
// Raise the limit if your params object legitimately nests deeper than 100 levels:
axios.get('/api', { params: deepObject, paramsSerializer: { maxDepth: 200 } });
```
::: warning Security note
Only raise `maxDepth` if your schema genuinely requires it. The default of 100 protects server-side code that forwards client-controlled data to axios as `params` from DoS attacks via deeply nested objects.
:::
```js
var app = express();
@@ -4,22 +4,22 @@ axios 支持以 `multipart/form-data` 格式发送请求,这种格式常用于
```js
const formData = new FormData();
formData.append("foo", "bar");
formData.append('foo', 'bar');
axios.post("https://httpbin.org/post", formData);
axios.post('https://httpbin.org/post', formData);
```
在 Node.js 中,可以使用 `form-data` 库,如下所示:
```js
const FormData = require("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"));
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);
axios.post('https://example.com', form);
```
## 自动序列化为 FormData <Badge type="tip" text="新特性" />
@@ -27,15 +27,15 @@ axios.post("https://example.com", form);
从 v0.27.0 起,如果请求的 Content-Type 请求头设置为 `multipart/form-data`,axios 支持自动将对象序列化为 FormData 对象。这意味着你可以直接将 JavaScript 对象传入 axios 请求配置的 `data` 属性。例如,向 POST 请求传递数据时:
```js
import axios from "axios";
import axios from 'axios';
axios
.post(
"https://httpbin.org/post",
'https://httpbin.org/post',
{ x: 1 },
{
headers: {
"Content-Type": "multipart/form-data",
'Content-Type': 'multipart/form-data',
},
}
)
@@ -45,16 +45,16 @@ axios
在 Node.js 构建中,默认使用 ([`form-data`](https://github.com/form-data/form-data)) 作为 polyfill。你可以通过设置 `env.FormData` 配置变量来覆盖 FormData 类,但大多数情况下不需要这样做:
```js
const axios = require("axios");
var FormData = require("form-data");
const axios = require('axios');
var FormData = require('form-data');
axios
.post(
"https://httpbin.org/post",
'https://httpbin.org/post',
{ x: 1, buf: Buffer.alloc(10) },
{
headers: {
"Content-Type": "multipart/form-data",
'Content-Type': 'multipart/form-data',
},
}
)
@@ -83,6 +83,16 @@ FormData 序列化器通过 `config.formSerializer` 对象属性支持以下额
- `null` - 不添加方括号(`arr: 1``arr: 2``arr: 3`
- `false`(默认)- 添加空方括号(`arr[]: 1``arr[]: 2``arr[]: 3`
- `true` - 添加带索引的方括号(`arr[0]: 1``arr[1]: 2``arr[2]: 3`
- `maxDepth: number = 100` - 序列化器递归的最大对象嵌套深度。如果输入超过此深度,将抛出 `code: 'ERR_FORM_DATA_DEPTH_EXCEEDED'``AxiosError`。这可以保护服务端应用免受深层嵌套载荷的 DoS 攻击。设置为 `Infinity` 可禁用此限制。
```js
// 当 schema 确实需要超过 100 层嵌套时,可提高限制:
axios.post('/api', data, { formSerializer: { maxDepth: 200 } });
```
::: warning 安全提示
默认限制 100 是有意为之。将客户端控制的 JSON 作为 `data` 转发给 axios 的服务端代码,如果没有此保护,容易发生调用栈溢出。除非你的 schema 确实需要,否则不要提高 `maxDepth`
:::
例如,对于以下对象:
@@ -92,10 +102,10 @@ const obj = {
arr: [1, 2, 3],
arr2: [1, [2], 3],
users: [
{ name: "Peter", surname: "Griffin" },
{ name: "Thomas", surname: "Anderson" },
{ name: 'Peter', surname: 'Griffin' },
{ name: 'Thomas', surname: 'Anderson' },
],
"obj2{}": [{ x: 1 }],
'obj2{}': [{ x: 1 }],
};
```
@@ -103,18 +113,18 @@ axios 序列化器内部将执行以下步骤:
```js
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}]');
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}]');
```
axios 支持以下快捷方法:`postForm``putForm``patchForm`,它们分别对应相应的 HTTP 方法,并预设 `Content-Type` 请求头为 `multipart/form-data`
+18 -2
View File
@@ -221,7 +221,15 @@ proxy: {
### `formSerializer`
`formSerializer` 函数允许你在数据发送到服务器之前自定义 `data` 对象的序列化方式,有多个可用选项,详见本页末尾的完整请求配置示例。
`formSerializer` 选项允许你配置普通对象作为请求 `data` 时如何序列化为 `multipart/form-data`。可用选项:
- `visitor` — 对每个值递归调用的自定义访问者函数
- `dots` — 使用点号表示法代替方括号表示法
- `metaTokens` — 保留特殊的键后缀(如 `{}`
- `indexes` — 控制数组键的方括号格式(`null` / `false` / `true`
- `maxDepth` _(默认:`100`_ — 抛出 `AxiosError`(错误码 `ERR_FORM_DATA_DEPTH_EXCEEDED`)前的最大嵌套深度。设置为 `Infinity` 可禁用。
详见 [multipart/form-data](/pages/advanced/multipart-form-data-format) 页面以及本页末尾的完整请求配置示例。
### `maxRate` <Badge type="warning" text="仅 Node.js" />
@@ -257,7 +265,11 @@ proxy: {
// (1) indexes: null(不添加方括号)
// (2)(默认)indexes: false(添加空方括号)
// (3) indexes: true(添加带索引的方括号)
indexes: false
indexes: false,
// 序列化参数时的最大对象嵌套深度。超过时抛出 AxiosError
// (ERR_FORM_DATA_DEPTH_EXCEEDED)。默认:100。设置为 Infinity 可禁用。
maxDepth: 100
},
data: {
@@ -341,6 +353,10 @@ proxy: {
// false - 添加空方括号
// true - 添加带索引的方括号
indexes: boolean;
// 最大对象嵌套深度。超过时抛出 AxiosError (ERR_FORM_DATA_DEPTH_EXCEEDED)。
// 默认:100。设置为 Infinity 可禁用。
maxDepth: 100;
},
maxRate: [
100 * 1024, // 上传限制 100KB/s
@@ -64,6 +64,19 @@ await axios.postForm('https://postman-echo.com/post', data, {
如果你的后端 body 解析器(如 `express.js``body-parser`)支持嵌套对象解码,服务器端将自动还原为相同的对象结构:
## 参数序列化的深度限制
当 axios 通过 `AxiosURLSearchParams` 序列化 `params` 对象时,会调用与 FormData 序列化器相同的递归遍历器。`maxDepth` 选项(默认 `100`)限制递归的最大深度。超过限制的载荷会抛出 `code: 'ERR_FORM_DATA_DEPTH_EXCEEDED'``AxiosError`,而不是导致调用栈溢出。
```js
// 如果你的 params 对象确实需要超过 100 层嵌套,可提高限制:
axios.get('/api', { params: deepObject, paramsSerializer: { maxDepth: 200 } });
```
::: warning 安全提示
除非你的 schema 确实需要,否则不要提高 `maxDepth`。默认值 100 可保护将客户端控制的数据作为 `params` 转发给 axios 的服务端代码免受深层嵌套对象的 DoS 攻击。
:::
```js
var app = express();
+1
View File
@@ -160,6 +160,7 @@ declare class AxiosError<T = unknown, D = any> extends Error {
static readonly ERR_NOT_SUPPORT = 'ERR_NOT_SUPPORT';
static readonly ERR_INVALID_URL = 'ERR_INVALID_URL';
static readonly ERR_CANCELED = 'ERR_CANCELED';
static readonly ERR_FORM_DATA_DEPTH_EXCEEDED = 'ERR_FORM_DATA_DEPTH_EXCEEDED';
static readonly ECONNABORTED = 'ECONNABORTED';
static readonly ETIMEDOUT = 'ETIMEDOUT';
}
Vendored
+1
View File
@@ -516,6 +516,7 @@ export class AxiosError<T = unknown, D = any> extends Error {
static readonly ERR_NOT_SUPPORT = 'ERR_NOT_SUPPORT';
static readonly ERR_INVALID_URL = 'ERR_INVALID_URL';
static readonly ERR_CANCELED = 'ERR_CANCELED';
static readonly ERR_FORM_DATA_DEPTH_EXCEEDED = 'ERR_FORM_DATA_DEPTH_EXCEEDED';
static readonly ECONNABORTED = 'ECONNABORTED';
static readonly ETIMEDOUT = 'ETIMEDOUT';
}
+34 -33
View File
@@ -17,40 +17,40 @@ class AxiosError extends Error {
return axiosError;
}
/**
* Create an Error with the specified message, config, error code, request and response.
*
* @param {string} message The error message.
* @param {string} [code] The error code (for example, 'ECONNABORTED').
* @param {Object} [config] The config.
* @param {Object} [request] The request.
* @param {Object} [response] The response.
*
* @returns {Error} The created error.
*/
constructor(message, code, config, request, response) {
super(message);
// Make message enumerable to maintain backward compatibility
// The native Error constructor sets message as non-enumerable,
// but axios < v1.13.3 had it as enumerable
Object.defineProperty(this, 'message', {
value: message,
enumerable: true,
writable: true,
configurable: true
});
this.name = 'AxiosError';
this.isAxiosError = true;
code && (this.code = code);
config && (this.config = config);
request && (this.request = request);
if (response) {
this.response = response;
this.status = response.status;
}
/**
* Create an Error with the specified message, config, error code, request and response.
*
* @param {string} message The error message.
* @param {string} [code] The error code (for example, 'ECONNABORTED').
* @param {Object} [config] The config.
* @param {Object} [request] The request.
* @param {Object} [response] The response.
*
* @returns {Error} The created error.
*/
constructor(message, code, config, request, response) {
super(message);
// Make message enumerable to maintain backward compatibility
// The native Error constructor sets message as non-enumerable,
// but axios < v1.13.3 had it as enumerable
Object.defineProperty(this, 'message', {
value: message,
enumerable: true,
writable: true,
configurable: true,
});
this.name = 'AxiosError';
this.isAxiosError = true;
code && (this.code = code);
config && (this.config = config);
request && (this.request = request);
if (response) {
this.response = response;
this.status = response.status;
}
}
toJSON() {
return {
@@ -86,5 +86,6 @@ AxiosError.ERR_BAD_REQUEST = 'ERR_BAD_REQUEST';
AxiosError.ERR_CANCELED = 'ERR_CANCELED';
AxiosError.ERR_NOT_SUPPORT = 'ERR_NOT_SUPPORT';
AxiosError.ERR_INVALID_URL = 'ERR_INVALID_URL';
AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED = 'ERR_FORM_DATA_DEPTH_EXCEEDED';
export default AxiosError;
+10 -2
View File
@@ -115,6 +115,7 @@ function toFormData(obj, formData, options) {
const dots = options.dots;
const indexes = options.indexes;
const _Blob = options.Blob || (typeof Blob !== 'undefined' && Blob);
const maxDepth = options.maxDepth === undefined ? 100 : options.maxDepth;
const useBlob = _Blob && utils.isSpecCompliantForm(formData);
if (!utils.isFunction(visitor)) {
@@ -207,9 +208,16 @@ function toFormData(obj, formData, options) {
isVisitable,
});
function build(value, path) {
function build(value, path, depth = 0) {
if (utils.isUndefined(value)) return;
if (depth > maxDepth) {
throw new AxiosError(
'Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth,
AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED
);
}
if (stack.indexOf(value) !== -1) {
throw Error('Circular reference detected in ' + path.join('.'));
}
@@ -222,7 +230,7 @@ function toFormData(obj, formData, options) {
visitor.call(formData, el, utils.isString(key) ? key.trim() : key, path, exposedHelpers);
if (result === true) {
build(el, path ? path.concat(key) : [key]);
build(el, path ? path.concat(key) : [key], depth + 1);
}
});
+99
View File
@@ -2,6 +2,8 @@ import { describe, it } from 'vitest';
import assert from 'assert';
import FormData from 'form-data';
import toFormData from '../../lib/helpers/toFormData.js';
import AxiosError from '../../lib/core/AxiosError.js';
import AxiosURLSearchParams from '../../lib/helpers/AxiosURLSearchParams.js';
describe('helpers::toFormData', () => {
const createRNFormDataSpy = () => {
@@ -111,6 +113,103 @@ describe('helpers::toFormData', () => {
assert.strictEqual(formData.calls[0][1], blob);
});
// --- Depth limit tests ---
function nest(depth) {
let o = { leaf: 1 };
for (let i = 0; i < depth; i++) o = { a: o };
return o;
}
describe('maxDepth option', () => {
it('should throw AxiosError when payload exceeds default depth limit (100)', () => {
try {
toFormData(nest(101), new FormData());
assert.fail('Should have thrown');
} catch (err) {
assert.ok(err instanceof AxiosError, 'error must be AxiosError, not RangeError');
assert.strictEqual(err.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED');
assert.ok(!(err instanceof RangeError));
}
});
it('should succeed when payload is exactly at the default depth limit (100)', () => {
const formData = toFormData(nest(100), new FormData());
assert.ok(formData instanceof FormData);
});
it('should succeed for a shallow payload (no regression)', () => {
const formData = toFormData(nest(5), new FormData());
assert.ok(formData instanceof FormData);
});
it('should allow deeper payloads when maxDepth is raised', () => {
const formData = toFormData(nest(150), new FormData(), { maxDepth: 200 });
assert.ok(formData instanceof FormData);
});
it('should reject shallower payloads when maxDepth is lowered', () => {
try {
toFormData(nest(10), new FormData(), { maxDepth: 5 });
assert.fail('Should have thrown');
} catch (err) {
assert.ok(err instanceof AxiosError);
assert.strictEqual(err.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED');
}
});
it('should not throw for depth guard when maxDepth is Infinity (guard disabled)', () => {
// Use 500 levels — deep enough to prove the guard is off, shallow enough not to overflow V8
const formData = toFormData(nest(500), new FormData(), { maxDepth: Infinity });
assert.ok(formData instanceof FormData);
});
it('should still detect circular references when depth guard is active', () => {
const data = { foo: 'bar' };
data.self = data;
try {
toFormData(data, new FormData());
assert.fail('Should have thrown');
} catch (err) {
assert.ok(
err.message.includes('Circular reference detected'),
'must be circular-ref error'
);
assert.ok(!(err instanceof AxiosError) || err.code !== 'ERR_FORM_DATA_DEPTH_EXCEEDED');
}
});
it('depth limit error is catchable as AxiosError with correct code', () => {
let caught;
try {
toFormData(nest(101), new FormData());
} catch (err) {
caught = err;
}
assert.ok(caught instanceof AxiosError);
assert.strictEqual(caught.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED');
assert.ok(!(caught instanceof RangeError));
});
});
describe('maxDepth — params serialization via AxiosURLSearchParams', () => {
it('should throw AxiosError for deeply nested params object (default limit)', () => {
try {
new AxiosURLSearchParams(nest(101));
assert.fail('Should have thrown');
} catch (err) {
assert.ok(err instanceof AxiosError);
assert.strictEqual(err.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED');
}
});
it('should build query string for deep params when maxDepth is raised', () => {
const params = new AxiosURLSearchParams(nest(150), { maxDepth: 200 });
const qs = params.toString();
assert.ok(typeof qs === 'string' && qs.length > 0);
});
});
it('should NOT recurse into React Native blob properties', () => {
const formData = createRNFormDataSpy();