2015-10-21 21 views
11

Czekam na przełączenie z przełączania z Apache CXF RS z JAX RS na Spring MVC REST i zobacz niektóre problemy ze sposobem Spring MVC REST aktualnie obsługuje ETags. Może nie rozumiem dobrze, czy jest lepszy sposób na osiągnięcie tego, co obecnie robi JAX RS?Obsługa ETag w Spring MVC REST

Używanie Apache CXF RS, warunki dla ostatniej modyfikacji znacznika czasu i ETag są oceniane wewnątrz usługi REST (ocena stanu jest rzeczywiście dość skomplikowana, patrz RFC 2616, sekcja 14.24 i 14.26, więc cieszę się, że to jest zrobione dla mnie). Kod wygląda mniej więcej tak:

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

Moja próba samo z wiosennym MVC REST wygląda mniej więcej tak:

@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! 
} 

zgadzam się z konieczności tworzenia wszystkich danych do pełnej odpowiedzi wyprzedzeniem , nawet jeśli nie jest to wymagane. To sprawia, że ​​ETag używany w Spring MVC REST jest o wiele mniej wartościowy niż mógłby być. Pomysł słabego ETag do mojego zrozumienia jest taki, że "tanie" może budować swoją wartość i porównywać ją. W szczęśliwym przypadku zapobiega to obciążeniu serwera. W przypadku zmodyfikowania zasobu należy oczywiście zbudować pełną odpowiedź.

Wydaje mi się, że zgodnie z projektem Spring MVC REST wymaga obecnie utworzenia pełnych danych odpowiedzi, bez względu na to, czy jednostka docelowa jest ostatecznie potrzebna, czy nie.

Więc podsumowując dwa pytania Spring MVC rekreacyjne:

  1. Czy istnieje odpowiednik evaluatePreconditions()?
  2. Czy istnieje lepszy lepszy sposób konstruowania ciągów ETag?

Dzięki za twoje myśli!

+0

Dobre pytanie. Sądzę, że jeśli Spring nie zapewni tego po wyjęciu z pudełka, można rozszerzyć 'ResponseEntity', aby wykonać wywołanie zwrotne, aby zbudować całe ciało. – Augusto

+0

Wygląda na to, że musisz wczytać "wynik" w obu przypadkach ... Ale rozumiem, że w pierwszym przypadku potrzebujesz tylko taniego (np. Numeru wersji) do obliczenia ETag. Najlepszą odpowiedzią, jak sądzę, jest leniwe budowanie ciała. – ZhongYu

+0

ostatecznie, w niektórych aplikacjach, aby uzyskać najlepszą wydajność, aplikacja musi sam przetestować warunki. To nie jest * to * trudne. [To] (https://github.com/zhong-j-yu/bayou/blob/0.9/src/bayou/http/ImplRespMod.java#L516) jest moją implementacją logiki opartej na dyskusjach najnowszej specyfikacji HTTP . – ZhongYu

Odpowiedz

7
  1. Tak, istnieje odpowiednia metoda.

Można go używać tak:

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

Należy zauważyć, że istnieją różne wersje metody checkNotModified, z lastModified, ETAG, albo jedno i drugie.

Dokumentację można znaleźć tutaj: Support for ETag and Last-Modified response headers.


  1. Tak, ale trzeba zmienić pewne rzeczy w pierwszej kolejności.

Możesz zmienić sposób obliczania ETag, aby nie trzeba było pobierać pełnego wyniku.

Jeśli na przykład ten wynik pobierania jest kwerendą dla bazy danych, można dodać pole wersji do encji i opatrzyć je adnotacją za pomocą @Version, aby było ono zwiększane za każdym razem, gdy zostanie zmodyfikowane, a następnie użyć tej wartości w przypadku ETag .

Co to oznacza?ponieważ możesz skonfigurować pobieranie jako leniwy, nie musisz odzyskiwać wszystkich pól swoich jednostek, a także unikasz konieczności mieszania go do budowania ETag. Po prostu użyj wersji jako łańcucha ETag.

Tutaj jest pytanie na temat Using @Version in Spring Data project.