axios源码学习
环境搭建:
-
npm install
-
点到入口文件,index.js,找到
lib\axios.js
就是入口文件了,node进入调试模式就可以进行调试了
首先看lib\axios.js
文件调用createInstance
函数创建一个axios
实例。这边返回的是一个axios实例,又因为其使用bind方法
将其指向当前实例的request方法,因此,这边instance
返回的就是request
函数
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
// 将request函数绑定到当前对象上,此处的bind方法其实就是对Function.bind方法的封装
var instance = bind(Axios.prototype.request, context);
// 继承Axios.prototype上的属性
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
// 适配axios可以直接使用axios.create(config)或者axios.get(url,config)的方式创建实例
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
//使用默认配置项创建axios实例
var axios = createInstance(defaults);
//下面代码省略,总结来说是对实例对象的一些配置信息
此处,一定有人会问,为什么不直接使用new Axios
创建实例呢?
摘自:axios执行原理了解一下!因为axios内部调用的都是Axios.prototype.request
方法,Axios.prototype.request
默认请求为get
,为了让开发这可以直接调用axios()
就可以发送请求,而不是axios.get()
。如果直接new一个axios对象是无法实现这种简写的。
接下来看看createInstance
到底做了什么:
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
//主要函数,request函数
Axios.prototype.request = function request(configOrUrl, config) {
// Allow for axios('example/url'[, config]) a la fetch API
//支持直接配置url的方式或者是axios(url,{})方式。axios(url)或者axios(url,config)
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
//合并配置项
config = mergeConfig(this.defaults, config);
// Set config.method
//配置请求方法,如果配置了请求方法则使用自定义配置,否则使用默认配置,如果没穿则默认使用Get
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
//这个不知道是用来做什么的,不过不影响看整体流程
var transitional = config.transitional;
if (transitional !== undefined) {
validator.assertOptions(transitional, {
silentJSONParsing: validators.transitional(validators.boolean),
forcedJSONParsing: validators.transitional(validators.boolean),
clarifyTimeoutError: validators.transitional(validators.boolean)
}, false);
}
// filter out skipped interceptors
//我们常用的请求拦截器配置
var requestInterceptorChain = [];
var synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
//响应拦截器配置
var responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
var promise;
var newConfig = config;
try {
//最终会调用dispatchRequest方法
promise = dispatchRequest(newConfig);
} catch (error) {
return Promise.reject(error);
}
return promise;
};
看看dispatchRequest
做了什么吧。顾名思义,派发请求,在此处会触发发起请求的方法。
用我自己简化版的代码来看吧
function dispatchRequest(config){
if(!config) return new Error('没有请求配置')
config.headers = config.headers || {};
config.data = config.data;
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function(method) {
delete config.headers[method];
}
);
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function(response){
return response
},function(reason){
return Promise.reject(reason)
})
}
总的来说做了两件事情,
-
配置请求头和请求数据信息
-
调用
adapter
适配器
adapter
是个什么东西呢?
溯源可以发现,在lib\defaults\index.js
中发现,适配器是用来区分不用环境的代码
axios官网有些,其支持这两个特性:
-
从浏览器中创建 XMLHttpRequests
-
从 node.js 创建 http 请求
适配器就是用来做不同环境区分用的,我们看的是浏览器环境的代码,Node环境流程和功能大致和浏览器端类似,后面有时间就继续更。
var defaults = {
adapter:getDefaultAdapter()
}
function getDefaultAdapter() {
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;
}
既然最终会调用adapter
函数,那么来看看require('../adapters/xhr')
中做了什么事情:
总的来说适配器发起了请求,并且重写了各个请求的回调方法。请求结束后会回调onloadend
方法,返回的是一个promise
对象,然后一个请求的流程就结束了后面是对调用结果的处理。
三大步:
- 创建请求
var request = new XMLHttpRequest();
- 发起请求
request.open()
- 发送请求
request.send(requestData)
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;
var responseType = config.responseType;
var onCanceled;
//创建一个请求
var request = new XMLHttpRequest();
var fullPath = buildFullPath(config.baseURL, config.url);
// 初始化一个请求,该方法只能在js代码中使用,原生代码中需要使用openRequest()方法
var parsed = url.parse(fullPath);
var protocol = utils.getProtocol(parsed.protocol);
//request.open(method,url,async,user,password)
request.open(
config.method.toUpperCase(),
buildURL(fullPath, config.params, config.paramsSerializer),
true
);
// Set the request timeout in MS
request.timeout = config.timeout;
//loadend事件总是在一个资源的加载进度停止之后被触发
function onloadend() {
if (!request) {
return;
}
var responseData =
!responseType || responseType === "text" || responseType === "json"
? request.responseText
: request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
config: config,
request: request,
};
settle(
function _resolve(value) {
resolve(value);
},
function _reject(err) {
reject(err);
},
response
);
// Clean up request
request = null;
}
// 重写onload函数
if ("onloadend" in request) {
// Use onloadend if available
request.onloadend = onloadend;
} else {
// 使用默认处理方法readystatechange改变时的回调函数
// Listen for ready state to emulate onloadend
request.onreadystatechange = function handleLoad() {
//readyState == 4表示已经发送请求,服务器已完成返回相应,浏览器已完成了下载响应内容
if (!request || request.readyState !== 4) {
return;
}
// The request errored out and we didn't get a response, this will be
// handled by onerror instead
// With one exception: request that using file: protocol, most browsers
// will return status as 0 even though it's a successful request
//处理错误信息
if (
request.status === 0 &&
!(request.responseURL && request.responseURL.indexOf("file:") === 0)
) {
return;
}
// readystate handler is calling before onerror or ontimeout handlers,
// so we should call onloadend on the next 'tick'
setTimeout(onloadend);
};
}
// Handle browser request cancellation (as opposed to a manual cancellation)
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(
//请求取消
);
// Clean up request
request = null;
};
// Handle low level network errors
request.onerror = function handleError() {
// Real errors are hidden from us by the browser
// onerror should only fire if it's a network error
reject(
//请求出错
);
// Clean up request
request = null;
};
// Handle timeout
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = config.timeout
? "timeout of " + config.timeout + "ms exceeded"
: "timeout exceeded";
var transitional = config.transitional || transitionalDefaults;
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(
//请求超时
);
// Clean up request
request = null;
};
if (!requestData) {
requestData = null;
}
if (parsed.path === null) {
reject(
//请求路径有问题
);
return;
}
// 发送请求
request.send(requestData);
});
};
axios是怎么做到防止跨站请求伪造的?
让每一个请求都带一个从cookie中拿到的key,根据浏览器同源策略,假的网站拿不到cookie中的key.这样后台就可以轻松辨别出这个请求是否是用户在假的网站上误导。
当用户进行登录请求时,后端把包含xsrf字段的cookie保存在session中并返回给前端,前端需要获取到cookie中的值并且能放入ajax请求体或请求头中,后端把这个值与session中的相应值进行判断,根据跨域不可访问不同域的cookie,攻击者也很难猜测出xsrf的值。
axios获取到值后默认放入request header
中的。
我自己仿照axios写的一个测试代码,里面有测试代码,直接扒下来就可以看,简易版的实现。
网友评论