Le filtre Jersey 2 utilise le contexte de requête de conteneur dans le filtre de requête client

J’ai un service Web Jersey 2 qui, à la réception d’une demande, envoie une autre demande à un autre service Web afin de former la réponse à la demande initiale. Ainsi, lorsque le client “A” fait une demande à mon service Web “B”, “B” fait une demande à “C” dans le cadre de la formation de la réponse à “A”.

A-> B-> C

Je souhaite implémenter un filtre pour un service Web Jersey 2 qui effectue essentiellement ceci:

  • Le client “A” enverra une demande avec un en-tête comme “My-Header: first”

  • Lorsque mon service Web “B” fait ensuite une demande client “C”, il doit être ajouté à cet en-tête. Il envoie donc une demande avec cet en-tête “My-Header: first, second”.

Je souhaite implémenter cela en tant que filtre afin que toutes mes ressources ne soient pas obligées de dupliquer la logique d’ajout à l’en-tête de la demande.

Cependant, dans Jersey 2, vous obtenez ces 4 filtres:

  • ContainerRequestFilter – Filtrer / modifier les demandes entrantes
  • ContainerResponseFilter – Filtrer / modifier les réponses sortantes
  • ClientRequestFilter – Filtrer / modifier les demandes sortantes
  • ClientResponseFilter – Filtrer / modifier les réponses entrantes

Diagramme de filtre de Jersey

J’ai besoin d’utiliser l’en-tête d’une demande entrante, de le modifier, puis de l’utiliser comme demande sortante. J’ai donc essentiellement besoin de quelque chose qui soit à la fois un conteneur, et un client et un filtre. Je ne pense pas que l’implémentation des deux dans le même filtre fonctionnera, car vous ne savez pas quelle requête client correspond à quelle requête conteneur, ou le faites-vous?

J’ai trouvé un moyen intéressant de ne pas utiliser ThreadLocal pour communiquer entre ContainerRequestFilter et ClientRequestFilter , car vous ne pouvez pas supposer que les demandes des clients faites en réponse à une demande de conteneur seront sur le même thread.

Pour y parvenir, définissez une propriété dans l’object ContainerRequestFilter du ContainerRequestFilter . Je peux ensuite transmettre l’object ContainerRequestContext (de manière explicite ou par dependency injection) dans mon ClientRequestFilter . Si vous utilisez l’dependency injection (si vous utilisez Jersey 2, vous utilisez probablement HK2), tout cela peut être réalisé sans modifier aucune de vos logiques de niveau de ressources.

Avoir un ContainerRequestFilter comme ceci:

 public class RequestIdContainerFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext containerRequestContext) throws IOException { containerRequestContext.setProperty("property-name", "any-object-you-like"); } 

Et un ClientRequestFilter qui prend un ContainerRequestContext dans son constructeur:

 public class RequestIdClientRequestFilter implements ClientRequestFilter { private ContainerRequestContext containerRequestContext; public RequestIdClientRequestFilter(ContainerRequestContext containerRequestContext) { this.containerRequestContext = containerRequestContext; } @Override public void filter(ClientRequestContext clientRequestContext) throws IOException { Ssortingng value = containerRequestContext.getProperty("property-name"); clientRequestContext.getHeaders().putSingle("MyHeader", value); } } 

Ensuite, il suffit de lier tout cela ensemble. Vous aurez besoin d’une usine pour créer tout Client ou WebTarget dont vous avez besoin:

 public class MyWebTargetFactory implements Factory { @Context private ContainerRequestContext containerRequestContext; @Inject public MyWebTargetFactory(ContainerRequestContext containerRequestContext) { this.containerRequestContext = containerRequestContext; } @Override public WebTarget provide() { Client client = ClientBuilder.newClient(); client.register(new RequestIdClientRequestFilter(containerRequestContext)); return client.target("path/to/api"); } @Override public void dispose(WebTarget target) { } } 

Enregistrez ensuite le filtre et liez votre fabrique à votre application principale ResourceConfig :

 public class MyApplication extends ResourceConfig { public MyApplication() { register(RequestIdContainerFilter.class); register(new AbstractBinder() { @Override protected void configure() { bindFactory(MyWebTargetFactory.class).to(WebTarget.class); } } } } 

Un filtre conteneur peut implémenter ContainerRequestFilter et ContainerResponseFilter dans une seule classe. Il en va de même pour les filtres client. ClientRequestFilter et ClientResponseFilter peuvent tous deux être implémentés dans une seule implémentation de filtre.

Mais vous ne pouvez pas mélanger autant que je sache. Au lieu de cela, vous pouvez avoir deux filtres distincts qui communiquent l’un avec l’autre, par exemple en utilisant le modèle ThreadLocal :

 // Container filter that stores the request context in a ThreadLocal variable public class MyContainerRequestFilter implements ContainerRequestFilter, ContainerResponseFilter { public static final ThreadLocal requestContextHolder; @Override public void filter(ContainerRequestContext requestContext) throws IOException { requestContextHolder.set(requestContext); } @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { // clean up after request requestContextHolder.remove(); } } // Client request filter that uses the info from MyContainerRequestFilter public class MyClientRequestFilter implements ClientRequestFilter { @Override public void filter(ClientRequestContext requestContext) throws IOException { ContainerRequestContext containerRequestContext = MyContainerRequestFilter.requestContextHolder.get(); if (containerRequestContext != null) { // TODO: use info from containerRequestContext to modify client request } } }