Interception de demandes et de réponses
L'interception HTTP est une caractéristique majeure de
@angular/common/http
.
Avec l'interception, vous déclarez les intercepteurs qui inspectent et transforment les demandes HTTP de votre application vers le serveur.
Les mêmes intercepteurs peuvent également inspecter et transformer les réponses du serveur sur le chemin du retour à l'application.
Les intercepteurs multiples forment une chaîne en avant et en arrière de gestionnaires de demandes / réponses.
Les intercepteurs peuvent effectuer diverses tâches implicites, de l'authentification à la journalisation, de manière standard et courante pour chaque requête / réponse HTTP.
Sans interception, les développeurs devraient implémenter ces tâches explicitement pour chaque appel de méthode de
HttpClient
.
Écrire un intercepteur
Pour implémenter un intercepteur, déclarez une classe qui implémente la méthode
intercept()
de l'interface
HttpInterceptor
.
Voici un intercepteur
Noop
qui ne fait rien et qui passe simplement la requête sans la toucher:
La méthode
intercept
transforme une requête en une requête Observable qui retourne éventuellement la réponse HTTP.
En ce sens, chaque intercepteur est tout à fait capable de traiter la demande entièrement par lui-même.
La plupart des intercepteurs inspectent la requête à l'entrée et la transmettent (éventuellement modifiée) à la méthode
handle()
de l'objet next qui implémente l'interface
HttpHandler
.
Comme
intercept()
, la méthode
handle()
transforme une requête HTTP dans un Observablede
HttpEvents
qui comprennent finalement la réponse du serveur.
La méthode
intercept()
pourrait inspecter cet observable et le modifier avant de le renvoyer à l'appelant.
Ce
no-op
intercepteur appelle simplement
next.handle()
à la demande initiale et renvoie l'observable sans faire une chose.
L'objet next
L'objet next représente le prochain intercepteur de la chaîne d'intercepteurs.
Le dernier membre next de la chaîne est le gestionnaire
HttpClient
dorsal qui envoie la demande au serveur et reçoit la réponse du serveur.
La plupart des intercepteurs appellent
next.handle()
pour que la demande soit acheminée vers l'intercepteur suivant et, éventuellement,
vers le gestionnaire d'arrière-plan. Un intercepteur peut ignorer l'appel
next.handle()
, court-circuiter la chaîne et renvoyer le sien, Observable avec une réponse de serveur artificielle.
Il s'agit d'un modèle d'intergiciel commun utilisé dans des infrastructures telles que
Express.js
.
Fournir l'intercepteur
Le
Noop
Interceptor est un service géré par le système d'injection de dépendance (DI) d'Angular.
Comme pour les autres services, vous devez fournir la classe d'intercepteur avant que l'application puisse l'utiliser.
Les intercepteurs étant des dépendances du service
HttpClient
, vous devez les fournir dans le même
injecteur (ou un parent de l'injecteur) fourni
HttpClient
. Les intercepteurs fournis après la création de DI
HttpClient
sont ignorés.
Cette application fournit
HttpClient
dans l'injecteur de racine de l'application, comme un effet secondaire d'importer le
HttpClientModule
dans
AppModule
.
Vous devez également fournir des intercepteurs
AppModule
.
Après avoir importé le jeton d'injection depuis
HTTP_INTERCEPTORS
, écrivez le fournisseur comme suit :
@angular/common/httpNoopInterceptor
Notez l'option multi :
true
.
Ce paramètre requis indique à Angular qu'il s'agit d'un jeton
HTTP_INTERCEPTORS
pour un fournisseur multiple qui injecte un tableau de valeurs plutôt qu'une valeur unique.
Vous pouvez ajouter ce fournisseur directement au tableau de fournisseurs du
AppModule
.
Cependant, c'est plutôt bavard et il y a de bonnes chances que vous créiez plus d'intercepteurs et que vous les fournissiez de la même manière.
Vous devez également porter une attention particulière à l'ordre dans lequel vous fournissez ces intercepteurs.
Envisagez de créer un fichier "
barrel
" regroupant tous les fournisseurs d'intercepteurs dans un tableau
httpInterceptorProviders
, en commençant par celui-ci
NoopInterceptor
.
Puis importez et ajoutez-le au tableau
AppModule
de fournisseurs comme ceci:
Lorsque vous créez de nouveaux intercepteurs, ajoutez-les au tableau
httpInterceptorProviders
et vous n'aurez plus à revenir sur le tableau
AppModule
.
Commande intercepteur
Angular applique les intercepteurs dans l'ordre dans lequel vous les fournissez.
Si vous fournissez des intercepteurs A , puis B , puis C , les demandes coulera dans A > B > C et réponses couleront C > B -> A .
Vous ne pouvez pas modifier l'ordre ou supprimer les intercepteurs ultérieurement.
Si vous devez activer et désactiver un intercepteur de manière dynamique, vous devez intégrer cette fonctionnalité dans l'intercepteur lui-même.
HttpEvents
Vous vous êtes peut-être attendu à ce que les méthodes
handle()
renvoient des éléments observables,
HttpResponse
‹any›
comme la plupart des méthodes
HttpClient
.
Au lieu de cela, ils retournent des observables de
HttpEvent
‹any›
.
En effet, les intercepteurs fonctionnent à un niveau inférieur à celui de ces méthodes
HttpClient
.
Une seule requête HTTP peut générer plusieurs événements, y compris des événements de progression de téléchargement et de téléchargement.
La classe
HttpResponse
elle-même est en réalité un événement dont le type est
HttpEventType.HttpResponseEvent
.
De nombreux intercepteurs ne sont concernés que par la demande sortante et renvoient simplement le flux d'événements
next.handle()
sans le modifier.
Mais les intercepteurs qui examinent et modifient la réponse
next.handle()
verront tous ces événements.
Votre intercepteur devrait laisser chaque événement intact, sauf s'il a une raison impérieuse de faire autrement .
Immutabilité
Bien que les intercepteurs soient capables de muter les demandes et les réponses, les propriétés d'instance
HttpRequest
et
HttpResponse
sont les
readonly
rendant largement immuables.
Ils sont immuables pour une bonne raison: l'application peut réessayer une demande plusieurs fois avant qu'elle ne réussisse, ce qui signifie que la chaîne d'intercepteur
peut retraiter la même demande plusieurs fois. Si un intercepteur pouvait modifier l'objet de demande d'origine, l'opération réessayée commencerait par la
demande modifiée plutôt que par l'original. L'immuabilité garantit que les intercepteurs voient la même demande pour chaque essai.
TypeScript
vous empêchera de définir des propriétés
HttpRequest
en lecture seule.
Pour modifier la requête, clonez-la d'abord et modifiez le clone avant de le transmettre à
next.handle()
.
Vous pouvez cloner et modifier la demande en une seule étape, comme dans cet exemple.
L'argument
clone()
de la méthode vous permet de muter des propriétés spécifiques de la demande tout en copiant les autres.
Le corps de la demande
Le
readonly
, garde d'affectation ne peut pas empêcher les mises à jour profondes et, en particulier,
il ne vous empêche pas de modifier une propriété d'un objet de corps de requête.
Si vous devez modifier le corps de la demande, copiez-le d'abord, modifiez la copie et la demande
clone()
,
puis définissez le corps du clone avec le nouveau corps, comme dans l'exemple suivant.
Effacement du corps de la demande
Parfois, vous devez vider le corps de la demande plutôt que de le remplacer.
Si vous définissez le corps de la demande clonée sur
undefined
, Angular suppose que vous avez l'intention de quitter le corps tel quel.
Ce n'est pas ce que vous voulez. Si vous définissez le corps de la demande clonée sur
null
, Angular sait que vous souhaitez effacer le corps de la demande.
Définir en-têtes par défaut
Les applications utilisent souvent un intercepteur pour définir les en-têtes par défaut des demandes sortantes.
L'exemple d'application contient un jeton
AuthService
d'autorisation.
Voici son
AuthInterceptor
qui injecte ce service pour obtenir le jeton et ajoute un en-tête d'autorisation avec ce jeton à chaque demande sortante:
La pratique consistant à cloner une demande pour définir de nouveaux en-têtes est si courante qu'il existe un raccourci
setHeaders
pour cela:
Un intercepteur qui modifie les en-têtes peut être utilisé pour différentes opérations, notamment:
- Autorisation d'authentification
- Comportement de mise en cache; par exemple,If-Modified-Since
XSRF
protection
Logging
Comme les intercepteurs peuvent traiter la demande et la réponse ensemble, ils peuvent effectuer des opérations telles que l'heure et consigner une opération HTTP complète.
Considérez ce qui suit
LoggingInterceptor
, qui capture le temps de la demande,
le temps de la réponse et enregistre le résultat avec le temps écoulé avec l'injecté
MessageService
.
L'opérateur tap RxJS capture si la requête a réussi ou échoué.
L'opérateur finalize RxJS est appelé lorsque la réponse constatée est soit erronée,
soit terminée (ce qu'il doit), et il en informe le résultat
MessageService
.
Mise en cache
Les intercepteurs peuvent gérer les demandes eux-mêmes, sans transfert de données
next.handle()
.
Par exemple, vous pouvez décider de mettre en cache certaines demandes et réponses pour améliorer les performances.
Vous pouvez déléguer la mise en cache à un intercepteur sans perturber vos services de données existants.
Le
CachingInterceptor
démontre cette approche.
La fonction
isCachable()
détermine si la requête est cachable.
Dans cet exemple, seules les demandes
GET
adressées à l'API de recherche de paquets npm sont cachables.
Si la demande ne peut pas être cachée, l'intercepteur transmet simplement la demande au prochain gestionnaire de la chaîne.
Si une demande cachable est trouvée dans le cache, l'intercepteur renvoie un observable
of()
avec la réponse en cache,
en contournant le gestionnaire
next
(et tous les autres intercepteurs en aval).
Si une demande cachable n'est pas dans le cache, le code appelle
sendRequest
.
La fonction
sendRequest
crée un clone de requête sans en-têtes car l'API
npm
les interdit.
Il transmet la demande à
next.handle()
laquelle appelle finalement le serveur et renvoie la réponse du serveur.
Notez comment
sendRequest
intercepte la réponse avant de retourner à l'application.
Il achemine la réponse via l'opérateur
tap()
, dont le rappel ajoute la réponse au cache.
La réponse d'origine continue à être sauvegardée sans interruption dans la chaîne d'intercepteurs jusqu'à l'appelant de l'application.
Les services de données, tels que
PackageSearchService
, ne savent pas que certaines de leurs demandes
HttpClient
renvoient des réponses en cache.
Renvoyer un observable à plusieurs valeurs
La méthode
HttpClient.get()
retourne normalement un observable qui émet les données ou une erreur.
Certaines personnes le décrivent comme un observable "un fait accompli".
Mais un intercepteur peut changer cela en un observable qui émet plus d'une fois.
Une version révisée du document renvoie
CachingInterceptor
facultativement un observable qui émet immédiatement la réponse mise en cache,
envoie quand même la demande à l'API Web NPM et émet à nouveau ultérieurement avec les résultats de recherche mis à jour.
L'option
cache-then-refresh
est déclenchée par la présence d'un en-tête personnalisé
x-refresh
Une case à cocher sur la bascule
PackageSearchComponent
un drapeau
withRefresh
, qui est l'un des arguments à
PackageSearchService.search()
.
Cette méthode
search()
crée l'en-tête
x-refresh
personnalisé et l'ajoute à la demande avant l'appel
HttpClient.get()
.
La version révisée
CachingInterceptor
configure une demande de serveur, qu'il y ait ou non une valeur mise en cache,
en utilisant la même méthode
sendRequest()
que celle décrite ci-dessus.
S'il n'y a pas de valeur en cache, l'intercepteur retourne
results$
.
S'il existe une valeur mise en cache, le code dirige la réponse mise en cache
results$
, produisant un observable recomposé qui émet deux fois,
la réponse mise en cache en premier (et immédiatement), suivie de la réponse du serveur. Les abonnés voient une séquence de deux réponses.