国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

Angular9 攔截請求和響應(yīng)

2020-07-06 15:25 更新

借助攔截機(jī)制,你可以聲明一些攔截器,它們可以檢查并轉(zhuǎn)換從應(yīng)用中發(fā)給服務(wù)器的 HTTP 請求。這些攔截器還可以在返回應(yīng)用的途中檢查和轉(zhuǎn)換來自服務(wù)器的響應(yīng)。多個攔截器構(gòu)成了請求/響應(yīng)處理器的雙向鏈表。

攔截器可以用一種常規(guī)的、標(biāo)準(zhǔn)的方式對每一次 HTTP 的請求/響應(yīng)任務(wù)執(zhí)行從認(rèn)證到記日志等很多種隱式任務(wù)。

如果沒有攔截機(jī)制,那么開發(fā)人員將不得不對每次 HttpClient 調(diào)用顯式實(shí)現(xiàn)這些任務(wù)。

編寫攔截器

要實(shí)現(xiàn)攔截器,就要實(shí)現(xiàn)一個實(shí)現(xiàn)了 HttpInterceptor 接口中的 intercept() 方法的類。

這里是一個什么也不做的空白攔截器,它只會不做任何修改的傳遞這個請求。

Path:"app/http-interceptors/noop-interceptor.ts" 。

import { Injectable } from '@angular/core';
import {
  HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
} from '@angular/common/http';


import { Observable } from 'rxjs';


/** Pass untouched request through to the next request handler. */
@Injectable()
export class NoopInterceptor implements HttpInterceptor {


  intercept(req: HttpRequest<any>, next: HttpHandler):
    Observable<HttpEvent<any>> {
    return next.handle(req);
  }
}

intercept 方法會把請求轉(zhuǎn)換成一個最終返回 HTTP 響應(yīng)體的 Observable。 在這個場景中,每個攔截器都完全能自己處理這個請求。

大多數(shù)攔截器攔截都會在傳入時檢查請求,然后把(可能被修改過的)請求轉(zhuǎn)發(fā)給 next 對象的 handle() 方法,而 next 對象實(shí)現(xiàn)了 HttpHandler 接口。

export abstract class HttpHandler {
  abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
}

intercept() 一樣,handle() 方法也會把 HTTP 請求轉(zhuǎn)換成 HttpEvents 組成的 Observable,它最終包含的是來自服務(wù)器的響應(yīng)。 intercept() 函數(shù)可以檢查這個可觀察對象,并在把它返回給調(diào)用者之前修改它。

這個無操作的攔截器,會直接使用原始的請求調(diào)用 next.handle(),并返回它返回的可觀察對象,而不做任何后續(xù)處理。

next 對象

next 對象表示攔截器鏈表中的下一個攔截器。 這個鏈表中的最后一個 next 對象就是 HttpClient 的后端處理器(backend handler),它會把請求發(fā)給服務(wù)器,并接收服務(wù)器的響應(yīng)。

大多數(shù)的攔截器都會調(diào)用 next.handle(),以便這個請求流能走到下一個攔截器,并最終傳給后端處理器。 攔截器也可以不調(diào)用 next.handle(),使這個鏈路短路,并返回一個帶有人工構(gòu)造出來的服務(wù)器響應(yīng)的 自己的 Observable。

這是一種常見的中間件模式,在像 "Express.js" 這樣的框架中也會找到它。

提供這個攔截器

這個 NoopInterceptor 就是一個由 Angular 依賴注入 (DI)系統(tǒng)管理的服務(wù)。 像其它服務(wù)一樣,你也必須先提供這個攔截器類,應(yīng)用才能使用它。

由于攔截器是 HttpClient 服務(wù)的(可選)依賴,所以你必須在提供 HttpClient 的同一個(或其各級父注入器)注入器中提供這些攔截器。 那些在 DI 創(chuàng)建完 HttpClient 之后再提供的攔截器將會被忽略。

由于在 AppModule 中導(dǎo)入了 HttpClientModule,導(dǎo)致本應(yīng)用在其根注入器中提供了 HttpClient。所以你也同樣要在 AppModule 中提供這些攔截器。

在從 @angular/common/http 中導(dǎo)入了 HTTP_INTERCEPTORS 注入令牌之后,編寫如下的 NoopInterceptor 提供者注冊語句:

{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },

注意 multi: true 選項(xiàng)。 這個必須的選項(xiàng)會告訴 Angular HTTP_INTERCEPTORS 是一個多重提供者的令牌,表示它會注入一個多值的數(shù)組,而不是單一的值。

你也可以直接把這個提供者添加到 AppModule 中的提供者數(shù)組中,不過那樣會非常啰嗦。況且,你將來還會用這種方式創(chuàng)建更多的攔截器并提供它們。 你還要特別注意提供這些攔截器的順序。

認(rèn)真考慮創(chuàng)建一個封裝桶(barrel)文件,用于把所有攔截器都收集起來,一起提供給 httpInterceptorProviders 數(shù)組,可以先從這個 NoopInterceptor 開始。

Path:"app/http-interceptors/index.ts" 。

/* "Barrel" of Http Interceptors */
import { HTTP_INTERCEPTORS } from '@angular/common/http';


import { NoopInterceptor } from './noop-interceptor';


/** Http interceptor providers in outside-in order */
export const httpInterceptorProviders = [
  { provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },
];

然后導(dǎo)入它,并把它加到 AppModuleproviders 數(shù)組中,就像這樣:

Path:"app/app.module.ts (interceptor providers)" 。

providers: [
  httpInterceptorProviders
],

當(dāng)你再創(chuàng)建新的攔截器時,就同樣把它們添加到 httpInterceptorProviders 數(shù)組中,而不用再修改 AppModule。

攔截器的順序

Angular 會按照你提供它們的順序應(yīng)用這些攔截器。 如果你提供攔截器的順序是先 A,再 B,再 C,那么請求階段的執(zhí)行順序就是 A->B->C,而響應(yīng)階段的執(zhí)行順序則是 C->B->A。

以后你就再也不能修改這些順序或移除某些攔截器了。 如果你需要動態(tài)啟用或禁用某個攔截器,那就要在那個攔截器中自行實(shí)現(xiàn)這個功能。

處理攔截器事件

大多數(shù) HttpClient 方法都會返回 HttpResponse<any> 型的可觀察對象。HttpResponse 類本身就是一個事件,它的類型是 HttpEventType.Response。但是,單個 HTTP 請求可以生成其它類型的多個事件,包括報(bào)告上傳和下載進(jìn)度的事件。HttpInterceptor.intercept()HttpHandler.handle() 會返回 HttpEvent<any> 型的可觀察對象。

很多攔截器只關(guān)心發(fā)出的請求,而對 next.handle() 返回的事件流不會做任何修改。 但是,有些攔截器需要檢查并修改 next.handle() 的響應(yīng)。上述做法就可以在流中看到所有這些事件。

雖然攔截器有能力改變請求和響應(yīng),但 HttpRequestHttpResponse 實(shí)例的屬性卻是只讀(readonly)的, 因此讓它們基本上是不可變的。

有充足的理由把它們做成不可變對象:應(yīng)用可能會重試發(fā)送很多次請求之后才能成功,這就意味著這個攔截器鏈表可能會多次重復(fù)處理同一個請求。 如果攔截器可以修改原始的請求對象,那么重試階段的操作就會從修改過的請求開始,而不是原始請求。 而這種不可變性,可以確保這些攔截器在每次重試時看到的都是同樣的原始請求。

你的攔截器應(yīng)該在沒有任何修改的情況下返回每一個事件,除非它有令人信服的理由去做。

TypeScript 會阻止你設(shè)置 HttpRequest 的只讀屬性。

// Typescript disallows the following assignment because req.url is readonly
req.url = req.url.replace('http://', 'https://');

如果你必須修改一個請求,先把它克隆一份,修改這個克隆體后再把它傳給 next.handle()。你可以在一步中克隆并修改此請求,例子如下。

Path:"app/http-interceptors/ensure-https-interceptor.ts (excerpt)" 。

// clone request and replace 'http://' with 'https://' at the same time
const secureReq = req.clone({
  url: req.url.replace('http://', 'https://')
});
// send the cloned, "secure" request to the next handler.
return next.handle(secureReq);

這個 clone() 方法的哈希型參數(shù)允許你在復(fù)制出克隆體的同時改變該請求的某些特定屬性。

  1. 修改請求體。

readonly 這種賦值保護(hù),無法防范深修改(修改子對象的屬性),也不能防范你修改請求體對象中的屬性。

    req.body.name = req.body.name.trim(); // bad idea!

如果必須修改請求體,請執(zhí)行以下步驟。

  • 復(fù)制請求體并在副本中進(jìn)行修改。

  • 使用 clone() 方法克隆這個請求對象。

  • 用修改過的副本替換被克隆的請求體。

    // copy the body and trim whitespace from the name property
    const newBody = { ...body, name: body.name.trim() };
    // clone request and set its body
    const newReq = req.clone({ body: newBody });
    // send the cloned request to the next handler.
    return next.handle(newReq);

  1. 克隆時清除請求體。

有時,你需要清除請求體而不是替換它。為此,請將克隆后的請求體設(shè)置為 null。

注:

  • 如果你把克隆后的請求體設(shè)為 undefined,那么 Angular 會認(rèn)為你想讓請求體保持原樣。

    newReq = req.clone({ ... }); // body not mentioned => preserve original body
    newReq = req.clone({ body: undefined }); // preserve original body
    newReq = req.clone({ body: null }); // clear the body

設(shè)置默認(rèn)請求頭

應(yīng)用通常會使用攔截器來設(shè)置外發(fā)請求的默認(rèn)請求頭。

該范例應(yīng)用具有一個 AuthService,它會生成一個認(rèn)證令牌。 在這里,AuthInterceptor 會注入該服務(wù)以獲取令牌,并對每一個外發(fā)的請求添加一個帶有該令牌的認(rèn)證頭:

Path:"app/http-interceptors/auth-interceptor.ts" 。

import { AuthService } from '../auth.service';


@Injectable()
export class AuthInterceptor implements HttpInterceptor {


  constructor(private auth: AuthService) {}


  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // Get the auth token from the service.
    const authToken = this.auth.getAuthorizationToken();


    // Clone the request and replace the original headers with
    // cloned headers, updated with the authorization.
    const authReq = req.clone({
      headers: req.headers.set('Authorization', authToken)
    });


    // send cloned request with header to the next handler.
    return next.handle(authReq);
  }
}

這種在克隆請求的同時設(shè)置新請求頭的操作太常見了,因此它還有一個快捷方式 setHeaders

// Clone the request and set the new header in one step.
const authReq = req.clone({ setHeaders: { Authorization: authToken } });

這種可以修改頭的攔截器可以用于很多不同的操作,比如:

  • 認(rèn)證 / 授權(quán)

  • 控制緩存行為。比如 If-Modified-Since

  • XSRF 防護(hù)

用攔截器記日志

因?yàn)閿r截器可以同時處理請求和響應(yīng),所以它們也可以對整個 HTTP 操作執(zhí)行計(jì)時和記錄日志等任務(wù)。

考慮下面這個 LoggingInterceptor,它捕獲請求的發(fā)起時間、響應(yīng)的接收時間,并使用注入的 MessageService 來發(fā)送總共花費(fèi)的時間。

Path:"app/http-interceptors/logging-interceptor.ts)" 。

import { finalize, tap } from 'rxjs/operators';
import { MessageService } from '../message.service';


@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  constructor(private messenger: MessageService) {}


  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const started = Date.now();
    let ok: string;


    // extend server response observable with logging
    return next.handle(req)
      .pipe(
        tap(
          // Succeeds when there is a response; ignore other events
          event => ok = event instanceof HttpResponse ? 'succeeded' : '',
          // Operation failed; error is an HttpErrorResponse
          error => ok = 'failed'
        ),
        // Log when response observable either completes or errors
        finalize(() => {
          const elapsed = Date.now() - started;
          const msg = `${req.method} "${req.urlWithParams}"
             ${ok} in ${elapsed} ms.`;
          this.messenger.add(msg);
        })
      );
  }
}

RxJS 的 tap 操作符會捕獲請求成功了還是失敗了。 RxJS 的 finalize 操作符無論在響應(yīng)成功還是失敗時都會調(diào)用(這是必須的),然后把結(jié)果匯報(bào)給 MessageService

在這個可觀察對象的流中,無論是 tap 還是 finalize 接觸過的值,都會照常發(fā)送給調(diào)用者。

用攔截器實(shí)現(xiàn)緩存

攔截器還可以自行處理這些請求,而不用轉(zhuǎn)發(fā)給 next.handle()。

比如,你可能會想緩存某些請求和響應(yīng),以便提升性能。 你可以把這種緩存操作委托給某個攔截器,而不破壞你現(xiàn)有的各個數(shù)據(jù)服務(wù)。

下例中的 CachingInterceptor 演示了這種方法。

Path:"app/http-interceptors/caching-interceptor.ts)" 。

@Injectable()
export class CachingInterceptor implements HttpInterceptor {
  constructor(private cache: RequestCache) {}


  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // continue if not cacheable.
    if (!isCacheable(req)) { return next.handle(req); }


    const cachedResponse = this.cache.get(req);
    return cachedResponse ?
      of(cachedResponse) : sendRequest(req, next, this.cache);
  }
}

  • isCacheable() 函數(shù)用于決定該請求是否允許緩存。 在這個例子中,只有發(fā)到 npm 包搜索 APIGET 請求才是可以緩存的。

  • 如果該請求是不可緩存的,該攔截器只會把該請求轉(zhuǎn)發(fā)給鏈表中的下一個處理器。

  • 如果可緩存的請求在緩存中找到了,該攔截器就會通過 of() 函數(shù)返回一個已緩存的響應(yīng)體的可觀察對象,然后繞過 next 處理器(以及所有其它下游攔截器)。

  • 如果可緩存的請求不在緩存中,代碼會調(diào)用 sendRequest()。這個函數(shù)會創(chuàng)建一個沒有請求頭的請求克隆體,這是因?yàn)?npm API 禁止它們。然后,該函數(shù)把請求的克隆體轉(zhuǎn)發(fā)給 next.handle(),它會最終調(diào)用服務(wù)器并返回來自服務(wù)器的響應(yīng)對象。

/**
 * Get server response observable by sending request to `next()`.
 * Will add the response to the cache on the way out.
 */
function sendRequest(
  req: HttpRequest<any>,
  next: HttpHandler,
  cache: RequestCache): Observable<HttpEvent<any>> {


  // No headers allowed in npm search request
  const noHeaderReq = req.clone({ headers: new HttpHeaders() });


  return next.handle(noHeaderReq).pipe(
    tap(event => {
      // There may be other events besides the response.
      if (event instanceof HttpResponse) {
        cache.put(req, event); // Update the cache.
      }
    })
  );
}

注意 sendRequest() 是如何在返回應(yīng)用程序的過程中攔截響應(yīng)的。該方法通過 tap() 操作符來管理響應(yīng)對象,該操作符的回調(diào)函數(shù)會把該響應(yīng)對象添加到緩存中。

然后,原始的響應(yīng)會通過這些攔截器鏈,原封不動的回到服務(wù)器的調(diào)用者那里。

數(shù)據(jù)服務(wù),比如 PackageSearchService,并不知道它們收到的某些 HttpClient 請求實(shí)際上是從緩存的請求中返回來的。

用攔截器來請求多個值

HttpClient.get() 方法通常會返回一個可觀察對象,它會發(fā)出一個值(數(shù)據(jù)或錯誤)。攔截器可以把它改成一個可以發(fā)出多個值的可觀察對象。

修改后的 CachingInterceptor 版本可以返回一個立即發(fā)出所緩存響應(yīng)的可觀察對象,然后把請求發(fā)送到 NPMWeb API,然后把修改過的搜索結(jié)果重新發(fā)出一次。

// cache-then-refresh
if (req.headers.get('x-refresh')) {
  const results$ = sendRequest(req, next, this.cache);
  return cachedResponse ?
    results$.pipe( startWith(cachedResponse) ) :
    results$;
}
// cache-or-fetch
return cachedResponse ?
  of(cachedResponse) : sendRequest(req, next, this.cache);

cache-then-refresh 選項(xiàng)是由一個自定義的 x-refresh 請求頭觸發(fā)的。

PackageSearchComponent 中的一個檢查框會切換 withRefresh 標(biāo)識, 它是 PackageSearchService.search() 的參數(shù)之一。 search() 方法創(chuàng)建了自定義的 x-refresh 頭,并在調(diào)用 HttpClient.get() 前把它添加到請求里。

修改后的 CachingInterceptor 會發(fā)起一個服務(wù)器請求,而不管有沒有緩存的值。 就像 前面 的 sendRequest() 方法一樣進(jìn)行訂閱。 在訂閱 results$ 可觀察對象時,就會發(fā)起這個請求。

  • 如果沒有緩存值,攔截器直接返回 results$

  • 如果有緩存的值,這些代碼就會把緩存的響應(yīng)加入到 result$ 的管道中,使用重組后的可觀察對象進(jìn)行處理,并發(fā)出兩次。 先立即發(fā)出一次緩存的響應(yīng)體,然后發(fā)出來自服務(wù)器的響應(yīng)。 訂閱者將會看到一個包含這兩個響應(yīng)的序列。
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號