axios简介
一、axios是一个基于Promise的HTTP客户端的网络请求库,作用于Node.js和浏览器中。它是isomorphic的(即同一套代码可以运行在浏览器和Node.js中)。在服务端它使用原生Node.js http模块,而在客户端则使用XMLHttpRequest。
二、拥有的特性
- 同时支持浏览器和Node.js环境
- 支持Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 能够取消请求
- 自动转换JSON数据
- 客户端支持防御CSRF攻击
三、axios生态
HTTP拦截器的设计与实现
拦截器简介
一、Axios是一个基于Promise的HTTP客户端,而HTTP协议是基于请求和响应:
所以Axios提供了请求拦截器和响应拦截器来分别处理请求和响应。
二、请求拦截器、响应拦截器的作用
1、请求拦截器:该类拦截器的作用是在请求发送前统一执行某些操作,比如在请求头中添加token字段。
2、响应拦截器:该类拦截器的作用是接收到服务器响应后统一执行某些操作,比如发现响应状态码为401时,自动跳转到登录页。
三、设置拦截器,通过axios.interceptors.request和axios.interceptors.response对象提供的use方法,就可以分别设置请求拦截器和响应拦截器
// 添加请求拦截器 —— 处理请求配置对象axios.interceptors.request.use(function (config) {config.headers.token = 'added by interceptor'return config})// 添加响应拦截器 —— 处理响应对象axios.interceptors.response.use(function (data) {data.data = data.data + '- modified by interceptor'return data})
拦截器实现
一、按照功能把发送HTTP请求拆解成不同类型的子任务
1、用于处理请求配置对象的子任务
2、用于发送HTTP请求的子任务
3、用于处理响应对象的子任务。
当我们按照指定的顺序来执行子任务时,就可以完成一次完成的HTTP请求。
二、从任务注册、任务编排、任务调度三个方面分析axios拦截器的实现
任务注册
一、在Axios源码中,可以找到axios对象的定义。
1、默认的axios实例是通过createInstance 方法创建的,该方法最终返回的是Axios.prototype.request函数对象。
// lib/axios.jsfunction createInstance(defaultConfig) {var context = new Axios(defaultConfig);var instance = bind(Axios.prototype.request, context);// Copy axios.prototype to instanceutils.extend(instance, Axios.prototype, context);// Copy context to instanceutils.extend(instance, context);return instance;}// Create the default instance to be exportedvar axios = createInstance(defaults);
二、Axios的构造函数
1、可以找到axios.interceptors对象的定义,也知道了interceptors.request和interceptors.response对象都是InterceptorManager类的实例。
// lib/core/Axios.jsfunction Axios(instanceConfig) {this.defaults = instanceConfig;this.interceptors = {request: new InterceptorManager(),response: new InterceptorManager()};}
三、InterceptorMangager构造函数及相关的use方法
// lib/core/InterceptorManager.jsfunction InterceptorManager() {this.handlers = [];}InterceptorManager.prototype.use = function use(fulfilled, rejected) {this.handlers.push({fulfilled: fulfilled,rejected: rejected});// 返回当前的索引,用于移除已注册的拦截器return this.handlers.length - 1;};
1、通过观察use方法,我们可知注册的拦截器都会被保存到InterceptorManager对象的handlers属性中。
2、Axios对象与InterceptorManager对象的内部结构与关系
任务编排
一、对已注册的任务进行编排,这样才能确保任务的执行顺序。
二、把一次完整的HTTP请求分为处理请求配置对象,发起HTTP请求和处理响应对象3个阶段。
三、Axios如何发请求的
1、
axios({url: '/hello',method: 'get',}).then(res =>{console.log('axios res: ', res)console.log('axios res.data: ', res.data)})
2、通过前面的分析,已知axios对象对应的是Axios.prototype.request函数对象,该函数的具体实现如下:
// lib/core/Axios.jsAxios.prototype.request = function request(config) {config = mergeConfig(this.defaults, config);// 省略部分代码var chain = [dispatchRequest, undefined];var promise = Promise.resolve(config);// 任务编排this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {chain.unshift(interceptor.fulfilled, interceptor.rejected);});this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {chain.push(interceptor.fulfilled, interceptor.rejected);});// 任务调度while (chain.length) {promise = promise.then(chain.shift(), chain.shift());}return promise;};
四、任务编排前后对比图
任务调度
一、任务编排完成后,要发起HTTP请求,我们还需要按编排后的顺序执行任务调度。
二、在Axios中具体的调度方式
// lib/core/Axios.jsAxios.prototype.request = function request(config) {// 省略部分代码var promise = Promise.resolve(config);while (chain.length) {promise = promise.then(chain.shift(), chain.shift());}}
1、因为chain是数组,所以通过while语句我们就可以不断地取出设置的任务,然后组装成Promise调用链从而实现任务调度,对应的处理流程如下图所示
Axios拦截器的优点
一、Axios通过提供拦截器机制,让开发者可以很容易在请求的生命周期中自定义不同的处理逻辑。
二、可以通过拦截器机制来灵活地扩展Axios的功能,比如Axios生态中列举的axios-response-logger和axios-debug-log这两个库。
三、参考Axios拦截器的设计模型,我们就可以抽出以下通用的任务处理模型
HTTP适配器的设计与实现
默认HTTP适配器
一、Axios同时支持浏览器和Node.js环境。
1、浏览器环境,我们可以通过XMLHttpRequst 或 fetch API 来发送HTTP请求。
2、Node.js环境,我们可以通过Node.js内置的http或https模块来发送HTTP请求。
二、为了支持不同的环境,Axios引入了适配器。
axios中适配器的实现
一、在HTTP拦截设计部分,我没看到了一个dispatchRequst方法,该方法用于发送HTTP请求,它的具体实现如下所示
// lib/core/dispatchRequest.jsmodule.exports = function dispatchRequest(config) {// 省略部分代码var adapter = config.adapter || defaults.adapter;return adapter(config).then(function onAdapterResolution(response) {// 省略部分代码return response;}, function onAdapterRejection(reason) {// 省略部分代码return Promise.reject(reason);});};
1、通过查看dispatchRequest方法,可知Axios支持自定义适配器,同时也提供了默认的适配器。对于大多数场景,我们并不需要自定义适配器,而是直接使用默认的适配器。
2、默认的适配器包含浏览器和Node.js环境的适配代码,具体的适配逻辑如下
(1)在getDefaultAdapter方法中,首先通过平台中特定的对象来区分不同的平台,然后再导入不同的适配器。
// lib/defaults.jsvar defaults = {adapter: getDefaultAdapter(),xsrfCookieName: 'XSRF-TOKEN',xsrfHeaderName: 'X-XSRF-TOKEN',//...}function getDefaultAdapter() {var adapter;if (typeof XMLHttpRequest !== 'undefined') {// For browsers use XHR adapteradapter = require('./adapters/xhr');} else if (typeof process !== 'undefined' &&Object.prototype.toString.call(process) === '[object process]') {// For node use HTTP adapteradapter = require('./adapters/http');}return adapter;}
自定义适配器
一、Axios提供的示例
var settle = require('./../core/settle');
module.exports = function myAdapter(config) {
// 当前时机点:
// - config配置对象已经与默认的请求配置合并
// - 请求转换器已经运行
// - 请求拦截器已经运行
// 使用提供的config配置对象发起请求
// 根据响应对象处理Promise的状态
return new Promise(function(resolve, reject) {
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
settle(resolve, reject, response);
// 此后:
// - 响应转换器将会运行
// - 响应拦截器将会运行
});
}
1、主要关注转换器、拦截器的运行是几点和适配器的基本要求。
(1)当调用自定义适配器之后,需要返回Promise对象。这是因为Axios内部是通过Promise链式调用来完成请求调度。
自定义适配器的用处
一、在Axios生态中,axios-mock-adapter这个库通过自定义适配器,让开发者可以轻松地模拟请求。
1、示例
var axios = require("axios");
var MockAdapter = require("axios-mock-adapter");
// 在默认的Axios实例上设置mock适配器
var mock = new MockAdapter(axios);
// 模拟 GET /users 请求
mock.onGet("/users").reply(200, {
users: [{ id: 1, name: "John Smith" }],
});
axios.get("/users").then(function (response) {
console.log(response.data);
});
请求的处理流程
Axios使用请求拦截器和响应拦截器后,请求的处理流程
CSRF防御
CSRF防御措施
一、Axios内部是使用双重Cookie防御的方案来防御CSRF攻击。
二、Axios提供了xsrfCookieName和xsrfHeaderName两个属性来分别设置CSRF的Cookie名称和HTTP请求头的名称,它们的默认值如下
// lib/defaults.js
var defaults = {
adapter: getDefaultAdapter(),
// 省略部分代码
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
};
1、以浏览器平台为例,来看一下Axios如何防御CSRF攻击
// lib/adapters/xhr.js
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestHeaders = config.headers;
var request = new XMLHttpRequest();
// 省略部分代码
// 添加xsrf头部
if (utils.isStandardBrowserEnv()) {
var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
cookies.read(config.xsrfCookieName) :
undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
request.send(requestData);
});
};
