<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                # 攔截器 完成上節的作業后,我們發現我們好像做了一個比較笨的工作。因為如果按在個人中心中加入 Login組件獲取的token的話,則意味著我們以后所有需要用戶認證的請求,都需要手動在請求時加入`x-auth-token`。而以懶人自居的我們,怎么能容忍這么冗余的動作呢? 除此以外,如果我們在每個需要認證的請求上都手動的加入`x-auth-token`,則會大幅降低整個項目的靈活性。比如后臺哪個抽瘋說咱換個認證方式,不使用`x-auth-token`了。這時候我們希望自己能做的不是罵街,而是能快速的響應這種需求。 > 世間唯有變化是永恒不變的。 為此,我們再來看看這個cookie的原理: ![image-20210308143321976](https://img.kancloud.cn/fa/ac/faac94d9e1c8427c35175bf9f061ccd9_2510x1644.png) 不然發現瀏覽器在處理cookie時,與用戶是否請求登錄無關。只是有請求,瀏覽器變會自動處理cookie,而不去管這個請求到底在實現什么邏輯功能。 其實我們當前后臺為我們提供的`x-auth-token`也是這樣的。我們在前臺向后臺發起任意請求時,后臺都會按上圖處理cookie的邏輯來處理`x-auth-token`。 那么,我們便可以使用Angular提供的**Interceptor攔截器**來快速完成在每次請求時自動處理`x-auth-token`的功能。 ## Interceptor攔截器 Angular提供的攔截器,可以在前臺發起任何的http請求時進行攔截。 ![image-20210309104836454](https://img.kancloud.cn/1b/86/1b86f3c365fbc64f972e3a6c7e8734d4_1474x456.png) 我們可以為Angular中的模塊配置多個攔截器,這些攔截器能夠對http請求以及響應進行攔截。近而提供:監視請求、響應數據;改變請求、響應數據等功能。比如在生產項目,我們可以使用攔截器來完成監聽長請求、監聽用戶需要授權、顯示loading等功能。 ![image-20210309110421080](https://img.kancloud.cn/11/b7/11b78fa5cc2228b8257f6e983ac6d590_1714x868.png) 除此以外,還可以在攔截器中加入緩存: ![image-20210309121743087](https://img.kancloud.cn/9c/7f/9c7f112fc0c1673d39e0ca30649f0002_1420x728.png) `x-auth-token`實現正是攔截器的基于監視請求、響應數據、改變請求數據三個基本的功能。 ![image-20210309122223223](https://img.kancloud.cn/4b/ad/4bad19c7707679eb08d5f21d0362699e_1878x722.png) 其處理流程如下: ![image-20210309122751092](https://img.kancloud.cn/ed/61/ed61e10ba6cd654d64f02ade991b22da_1622x1134.png) ## 定義攔截器 打開shell并定位到`src/app`文件夾,使用以下命令初始化xAuthToken攔截器: ```bash panjiedeMacBook-Pro:app panjie$ pwd /Users/panjie/github/mengyunzhi/angular11-guild/first-app/src/app panjiedeMacBook-Pro:app panjie$ ng g interceptor xAuthToken CREATE src/app/x-auth-token.interceptor.spec.ts (442 bytes) CREATE src/app/x-auth-token.interceptor.ts (415 bytes) ``` 在`x-auth-token.interceptor.ts`打如下斷點,以驗證其是否執行: ```typescript +++ b/first-app/src/app/x-auth-token.interceptor.ts @@ -13,6 +13,7 @@ export class XAuthTokenInterceptor implements HttpInterceptor { constructor() {} intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { + console.log('xAuthTokenInterceptor is called'); return next.handle(request); } } ``` 然后在任意測試模塊中加入此攔截器,比如我們將其加入到login組件對應的動態測試模塊中: ```typescript +++ b/first-app/src/app/login/login.component.spec.ts -import {HttpClientModule} from "@angular/common/http"; +import {HttpClientModule, HTTP_INTERCEPTORS} from "@angular/common/http"; @@ -16,6 +16,9 @@ describe('LoginComponent', () => { imports: [ FormsModule, HttpClientModule + ], + providers: [ + {provide: HTTP_INTERCEPTORS, useClass: XAuthTokenInterceptor, multi: true} ] }) .compileComponents(); @@ -44,7 +47,7 @@ describe('LoginComponent', () => { expect(component.onSubmit).toHaveBeenCalledTimes(1); }); - it('onSubmit 用戶登錄', () => { + fit('onSubmit 用戶登錄', () => { // 啟動自動變更檢測 fixture.autoDetectChanges(); component.teacher = {username: '張三', password: 'codedemo.club'} as Teacher; ``` `providers`譯為**具有某種能力的提供者**,我們在其中聲明了一個具有提供`HTTP_INTERCEPTORS HTTP`**攔截器**能力的提供者`XAuthTokenInterceptor`,使用`multi: true`來表時當前提供者為多個具有這種能力的一個(使用該方案保證了可以定義多個攔截器)。 Angular在發起Http請求時,會查詢當前模塊是否有`HTTP_INTERCEPTORS`的提供者, 如果有則會使用該提供者對請求進行攔截。我們使用`ng t`來觸發該單元測試: ![image-20210309124446847](https://img.kancloud.cn/ae/c9/aec9fb252514895df1d590a72d746bc6_952x242.png) 控制臺的成功打印了信息,表明在發請http請求時該攔截器中的代碼被調用,攔截器起作用了。 ## 初識Observable ```typescript intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { ?? console.log('xAuthTokenInterceptor is called'); return next.handle(request); } ``` intercept方法中有兩個參數,顧名思義第一個參數為請求,第二個參數為下一個處理者(可能是攔截器)。所以我們可以通過request來獲取關于請求的相關信息,那么如何攔截到響應信息呢?這要深入學習下`Observable`這個返回值類型。 其實我們早早的就接觸了`Observable`,該類型是設計模式中**觀察者模式**在js中的具體體現。我們可以在此返回值上調用`subscribe(success, error, complete)`方法。當`Observable`發送數據時,則會觸發`success`;當`Observable`發生錯誤時,則會調用`error` ;此外,`Observable`還會發送一個`完成`的通知,當`Observable`發送完所有的數據時,就會調用`complete`。 所以才有了我們在進行http請求時的如下代碼: ```typescript httpClient.get(url) .subscribe(data => console.log('成功', data), error => console.log('失敗', error), () => console.log('數據發送完畢')); ``` 也就說`httpClient.get()`方法實際上返回了一個`Observable`,當請求成功時,`Observable`開始發送數據,觸發`data => console.log('成功', data)`;請求失敗時,觸發`error => console.log('失敗', error),`;在http請求中無論成功或是失敗,后臺都會一次性的把數據發送回來,也就說請求的過程都完成了,所以`() => console.log('數據發送完畢')`在上述代碼中必然執行。 語言描述是空洞的,我們在攔截器對應的單元測試文件中建立如下測試代碼: ```typescript +++ b/first-app/src/app/x-auth-token.interceptor.spec.ts @@ -1,6 +1,7 @@ import {TestBed} from '@angular/core/testing'; import {XAuthTokenInterceptor} from './x-auth-token.interceptor'; +import {of} from 'rxjs'; describe('XAuthTokenInterceptor', () => { beforeEach(() => TestBed.configureTestingModule({ @@ -9,8 +10,17 @@ describe('XAuthTokenInterceptor', () => { ] })); - it('should be created', () => { + fit('should be created', () => { const interceptor: XAuthTokenInterceptor = TestBed.inject(XAuthTokenInterceptor); expect(interceptor).toBeTruthy(); + + // 定義一個observable,在其上調用subscribe將得到數字1 + const observable = of(1); + observable.subscribe( + data => console.log(data), + error => { + }, + () => console.log('complete') + ); }); }); ``` 執行結果如下: ![image-20210309142900492](https://img.kancloud.cn/a6/98/a698371bdd7d5de98dff0f8f41e7f57c_832x164.png) ## 再識管道 在V層顯示性別的時候,我們接觸了管道的概念。數據由第一個管道的這頭流入,最后由最后一個管道的那頭流出。`Observable`也支持了管道的思想,我們可以在`Observable`類型上添加任意多個管道,它們使得`Observable`在向外發送數據前,先將這些數據依次通過這些管道。 `Observable`提供了`pipe()`方法來快速的添加管道: ```typescript - const observable = of(1); + const observable = of(1).pipe(); ``` 如果我們想使值在管道中發生變化,則可以使用`map()`管道。在`Observable`的世界里,所有的管道都被習慣稱為`操作符`。`map()`便是一個可以改變數據值的操作符: ```typescript +++ b/first-app/src/app/x-auth-token.interceptor.spec.ts @@ -2,6 +2,7 @@ import {TestBed} from '@angular/core/testing'; import {XAuthTokenInterceptor} from './x-auth-token.interceptor'; import {of} from 'rxjs'; +import {map} from 'rxjs/operators'; describe('XAuthTokenInterceptor', () => { beforeEach(() => TestBed.configureTestingModule({ @@ -15,7 +16,7 @@ describe('XAuthTokenInterceptor', () => { expect(interceptor).toBeTruthy(); // 定義一個observable,在其上調用subscribe將得到數字1 - const observable = of(1).pipe(); + const observable = of(1).pipe(map(input => input * 2)); observable.subscribe( ``` `map`操作符中的參數類型是個回調函數,該函數的參數`input`為流入,而方法中的函數則為流出,所以最終控制臺打印如下: ![image-20210309143833367](https://img.kancloud.cn/d0/c5/d0c50a2841d4ade9999fb36483b7fafc_806x188.png) 其實`Observable`并不是Angular的特性,而是屬于一個叫做`rxjs`的分支。我們可以搜索關鍵字`rxjs`以獲取更多的信息,`rxjs`同時提供了多的記不過來的操作符來滿足現實編程中的各種環境。 ## tap操作符 我們再回來攔截器攔截響應的需求上來。攔截器的如下代碼返回了一個`Observable`,http請求的數據將通過該`Observable`向前返回。而我們則可以利用`Observable`可設置管道的特性,在其上設置一個監聽管道,以起到監聽header中的x-auth-token的目的。而rxjs提供的tap操作符的作用正合適: ```typescript +++ b/first-app/src/app/x-auth-token.interceptor.ts @@ -6,6 +6,7 @@ import { HttpInterceptor } from '@angular/common/http'; import { Observable } from 'rxjs'; +import {tap} from 'rxjs/operators'; @Injectable() export class XAuthTokenInterceptor implements HttpInterceptor { @@ -14,6 +15,6 @@ export class XAuthTokenInterceptor implements HttpInterceptor { intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { console.log('xAuthTokenInterceptor is called'); - return next.handle(request); + return next.handle(request).pipe(tap(input => console.log('監聽到響應', input))); } } ``` 繼續使用login組件單元測試文件進行測試: ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -47,7 +47,7 @@ describe('LoginComponent', () => { expect(component.onSubmit).toHaveBeenCalledTimes(1); }); - it('onSubmit 用戶登錄', () => { + fit('onSubmit 用戶登錄', () => { // 啟動自動變更檢測 fixture.autoDetectChanges(); component.teacher = {username: '張三', password: 'codedemo.club'} as Teacher; ``` ![image-20210309144817908](https://img.kancloud.cn/24/92/2492d23a3d1e27dcb2fc9af28bb84690_1094x250.png) ## HttpEvent `Observable`支持泛型,當前攔截器泛型對應的為`HttpEvent<unknown>`: ```typescript intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { ``` `HttpEvent`是個由5種類型組成的混合類型: ```typescript export declare type HttpEvent<T> = HttpSentEvent | HttpHeaderResponse | HttpResponse<T> | HttpProgressEvent | HttpUserEvent<T>; ``` 在此并不是所有的返回類型都攜帶了header信息(可在IDE中依次點擊其類型查看,不在詳細闡述原因),攜帶有header信息的信息為:`HttpHeaderResponse`以及`HttpResponse`,這兩個類型又全部繼承于`HttpResponseBase`,所以在進行監聽時,我們僅監聽這兩種類型的數據。 在TypeScirpt中,可以使用`instance of`對類型進行判斷。 ## 完成功能 當一切**知識盲區**都被我們解決掉的時候,完成功能則成為了最簡單的一環。下面,我們實現x-auth-token攔截器的功能: ```typescript +++ b/first-app/src/app/x-auth-token.interceptor.ts @@ -1,20 +1,48 @@ -import { Injectable } from '@angular/core'; +import {Injectable} from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, - HttpInterceptor + HttpInterceptor, HttpResponseBase } from '@angular/common/http'; -import { Observable } from 'rxjs'; +import {Observable} from 'rxjs'; import {tap} from 'rxjs/operators'; @Injectable() export class XAuthTokenInterceptor implements HttpInterceptor { + /** + * 由緩存中獲取token,防止頁面刷新后失效 + */ + private token = window.sessionStorage.getItem('x-auth-token'); - constructor() {} + constructor() { + } intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { - console.log('xAuthTokenInterceptor is called'); - return next.handle(request).pipe(tap(input => console.log('監聽到響應', input))); + if (this.token !== null) { + request.headers.set('x-auth-token', this.token); + } + return next.handle(request).pipe(tap(input => { + // 僅當input類型為HttpResponseBase,才嘗試獲取token并更新 + if (input instanceof HttpResponseBase) { + const httpHeader = input.headers; + const xAuthToken = httpHeader.get('x-auth-token'); + if (xAuthToken !== null) { + this.setToken(xAuthToken); + } + } + })); + } + + /** + * 設置token + * 如果接收到了新的token則更新,否則什么也不做 + * @param xAuthToken token + */ + private setToken(xAuthToken: string): void { + if (this.token !== xAuthToken) { + this.token = xAuthToken; + window.sessionStorage.setItem('x-auth-token', this.token); + } } } ``` ## 測試 任何沒有經過測試代碼都是坑隊友的利器。由于我們夢想著攔截器會有登錄組件、個人中心組件中自動發揮作用,所以我們在此應該使用`ng s`來完成測試。 ### 添加攔截器 為使其在`ng s`生效,則需要將其配置到`AppModule`中: ```typescript +++ b/first-app/src/app/app.module.ts @@ -3,7 +3,7 @@ import {NgModule} from '@angular/core'; import {AppRoutingModule} from './app-routing.module'; import {AppComponent} from './app.component'; -import {HttpClientModule} from '@angular/common/http'; +import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; import {AddComponent} from './add/add.component'; import {FormsModule} from '@angular/forms'; import {EditComponent} from './edit/edit.component'; @@ -12,6 +12,7 @@ import {LoginComponent} from './login/login.component'; import {IndexComponent} from './index/index.component'; import { PersonalCenterComponent } from './personal-center/personal-center.component'; import { SexPipe } from './personal-center/sex.pipe'; +import {XAuthTokenInterceptor} from './x-auth-token.interceptor'; @NgModule({ @@ -31,7 +32,9 @@ import { SexPipe } from './personal-center/sex.pipe'; FormsModule, RouterModule ], - providers: [], + providers: [ + {provide: HTTP_INTERCEPTORS, useClass: XAuthTokenInterceptor, multi: true} + ], bootstrap: [IndexComponent] }) export class AppModule { ``` ### 刪除冗余代碼 然后刪除個人中心組件進行后臺請求時手動添加header的代碼,將其恢復為: ```typescript +++ b/first-app/src/app/personal-center/personal-center.component.ts ngOnInit(): void { const url = 'http://angular.api.codedemo.club:81/teacher/me'; this.httpClient.get<Teacher>(url) .subscribe(teacher => { console.log('請求當前登錄用戶成功'); this.me = teacher; }, error => console.log('請求當前登錄用戶發生錯誤', error)); } ``` ### 定制路由 ```typescript +++ b/first-app/src/app/app-routing.module.ts @@ -2,6 +2,7 @@ import {NgModule} from '@angular/core'; import {Routes, RouterModule} from '@angular/router'; import {AddComponent} from './add/add.component'; import {EditComponent} from './edit/edit.component'; +import {PersonalCenterComponent} from './personal-center/personal-center.component'; const routes: Routes = [ { @@ -11,6 +12,10 @@ const routes: Routes = [ { path: 'edit/:id', component: EditComponent + }, + { + path: 'personal-center', + component: PersonalCenterComponent } ]; ``` ## 測試 使用正常的用戶名密碼登錄后,打開http://localhost:4200/personal-center,卻在控制臺中發生了錯誤: ![image-20210309151920846](https://img.kancloud.cn/e4/5e/e45e4b894e03b4391d38538385bdd694_2450x226.png) 此時,我們正常人做的一定是去盯代碼,然后開始意念是哪塊的代碼出了錯誤,接著就開始了萬劫不復之旅。正確的做法應該是去科學的`debug`。 既然用戶認證發生了錯誤,那么說明在請求時要么沒有帶入`x-auth-token`信息,要么帶入的`x-auth-token`是無效的。所以我們第一步應該去找到網絡選項卡,并找到個人中心的請求,查看其請求頭中是否存在`x-auth-token`: ![image-20210309152357043](https://img.kancloud.cn/32/af/32af8afddb08972bf9c95fdfaa5b3068_3252x658.png) 由上圖得知并未攜帶`x-auth-token`。那么未攜帶的原因有兩種:1. 首次與后臺交互時攔截器并未成功緩存這個`x-auth-token`;2. 雖然攔截器緩存了,并由于書寫失誤,在請求時忘了加入此`x-auth-token`了。攔截器是否緩存了該值應該去控制臺中的sessionStorege中查看: ![image-20210309152631206](https://img.kancloud.cn/18/e4/18e4fababdd0473a62e47979cab8bd42_2586x594.png) 可見,瀏覽器已存儲該值。則問題應該出現在攔截器請求時加入x-auth-token的代碼上了,那就乖乖的打斷點吧: ```typescript +++ b/first-app/src/app/x-auth-token.interceptor.ts @@ -20,7 +20,9 @@ export class XAuthTokenInterceptor implements HttpInterceptor { intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { if (this.token !== null) { + console.log('設置token', this.token); request.headers.set('x-auth-token', this.token); + console.log(request.headers.get('x-auth-token')); } return next.handle(request).pipe(tap(input => { // 僅當input類型為HttpResponseBase,才嘗試獲取token并更新 ``` 結果另我們想不到: ![image-20210309152929200](https://img.kancloud.cn/d2/ef/d2efe71dd267f03bf96f0bfccd05c9c5_3234x144.png) 控制臺顯示了如上信息,表明`if`中的程序塊已執行;23行中顯示了`this.token`的值;24行設置值后,25行卻獲取到了`null`。該情況已經超出了我們的認識,那么下一步做的就是去相應的google了: ![image-20210309153234274](https://img.kancloud.cn/f7/45/f7458f6bcc531b5120b8da6dd6bddead_2636x432.png) 除此以外,另外一種更便捷的方法是讀官方文檔,我們點擊`request.headers.set`后進入其源碼,查看注釋信息如下: ```typescript /** * Sets or modifies a value for a given header in a clone of the original instance. * If the header already exists, its value is replaced with the given value * in the returned object. * * @param name The header name. * @param value The value or values to set or overide for the given header. * * @returns A clone of the HTTP headers object with the newly set header value. */ ``` 譯文: ```typescript /** * 在源實際的 克隆 上設置或是修改header值 * If the header already exists, its value is replaced with the given value * in the returned object. * * @param name The header name. * @param value The value or values to set or overide for the given header. * * @returns 一個HTTP頭對象的 克隆,該克隆對象中header值是設置過的 */ ``` 原來更快的解決方案在于官方文檔中!由于` request.headers.set('x-auth-token', this.token);`并未改變原實例`request.headers`,所以在原實例上獲取`x-auth-token`仍為null,也就是說請求并未帶入`x-auth-token`。原因有了,解決方案當然也就隨著頁出來: ![image-20210309153950122](https://img.kancloud.cn/62/7f/627f6cb3fb30b3e8798b3b81f9bc3547_2204x292.png) 好事多磨,當我們想使用返回的新實例來設置`request.header`時,卻又發生了read-only錯誤。這并不是angular的專利,在其它的框架中也是不允許我們直接對請求對象修改的。好在Angular提供了clone方法來解決此問題: ```typescript +++ b/first-app/src/app/x-auth-token.interceptor.ts @@ -21,7 +21,7 @@ export class XAuthTokenInterceptor implements HttpInterceptor { intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { if (this.token !== null) { console.log('設置token', this.token); - request.headers.set('x-auth-token', this.token); + request = request.clone({setHeaders: {'x-auth-token': this.token}}); console.log(request.headers.get('x-auth-token')); } return next.handle(request).pipe(tap(input => { ``` 我們關閉原窗口(防止登錄過期問題),再重新打開一個,訪問http://localhost:4200/personal-center進行測試。 ![image-20210309154914944](https://img.kancloud.cn/fe/b5/feb5e717eadb0e7e693bdb701640e0b9_3764x926.png) 成功的請求到個人中心的信息,盡情地享受這種喜悅吧。 ## 本節作業 1. 在前面的已學教程中,我們成功的規避掉了設置header時原header實例并未改變的情況,請把它們找出來。 2. 本節最后一張圖片打印了兩次token值,請解釋其原因。 3. 在同一個瀏覽器的兩個窗口中分別打開http://localhost:4200,A窗口中完成登錄后,在B窗口中刷新界面B窗口仍然需要登錄。但大多數我們所使用的應用(比如思否、京東等)并非如此,你能解釋這其中的原因嗎? | 名稱 | 地址 | | | --------------- | ------------------------------------------------------------ | ---- | | 請求攔截和響應 | [https://angular.cn/guide/http#intercepting-requests-and-responses](https://angular.cn/guide/http#intercepting-requests-and-responses) | | | Observable | [https://cn.rx.js.org/class/es6/Observable.js~Observable.html](https://cn.rx.js.org/class/es6/Observable.js~Observable.html) | | | Of | [https://cn.rx.js.org/class/es6/Observable.js~Observable.html#static-method-of](https://cn.rx.js.org/class/es6/Observable.js~Observable.html#static-method-of) | | | subscribe | [https://cn.rx.js.org/class/es6/Observable.js~Observable.html#instance-method-subscribe](https://cn.rx.js.org/class/es6/Observable.js~Observable.html#instance-method-subscribe) | | | Pipeable 操作符 | [https://cn.rx.js.org/manual/usage.html](https://cn.rx.js.org/manual/usage.html) | | | Map | [https://rxjs-cn.github.io/learn-rxjs-operators/operators/transformation/map.html](https://rxjs-cn.github.io/learn-rxjs-operators/operators/transformation/map.html) | | | 本節源碼 | [https://github.com/mengyunzhi/angular11-guild/archive/step4.5.zip](https://github.com/mengyunzhi/angular11-guild/archive/step4.5.zip) | |
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看