Axios源码分析
# Axios 源码分析
从封装自己的 Ajax,再到 axios 源码。
总体来说,axios 在配置项上做了很多
我们能学习的地方不仅仅在于如何封装,同等重要的还有它实现的技巧
# XMLHttpRequest 简单使用
# 介绍
- 实例
let xhr = new XMLHttpRequest()
1
- 发起 http 请求
xhr.open('GET', '/api/data')
xhr.send()
1
2
2
- 设置回调函数
xhr.onreadystatechange = function() {
// readyState: xhr 对象的状态
// status:xhr 响应的状态
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText)
} else {
alert(xhr.statusText)
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 封装 Ajax
封装自己的 Ajax
const fn = (res) => { }
let { method = 'GET', url, data, onSuccess = fn, onError = fn, type = '' } = options || {}
// 创建兼容 XMLHttpRequest 对象
let xhr
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest()
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP')
}
// 给请求添加状态变化事件处理函数
xhr.onreadystatechange = function () {
// 判断状态码
if (xhr.readyState == 4) {
if (xhr.status == 200) {
let res
// 根据type参数,判断返回的内容需要进行怎样的处理
if (type == 'json') {
// 获得 json 形式的响应数据,并使用parse方法解析
res = JSON.parse(xhr.responseText)
} else if (type == 'xml') {
// 获得 XML 形式的响应数据
res = xhr.responseXML
} else {
// 获得字符串形式的响应数据
res = xhr.responseText
}
onSuccess(res) // 成功回调
} else {
onError(xhr.responseText)
}
}
}
// 判断data是否有数据
let param = ''
if (data) {
if (JSON.stringify(data) != '{}') { // 这里使用stringify方法将js对象格式化为json字符串
url += '?'
for (let i in data) {
param += i + '=' + data[i] + '&' // 将js对象重组,拼接成url参数存入param变量中
}
// 去除拼接的最后一个&字符
param = param.slice(0, param.length - 1)
}
}
// 判断method是否为get
if (method == 'get') {
// 是则将数据拼接在url后面
url = url + param
}
// 初始化请求
xhr.open(method, url, true)
// 如果method == post
if (method == 'post') {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
//发送请求
xhr.send(param)
} else {
// 发送请求
xhr.send(null)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
验证
myAjax({
method: 'get',
url: 'https://coderly.cn/',
type: '',
onSuccess(result) {
console.log('result', result)
},
onError(error) {
console.log('error', error)
},
})
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11

# Node 的 http 模块 使用
const http = require('http')
http
.request(
{
url: 'http://192.168.1.2/index.html',
method: 'GET',
},
(response) => {
let result = ''
response.on('data', (chunk) => {
result += chunk
})
response.on('end', () => {
console.log('Success:', result)
})
}
)
.on('error', (error) => {
console.log('Error:' + error.message)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Axios
版本:v0.20.0
# 目录结构
|- examples
|- lib
|- adapters # 真实发起请求的(node的http模块和XMLHttpRequest做兼容node环境和浏览器环境)
|- http.js
|- xhr.js
|- cancel # 取消请求的操作
|- core # axios 核心功能
|- Axios.js # Axios 构造函数
|- buildFullPath.js
|- createError.js
|- dispatchRequest.js # request() 实际发起请求的函数(是adapters兼容性处理)
|- enhanceError.js
|- InterceptorManager.js
|- mergeConfig.js # config 配置项合并函数
|- settle.js
|- transformData.js
|- helpers # 辅助工具,一般和解析、url、cookie等有关
|- axios.js # 导出 axios 对象以及在该对象上添加额外方法
|- defaults.js # axios 内部默认配置
|- utils.js # 常用转换(判断)工具
|- sandbox
|- index.js # 仅导出文件
|- package.json
|- webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# axios.js
非构造函数
负责创建实例额外添加方法并导出
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig) // 创建实例
var instance = bind(Axios.prototype.request, context) // bind 返回一个函数,直接执行 instance 函数将会代理到 Axios 原型上的 request 方法上 例如:axios() -> 调用的是 Axios.prototype.request()
utils.extend(instance, Axios.prototype, context) // 将 axios.prototype 上属性和方法 复制到 instance 函数上 并将 this 指向 context 对象
utils.extend(instance, context) // 将 context 上属性和方法 复制到 instance 函数上
return instance
}
var axios = createInstance(defaults) // 调用创建实例函数
axios.Axios = Axios // 公开Axios类以允许类继承
axios.create = function create(instanceConfig) {
// 工厂函数,返回一个新对象,常用在
return createInstance(mergeConfig(axios.defaults, instanceConfig))
}
// 添加创建取消请求的函数
axios.Cancel = require('./cancel/Cancel')
axios.CancelToken = require('./cancel/CancelToken')
axios.isCancel = require('./cancel/isCancel')
axios.all = function all(promises) {
// axios.all 调用的是 Promise.all
return Promise.all(promises)
}
axios.spread = require('./helpers/spread') // 函数柯里化
module.exports = axios
module.exports.default = axios // 允许在TypeScript中使用默认导入语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# core
# Axios.js
Axios 构造函数
- this.interceptors.request(response).use 说明
function InterceptorManager() {
this.handlers = []
}
// 调用use,相当于往数组中放入了一个对象(fulfilled, rejected)
// 请求拦截器:
// axios取数据时,从第一个往后遍历,插入到 chain 链的前面
// 所以最后执行顺序是:后调用 use 的 先执行
// 响应拦截器:
// axios取数据时,从第一个往后遍历,插入到 chain 链的后面
// 所以最后执行顺序是:先调用 use 的 先执行
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
})
return this.handlers.length - 1
}
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null
}
}
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h)
}
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
用一张图表示如下

- Axios 构造函数
- 给构造函数原型添加各种方法
- 通过 chain 数组,控制执行顺序(对请求前,响应后做一些处理)
- 最终所有类型方法请求都是在调用
request
方法
function Axios(instanceConfig) {
// Axios 构造函数
this.defaults = instanceConfig
this.interceptors = {
// 拦截器
request: new InterceptorManager(),
response: new InterceptorManager(),
}
}
Axios.prototype.request = function request(config) {
// 发起请求最重要的方法(最后各类请求都会调用该方法)
if (typeof config === 'string') {
config = arguments[1] || {}
config.url = arguments[0]
} else {
config = config || {}
}
config = mergeConfig(this.defaults, config) // 合并配置
// 设置 config.method
if (config.method) {
config.method = config.method.toLowerCase()
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase()
} else {
config.method = 'get'
}
// 连接拦截器中间件
var chain = [dispatchRequest, undefined] // dispatchRequest 为真实发起请求的函数,dispatchRequest 前为请求拦截器,后是响应拦截器
var promise = Promise.resolve(config) // 将 config 参数作为值 在 chain 链中传递(每个拦截器都必须将return config|Error)
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
chain.unshift(interceptor.fulfilled, interceptor.rejected) // 添加请求拦截,[fulfilled, rejected]
})
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
chain.push(interceptor.fulfilled, interceptor.rejected) // 添加响应拦截,[fulfilled, rejected]
})
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift()) // 每次取出两个用 Promise 将 chain 链串联起来,[resolve, reject]
}
return promise
}
Axios.prototype.getUri = function getUri(config) {
config = mergeConfig(this.defaults, config)
return buildURL(config.url, config.params, config.paramsSerializer).replace(
/^\?/,
''
)
}
// utils.forEach 其实就是 遍历的方法, 在Axios 原型上添加 delete, get, head, options 方法
utils.forEach(
['delete', 'get', 'head', 'options'],
function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(
// 最后调用的是 Axios.prototype.request 方法 看上↑↑↑
mergeConfig(config || {}, {
method: method,
url: url,
})
)
}
}
)
// 同上
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(
mergeConfig(config || {}, {
method: method,
url: url,
data: data,
})
)
}
})
module.exports = Axios
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# dispatchRequest.js
Axios 实例对象的 request 方法中会调用这个函数
这个函数是实际发起请求的函数
兼容浏览器和 node 环境(浏览器的 XMLHttpRequest 和 node 的 http)
同时支持 node 环境 和浏览器环境
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested()
}
}
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config) // 如果此时 已经取消了请求,直接抛出异常
// Ensure headers exist
config.headers = config.headers || {}
// Transform request data
config.data = transformData(
config.data,
config.headers,
config.transformRequest // transformRequest 是一个数组,由用户自定义转换 data 数据的,并返回转换后的 data,可看👇👇👇
)
// Flatten headers
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
)
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method]
}
)
var adapter = config.adapter || defaults.adapter
// 发起请求
return adapter(config).then(
function onAdapterResolution(response) {
throwIfCancellationRequested(config)
// Transform response data
response.data = transformData(
response.data,
response.headers,
config.transformResponse // transformResponse 是一个数组,用户自定义的响应数据转换,看👆👆👆
)
return response // 成功的响应
},
function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config) // 如果此时已经取消了请求,直接抛出异常
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse // transformResponse 是一个数组,用户自定义的响应数据转换,看👆👆👆
)
}
}
return Promise.reject(reason)
}
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# cancel
取消请求的操作
实际上是调用 http 的 aborted(XMLHttpRequest 的 abort)方法
# Cancel.js
// 在取消操作时抛出的对象
function Cancel(message) {
this.message = message
}
Cancel.prototype.toString = function toString() {
return 'Cancel' + (this.message ? ': ' + this.message : '')
}
Cancel.prototype.__CANCEL__ = true // 用来判断是否加了 取消请求的 配置
module.exports = Cancel
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# CancelToken.js
通过 CancelToken 创建对象,并传入 config 配置中
回调函数中会向外暴露一个可以控制 Promise 状态的函数
// CancelToken 构造函数,用于请求取消操作
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.')
}
var resolvePromise // 存储控制 Promse 状态为成功 的函数, 看👇👇👇
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve
})
var token = this
executor(function cancel(message) {
// 这个函数是新建对象时作为参数传递出去的
if (token.reason) {
// 已取消直接 return
return
}
token.reason = new Cancel(message) // 创建取消操作时抛出的对象
resolvePromise(token.reason) // 改变 Promse 状态为成功, 看👆👆👆
})
}
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
// 如果已请求取消,则抛出
if (this.reason) {
throw this.reason
}
}
// 返回一个对象,该对象包含一个新的 CancelToken 和一个函数,调用该函数时 取消请求
CancelToken.source = function source() {
var cancel
var token = new CancelToken(function executor(c) {
cancel = c
})
return {
token: token,
cancel: cancel,
}
}
module.exports = CancelToken
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# isCancel.js
仅判断是否是 Cancel 实例对象
module.exports = function isCancel(value) {
return !!(value && value.__CANCEL__)
}
1
2
3
2
3
# adapters
node 的 http 模块封装的 ajax 和 XMLHttpRequest 封装的 ajax
可参考封装自己的 Ajax 操作
# xhr.js
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
// 返回 Promise
var requestData = config.data
var requestHeaders = config.headers
if (utils.isFormData(requestData)) {
// 是 FormData 数据
delete requestHeaders['Content-Type']
}
if (
(utils.isBlob(requestData) || utils.isFile(requestData)) &&
requestData.type
) {
// 是 二进制、文件 数据
delete requestHeaders['Content-Type']
}
var request = new XMLHttpRequest() // 创建 XMLHttpRequest 对象,可参考 "XMLHttpRequest 简单使用" 里介绍的 封装 Ajax
if (config.auth) {
// Authorization 处理
var username = config.auth.username || ''
var password = config.auth.password
? unescape(encodeURIComponent(config.auth.password)) // 对特殊字符进行转义
: ''
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password) // 转换为 base64
}
var fullPath = buildFullPath(config.baseURL, config.url) // 完整请求地址
// 初始化一个请求
request.open(
config.method.toUpperCase(),
buildURL(fullPath, config.params, config.paramsSerializer), // 一定规则拼接 params 到路径上,paramsSerializer 用户自定义的 参数序列化函数
true
)
request.timeout = config.timeout // 超时时间
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return
}
// 如果出错,return
if (
request.status === 0 &&
!(request.responseURL && request.responseURL.indexOf('file:') === 0)
) {
return
}
// 响应数据
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) // 验证响应状态码默认 200 - 300, 根据状态码判断是 resolve 还是 reject。 用户可自定义合法值验证(config.validateStatus函数)
request = null // 清空当前请求
}
// 当 request 被停止时触发(浏览器行为,非手动取消行为)
request.onabort = function handleAbort() {
if (!request) {
return
}
reject(createError('Request aborted', config, 'ECONNABORTED', request))
request = null
}
// 当 request 遭遇错误时触发
request.onerror = function handleError() {
reject(createError('Network Error', config, null, request))
request = null
}
// 在预设时间内没有接收到响应时触发
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded'
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage
}
reject(createError(timeoutErrorMessage, config, 'ECONNABORTED', request))
request = null
}
if (utils.isStandardBrowserEnv()) {
// 标准浏览器环境
var xsrfValue =
(config.withCredentials || isURLSameOrigin(fullPath)) &&
config.xsrfCookieName
? cookies.read(config.xsrfCookieName)
: undefined
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue
}
}
// request 添加 headers
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (
typeof requestData === 'undefined' &&
key.toLowerCase() === 'content-type'
) {
// content-type 是 undefined 删除
delete requestHeaders[key]
} else {
request.setRequestHeader(key, val)
}
})
}
// 用来指定跨域 Access-Control 请求是否应当带有授权信息,如 cookie 或授权 header 头。
if (!utils.isUndefined(config.withCredentials)) {
request.withCredentials = !!config.withCredentials
}
// 给 request 添加 responseType
if (config.responseType) {
try {
request.responseType = config.responseType
} catch (e) {
if (config.responseType !== 'json') {
throw e
}
}
}
// 下载进度 事件
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress)
}
// 上传进度 事件
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress)
}
if (config.cancelToken) {
// 取消请求
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return
}
request.abort() // 真实调用 request 取消请求操作
reject(cancel)
request = null
})
}
if (!requestData) {
requestData = null
}
// 发送请求
request.send(requestData)
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# http.js
module.exports = function httpAdapter(config) {
return new Promise(function dispatchHttpRequest(
resolvePromise,
rejectPromise
) {
var resolve = function resolve(value) {
resolvePromise(value)
}
var reject = function reject(value) {
rejectPromise(value)
}
var data = config.data
var headers = config.headers
// 设置用户代理
if (!headers['User-Agent'] && !headers['user-agent']) {
headers['User-Agent'] = 'axios/' + pkg.version // axios 版本
}
// data 是对象或函数,转换为 流的形式传输
if (data && !utils.isStream(data)) {
if (Buffer.isBuffer(data)) {
} else if (utils.isArrayBuffer(data)) {
data = Buffer.from(new Uint8Array(data))
} else if (utils.isString(data)) {
data = Buffer.from(data, 'utf-8')
} else {
return reject(
createError(
'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream',
config
)
)
}
// 添加 Content-Length 属性
headers['Content-Length'] = data.length
}
// Http的安全认证机制
var auth = undefined
if (config.auth) {
var username = config.auth.username || ''
var password = config.auth.password || ''
auth = username + ':' + password
}
// 解析url
var fullPath = buildFullPath(config.baseURL, config.url)
var parsed = url.parse(fullPath) // 将一个完整的URL地址,分为很多部分,常用的有:host、port、pathname、path、query
var protocol = parsed.protocol || 'http:'
if (!auth && parsed.auth) {
var urlAuth = parsed.auth.split(':')
var urlUsername = urlAuth[0] || ''
var urlPassword = urlAuth[1] || ''
auth = urlUsername + ':' + urlPassword
}
if (auth) {
delete headers.Authorization
}
var isHttpsRequest = isHttps.test(protocol) // 是发起 https 请求
var agent = isHttpsRequest ? config.httpsAgent : config.httpAgent
var options = {
// http.request 参数
path: buildURL(
parsed.path,
config.params,
config.paramsSerializer
).replace(/^\?/, ''),
method: config.method.toUpperCase(),
headers: headers,
agent: agent,
agents: { http: config.httpAgent, https: config.httpsAgent },
auth: auth,
}
if (config.socketPath) {
// Unix 域套接字
options.socketPath = config.socketPath
} else {
options.hostname = parsed.hostname
options.port = parsed.port
}
var proxy = config.proxy // 设置了代理
if (!proxy && proxy !== false) {
// 如果config 没有设置代理,从 当前运行环境中获取代理
var proxyEnv = protocol.slice(0, -1) + '_proxy'
var proxyUrl =
process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()] // 运行环境中的代理地址
if (proxyUrl) {
var parsedProxyUrl = url.parse(proxyUrl)
var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY
var shouldProxy = true
if (noProxyEnv) {
var noProxy = noProxyEnv.split(',').map(function trim(s) {
return s.trim()
})
shouldProxy = !noProxy.some(function proxyMatch(proxyElement) {
if (!proxyElement) {
return false
}
if (proxyElement === '*') {
return true
}
if (
proxyElement[0] === '.' &&
parsed.hostname.substr(
parsed.hostname.length - proxyElement.length
) === proxyElement
) {
return true
}
return parsed.hostname === proxyElement
})
}
if (shouldProxy) {
proxy = {
host: parsedProxyUrl.hostname,
port: parsedProxyUrl.port,
}
if (parsedProxyUrl.auth) {
var proxyUrlAuth = parsedProxyUrl.auth.split(':')
proxy.auth = {
username: proxyUrlAuth[0],
password: proxyUrlAuth[1],
}
}
}
}
}
if (proxy) {
options.hostname = proxy.host
options.host = proxy.host
options.headers.host =
parsed.hostname + (parsed.port ? ':' + parsed.port : '')
options.port = proxy.port
options.path =
protocol +
'//' +
parsed.hostname +
(parsed.port ? ':' + parsed.port : '') +
options.path
// 代理安全认证
if (proxy.auth) {
var base64 = Buffer.from(
proxy.auth.username + ':' + proxy.auth.password,
'utf8'
).toString('base64')
options.headers['Proxy-Authorization'] = 'Basic ' + base64
}
}
var transport // 通过判断采用何种 node http(https|follow-redirects) 模块
var isHttpsProxy =
isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true)
if (config.transport) {
transport = config.transport // 优先使用 用户配置的 传输模块
} else if (config.maxRedirects === 0) {
transport = isHttpsProxy ? https : http // http(https)
} else {
if (config.maxRedirects) {
options.maxRedirects = config.maxRedirects
}
transport = isHttpsProxy ? httpsFollow : httpFollow // follow-redirects 重定向
}
if (config.maxBodyLength > -1) {
options.maxBodyLength = config.maxBodyLength
}
// 创建请求
var req = transport.request(options, function handleResponse(res) {
if (req.aborted) return // 请求已取消 return
// 压缩响应主体
var stream = res
// 在重定向的情况下返回最后一个请求
var lastRequest = res.req || req
// 如果没有内容,HEAD请求(或config配置了) 不应该解压
if (
res.statusCode !== 204 &&
lastRequest.method !== 'HEAD' &&
config.decompress !== false
) {
switch (res.headers['content-encoding']) {
case 'gzip':
case 'compress':
case 'deflate':
// 将 unzipper 添加到 body 流处理管道
stream = stream.pipe(zlib.createUnzip()) // 将流数据解压缩
// 删除内容编码
delete res.headers['content-encoding']
break
}
}
var response = {
// 构建响应对象
status: res.statusCode,
statusText: res.statusMessage,
headers: res.headers,
config: config,
request: lastRequest,
}
if (config.responseType === 'stream') {
// 如果要求响应类型为 流,直接返回 流
response.data = stream
settle(resolve, reject, response) // 验证响应状态码默认 200 - 300, 根据状态码判断是 resolve 还是 reject。 用户可自定义合法值验证(config.validateStatus函数)
} else {
var responseBuffer = []
stream.on('data', function handleStreamData(chunk) {
responseBuffer.push(chunk)
// 如果设置了 maxContentLength,在不符合 maxContentLength 的情况下,返回失败结果并关闭 读取流
if (
config.maxContentLength > -1 &&
Buffer.concat(responseBuffer).length > config.maxContentLength
) {
stream.destroy()
reject(
createError(
'maxContentLength size of ' +
config.maxContentLength +
' exceeded',
config,
null,
lastRequest
)
)
}
})
// 由于底层内部的故障而无法生成数据,或者流的实现尝试推送无效的数据块时触发
stream.on('error', function handleStreamError(err) {
if (req.aborted) return
reject(enhanceError(err, config, null, lastRequest))
})
// 流 读取完毕时触发
stream.on('end', function handleStreamEnd() {
var responseData = Buffer.concat(responseBuffer) // 将接收的数据 合并成一个 buffer
if (config.responseType !== 'arraybuffer') {
// 非 arraybuffer
responseData = responseData.toString(config.responseEncoding) // 转换成用户配置的 responseEncoding(2-36) 进制
if (
!config.responseEncoding ||
config.responseEncoding === 'utf8'
) {
responseData = utils.stripBOM(responseData)
}
}
response.data = responseData
settle(resolve, reject, response)
})
}
})
// 请求遇到问题
req.on('error', function handleRequestError(err) {
if (req.aborted && err.code !== 'ERR_FR_TOO_MANY_REDIRECTS') return
reject(enhanceError(err, config, null, req))
})
if (config.timeout) {
// 一旦将套接字分配给此请求并且连接了套接字,就会调用 socket.setTimeout()
// config.timeout: 请求超时前的毫秒数, handleRequestTimeout: 发生超时时要调用的可选函数。相当于绑定到 'timeout' 事件
req.setTimeout(config.timeout, function handleRequestTimeout() {
req.abort()
reject(
createError(
'timeout of ' + config.timeout + 'ms exceeded',
config,
'ECONNABORTED',
req
)
)
})
}
if (config.cancelToken) {
// 取消处理
config.cancelToken.promise.then(function onCanceled(cancel) {
if (req.aborted) return
req.abort()
reject(cancel)
})
}
// 发送请求
if (utils.isStream(data)) {
data
.on('error', function handleStreamError(err) {
reject(enhanceError(err, config, null, req))
})
.pipe(req)
} else {
req.end(data)
}
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
上次更新: 2021/08/13, 10:20:29