axios 中统一的封装了如下的问题
- 支持浏览器请求 XMLHttpRequests 请求
- 支持 node 中的 http 模块的请求 http 请求
- 支持 Promise API
- 可以拦截请求和响应
- 转换 request 和 response 数据
- 取消请求
- 自动转换 JSON 数据
- 默认增加解决 XSRF(CSRF)攻击的参数 XSRF
- axios 中是如何实现在浏览器和 node 中发起 http 请求的?且支持 Promise 调用
在 axios 中,针对浏览器和 node 环境中,分别通过 Promise 配合 XMLHttpRequest 和 http 模块,来实现了两个不同环境下的 adapter,具体代码如下:
function getDefaultAdapter() {
// 判断当前是在浏览器环境还是node环境
var adapter;
if (typeof XMLHttpRequest !== "undefined") {
// For browsers use XHR adapter
adapter = require("./adapters/xhr");
} else if (
typeof process !== "undefined" &&
Object.prototype.toString.call(process) === "[object process]"
) {
// For node use HTTP adapter
adapter = require("./adapters/http");
}
return adapter;
}
- 它是如何拦截请求的?
当你通过 axios 发起请求时,且注册了request interceptors
或者 response interceptors
后,axios 会在初始化阶段创建一个var chain = [dispatchRequest, undefined]
数组。然后会将注册的request interceptors
和 response interceptors
分别插入到chain
的头部和尾部。
最后再通过 promise 顺序执行链式调用。完成请求和响应的拦截功能。
// Hook up interceptors middleware
// 初始化的chain中的dispatchRequest就是执行的http请求
// 添加拦截器中间件
var chain = [dispatchRequest, undefined];
// 初始化一个promise实例
var promise = Promise.resolve(config);
// 将request拦截器添加到chain的头
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// response拦截器添加到chain的尾部
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// 在向chain的头尾添加拦截器后,按照promise的then,顺序执行
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
- 它是如何取消请求的呢?
首先,我们需要 通过 config.cancelToken = new axios.CancelToken(function executor(c) {}) 去实例化一个 cancelToken, 传入 executor 的 c 则就是 cancel 函数,调用它则会取消当前请求
function CancelToken(executor) {
if (typeof executor !== "function") {
throw new TypeError("executor must be a function.");
}
var resolvePromise;
// 创建一个promise
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
// 执行executor,并传入一个函数,当用户想要cancel掉某个请求时,只需要执行这个传入的函数,并且输入相应的提示信息即可
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
// 执行promise的resolve,传入cancel
resolvePromise(token.reason);
});
}
// 创建一个cancel原因
function Cancel(message) {
this.message = message;
}
如果熟悉XMLHttpRequest
的方法,那我们应该知道一个abort
。对,在 axios 中同样是通过 abort 来实现的取消请求。在xhr.js
中有这么一段代码。
如果实例化了 config.cancelToken,则会执行,并且 reject 一个原因
// 首先需要在request config中配置cancelToken,
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
// 通过abort终止当前请求
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
实际应用中,我们可以通过 cancelToken 来记录请求 url,以避免重复请求。并且可创建一个白名单,以防止一些需要多次请求的接口
import axios from "axios";
const whiteList = ["/api/1", "/api/2"]; //这些接口不进行 防重复提交
const requestMap = new Map();
const CancelToken = axios.CancelToken;
// 防止重复请求
const removeRequest = (mapkey) => {
const cancel = requestMap.get(mapkey);
cancel("请勿重复操作");
requestMap.delete(mapkey);
};
// 过滤请求
axios.interceptors.request.use(
(config) => {
/** 其他的配置 **/
// 使用的是请求方法和请求url组成的key
const mapkey = `${config.method}-${config.url}`;
if (requestMap.has(mapkey)) {
// 上删除上一次未完成的请求
removeRequest(mapkey);
}
if (!requestMap.has(mapkey)) {
config.cancelToken = new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
if (config.url && whiteList.indexOf(config.url) === -1) {
requestMap.set(mapkey, c);
}
});
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
(response) => {
const mapkey = `${response.config.method}-${response.config.url}`;
// 在一个ajax响应后再执行一下取消操作
if (requestMap.has(mapkey)) {
removeRequest(mapkey);
}
},
(error) => {
// 在一个ajax响应后再执行一下取消操作
const mapkey = `${error.config.method}-${error.config.url}`;
if (requestMap.has(mapkey)) {
removeRequest(mapkey);
}
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
(response) => {
/* 一些你自己的配置 */
removePending(response.config, "response"); // 在一个ajax响应后再执行一下取消操作,把已经完成的请求从pending中移除 下次请求同样的url就不会执行
},
(error) => {
/* 一些你自己的配置 */
return Promise.reject(error);
}
);
- axios 中如何解决的 XSRF(CSRF)安全问题?
在defaults.js
中,有一些默认的 config 配置, 这需要后端来协同。具体文章可查看
{
xsrfCookieName: 'XSRF-TOKEN', // 默认值, 表示如果需要防止XSRF攻击,则需要后端生成一个key为XSRF-TOKEN的session(当然,也可以和后端协商,然后自己写config来覆盖)
xsrfHeaderName: 'X-XSRF-TOKEN', // 然后在axios中自动的读取cookie中的值并在header中写入一个自定义的头X-XSRF-TOKEN
}
读取的操作在xhr.js
中
if (utils.isStandardBrowserEnv()) {
var cookies = require("./../helpers/cookies");
// Add xsrf header
// (当配置了允许访问cookie设置 || 访问该请求在同一个源 ) && config.xsrfCookieName
var xsrfValue =
(config.withCredentials || isURLSameOrigin(fullPath)) &&
config.xsrfCookieName
? cookies.read(config.xsrfCookieName)
: undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}