<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          77.9K 的 Axios 項目有哪些值得借鑒的地方

          共 10728字,需瀏覽 22分鐘

           ·

          2020-11-06 08:31


          Axios 是一個基于 Promise 的 HTTP 客戶端,同時支持瀏覽器和 Node.js 環(huán)境。它是一個優(yōu)秀的 HTTP 客戶端,被廣泛地應(yīng)用在大量的 Web 項目中。

          由上圖可知,Axios 項目的 Star 數(shù)為 「77.9K」,F(xiàn)ork 數(shù)也高達(dá) 「7.3K」,是一個很優(yōu)秀的開源項目,所以接下來阿寶哥將帶大家一起來分析 Axios 項目中一些值得借鑒的地方。

          閱讀完本文,你將了解以下內(nèi)容:

          • HTTP 攔截器的設(shè)計與實現(xiàn);
          • HTTP 適配器的設(shè)計與實現(xiàn);
          • 如何防御 CSRF 攻擊。

          下面我們從簡單的開始,先來了解一下 Axios。

          一、Axios 簡介

          Axios 是一個基于 Promise 的 HTTP 客戶端,擁有以下特性:

          • 支持 Promise API;
          • 能夠攔截請求和響應(yīng);
          • 能夠轉(zhuǎn)換請求和響應(yīng)數(shù)據(jù);
          • 客戶端支持防御?CSRF 攻擊;
          • 同時支持瀏覽器和 Node.js 環(huán)境;
          • 能夠取消請求及自動轉(zhuǎn)換 JSON 數(shù)據(jù)。

          在瀏覽器端 Axios 支持大多數(shù)主流的瀏覽器,比如 Chrome、Firefox、Safari 和 IE 11。此外,Axios 還擁有自己的生態(tài):

          (數(shù)據(jù)來源 —— https://github.com/axios/axios/blob/master/ECOSYSTEM.md)

          簡單介紹完 Axios,我們來分析一下它提供的一個核心功能 —— 攔截器。

          二、HTTP 攔截器的設(shè)計與實現(xiàn)

          2.1 攔截器簡介

          對于大多數(shù) SPA 應(yīng)用程序來說, 通常會使用 token 進(jìn)行用戶的身份認(rèn)證。這就要求在認(rèn)證通過后,我們需要在每個請求上都攜帶認(rèn)證信息。針對這個需求,為了避免為每個請求單獨處理,我們可以通過封裝統(tǒng)一的 request 函數(shù)來為每個請求統(tǒng)一添加 token 信息。

          但后期如果需要為某些 GET 請求設(shè)置緩存時間或者控制某些請求的調(diào)用頻率的話,我們就需要不斷修改 request 函數(shù)來擴(kuò)展對應(yīng)的功能。此時,如果在考慮對響應(yīng)進(jìn)行統(tǒng)一處理的話,我們的 request 函數(shù)將變得越來越龐大,也越來越難維護(hù)。那么對于這個問題,該如何解決呢?Axios 為我們提供了解決方案 —— 攔截器。

          Axios 是一個基于 Promise 的 HTTP 客戶端,而 HTTP 協(xié)議是基于請求和響應(yīng):

          所以 Axios 提供了請求攔截器和響應(yīng)攔截器來分別處理請求和響應(yīng),它們的作用如下:

          • 請求攔截器:該類攔截器的作用是在請求發(fā)送前統(tǒng)一執(zhí)行某些操作,比如在請求頭中添加 token 字段。
          • 響應(yīng)攔截器:該類攔截器的作用是在接收到服務(wù)器響應(yīng)后統(tǒng)一執(zhí)行某些操作,比如發(fā)現(xiàn)響應(yīng)狀態(tài)碼為 401 時,自動跳轉(zhuǎn)到登錄頁。

          在 Axios 中設(shè)置攔截器很簡單,通過 axios.interceptors.requestaxios.interceptors.response 對象提供的 use 方法,就可以分別設(shè)置請求攔截器和響應(yīng)攔截器:

          //?添加請求攔截器
          axios.interceptors.request.use(function?(config)?{
          ??config.headers.token?=?'added?by?interceptor';
          ??return?config;
          });

          //?添加響應(yīng)攔截器
          axios.interceptors.response.use(function?(data)?{
          ??data.data?=?data.data?+?'?-?modified?by?interceptor';
          ??return?data;
          });

          那么攔截器是如何工作的呢?在看具體的代碼之前,我們先來分析一下它的設(shè)計思路。Axios 的作用是用于發(fā)送 HTTP 請求,而請求攔截器和響應(yīng)攔截器的本質(zhì)都是一個實現(xiàn)特定功能的函數(shù)。

          我們可以按照功能把發(fā)送 HTTP 請求拆解成不同類型的子任務(wù),比如有用于處理請求配置對象的子任務(wù),用于發(fā)送 HTTP 請求的子任務(wù)和用于處理響應(yīng)對象的子任務(wù)。當(dāng)我們按照指定的順序來執(zhí)行這些子任務(wù)時,就可以完成一次完整的 HTTP 請求。

          了解完這些,接下來我們將從 「任務(wù)注冊、任務(wù)編排和任務(wù)調(diào)度」 三個方面來分析 Axios 攔截器的實現(xiàn)。

          2.2 任務(wù)注冊

          通過前面攔截器的使用示例,我們已經(jīng)知道如何注冊請求攔截器和響應(yīng)攔截器,其中請求攔截器用于處理請求配置對象的子任務(wù),而響應(yīng)攔截器用于處理響應(yīng)對象的子任務(wù)。要搞清楚任務(wù)是如何注冊的,就需要了解 axiosaxios.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 對象的定義,很明顯默認(rèn)的 axios 實例是通過 createInstance 方法創(chuàng)建的,該方法最終返回的是 Axios.prototype.request 函數(shù)對象。同時,我們發(fā)現(xiàn)了 Axios 的構(gòu)造函數(shù):

          //?lib/core/Axios.js
          function?Axios(instanceConfig)?{
          ??this.defaults?=?instanceConfig;
          ??this.interceptors?=?{
          ????request:?new?InterceptorManager(),
          ????response:?new?InterceptorManager()
          ??};
          }

          在構(gòu)造函數(shù)中,我們找到了 axios.interceptors 對象的定義,也知道了 interceptors.requestinterceptors.response 對象都是 InterceptorManager 類的實例。因此接下來,進(jìn)一步分析 InterceptorManager 構(gòu)造函數(shù)及相關(guān)的 use 方法就可以知道任務(wù)是如何注冊的:

          //?lib/core/InterceptorManager.js
          function?InterceptorManager()?{
          ??this.handlers?=?[];
          }

          InterceptorManager.prototype.use?=?function?use(fulfilled,?rejected)?{
          ??this.handlers.push({
          ????fulfilled:?fulfilled,
          ????rejected:?rejected
          ??});
          ??//?返回當(dāng)前的索引,用于移除已注冊的攔截器
          ??return?this.handlers.length?-?1;
          };

          通過觀察 use 方法,我們可知注冊的攔截器都會被保存到 InterceptorManager 對象的 handlers 屬性中。下面我們用一張圖來總結(jié)一下 Axios 對象與 InterceptorManager 對象的內(nèi)部結(jié)構(gòu)與關(guān)系:

          2.3 任務(wù)編排

          現(xiàn)在我們已經(jīng)知道如何注冊攔截器任務(wù),但僅僅注冊任務(wù)是不夠,我們還需要對已注冊的任務(wù)進(jìn)行編排,這樣才能確保任務(wù)的執(zhí)行順序。這里我們把完成一次完整的 HTTP 請求分為處理請求配置對象、發(fā)起 HTTP 請求和處理響應(yīng)對象 3 個階段。

          接下來我們來看一下 Axios 如何發(fā)請求的:

          axios({
          ??url:?'/hello',
          ??method:?'get',
          }).then(res?=>{
          ??console.log('axios?res:?',?res)
          ??console.log('axios?res.data:?',?res.data)
          })

          通過前面的分析,我們已經(jīng)知道 axios 對象對應(yīng)的是 Axios.prototype.request 函數(shù)對象,該函數(shù)的具體實現(xiàn)如下:

          //?lib/core/Axios.js
          Axios.prototype.request?=?function?request(config)?{
          ??config?=?mergeConfig(this.defaults,?config);

          ??//?省略部分代碼
          ??var?chain?=?[dispatchRequest,?undefined];
          ??var?promise?=?Promise.resolve(config);

          ??//?任務(wù)編排
          ??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);
          ??});

          ??//?任務(wù)調(diào)度
          ??while?(chain.length)?{
          ????promise?=?promise.then(chain.shift(),?chain.shift());
          ??}

          ??return?promise;
          };

          任務(wù)編排的代碼比較簡單,我們來看一下任務(wù)編排前和任務(wù)編排后的對比圖:

          2.4 任務(wù)調(diào)度

          任務(wù)編排完成后,要發(fā)起 HTTP 請求,我們還需要按編排后的順序執(zhí)行任務(wù)調(diào)度。在 Axios 中具體的調(diào)度方式很簡單,具體如下所示:

          ?//?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 是數(shù)組,所以通過 while 語句我們就可以不斷地取出設(shè)置的任務(wù),然后組裝成 Promise 調(diào)用鏈從而實現(xiàn)任務(wù)調(diào)度,對應(yīng)的處理流程如下圖所示:

          下面我們來回顧一下 Axios 攔截器完整的使用流程:

          //?添加請求攔截器?——?處理請求配置對象
          axios.interceptors.request.use(function?(config)?{
          ??config.headers.token?=?'added?by?interceptor';
          ??return?config;
          });

          //?添加響應(yīng)攔截器?——?處理響應(yīng)對象
          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 的攔截器,我們來總結(jié)一下它的優(yōu)點。Axios 通過提供攔截器機制,讓開發(fā)者可以很容易在請求的生命周期中自定義不同的處理邏輯。

          此外,也可以通過攔截器機制來靈活地擴(kuò)展 Axios 的功能,比如 Axios 生態(tài)中列舉的 axios-response-logger 和 axios-debug-log 這兩個庫。

          參考 Axios 攔截器的設(shè)計模型,我們就可以抽出以下通用的任務(wù)處理模型:

          三、HTTP 適配器的設(shè)計與實現(xiàn)

          3.1 默認(rèn) HTTP 適配器

          Axios 同時支持瀏覽器和 Node.js 環(huán)境,對于瀏覽器環(huán)境來說,我們可以通過 XMLHttpRequestfetch API 來發(fā)送 HTTP 請求,而對于 Node.js 環(huán)境來說,我們可以通過 Node.js 內(nèi)置的 httphttps 模塊來發(fā)送 HTTP 請求。

          為了支持不同的環(huán)境,Axios 引入了適配器。在 HTTP 攔截器設(shè)計部分,我們看到了一個 dispatchRequest 方法,該方法用于發(fā)送 HTTP 請求,它的具體實現(xiàn)如下所示:

          //?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 支持自定義適配器,同時也提供了默認(rèn)的適配器。對于大多數(shù)場景,我們并不需要自定義適配器,而是直接使用默認(rèn)的適配器。因此,默認(rèn)的適配器就會包含瀏覽器和 Node.js 環(huán)境的適配代碼,其具體的適配邏輯如下所示:

          //?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 方法中,首先通過平臺中特定的對象來區(qū)分不同的平臺,然后再導(dǎo)入不同的適配器,具體的代碼比較簡單,這里就不展開介紹。

          3.2 自定義適配器

          其實除了默認(rèn)的適配器外,我們還可以自定義適配器。那么如何自定義適配器呢?這里我們可以參考 Axios 提供的示例:

          var?settle?=?require('./../core/settle');
          module.exports?=?function?myAdapter(config)?{
          ??//?當(dāng)前時機點:
          ??//??-?config配置對象已經(jīng)與默認(rèn)的請求配置合并
          ??//??-?請求轉(zhuǎn)換器已經(jīng)運行
          ??//??-?請求攔截器已經(jīng)運行
          ??
          ??//?使用提供的config配置對象發(fā)起請求
          ??//?根據(jù)響應(yīng)對象處理Promise的狀態(tài)
          ??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);

          ????//?此后:
          ????//??-?響應(yīng)轉(zhuǎn)換器將會運行
          ????//??-?響應(yīng)攔截器將會運行
          ??});
          }

          在以上示例中,我們主要關(guān)注轉(zhuǎn)換器、攔截器的運行時機點和適配器的基本要求。比如當(dāng)調(diào)用自定義適配器之后,需要返回 Promise 對象。這是因為 Axios 內(nèi)部是通過 Promise 鏈?zhǔn)秸{(diào)用來完成請求調(diào)度,不清楚的小伙伴可以重新閱讀 “攔截器的設(shè)計與實現(xiàn)” 部分的內(nèi)容。

          現(xiàn)在我們已經(jīng)知道如何自定義適配器了,那么自定義適配器有什么用呢?在 Axios 生態(tài)中,阿寶哥發(fā)現(xiàn)了 axios-mock-adapter 這個庫,該庫通過自定義適配器,讓開發(fā)者可以輕松地模擬請求。對應(yīng)的使用示例如下所示:

          var?axios?=?require("axios");
          var?MockAdapter?=?require("axios-mock-adapter");

          //?在默認(rèn)的Axios實例上設(shè)置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 這個庫。到這里我們已經(jīng)介紹了 Axios 的攔截器與適配器,下面阿寶哥用一張圖來總結(jié)一下 Axios 使用請求攔截器和響應(yīng)攔截器后,請求的處理流程:

          四、CSRF 防御

          4.1 CSRF 簡介

          「跨站請求偽造」(Cross-site request forgery),通常縮寫為 「CSRF」 或者 「XSRF」, 是一種挾制用戶在當(dāng)前已登錄的 Web 應(yīng)用程序上執(zhí)行非本意的操作的攻擊方法。

          跨站請求攻擊,簡單地說,是攻擊者通過一些技術(shù)手段欺騙用戶的瀏覽器去訪問一個自己曾經(jīng)認(rèn)證過的網(wǎng)站并運行一些操作(如發(fā)郵件,發(fā)消息,甚至財產(chǎn)操作如轉(zhuǎn)賬和購買商品)。由于瀏覽器曾經(jīng)認(rèn)證過,所以被訪問的網(wǎng)站會認(rèn)為是真正的用戶操作而去運行。

          為了讓小伙伴更好地理解上述的內(nèi)容,阿寶哥畫了一張跨站請求攻擊示例圖:

          在上圖中攻擊者利用了 Web 中用戶身份驗證的一個漏洞:「簡單的身份驗證只能保證請求發(fā)自某個用戶的瀏覽器,卻不能保證請求本身是用戶自愿發(fā)出的」。既然存在以上的漏洞,那么我們應(yīng)該怎么進(jìn)行防御呢?接下來我們來介紹一些常見的 CSRF 防御措施。

          4.2 CSRF 防御措施

          4.2.1 檢查 Referer 字段

          HTTP 頭中有一個 Referer 字段,這個字段用以標(biāo)明請求來源于哪個地址。「在處理敏感數(shù)據(jù)請求時,通常來說,Referer 字段應(yīng)和請求的地址位于同一域名下」

          以示例中商城操作為例,Referer 字段地址通常應(yīng)該是商城所在的網(wǎng)頁地址,應(yīng)該也位于 www.semlinker.com 之下。而如果是 CSRF 攻擊傳來的請求,Referer 字段會是包含惡意網(wǎng)址的地址,不會位于 www.semlinker.com 之下,這時候服務(wù)器就能識別出惡意的訪問。

          這種辦法簡單易行,僅需要在關(guān)鍵訪問處增加一步校驗。但這種辦法也有其局限性,因其完全依賴瀏覽器發(fā)送正確的 Referer 字段。雖然 HTTP 協(xié)議對此字段的內(nèi)容有明確的規(guī)定,但并無法保證來訪的瀏覽器的具體實現(xiàn),亦無法保證瀏覽器沒有安全漏洞影響到此字段。并且也存在攻擊者攻擊某些瀏覽器,篡改其 Referer 字段的可能。

          4.2.2 同步表單 CSRF 校驗

          CSRF 攻擊之所以能夠成功,是因為服務(wù)器無法區(qū)分正常請求和攻擊請求。針對這個問題我們可以要求所有的用戶請求都攜帶一個 CSRF 攻擊者無法獲取到的 token。對于 CSRF 示例圖中的表單攻擊,我們可以使用 「同步表單 CSRF 校驗」 的防御措施。

          「同步表單 CSRF 校驗」 就是在返回頁面時將 token 渲染到頁面上,在 form 表單提交的時候通過隱藏域或者作為查詢參數(shù)把 CSRF token 提交到服務(wù)器。比如,在同步渲染頁面時,在表單請求中增加一個 _csrf 的查詢參數(shù),這樣當(dāng)用戶在提交這個表單的時候就會將 CSRF token 提交上來:

          <form?method="POST"?action="/upload?_csrf={{由服務(wù)端生成}}"?enctype="multipart/form-data">
          ??用戶名:?<input?name="name"?/>
          ??選擇頭像:?<input?name="file"?type="file"?/>
          ??<button?type="submit">提交button>
          form>
          4.2.3 雙重 Cookie 防御

          「雙重 Cookie 防御」 就是將 token 設(shè)置在 Cookie 中,在提交(POST、PUT、PATCH、DELETE)等請求時提交 Cookie,并通過請求頭或請求體帶上 Cookie 中已設(shè)置的 token,服務(wù)端接收到請求后,再進(jìn)行對比校驗。

          下面我們以 jQuery 為例,來看一下如何設(shè)置 CSRF token:

          let?csrfToken?=?Cookies.get('csrfToken');

          function?csrfSafeMethod(method)?{
          ??//?以下HTTP方法不需要進(jìn)行CSRF防護(hù)
          ??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 攻擊的。

          4.3 Axios CSRF 防御

          Axios 提供了 xsrfCookieNamexsrfHeaderName 兩個屬性來分別設(shè)置 CSRF 的 Cookie 名稱和 HTTP 請求頭的名稱,它們的默認(rèn)值如下所示:

          //?lib/defaults.js
          var?defaults?=?{
          ??adapter:?getDefaultAdapter(),

          ??//?省略部分代碼
          ??xsrfCookieName:?'XSRF-TOKEN',
          ??xsrfHeaderName:?'X-XSRF-TOKEN',
          };

          前面我們已經(jīng)知道在不同的平臺中,Axios 使用不同的適配器來發(fā)送 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);
          ??});
          };

          看完以上的代碼,相信小伙伴們就已經(jīng)知道答案了,原來 Axios 內(nèi)部是使用 「雙重 Cookie 防御」 的方案來防御 CSRF 攻擊。

          好的,到這里本文的主要內(nèi)容都已經(jīng)介紹完了,其實 Axios 項目還有一些值得我們借鑒的地方,比如 CancelToken 的設(shè)計、異常處理機制等,感興趣的小伙伴可以自行學(xué)習(xí)一下。

          五、參考資源

          • Github - axios
          • 維基百科 - 跨站請求偽造
          • Egg - 安全威脅 CSRF 的防范
          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲色欲一区二区 | 韩国一区二区不卡 | 亚洲人妻综合网 | 日比视频网站 | 成人一级黄片 |