这个系列是个人写的第一个系列博客,也是个人第一次尝试阅读源码,为此特地开了个博客园账号。因为笔者是菜鸟,所以写的东西肯定非常混乱,内容分散,缺乏架构和组织,知识点不到位等等。这些都是可以预见的缺点,但自己还是坚持尝试阅读和记录源码,将所学的知识点总结记录以供自勉吧。
这一篇源码的主要内容是,axios在不同平台的兼容性的实现以及浏览器发送请求的方式。
这一块的代码在lib/default.js中。axios通过adapter获得node和浏览器两个平台中的不同的适配器。
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// 浏览器使用的adapter
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// node 使用的adapter
adapter = require('./adapters/http');
}
return adapter;
}
从代码中可以看到,axios根据浏览器和node环境独有的属性区分不同的平台。浏览器环境中实现了XMLHttpRequest对象,因此该对象存在,便返回一个浏览器平台使用的适配器。而根据require("./adapters/xhr")找打xhr.js。我们可以看到一个非常非常长的
xhrAdapter()方法。
由于代码太长,这次就不拆解了。这篇文章和这次阅读的目的,是为了弄清浏览器和node是怎么发送http请求的。因此我只做相应的截取来记录。
首先浏览器发送请求的第一步,就是新建一个xhr对象。
var request = new XMLHttpRequest();
然后进行最基础的basic认证。
if (config.auth) {
var username = config.auth.username || '';
var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}
这种认证方式如此朴实无华,把用户名和密码拿冒号连接起来,再用window.btoa()方法加密成一个base-64编码的字符串就行了。
加密完成后便是打开连接。
var fullPath = buildFullPath(config.baseURL, config.url);
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true)
fullPath方法和buildURL方法就不细说了,主要作用是拼凑url。重要的是open方法。通常我把浏览器发送请求分为四个步骤,打开连接,发送数据,接收数据,关闭连接。XMLHttpRequest.open()便是打开连接。该方法的详细讲解请看MDN https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/open 。而XMLHttpRequest的属性readyState便用五个状态区分浏览器发送请求的四个步骤,详请如下。
了解了open方法和readyState方法后我们继续往下读,可以看到一下代码 :
request.onreadystatechange = function handleLoad() {
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;
}
// 获取response数据
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
settle(resolve, reject, response);
// Clean up request
request = null;
};
XMLHttpRequest.onreadyState() 方法从方法名就很明显地看出,这个方法在readyState改变时被调用。
if (!request || request.readyState !== 4) {
return;
}
结合上面的readyState的含义不难理解,该函数是在成功接收数据后再调用。而readyState变为4,也就是请求完成,所有数据都成功获取后再开始执行后面的操作,与就是获取返回的数据。
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
这一段代码中涉及的属性的XHRHttpRequest的MDN文档上都有说明,就不细说了。
最后我们用原生的js配合XHRHttpRequest对象,根据这次阅读学到的知识,来写一个简单的请求吧。
// 获取xhr对象
var xhr = new XMLHttpRequest();
// 打开连接
xhr.open("GET", "https://www.fastmock.site/mock/53689431081330c231eb05110e2161b0/first/api/test", true);
// 监听连接状况,获取数据后将结果及状态打印出来
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) {
return;
}
if (xhr.response) {
console.log("responseData" + xhr.response);
}
if (xhr.responseText) {
console.log("responseText" + xhr.responseText);
}
if (xhr.getAllResponseHeaders) {
console.log("responseHeader" + xhr.getAllResponseHeaders())
}
console.log(xhr.status, xhr.statusText);
}
// 发送请求
xhr.send();