axios 是前端使用的主流网络请求库。本文从源码角度进行解读,主要分析浏览器环境下具体实现。
node环境下原理相同,区别仅是 axios
对 XMLHttpRequests 与 http 的封装不同。
主要是对axios功能点从源码角度的解读,基于v0.21.0
版本
features 解读
Make XMLHttpRequests from the browser
Make http requests from node.js
Supports the Promise API
Intercept request and response
Transform request and response data
Cancel requests
Automatic transforms for JSON data
Client side support for protecting against XSRF
请求的适配 1和2表明了 axios
能够在两种环境环境下使用 browser
和 node
。
在这两种环境下实际使用 XMLHttpRequests 和 http requests from node.js 发出请求。
在不同环境下, axios
使用了 适配器模式
,来进行兼容。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function getDefaultAdapter ( ) { var adapter; if (typeof XMLHttpRequest !== 'undefined' ) { adapter = require ('./adapters/xhr' ); } else if (typeof process !== 'undefined' && Object .prototype.toString.call(process) === '[object process]' ) { adapter = require ('./adapters/http' ); } return adapter; }
Supports the Promise API axios
是基于 promise
来进行封住的请求,可以简化成如下形式:
注释部分代表在axios中实现的核心逻辑
Intercept request and response 借助于 Promise
的链式调用,在请求发起前进行 request
处理, 在请求结束,进行 response
处理。相关代码如下
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 Axios.prototype.request = function request (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
会有一个默认配置对象 defaultConfig
,配置了默认转换方法。
如果是字符串,使用 try
包裹起来,进行简单的json序列化。
1 2 3 4 5 6 7 8 9 10 transformResponse: [function transformResponse (data ) { if (typeof data === 'string' ) { try { data = JSON .parse(data); } catch (e) { } } return data; }]
// request 根据不同的数据类型,进行相应的处理
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 transformRequest: [function transformRequest (data, headers ) { normalizeHeaderName(headers, 'Accept' ); normalizeHeaderName(headers, 'Content-Type' ); if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.isBuffer(data) || utils.isStream(data) || utils.isFile(data) || utils.isBlob(data) ) { return data; } if (utils.isArrayBufferView(data)) { return data.buffer; } if (utils.isURLSearchParams(data)) { setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8' ); return data.toString(); } if (utils.isObject(data)) { setContentTypeIfUnset(headers, 'application/json;charset=utf-8' ); return JSON .stringify(data); } return data; }]
request
和 response
的转换方法都是放在数组里面。 axios
支持多个转换。
Cancel requests 在浏览器环境,取消最终是调用的 XMLHttpRequest.abort()
。我们从取消一个请求的使用来分析 axios
对取消请求的实现。
1 2 3 4 5 6 7 8 9 10 11 12 const CancelToken = axios.CancelToken;let cancel;axios.get('/user/12345' , { cancelToken: new CancelToken(function executor (c ) { cancel = c; }) }); cancel();
调用 cancel()
,会执行 c
。 c
是一个函数的参数。源码如下
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 function CancelToken (executor ) { if (typeof executor !== 'function' ) { throw new TypeError ('executor must be a function.' ); } var resolvePromise; this .promise = new Promise (function promiseExecutor (resolve ) { resolvePromise = resolve; }); var token = this ; executor(function cancel (message ) { if (token.reason) { return ; } token.reason = new Cancel(message); resolvePromise(token.reason); }); }
总结来说,取消请求的实现,是借助了 Promise
。 每一个请求的配置中会有一个处于 pedding
状态的实例化对象。当需要取消请求时,调用 resolve
,触发 promise.then
的执行来取消网络请求
参考上面Transform request and response data
Client side support for protecting against XSRF XSRF 又称作 CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
因为同源策略的限制,第三方网站无法获取到被攻击网站的cookie。所以前端可以在请求中携带后端返回的cookie来防止 CSRF。
axios中代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 if (utils.isStandardBrowserEnv()) { var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ? cookies.read(config.xsrfCookieName) : undefined ; if (xsrfValue) { requestHeaders[config.xsrfHeaderName] = xsrfValue; } }
以上内容其实也可以使用拦截器实现。
config axios 的使用基本都是围绕config,来进行。
从 Supports the Promise API 中也可以看出,config贯穿整个项目。
axios 文档对config进行了细致的说明,可以参考axios
总结 从axios源码的学习,我们可以总结一个网络请求库大致做了什么事情
url 的组装(baseUrl,query参数挂载)
拦截器的设计
响应体数据结构的设计
请求取消的实现
数据转换设计
请求发起的封装