Cache HTTP Client avec CXF
Dans nos applications moderne, le cache est une fonctionnalité technique majeur qui permet de limiter les accès redondant à la même ressources (base de données, fichier, service distant HTTP, …).
On peut implémenter du cache à plusieurs niveau, et généralement nos applications en utilisent sans le savoir (cache OS, cache BDD, cache applicatif de type EhCache, …) et nos browser le font aussi massivement grâce aux directives de cache de la spécification HTTP.
Si vous avait des webservices REST développé avec Apache CXF, vous pouvez vous aussi tirer parti simplement des directives de caches HTTP pour que les clients de vos services puissent mettre en cache les requêtes à vos services et en limiter la charge.
Bien qu’il existe plusieurs type de mise en cache via directives HTTP, je vais uniquement m’attarder ici sur la directive HTTP Cache-Control : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
CXF n’implémentant par les directives de cache HTTP et JaxRS ne contenant pas d’annotations permettant de mettre des headers arbitraires sur une des opérations de votre web service REST, je vais vous proposer ici une façon simple et élégante de faire.
Pour commencer, création d’une annotation @CacheControl qui nous permettera de spécifier la directive de cache que nous voudrions envoyer au client
@Target(ElementType.METHOD ) @Retention(RetentionPolicy.RUNTIME) public @interface CacheControl { String value() default "no-cache"; }
Cette annotation sera ensuite utilisée pour configurer une directive de cache HTTP sur votre service web CXF JaxRS
@CacheControl("max-age=600") @GET @Path("/person") public Person getPerson(String name) { return personService.getPerson(name); }
Pour que cette annotation soit transformée en header de cache par CXF, il faut implémenter un interceptor CXF qui se chargera de récupérer les informations de l’annotations et d’ajouter le header nécessaire.
public class CacheInterceptor extends AbstractOutDatabindingInterceptor{ public CacheInterceptor() { super(Phase.MARSHAL); } @Override public void handleMessage(Message outMessage) throws Fault { //search for a CacheControl annotation on the operation OperationResourceInfo resourceInfo = outMessage.getExchange().get(OperationResourceInfo.class); CacheControl cacheControl = null; for (Annotation annot : resourceInfo.getOutAnnotations()) { if(annot instanceof CacheControl) { cacheControl = (CacheControl) annot; break; } } //fast path for no cache control if(cacheControl == null) { return; } //search for existing headers or create new ones Map<String, List<String>> headers = (Map<String, List<String>>) outMessage.get(Message.PROTOCOL_HEADERS); if (headers == null) { headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); outMessage.put(Message.PROTOCOL_HEADERS, headers); } //add Cache-Control header headers.put("Cache-Control", Collections.singletonList(cacheControl.value())); } }
Pour terminer, il faut ajouter cet intercepteur à votre server CXF, cela dépend de la manière dont vous définissez votre serveur CXF (standalone, spring, …), plus d’info sur les intercepteurs ici : http://cxf.apache.org/docs/interceptors.html
Par exemple, si vous utilisez Spring, vous pouvez ajouter les lignes suivantes à votre fichier cxf-servlet.xml :
<jaxrs:server id="v1Server" address="/v1"> <jaxrs:serviceBeans > <ref bean="personRestWS"/> </jaxrs:serviceBeans> <!-- other stuff goes here ... --> <jaxrs:outInterceptors> <ref bean="cacheControlInterceptor" /> </jaxrs:outInterceptors> </jaxrs:server> <bean id="cacheControlInterceptor" class="fr.loicmathieur.rest.cache.CacheInterceptor" />