JAX-RS - Filtres et intercepteurs
Table of Contents
1 Bibliographie
Cf. le chap. 6 de la spécification de JAX-RS 2.0.
"Filters and entity interceptors can be registered for execution at well-defined extension points in JAX-RS implementations. They are used to extend an implementation in order to provide capabilities such as logging, confidentiality, authentication, entity compression, etc."
2 Architecture pour le traitement des messages HTTP
La communcation avec le réseau se fait par l'intermédiaire de flux entrants et sortants (relativement au programme). La fonction de marshalling prend également en entrée un flux sortant, en plus de l'objet à traduire en document (XML ou JSON) : le document est écrit dans le flux sortant. De même, la fonction d'unmarshalling produit un objet à partir d'un flux entrant contenant le document. On qualifie un filtre ou un intercepteur d'entrant ou de sortant suivant le flux qu'il contient (entrant de type InputStream ou sortant de type OutputStream).
- Filtre ou intercepteur sortant : requête côté client ou réponse côté serveur
flux de type OutputStream
- Filtre ou intercepteur entrant : requête côté serveur ou réponse côté client
flux de type InputStream
2.1 Les filtres
Un filtre permet d'observer et de modifier les informations annotant le corps du message (requête ou statut HTTP, en-têtes).
Un filtre sortant permet aussi de manipuler l'objet Java représentant le contenu de message, appelé entité, contrairement à un filtre entrant.
Un filtre entrant permet de remplacer le flux entrant par un nouveau flux entrant (typiquement de source un tableau d'octets, et de type ByteArrayInputStream).
Exemple : mise à jour du cache local chez un client
- On vide le flux entrant pour obtenir un tableau d'octets, qui devient la valeur courante du cache.
- On remplace le flux vidé par un nouveau flux ayant pour source le tableau d'octets. C'est ce flux qui est utilisé lors de traduction "document vers objet" (unmarshalling).
Cf. TP2, classe infrastructure.jaxrs.Cacher (côté client).
2.2 Les intercepteurs
Un intercepteur permet de manipuler l'objet entité, les en-têtes mais ni la partie requête ni le statut du message HTTP.
Un intercepteur sortant permet d'envelopper les appels à la fonction de marshalling (fait par un "Message Body Writer" (MBW), qui traduit l'objet entité correspondant au contenu du message en un document envoyé via le flux sortant).
Un intercepteur sortant permet aussi de remplacer le flux sortant par une enveloppe, contenant non seulement le flux sortant original mais aussi un nouveau flux sortant (typiquement de destination un tableau d'octets, et de type ByteArrayOutputStream), ce qui permet de récupérer le contenu transmis du message. Cette astuce est à connaître.
Exemple : récupération de la valeur écrite dans le flux sortant à partir d'un intercepteur sortant
- Remplacement dans un intercepteur sortant du flux sortant par une
enveloppe
- qui contient le flux sortant original et un autre flux sortant de destination un tableau d'octets, et
- qui réalise des écritures sur les deux flux contenus.
- Déclenchement de la traduction (via un appel à proceed) à l'intérieur de l'intercepteur sortant.
- Récupération de la valeur du tableau d'octets contenu dans l'enveloppe.
Un intercepteur entrant permet d'envelopper les appels à la fonction d'unmarshalling (fait par un "Message Body Reader" (MBR) qui traduit en un objet le document reçu via le flux entrant).
2.3 Fonctionnement
Les filtres et les intercepteurs sont ordonnés dans des chaînes. Voici un schéma général.
- Ai, Bi, Ci, Di : filtres (indexés par des entiers i indiquant leur ordre)
- Pj, Qj, Rj, Sj : intercepteurs (indexés par des entiers j indiquant leur ordre)
Notation pour un intercepteur P:
- P[f] = fonction de G vers H pouvant utiliser f où f : G -> H est une fonction de G vers H (avec JAX-RS : appel de f par un appel proceed)
Fonctionnement
- Requête
Client
A_1 A_2 ... A_j // filtres sortants P_1 [ P_2 [ ... P_i[marshalling] ...]] // intercepteurs sortants
Serveur
B_1 B_2 ... B_k // filtres entrants Q_1 [ Q_2 [ ... Q_l[unmarshalling] ...]] // intercepteurs entrants
- Réponse
Serveur
C_n ... C_2 C_1 // filtres sortants R_1 [ R_2 [ ... R_m[marshalling] ...]] // intercepteurs sortants
Client
D_o ... D_2 D_1 // filtres entrants S_1 [ S_2 [ ... S_p[marshalling] ...]] // intercepteurs entrants
L'ordre d'exécution des filtres et des intercepteurs est indiqué par un entier. Les intercepteurs sont toujours ordonnés de manière croissante. Pour un filtre, la chaîne des requêtes suit un ordre croissant alors que la chaîne des réponses suit un ordre décroissant : ainsi, côté client comme côté serveur, si chaque filtre s'applique aussi bien à la requête qu'à la réponse, ils s'imbriquent correctement.
Ordre des filtres :
- requête
F1 puis F2 puis F3
- réponse
F3 puis F2 puis F1
Les entiers s'organisent en des plages particulières définies dans l'interface javax.ws.rs.BindingPriority. On utilise pour ordonner les filtres et les intercepteurs des entiers définis relativement aux constantes suivantes.
- Security authentication filter/interceptor priority.
public static final int AUTHENTICATION = 2000;
- Security authorization filter/interceptor priority.
public static final int AUTHORIZATION = 3000;
- Header decorator filter/interceptor priority.
public static final int HEADER_DECORATOR = 5000;
- Message encoder or decoder filter/interceptor priority.
public static final int ENTITY_CODER = 6000;
- User-level filter/interceptor priority.
public static final int USER = 7000;
2.4 Exemples
Cf. sections 6.2 et 6.3 de la spécification pour des exemples.
Cf. TP2, paquet infrastructure.jaxrs.
3 Les interfaces
3.1 Les filtres
Transformation des messages HTTP lors des requêtes ou des réponses, sur le serveur ou le client
Quatre interfaces
- serveur :
- requête :
public interface ContainerRequestFilter { void filter(ContainerRequestContext requestContext) throws IOException; }
- réponse
public interface ContainerResponseFilter { void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException; }
- requête :
- client :
- requête :
public interface ClientRequestFilter { void filter(ClientRequestContext requestContext) throws IOException; }
- réponse :
public interface ClientResponseFilter { void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException; }
- requête :
3.2 Les intercepteurs
Enveloppe de la fonction de marshallling ou d'unmarshalling réalisant la traduction entre objets et le corps des messages HTTP (appelé "entity").
Deux interfaces
- requête côté serveur ou réponse côté client (enveloppe d'unmarshalling)
public interface ReaderInterceptor { Object aroundReadFrom(ReaderInterceptorContext context) throws java.io.IOException, javax.ws.rs.WebApplicationException; }
- réponse côté serveur ou requête côté client (enveloppe de marshalling)
public interface WriterInterceptor { void aroundWriteTo(WriterInterceptorContext context) throws java.io.IOException, javax.ws.rs.WebApplicationException; }
Utilisation de la méthode proceed pour lancer les fonctions de marshallling ou d'unmarshalling.