当前位置 : 首页 > 维修数据

Axios请求头Content-Type动态设置与规范化处理

<|begin▁of▁sentence|># 1. 前言 在上一篇文章中,我们实现了`axios`的请求和响应配置化,即用户可以配置`config`对象中的`url`、`method`、`params`、`data`、`headers`、`timeout`、`responseType`等属性。其中,`headers`配置对象中的`Content-Type`属性是`axios`内部帮我们自动设置的,而内部是如何判断并设置该属性的呢?这就是本篇文章要讨论的内容。 # 2. 需求分析 在发送`AJAX`请求时,我们可以通过配置`config`对象中的`headers`属性来给请求头添加一些字段,而`headers`属性是个对象,如下: ```javascript headers: { 'content-type': 'application/x-www-form-urlencoded', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } ``` 在之前,我们只处理了`headers`中`content-type`属性,并且是写死的,如下: ```typescript // src/xhr.ts if (utils.isObject(data)) { setContentTypeIfUnset(headers, "application/json;charset=utf-8"); } ``` 而实际上,`content-type`的值应该根据`data`的数据类型来动态设置,并且用户也可以自行在`headers`中配置`content-type`属性,如果用户配置了该属性,我们就用用户配置的,如果用户没有配置,我们再根据`data`的数据类型来动态设置。 所以,我们需要实现以下需求: 1. 用户如果配置了`headers`中的`content-type`属性,则用用户配置的; 2. 用户如果没有配置`headers`中的`content-type`属性,则根据`data`的数据类型来动态设置; # 3. 实现思路 根据需求,我们可以写出以下伪代码: ```javascript if (用户配置了headers中的content-type属性) { 就用用户配置的 } else { 根据data的数据类型来动态设置content-type } ``` 那么,我们该如何判断用户是否配置了`headers`中的`content-type`属性呢?并且,`headers`属性中字段名可能是任意大小写字母,比如`content-type`、`Content-Type`、`CONTENT-TYPE`等等,我们不可能把所有情况都枚举出来,所以我们需要对`headers`属性做一层规范化:将`headers`属性中字段名全部转换成首字母小写,其余单词首字母大写的驼峰格式,例如:将`content-type`转换成`Content-Type`。 规范化之后,我们就可以通过判断`headers`中是否存在`Content-Type`属性来判断用户是否配置了`content-type`属性。 # 4. 代码实现 ## 4.1 辅助函数 根据实现思路,我们需要先实现一个规范化`headers`属性中字段名的辅助函数,我们在`src/helpers/headers.ts`中实现该函数: ```typescript // src/helpers/headers.ts /** * 把headers属性中字段名全部转换成首字母小写,其余单词首字母大写的驼峰格式 * @param headers * @param normalizedName */ export function normalizeHeaderName(headers: any, normalizedName: string): void { if (!headers) { return; } Object.keys(headers).forEach(name => { if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { headers[normalizedName] = headers[name]; delete headers[name]; } }); } ``` 该函数接收两个参数:`headers`和`normalizedName`,其中`headers`是`config`对象中的`headers`属性,`normalizedName`是我们想要规范化成的字段名,例如`Content-Type`。该函数的作用是:遍历`headers`对象中的所有属性,如果属性名与`normalizedName`不相等,但是转换成大写后与`normalizedName`转换成大写后相等,那么就把该属性名改成`normalizedName`,并删除原来的属性名。 ## 4.2 处理请求 headers 接下来,我们在发送请求之前对`config`中的`headers`做一层规范化处理,我们在`src/core/xhr.ts`中实现: ```typescript // src/core/xhr.ts import { normalizeHeaderName } from "./helpers/headers"; export default function xhr(config: AxiosRequestConfig): AxiosPromise { return new Promise(resolve => { const { data = null, url, method = "get", headers, responseType, timeout } = config; const request = new XMLHttpRequest(); if (responseType) { request.responseType = responseType; } if (timeout) { request.timeout = timeout; } request.open(method.toUpperCase(), url, true); // 在设置请求header之前先对headers做一层规范化处理 Object.keys(headers).forEach(name => { // 如果data为null,那么headers中设置content-type是没有意义的,直接删除 if (data === null && name.toLowerCase() === "content-type") { delete headers[name]; } else { request.setRequestHeader(name, headers[name]); } }); // 规范化headers中的Content-Type属性 normalizeHeaderName(headers, "Content-Type"); request.send(data); }); } ``` OK,这样我们就实现了对`headers`的规范化处理,并且如果`data`为`null`,那么`headers`中设置`content-type`是没有意义的,我们直接删除。 ## 4.3 根据 data 类型动态设置 content-type 接下来,我们就要根据`data`的数据类型来动态设置`content-type`属性了。我们在`src/helpers/headers.ts`中再实现一个辅助函数: ```typescript // src/helpers/headers.ts /** * 根据data的数据类型来动态设置headers中的Content-Type属性 * @param headers * @param data */ export function processHeaders(headers: any, data: any): any { // 规范化headers中的Content-Type属性 normalizeHeaderName(headers, "Content-Type"); // 如果data是普通对象 if (utils.isObject(data)) { // 并且如果headers中存在Content-Type属性,那么就用用户设置的 if (headers && !headers["Content-Type"]) { // 否则,如果没有设置,就默认设置application/json;charset=utf-8 headers["Content-Type"] = "application/json;charset=utf-8"; } } return headers; } ``` 该函数接收两个参数:`headers`和`data`,其中`headers`是`config`对象中的`headers`属性,`data`是`config`对象中的`data`属性。该函数的作用是:先对`headers`中的`Content-Type`属性做规范化处理,然后判断`data`是否是普通对象,如果是,并且`headers`中不存在`Content-Type`属性,那么就默认设置`application/json;charset=utf-8`。 然后,我们在发送请求之前调用该函数,对`headers`做处理: ```typescript // src/core/xhr.ts import { processHeaders } from "./helpers/headers"; export default function xhr(config: AxiosRequestConfig): AxiosPromise { return new Promise(resolve => { const { data = null, url, method = "get", headers, responseType, timeout } = config; // 在处理请求headers之前先对headers做一层处理 const processedHeaders = processHeaders(headers, data); const request = new XMLHttpRequest(); if (responseType) { request.responseType = responseType; } if (timeout) { request.timeout = timeout; } request.open(method.toUpperCase(), url, true); // 设置请求头 Object.keys(processedHeaders).forEach(name => { if (data === null && name.toLowerCase() === "content-type") { delete processedHeaders[name]; } else { request.setRequestHeader(name, processedHeaders[name]); } }); request.send(data); }); } ``` OK,这样我们就实现了根据`data`的数据类型来动态设置`content-type`属性。 # 5. 编写 demo 接下来,我们编写 `demo` 来测试下以上功能是否正常。 `examples/more/app.ts`: ```typescript import axios from "../../src/index"; // 用户配置了headers中的content-type属性 axios({ method: "post", url: "/api/more/post", data: { a: 1 }, headers: { "content-type": "application/x-www-form-urlencoded", Accept: "application/json, text/plain, */*" } }).then(res => { console.log(res); }); // 用户没有配置headers中的content-type属性 axios({ method: "post", url: "/api/more/post", data: { a: 1 } }).then(res => { console.log(res); }); ``` 我们运行项目后,在浏览器中打开控制台,可以看到两个请求的请求头中`Content-Type`属性分别是: - 第一个请求:`content-type: application/x-www-form-urlencoded` - 第二个请求:`Content-Type: application/json;charset=utf-8` ![](~@/axios/07/01.png) ![](~@/axios/07/02.png) OK,以上结果符合我们的预期,第一个请求使用了用户配置的`content-type`属性,第二个请求根据`data`的数据类型动态设置了`Content-Type`属性。 # 6. 遗留问题 我们虽然实现了根据`data`的数据类型来动态设置`content-type`属性,但是目前只处理了`data`是普通对象的情况,如果`data`是其他类型,比如`URLSearchParams`、`FormData`、`ArrayBuffer`等等,我们还需要根据不同的数据类型来设置不同的`content-type`属性。这个我们会在后面的文章中继续完善。 # 7. 总结 本篇文章我们实现了根据`data`的数据类型来动态设置`content-type`属性,并且用户也可以自行在`headers`中配置`content-type`属性,如果用户配置了该属性,我们就用用户配置的,如果用户没有配置,我们再根据`data`的数据类型来动态设置。 下一篇文章,我们将实现`axios`的响应数据支持泛型。

栏目列表