2015-10-21 11 views
11

Sto cercando di passare dal passaggio da Apache CXF RS a JAX RS a Spring MVC REST e vedere alcuni problemi con il modo in cui Spring MVC REST sta gestendo attualmente gli ETags. Forse non sto capendo bene o c'è un modo migliore per realizzare ciò che viene attualmente fatto con JAX RS?Gestione ETag in Spring MVC REST

Utilizzando Apache CXF RS, le condizioni per l'ultimo timestamp modificato e l'ETag vengono valutate all'interno del servizio REST (la valutazione delle condizioni è in realtà piuttosto complicata, vedere le sezioni 14.24 e 14.26 di RFC 2616, quindi sono contento che ciò sia fatto per me). Il codice simile a questa:

@GET 
@Path("...") 
@Produces(MediaType.APPLICATION_JSON) 
public Response findBy...(..., @Context Request request) { 
     ... result = ...fetch-result-or-parts-of-it...; 
     final EntityTag eTag = new EntityTag(computeETagValue(result), true); 
     ResponseBuilder builder = request.evaluatePreconditions(lastModified, eTag); 
     if (builder == null) { 
       // a new response is required, because the ETag or time stamp do not match 
       // ...potentially fetch full result object now, then: 
       builder = Response.ok(result); 
     } else { 
       // a new response is not needed, send "not modified" status without a body 
     } 
     final CacheControl cacheControl = new CacheControl(); 
     cacheControl.setPrivate(true); // store in private browser cache of user only 
     cacheControl.setMaxAge(15); // may stay unchecked in private browser cache for 15s, afterwards revalidation is required 
     cacheControl.setNoTransform(true); // proxies must not transform the response 
     return builder 
       .cacheControl(cacheControl) 
       .lastModified(lastModified) 
       .tag(eTag) 
       .build(); 
} 

Il mio tentativo della stessa cosa con Spring MVC REST sembra qualcosa di simile:

@RequestMapping(value="...", produces=MediaType.APPLICATION_JSON_VALUE) 
public ResponseEntity<...> findByGpnPrefixCacheable(...) { 
     ... result = ...fetch-result...; 
//  ... result = ...fetch-result-or-parts-of-it...; - can't fetch parts, must obtain full result, see below 
     final String eTag = "W/\""+computeETagValue(result)+"\""; // need to manually construct, as opposed to convenient JAX RS above 
     return ResponseEntity 
       .ok() // always say 'ok' (200)? 
       .cacheControl(
        CacheControl 
          .cachePrivate() 
          .maxAge(15, TimeUnit.SECONDS) 
          .noTransform() 
        ) 
       .eTag(eTag) 
       .body(result); // ETag comparison takes place outside controller(?), but data for a full response has already been built - that is wasteful! 
} 

sono d'accordo con la necessità di costruire tutti i dati per una risposta completa in anticipo , anche se non richiesto. Ciò rende l'ETag come usato in Spring MVC REST molto meno prezioso di quanto potrebbe essere. L'idea di un ETag debole per la mia comprensione è che potrebbe essere "economico" costruire il suo valore e confrontarlo. Nel caso felice questo impedisce il carico sul server. Nel caso in cui la risorsa sia stata modificata, la risposta completa deve essere costruita, ovviamente.

Mi sembra che per progettazione Spring MVC REST richieda attualmente la compilazione dei dati completi di risposta, non importa se un corpo per la risposta è in definitiva necessario o meno.

Così, in sintesi due domande per Spring MVC RIPOSO:

  1. Esiste un equivalente a evaluatePreconditions()?
  2. Esiste un modo migliore per costruire stringhe ETag?

Grazie per i vostri pensieri!

+0

Buona domanda. Suppongo che se Spring non lo fornisce, puoi estendere 'ResponseEntity' per eseguire un callback per costruire l'intero corpo. – Augusto

+0

Sembra che sia necessario precaricare 'result' in entrambi i casi ... Ma capisco, nel primo caso, è necessario solo qualcosa di economico (ad esempio un numero di versione) per calcolare ETag. La risposta migliore credo sia quella di fare il body building pigro. – ZhongYu

+0

in ultima analisi, in alcune applicazioni, per prestazioni ottimali, l'applicazione deve verificare le condizioni da sola. Questo non è * così * difficile. [Questo] (https://github.com/zhong-j-yu/bayou/blob/0.9/src/bayou/http/ImplRespMod.java#L516) è la mia implementazione della logica, basata sulle discussioni delle ultime specifiche HTTP . – ZhongYu

risposta

7
  1. , v'è un metodo equivalente.

Si può usare in questo modo:

@RequestMapping 
public ResponseEntity<...> findByGpnPrefixCacheable(WebRequest request) { 

    // 1. application-specific calculations with full/partial data 
    long lastModified = ...; 
    String etag = ...; 

    if (request.checkNotModified(lastModified, etag)) { 
     // 2. shortcut exit - no further processing necessary 
     // it will also convert the response to an 304 Not Modified 
     // with an empty body 
     return null; 
    } 

    // 3. or otherwise further request processing, actually preparing content 
    return ...; 
} 

noti che ci sono diverse versioni del metodo checkNotModified, con lastModified, ETag, o entrambi.

È possibile trovare la documentazione qui: Support for ETag and Last-Modified response headers.


  1. , ma è necessario cambiare alcune cose prima.

È possibile modificare il modo in cui si calcola l'ETag in modo da non dover recuperare il risultato completo. Ad esempio, se questo risultato di recupero è una query di database, è possibile aggiungere un campo di versione all'entità e annotarlo con @Version in modo che venga incrementato ogni volta che viene modificato e quindi utilizzare tale valore per ETag. .

Cosa compie?Dal momento che è possibile configurare il recupero per essere pigro, non è necessario recuperare tutti i campi delle entità e si evita anche di doverlo hash per creare l'ETag. Basta usare la versione come stringa ETag.

Ecco una domanda su Using @Version in Spring Data project.