赞
踩
Axios 是一个基于 Promise 的 HTTP 客户端,同时支持浏览器和 Node.js 环境。拥有以下特性:
Axios 提供了请求拦截器和响应拦截器来分别处理请求和响应,它们的作用如下:
Axios 拦截器完整的使用流程:
- /** 添加请求拦截器 —— 处理请求配置对象 */
- axios.interceptors.request.use(function (config) {
- config.headers.token = 'added by interceptor';
- return config;
- }, function (error) {
- /** 对请求错误做些什么 */
- return Promise.reject(error);
- });
-
- /** 添加响应拦截器 —— 处理响应对象 */
- axios.interceptors.response.use(function (data) {
- data.data = data.data + ' - modified by interceptor';
- return data;
- }, function (error) {
- /** 超出 2xx 范围的状态码都会触发该函数。对响应错误做点什么 */
- return Promise.reject(error);
- });
-
- axios({
- url: '/hello',
- method: 'get',
- }).then(res =>{
- console.log('axios res.data: ', res.data)
- })
接下来将从任务注册、任务编排和任务调度 三个方面来分析 Axios 拦截器的实现。
axios
实例是通过 createInstance
方法创建的,该方法最终返回是 Axios.prototype.request
函数对象。interceptors.request
和 interceptors.response
对象都是 InterceptorManager
类的实例。
- /** lib/axios.js */
- function createInstance(defaultConfig) {
- var context = new Axios(defaultConfig);
- var instance = bind(Axios.prototype.request, context);
-
- /** 拷贝 axios.prototype 给 instance */
- utils.extend(instance, Axios.prototype, context);
- /** 拷贝 context 给 instance */
- utils.extend(instance, context);
- return instance;
- }
-
- /** Create the default instance to be exported */
- var axios = createInstance(defaults);
- /** lib/core/Axios.js */
- function Axios(instanceConfig) {
- this.defaults = instanceConfig;
- this.interceptors = {
- request: new InterceptorManager(),
- response: new InterceptorManager()
- };
- }
- /** lib/core/InterceptorManager.js */
- function InterceptorManager() {
- this.handlers = [];
- }
-
- InterceptorManager.prototype.use = function use(fulfilled, rejected) {
- this.handlers.push({
- fulfilled: fulfilled,
- rejected: rejected
- });
- /** 返回当前的索引,用于移除已注册的拦截器 */
- return this.handlers.length - 1;
- };
通过观察 use
方法,我们可知注册的拦截器都会被保存到 InterceptorManager
对象的 handlers
属性中。下面我们用一张图来总结一下 Axios
对象与 InterceptorManager
对象的内部结构与关系:
注册后,对已注册的任务进行编排,这样才能确保任务的执行顺序。axios
对象对应的 Axios.prototype.request
函数对象的具体实现如下:
- /** lib/core/Axios.js */
- Axios.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;
- };
任务编排前后的对比图:
从 Axios.prototype.request
中可以看到,chain 是数组,所以通过 while 语句我们就可以不断地取出设置的任务,然后组装成 Promise 调用链从而实现任务调度,对应的处理流程如下图所示:
Axios 通过提供拦截器机制,让开发者可以很容易在请求的生命周期中自定义不同的处理逻辑。此外,也可以通过拦截器机制来灵活地扩展 Axios 的功能。综上,axios 具有以下的通用的任务处理模型:
Axios 同时支持浏览器和 Node.js 环境,对于浏览器环境来说是通过 XMLHttpRequest
或 fetch
API 来发送 HTTP 请求,而对于 Node.js 环境来说是通过 Node.js 内置的 http
或 https
模块来发送 HTTP 请求。为了支持不同的环境,Axios 引入了 HTTP 适配器。用于发送 HTTP 请求的 dispatchRequest 方法部分细节如下:
- /** lib/core/dispatchRequest.js */
- module.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);
- });
- };
可以看的,Axios 支持自定义适配器,同时也提供了默认的适配器。对于大多数场景,直接使用默认的适配器(包含浏览器和 Node.js 环境的适配代码)即可。默认适配器逻辑为在 getDefaultAdapter
方法中,首先通过平台中特定的对象来区分不同的平台,然后再导入不同的适配器,具体的代码比较简单:
- /** lib/defaults.js */
- var defaults = {
- adapter: getDefaultAdapter(),
- xsrfCookieName: 'XSRF-TOKEN',
- xsrfHeaderName: 'X-XSRF-TOKEN',
- //...
- }
-
- function getDefaultAdapter() {
- var adapter;
- if (typeof XMLHttpRequest !== 'undefined') {
- /** 浏览器 使用 XHR adapter */
- adapter = require('./adapters/xhr');
- } else if (typeof process !== 'undefined' &&
- Object.prototype.toString.call(process) === '[object process]') {
- /** node 使用 HTTP adapter */
- adapter = 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);
-
- // 此后:
- // - 响应转换器将会运行
- // - 响应拦截器将会运行
- });
- }
当调用自定义适配器之后,需要返回 Promise 对象。这是因为 Axios 内部是通过 Promise 链式调用来完成请求调度的。
跨站请求伪造(Cross-site request forgery),通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。
简单来说就是,利用用户的登录被攻击网站在毫不知情情况下点击发起攻击网站 中的指向被攻击网站的链接。跨站请求可以是:图片 URL、超链接、CORS、Form 提交等等。因为,如果只有简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
CSRF 防御措施一般有:
- <form method="POST" action="/upload?_csrf={{由服务端生成}}" enctype="multipart/form-data">
- 用户名: <input name="name" />
- 选择头像: <input name="file" type="file" />
- <button type="submit">提交</button>
- </form>
3. 双重 Cookie 防御:将 token 设置在 Cookie 中,在提交(POST、PUT、PATCH、DELETE)等请求时提交 Cookie,并通过请求头或请求体带上 Cookie 中已设置的 token,服务端接收到请求后,再进行对比校验:
- let csrfToken = Cookies.get('csrfToken');
-
- function csrfSafeMethod(method) {
- /** 以下HTTP方法不需要进行CSRF防护 */
- return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
- }
-
- $.ajaxSetup({
- beforeSend: function(xhr, settings) {
- if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
- xhr.setRequestHeader('x-csrf-token', csrfToken);
- }
- },
- });
而 Axios 中采用的是双重 Cookie 防御 的方案来防御 CSRF 攻击,提供了 xsrfCookieName
和 xsrfHeaderName
两个属性来分别设置 CSRF 的 Cookie 名称和 HTTP 请求头的名称,默认值如下:
- /** lib/defaults.js */
- var defaults = {
- adapter: getDefaultAdapter(),
-
- // 省略部分代码
- xsrfCookieName: 'XSRF-TOKEN',
- xsrfHeaderName: 'X-XSRF-TOKEN',
- };
以浏览器的适配器为例,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);
- });
- };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。