Axios请求配置详解:headers属性实现与合并策略
<|begin▁of▁sentence|># 1. 前言
在上一篇文章中,我们实现了`axios`的请求和响应配置化,即用户可以配置`config`对象中的`headers`、`data`、`timeout`等属性来改变请求和响应。但是,在之前的文章中,我们只实现了`data`属性可以配置为对象类型,而`headers`属性我们并没有实现,我们只给它配置了默认支持`content-type`。那么从这篇文章开始,我们就来详细实现`config`对象中的各个属性配置。
# 2. 需求分析
根据[axios官方文档](https://github.com/axios/axios#request-config)给出的请求配置属性,我们可以看到`config`对象中除了`url`属性是必传的之外,其他属性都是可选了,并且属性值都有默认值。如下:
```javascript
{
// `url` 是用于请求的服务器 URL
url: '/user',
// `method` 是创建请求时使用的方法
method: 'get', // default
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
// `transformRequest` 允许在向服务器发送前,修改请求数据
// 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
transformRequest: [function (data, headers) {
// 对 data 进行任意转换处理
return data;
}],
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对 data 进行任意转换处理
return data;
}],
// `headers` 是即将被发送的自定义请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `params` 是即将与请求一起发送的 URL 参数
// 必须是一个无格式对象(plain object)或 URLSearchParams 对象
params: {
ID: 12345
},
// `paramsSerializer` 是一个负责 `params` 序列化的函数
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` 是作为请求主体被发送的数据
// 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
// 在没有设置 `transformRequest` 时,必须是以下类型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, FormData
// - 浏览器专属:FormData, File, Blob
// - Node 专属: Stream
data: {
firstName: 'Fred'
},
// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求话费了超过 `timeout` 的时间,请求将被中断
timeout: 1000,
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
// `adapter` 允许自定义处理请求,以使测试更轻松
// 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).
adapter: function (config) {
/* ... */
},
// `auth` 表示应该使用 HTTP 基础验证,并提供凭据
// 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // default
// `responseEncoding` indicates encoding to use for decoding responses
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // default
// `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default
// `onUploadProgress` 允许为上传处理进度事件
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `onDownloadProgress` 允许为下载处理进度事件
onDownloadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `maxContentLength` 定义允许的响应内容的最大尺寸
maxContentLength: 2000,
// `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
// `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
// 如果设置为0,将不会 follow 任何重定向
maxRedirects: 5, // default
// `socketPath` defines a UNIX Socket to be used in node.js.
// e.g. '/var/run/docker.sock' to send requests to the docker daemon.
// Only applicable in node.js.
socketPath: null, // default
// `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:
// `keepAlive` 默认没有启用
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// 'proxy' 定义代理服务器的主机名称和端口
// `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据
// 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。
proxy: {
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
// `cancelToken` 指定用于取消请求的 cancel token
// (查看后面的 Cancellation 这节了解更多)
cancelToken: new CancelToken(function (cancel) {
})
}
```
从上面可以看出,`config`对象中的属性非常多,我们不可能在一篇文章内就全部实现,所以我们只能一个一个来,循序渐进。
那么,我们就先从`headers`属性开始吧。
# 3. headers 属性
## 3.1 官方的 headers
根据官方文档,`headers`是即将被发送的自定义请求头,并且它必须是一个纯对象或者`undefined`。在浏览器中,我们可以通过`XMLHttpRequest`对象的`setRequestHeader`方法来设置请求头,例如:
```javascript
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8');
```
在`axios`中,我们可以通过如下方式设置请求头:
```javascript
axios({
method: 'post',
url: '/base/post',
data: {
a: 1,
b: 2
},
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
})
```
## 3.2 实现思路
我们之前已经给`headers`设置了默认值,即默认支持`content-type`,并且默认值是在`src/core/xhr.ts`中的`processConfig`函数内处理的,如下:
```javascript
// src/core/xhr.ts
const processConfig = (config: AxiosRequestConfig): void => {
config.url = transformUrl(config);
config.headers = transformHeaders(config);
config.data = transformRequestData(config);
};
// src/helpers/headers.ts
export function transformHeaders(config: AxiosRequestConfig): any {
const { headers = {}, data } = config;
// 设置默认请求头 Content-Type
setContentTypeIfUnset(headers, "application/json;charset=utf-8");
return headers;
}
function setContentTypeIfUnset(headers: any, value: string): void {
if (!headers["Content-Type"]) {
headers["Content-Type"] = value;
}
}
```
从上面代码中可以看到,我们默认给`headers`设置了`Content-Type`,并且如果用户没有配置`headers`,我们就给一个空对象,然后设置默认的`Content-Type`;如果用户配置了`headers`,并且配置的`headers`中没有`Content-Type`,那我们也会给它设置一个默认的`Content-Type`;如果用户配置了`headers`,并且也配置了`Content-Type`,那我们就不做任何处理。
但是,我们之前并没有把用户配置的`headers`属性合并到默认的`headers`中,我们只是设置了默认的`Content-Type`,所以接下来,我们就要把用户配置的`headers`属性合并到默认的`headers`中。
## 3.3 代码实现
首先,我们在`src/helpers/headers.ts`中新增一个`flattenHeaders`函数,用于把用户配置的`headers`和默认的`headers`进行合并。
```javascript
// src/helpers/headers.ts
export function flattenHeaders(headers: any, method: Method): any {
if (!headers) {
return headers;
}
// 将用户配置的headers和默认的headers进行合并
headers = deepMerge(headers.common, headers[method], headers);
// 需要删除的键
const methodsToDelete = ["get", "post", "head", "put", "delete", "patch", "options", "common"];
methodsToDelete.forEach((method) => {
delete headers[method];
});
return headers;
}
```
然后,我们在`src/core/xhr.ts`中的`processConfig`函数内使用`flattenHeaders`函数,如下:
```javascript
// src/core/xhr.ts
const processConfig = (config: AxiosRequestConfig): void => {
config.url = transformUrl(config);
config.headers = transformHeaders(config);
config.data = transformRequestData(config);
// 合并headers
config.headers = flattenHeaders(config.headers, config.method!);
};
```
OK,这样我们就实现了`headers`属性的合并。
## 3.4 编写 demo
接下来,我们编写`demo`来测试下我们实现的`headers`属性。
我们在`examples`目录下创建`more`目录,在`more`目录下创建`index.`和`app.ts`作为本次的`demo`。
`index.`文件内容如下:
```
< lang="en">
more example
>
```
`app.ts`文件内容如下:
```typescript
import axios from "../../src/index";
// 不设置headers
axios({
method: "post",
url: "/api/more/post",
data: {
a: 1,
b: 2,
},
});
// 设置headers
axios({
method: "post",
url: "/api/more/post",
headers: {
"Content-Type": "application/json;",
Accept: "application/json, text/plain, */*",
},
data: {
a: 1,
b: 2,
},
});
// 设置headers,并且data为URLSearchParams类型
const paramsString = "q=URLUtils.searchParams&topic=api";
const searchParams = new URLSearchParams(paramsString);
axios({
method: "post",
url: "/api/more/post",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
},
data: searchParams,
});
```
接着,在`server.js`中添加路由:
```javascript
// 添加more接口
router.post("/api/more/post", function(req, res) {
const body = req.body;
res.json(body);
});
```
最后,在根目录下的`index.`中加上启动该`demo`的入口:
```
more
```
## 3.5 运行 demo
接着,我们运行项目,然后打开浏览器,访问 http://localhost:8000/ 即可。
我们点击 `more`,通过`F12`的 `network` 部分我们可以看到三个请求,我们分别观察三个请求的请求头:
- 第一个请求:没有设置`headers`,所以请求头中`Content-Type`为默认值`application/json;charset=utf-8`

- 第二个请求:设置了`headers`,请求头中`Content-Type`为我们设置的`application/json;`,并且多了`Accept`请求头

- 第三个请求:设置了`headers`,并且`data`为`URLSearchParams`类型,请求头中`Content-Type`为我们设置的`application/x-www-form-urlencoded; charset=UTF-8`

OK,我们`headers`属性已经实现了。
# 4. 遗留问题
虽然我们已经实现了`headers`属性,但是这里还有一个问题,那就是我们之前在处理请求`data`的时候,并没有根据不同的`Content-Type`对`data`做处理,我们只是简单的把`data`用`JSON.stringify`序列化了一下。而实际上,当`data`为普通对象的时候,且`Content-Type`为`application/json`的时候,我们才应该用`JSON.stringify`序列化;当`data`为某些类型如`URLSearchParams`的时候,我们可以直接传`data`给`xhr.send`,并且浏览器会自动为`data`设置合适的`Content-Type`,而我们却多此一举的设置了`Content-Type`。
所以,下一篇文章我们就来处理这个问题,根据不同的`data`类型和`Content-Type`来动态处理请求`data`。
# 5. 总结
本篇文章我们实现了`config`对象中的`headers`属性,通过把用户配置的`headers`和默认的`headers`进行合并,从而实现了自定义请求头。并且编写了`demo`进行了测试,结果证明实现是`OK`的。
另外,我们还遗留了一个问题,那就是没有根据不同的`data`类型和`Content-Type`来动态处理请求`data`,这个问题我们会在下一篇文章中解决。
最新文章
- 智能驾驶技术:现状、挑战与未来发展趋势
- 车主必读:伤残赔偿与盗抢险的全面保障指南
- 电动化浪潮:固态电池与高压平台引领汽车产业变革
- 电动化与智能化:全球汽车产业变革的四大趋势解析
- 智能驾驶与新能源汽车:车联网技术的未来展望
- 车载灭火器与ABS系统:行车安全防护全指南
- 汽车引擎轰鸣声在夜晚格外清晰
- 汽车防垢涂层技术提升驾驶安全
- 交通事故理赔指南:必备材料与流程详解
- 发动机轰鸣汽车飞驰在高速公路上
- 轮胎气压保养全攻略:安全行车从胎压开始
- 未来出行革命:电动化、自动驾驶与共享模式破解城市交通困境
- 长城脚下汽车驰骋
- 汽车雨刮器保养全攻略:延长寿命确保雨天行车安全
- V2X车联网与激光雷达技术重塑智能出行新时代
- 燃油系统与汽车动力关系
- 特斯拉自动驾驶技术引领未来出行新潮流
- 汽车动力系统优化技术研究
- 固态电池革命:能量密度提升50%,重塑未来汽车动力格局
- 车险指南:交强险、车损险与三者险全面解析
