您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“GitHub上77.9K的Axios項目有哪些值得借鑒的地方詳析”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“GitHub上77.9K的Axios項目有哪些值得借鑒的地方詳析”這篇文章吧。
Axios 是一個基于 Promise 的 HTTP 客戶端,同時支持瀏覽器和 Node.js 環境。它是一個優秀的 HTTP 客戶端,被廣泛地應用在大量的 Web 項目中。
由上圖可知,Axios 項目的 Star 數為 「77.9K」,Fork 數也高達 「7.3K」,是一個很優秀的開源項目,所以接下來阿寶哥將帶大家一起來分析 Axios 項目中一些值得借鑒的地方。
閱讀完本文,你將了解以下內容:
HTTP 攔截器的設計與實現;
HTTP 適配器的設計與實現;
如何防御 CSRF 攻擊。
下面我們從簡單的開始,先來了解一下 Axios。
Axios 是一個基于 Promise 的 HTTP 客戶端,擁有以下特性:
支持 Promise API;
能夠攔截請求和響應;
能夠轉換請求和響應數據;
客戶端支持防御 CSRF 攻擊;
同時支持瀏覽器和 Node.js 環境;
能夠取消請求及自動轉換 JSON 數據。
在瀏覽器端 Axios 支持大多數主流的瀏覽器,比如 Chrome、Firefox、Safari 和 IE 11。此外,Axios 還擁有自己的生態:
(數據來源 —— https://github.com/axios/axios/blob/master/ECOSYSTEM.md)
簡單介紹完 Axios,我們來分析一下它提供的一個核心功能 —— 攔截器。
對于大多數 SPA 應用程序來說, 通常會使用 token 進行用戶的身份認證。這就要求在認證通過后,我們需要在每個請求上都攜帶認證信息。針對這個需求,為了避免為每個請求單獨處理,我們可以通過封裝統一的 request 函數來為每個請求統一添加 token 信息。
但后期如果需要為某些 GET 請求設置緩存時間或者控制某些請求的調用頻率的話,我們就需要不斷修改 request 函數來擴展對應的功能。此時,如果在考慮對響應進行統一處理的話,我們的 request 函數將變得越來越龐大,也越來越難維護。那么對于這個問題,該如何解決呢?Axios 為我們提供了解決方案 —— 攔截器。
Axios 是一個基于 Promise 的 HTTP 客戶端,而 HTTP 協議是基于請求和響應:
所以 Axios 提供了請求攔截器和響應攔截器來分別處理請求和響應,它們的作用如下:
請求攔截器:該類攔截器的作用是在請求發送前統一執行某些操作,比如在請求頭中添加 token 字段。
響應攔截器:該類攔截器的作用是在接收到服務器響應后統一執行某些操作,比如發現響應狀態碼為 401 時,自動跳轉到登錄頁。
在 Axios 中設置攔截器很簡單,通過 axios.interceptors.request 和 axios.interceptors.response 對象提供的 use 方法,就可以分別設置請求攔截器和響應攔截器:
// 添加請求攔截器 axios.interceptors.request.use(function (config) { config.headers.token = 'added by interceptor'; return config; }); // 添加響應攔截器 axios.interceptors.response.use(function (data) { data.data = data.data + ' - modified by interceptor'; return data; });
那么攔截器是如何工作的呢?在看具體的代碼之前,我們先來分析一下它的設計思路。Axios 的作用是用于發送 HTTP 請求,而請求攔截器和響應攔截器的本質都是一個實現特定功能的函數。
我們可以按照功能把發送 HTTP 請求拆解成不同類型的子任務,比如有用于處理請求配置對象的子任務,用于發送 HTTP 請求的子任務和用于處理響應對象的子任務。當我們按照指定的順序來執行這些子任務時,就可以完成一次完整的 HTTP 請求。
了解完這些,接下來我們將從 「任務注冊、任務編排和任務調度」 三個方面來分析 Axios 攔截器的實現。
通過前面攔截器的使用示例,我們已經知道如何注冊請求攔截器和響應攔截器,其中請求攔截器用于處理請求配置對象的子任務,而響應攔截器用于處理響應對象的子任務。要搞清楚任務是如何注冊的,就需要了解 axios 和 axios.interceptors 對象。
// lib/axios.js function createInstance(defaultConfig) { var context = new Axios(defaultConfig); var instance = bind(Axios.prototype.request, context); // Copy axios.prototype to instance utils.extend(instance, Axios.prototype, context); // Copy context to instance utils.extend(instance, context); return instance; } // Create the default instance to be exported var axios = createInstance(defaults);
在 Axios 的源碼中,我們找到了 axios 對象的定義,很明顯默認的 axios 實例是通過 createInstance 方法創建的,該方法最終返回的是 Axios.prototype.request 函數對象。同時,我們發現了 Axios 的構造函數:
// lib/core/Axios.js function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; }
在構造函數中,我們找到了 axios.interceptors 對象的定義,也知道了 interceptors.request 和 interceptors.response 對象都是 InterceptorManager 類的實例。因此接下來,進一步分析 InterceptorManager 構造函數及相關的 use 方法就可以知道任務是如何注冊的:
// 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 對象的內部結構與關系:
現在我們已經知道如何注冊攔截器任務,但僅僅注冊任務是不夠,我們還需要對已注冊的任務進行編排,這樣才能確保任務的執行順序。這里我們把完成一次完整的 HTTP 請求分為處理請求配置對象、發起 HTTP 請求和處理響應對象 3 個階段。
接下來我們來看一下 Axios 如何發請求的:
axios({ url: '/hello', method: 'get', }).then(res =>{ console.log('axios res: ', res) console.log('axios res.data: ', res.data) })
通過前面的分析,我們已經知道 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; };
任務編排的代碼比較簡單,我們來看一下任務編排前和任務編排后的對比圖:
任務編排完成后,要發起 HTTP 請求,我們還需要按編排后的順序執行任務調度。在 Axios 中具體的調度方式很簡單,具體如下所示:
// lib/core/Axios.js Axios.prototype.request = function request(config) { // 省略部分代碼 var promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } }
因為 chain 是數組,所以通過 while 語句我們就可以不斷地取出設置的任務,然后組裝成 Promise 調用鏈從而實現任務調度,對應的處理流程如下圖所示:
下面我們來回顧一下 Axios 攔截器完整的使用流程:
// 添加請求攔截器 —— 處理請求配置對象 axios.interceptors.request.use(function (config) { config.headers.token = 'added by interceptor'; return config; }); // 添加響應攔截器 —— 處理響應對象 axios.interceptors.response.use(function (data) { data.data = data.data + ' - modified by interceptor'; return data; }); axios({ url: '/hello', method: 'get', }).then(res =>{ console.log('axios res.data: ', res.data) })
介紹完 Axios 的攔截器,我們來總結一下它的優點。Axios 通過提供攔截器機制,讓開發者可以很容易在請求的生命周期中自定義不同的處理邏輯。
此外,也可以通過攔截器機制來靈活地擴展 Axios 的功能,比如 Axios 生態中列舉的 axios-response-logger 和 axios-debug-log 這兩個庫。
參考 Axios 攔截器的設計模型,我們就可以抽出以下通用的任務處理模型:
Axios 同時支持瀏覽器和 Node.js 環境,對于瀏覽器環境來說,我們可以通過 XMLHttpRequest 或 fetch API 來發送 HTTP 請求,而對于 Node.js 環境來說,我們可以通過 Node.js 內置的 http 或 https 模塊來發送 HTTP 請求。
為了支持不同的環境,Axios 引入了適配器。在 HTTP 攔截器設計部分,我們看到了一個 dispatchRequest 方法,該方法用于發送 HTTP 請求,它的具體實現如下所示:
// 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); }); };
通過查看以上的 dispatchRequest 方法,我們可知 Axios 支持自定義適配器,同時也提供了默認的適配器。對于大多數場景,我們并不需要自定義適配器,而是直接使用默認的適配器。因此,默認的適配器就會包含瀏覽器和 Node.js 環境的適配代碼,其具體的適配邏輯如下所示:
// lib/defaults.js var defaults = { adapter: getDefaultAdapter(), xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', //... } 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; }
在 getDefaultAdapter 方法中,首先通過平臺中特定的對象來區分不同的平臺,然后再導入不同的適配器,具體的代碼比較簡單,這里就不展開介紹。
其實除了默認的適配器外,我們還可以自定義適配器。那么如何自定義適配器呢?這里我們可以參考 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 鏈式調用來完成請求調度,不清楚的小伙伴可以重新閱讀 “攔截器的設計與實現” 部分的內容。
現在我們已經知道如何自定義適配器了,那么自定義適配器有什么用呢?在 Axios 生態中,阿寶哥發現了 axios-mock-adapter 這個庫,該庫通過自定義適配器,讓開發者可以輕松地模擬請求。對應的使用示例如下所示:
var axios = require("axios"); var MockAdapter = require("axios-mock-adapter"); // 在默認的Axios實例上設置mock適配器 var mock = new MockAdapter(axios); // 模擬 GET /users 請求 mock.onGet("/users").reply(200, { users: [{ id: 1, name: "John Smith" }], }); axios.get("/users").then(function (response) { console.log(response.data); });
對 MockAdapter 感興趣的小伙伴,可以自行了解一下 axios-mock-adapter 這個庫。到這里我們已經介紹了 Axios 的攔截器與適配器,下面阿寶哥用一張圖來總結一下 Axios 使用請求攔截器和響應攔截器后,請求的處理流程:
「跨站請求偽造」(Cross-site request forgery),通常縮寫為 「CSRF」 或者 「XSRF」, 是一種挾制用戶在當前已登錄的 Web 應用程序上執行非本意的操作的攻擊方法。
跨站請求攻擊,簡單地說,是攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經認證過的網站并運行一些操作(如發郵件,發消息,甚至財產操作如轉賬和購買商品)。由于瀏覽器曾經認證過,所以被訪問的網站會認為是真正的用戶操作而去運行。
為了讓小伙伴更好地理解上述的內容,阿寶哥畫了一張跨站請求攻擊示例圖:
在上圖中攻擊者利用了 Web 中用戶身份驗證的一個漏洞:「簡單的身份驗證只能保證請求發自某個用戶的瀏覽器,卻不能保證請求本身是用戶自愿發出的」。既然存在以上的漏洞,那么我們應該怎么進行防御呢?接下來我們來介紹一些常見的 CSRF 防御措施。
HTTP 頭中有一個 Referer 字段,這個字段用以標明請求來源于哪個地址。「在處理敏感數據請求時,通常來說,Referer 字段應和請求的地址位于同一域名下」。
以示例中商城操作為例,Referer 字段地址通常應該是商城所在的網頁地址,應該也位于 www.semlinker.com 之下。而如果是 CSRF 攻擊傳來的請求,Referer 字段會是包含惡意網址的地址,不會位于 www.semlinker.com 之下,這時候服務器就能識別出惡意的訪問。
這種辦法簡單易行,僅需要在關鍵訪問處增加一步校驗。但這種辦法也有其局限性,因其完全依賴瀏覽器發送正確的 Referer 字段。雖然 HTTP 協議對此字段的內容有明確的規定,但并無法保證來訪的瀏覽器的具體實現,亦無法保證瀏覽器沒有安全漏洞影響到此字段。并且也存在攻擊者攻擊某些瀏覽器,篡改其 Referer 字段的可能。
CSRF 攻擊之所以能夠成功,是因為服務器無法區分正常請求和攻擊請求。針對這個問題我們可以要求所有的用戶請求都攜帶一個 CSRF 攻擊者無法獲取到的 token。對于 CSRF 示例圖中的表單攻擊,我們可以使用 「同步表單 CSRF 校驗」 的防御措施。
「同步表單 CSRF 校驗」 就是在返回頁面時將 token 渲染到頁面上,在 form 表單提交的時候通過隱藏域或者作為查詢參數把 CSRF token 提交到服務器。比如,在同步渲染頁面時,在表單請求中增加一個 _csrf 的查詢參數,這樣當用戶在提交這個表單的時候就會將 CSRF token 提交上來:
<form method="POST" action="/upload?_csrf={{由服務端生成}}" enctype="multipart/form-data"> 用戶名: <input name="name" /> 選擇頭像: <input name="file" type="file" /> <button type="submit">提交</button> </form>
「雙重 Cookie 防御」 就是將 token 設置在 Cookie 中,在提交(POST、PUT、PATCH、DELETE)等請求時提交 Cookie,并通過請求頭或請求體帶上 Cookie 中已設置的 token,服務端接收到請求后,再進行對比校驗。
下面我們以 jQuery 為例,來看一下如何設置 CSRF 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); } }, });
介紹完 CSRF 攻擊的方式和防御手段,最后我們來看一下 Axios 是如何防御 CSRF 攻擊的。
Axios 提供了 xsrfCookieName 和 xsrfHeaderName 兩個屬性來分別設置 CSRF 的 Cookie 名稱和 HTTP 請求頭的名稱,它們的默認值如下所示:
// lib/defaults.js var defaults = { adapter: getDefaultAdapter(), // 省略部分代碼 xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', };
前面我們已經知道在不同的平臺中,Axios 使用不同的適配器來發送 HTTP 請求,這里我們以瀏覽器平臺為例,來看一下 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); }); };
看完以上的代碼,相信小伙伴們就已經知道答案了,原來 Axios 內部是使用 「雙重 Cookie 防御」 的方案來防御 CSRF 攻擊。
好的,到這里本文的主要內容都已經介紹完了,其實 Axios 項目還有一些值得我們借鑒的地方,比如 CancelToken 的設計、異常處理機制等,感興趣的小伙伴可以自行學習一下。
以上是“GitHub上77.9K的Axios項目有哪些值得借鑒的地方詳析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。