web-dev-qa-db-fra.com

Jersey: Contrôle du cache par défaut sur no-cache

Lors de l'écriture d'un service Web RESTful, je rencontre des problèmes si j'active tout type de mise en cache sur mon client (actuellement client lourd .NET). Par défaut, Jersey n’envoie aucune sorte d’en-tête de contrôle du cache. Le client met donc automatiquement en cache la plupart des pages (ce qui semble être un comportement valide).

J'aimerais que Jersey envoie par défaut un contrôle de cache "no-cache", et que les réponses remplacent notamment le contrôle de cache.

Est-il possible de faire cela avec Jersey?

J'ai constaté que RESTeasy pouvait utiliser l'annotation @NoCache pour spécifier le paramètre de la classe entière, mais je n'ai rien trouvé de similaire avec Jersey.

20
Pete

Cela est facile avec Jersey en utilisant un ResourceFilterFactory - vous pouvez créer n'importe quelle annotation personnalisée que vous attachez à vos méthodes pour définir les paramètres de contrôle du cache. Lors de l’initialisation de l’application, ResourceFilterFactories est appelée pour chaque méthode de ressource découverte. Dans votre ResourceFilterFactory, vous pouvez vérifier si la méthode a votre annotation @CacheControlHeader (ou ce que vous voulez appeler), sinon renvoyez simplement un filtre de réponse qui ajoute "no-cache "directive à la réponse, sinon il devrait utiliser les paramètres de l'annotation. Voici un exemple de comment faire cela:

public class CacheFilterFactory implements ResourceFilterFactory {
    private static final List<ResourceFilter> NO_CACHE_FILTER = Collections.<ResourceFilter>singletonList(new CacheResponseFilter("no-cache"));

    @Override
    public List<ResourceFilter> create(AbstractMethod am) {
        CacheControlHeader cch = am.getAnnotation(CacheControlHeader.class);
        if (cch == null) {
            return NO_CACHE_FILTER;
        } else {
            return Collections.<ResourceFilter>singletonList(new CacheResponseFilter(cch.value()));
        }
    }

    private static class CacheResponseFilter implements ResourceFilter, ContainerResponseFilter {
        private final String headerValue;

        CacheResponseFilter(String headerValue) {
            this.headerValue = headerValue;
        }

        @Override
        public ContainerRequestFilter getRequestFilter() {
            return null;
        }

        @Override
        public ContainerResponseFilter getResponseFilter() {
            return this;
        }

        @Override
        public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
            // attache Cache Control header to each response based on the annotation value
            response.getHttpHeaders().putSingle(HttpHeaders.CACHE_CONTROL, headerValue);
            return response;
        }
    }
}

L'annotation peut ressembler à ceci:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheControlHeader {
    String value();
}

ResourceFilterFactory peut être enregistré dans votre application en ajoutant le paramètre init suivant à la définition du servlet Jersey dans le fichier web.xml:

<init-param>
    <param-name>com.Sun.jersey.spi.container.ResourceFilters</param-name>
    <param-value>package.name.CacheFilterFactory</param-value>
</init-param>
23
Martin Matula

Basé sur la solution de @ martin-matula, j'ai créé deux annotations dans le cache. Un @NoCache pour ne pas mettre en cache du tout et un @CacheMaxAge pour une mise en cache spécifique. La CacheMaxAge prend deux arguments, vous n'avez donc pas à calculer les secondes vous-même:

@GET
@CacheMaxAge(time = 10, unit = TimeUnit.MINUTES)
@Path("/awesome")
public String returnSomethingAwesome() {
    ...
}

ResourceFilter a maintenant cette méthode de création qui, par défaut, n’interfère pas (les autres mécanismes de mise en cache continuent de fonctionner):

@Override
public List<ResourceFilter> create(AbstractMethod am) {
    if (am.isAnnotationPresent(CacheMaxAge.class)) {
        CacheMaxAge maxAge = am.getAnnotation(CacheMaxAge.class);
        return newCacheFilter("max-age: " + maxAge.unit().toSeconds(maxAge.time()));
    } else if (am.isAnnotationPresent(NoCache.class)) {
        return newCacheFilter("no-cache");
    } else {
        return Collections.emptyList();
    }
}

private List<ResourceFilter> newCacheFilter(String content) {
    return Collections
            .<ResourceFilter> singletonList(new CacheResponseFilter(content));
}

Vous pouvez voir la solution complète dans mon article de blog

Merci pour la solution Martin!

14
Alex

La solution de @ martin-matula ne fonctionne pas avec JAX-RS 2.0/Jersey 2.x car ResourceFilterFactory et ResourceFilter ont été supprimés. La solution peut être adaptée à JAX-RS 2.0 comme suit.

Annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheControlHeader {
  String value();
}

Fonction dynamique:

@Provider
public class CacheFilterFactory implements DynamicFeature {

  private static final CacheResponseFilter NO_CACHE_FILTER = 
          new CacheResponseFilter("no-cache");

  @Override
  public void configure(ResourceInfo resourceInfo, 
                        FeatureContext featureContext) {

    CacheControlHeader cch = resourceInfo.getResourceMethod()
            .getAnnotation(CacheControlHeader.class);
    if (cch == null) {
      featureContext.register(NO_CACHE_FILTER);
    } else {
      featureContext.register(new CacheResponseFilter(cch.value()));
    }
  }

  private static class CacheResponseFilter implements ContainerResponseFilter {
    private final String headerValue;

    CacheResponseFilter(String headerValue) {
      this.headerValue = headerValue;
    }

    @Override
    public void filter(ContainerRequestContext containerRequestContext,
                       ContainerResponseContext containerResponseContext) {
      // attache Cache Control header to each response
      // based on the annotation value                     
      containerResponseContext
              .getHeaders()
              .putSingle(HttpHeaders.CACHE_CONTROL, headerValue);
    }

  }
}

CacheFilterFactory doit être enregistré auprès de Jersey. Je le fais via Dropwizard - en utilisant environment.jersey (). Register () - mais je comprends que cela peut être fait, par exemple, en laissant Jersey analyser vos classes pour les annotations @Provider en définissant les éléments suivants dans votre web.xml :

<servlet>
    <servlet-name>my.package.MyApplication</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>

    <!-- Register resources and providers under my.package. -->
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>my.package</param-value>
    </init-param>
</servlet>

Voir cet article pour plus d'informations sur l'enregistrement des composants.

7
JanneK

Je pense que vous pouvez utiliser le

isNoCache(true)

qui va arrêter la mise en cache dans le navigateur.

Voir: 

http://jersey.Java.net/nonav/apidocs/1.12/jersey/javax/ws/rs/core/CacheControl.html#isNoCache%28%29

J'espère que cela t'aides.

2
Stephen

J'ai trouvé une annotation qui peut désactiver la mise en cache. Vous pouvez utiliser les annotations suivantes pour votre API:

@CacheControl(noCache = true)

Ref: Jersey Annotation pour le contrôle du cache

0
Sahil Bhavsar