2012-06-07 14 views
20

Al escribir un servicio web RESTful, estoy teniendo problemas si habilito cualquier tipo de almacenamiento en caché en mi cliente (actualmente un cliente grueso .NET). De forma predeterminada, Jersey no está enviando ningún tipo de encabezado de control de caché, por lo que el cliente almacena en caché la mayoría de las páginas automáticamente (lo que parece ser un comportamiento válido).Jersey: Control de caché predeterminado en no-caché

Me gustaría que Jersey envíe de forma predeterminada un control de caché de "no-caché", y luego en determinadas respuestas anula el control de caché.

¿Hay alguna manera de hacer esto con Jersey?

Descubrí que RESTeasy tiene la capacidad de usar la anotación @NoCache para especificar la configuración de toda la clase, pero no he encontrado nada similar con Jersey.

Respuesta

23

Esto es fácil con Jersey usando ResourceFilterFactory: puede crear cualquier anotación personalizada que adjunte a sus métodos para establecer la configuración de control de caché. Se llama a ResourceFilterFactories para cada método de recurso descubierto cuando la aplicación se inicializa; en ResourceFilterFactory puede verificar si el método tiene su anotación @CacheControlHeader (o lo que quiera llamar); si no, simplemente devuelva el filtro de respuesta que agrega "no-cache" "directiva a la respuesta, de lo contrario, debe utilizar la configuración de la anotación. Aquí está un ejemplo de cómo hacerlo:

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; 
     } 
    } 
} 

La anotación puede tener este aspecto:

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

El ResourceFilterFactory puede ser registrado en la aplicación agregando el siguiente parámetro init para la definición de Jersey servlet en web.xml:

<init-param> 
    <param-name>com.sun.jersey.spi.container.ResourceFilters</param-name> 
    <param-value>package.name.CacheFilterFactory</param-value> 
</init-param> 
+0

Gracias por resumir esto tan bien. Lo había hecho solo, pero esto resume perfectamente el "Camino Correcto" para hacer esto. – Pete

+0

¿Hay alguna manera de establecer el control de caché en no-caché para todos los métodos sin tener que agregar la anotación para cada uno? –

+0

[Control de caché mediante anotaciones con Jersey] (http://alex.nederlof.com/blog/2013/07/28/caching-using-annotations-with-jersey/) es mucho más limpio y completo –

13

Basado en la solución de @ martin-matula Creé dos anotaciones de caché. Una @NoCache para no almacenar en caché y una @CacheMaxAge para el almacenamiento en caché específico. El CacheMaxAge toma dos argumentos por lo que no tiene que calcular los segundos sí mismo:

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

la ResourceFilter ahora con este método de crear que por defecto no interfiera (por lo demás mecanismos de caché siguen trabajando):

@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)); 
} 

Puede ver la solución completa in my blogpost.

Gracias por la solución Martin!

6

La solución de @martin-matula no funciona con JAX-RS 2.0/Jersey 2.x ya que se han eliminado ResourceFilterFactory y ResourceFilter. La solución se puede adaptar a JAX-RS 2.0 de la siguiente manera.

Anotación:

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

DynamicFeature:

@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 tiene que estar registrado en Jersey. Lo estoy haciendo a través de Dropwizard - usando environment.jersey(). Register() - pero en sistemas independientes, entiendo que esto se puede hacer, por ejemplo, dejando que Jersey escanee tus clases para anotaciones @Provider definiendo lo siguiente en tu 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> 

Ver this post para obtener más información sobre el registro de componentes.

0

Encontré una anotación que puede desactivar el almacenamiento en caché. Puede utilizar siguiente anotación para su API:

@CacheControl(noCache = true) 

Ref: Jersey Annotation for cache control

+0

Estás enlazando a una clase org.springframework.http.CacheControl from Spring que tampoco es una anotación. Probablemente quisiste decir javax.ws.rs.core.CacheControl que tampoco es una anotación. – FelixJongleur42

Cuestiones relacionadas