Uploaded image for project: 'Spring Framework'
  1. Spring Framework
  2. SPR-5821

HTTP cache and conditional requests support in RestTemplate

    Details

    • Type: Improvement
    • Status: Open
    • Priority: Major
    • Resolution: Unresolved
    • Affects Version/s: 3.0 M3
    • Fix Version/s: 5.1 Backlog
    • Component/s: Web:Client
    • Labels:
      None
    • Last commented by a User:
      false

      Description

      The main goal is to create a CachingClientHttpRequestInterceptor which provides the following:

      • Cache the received HTTP responses for further use, if those are marked as cacheable
      • Cache those response in a org.springframework.cache.Cache
      • if the Cache contains a valid response but its content is stale, then the Interceptor can issue conditional requests to revalidate the cached content
      • by default, a sane configuration should be provided and the Cache should be backed by a ConcurrentCacheMap

      This could be used like this:

      RestTemplate restTemplate = new RestTemplate();
      restTemplate.setInterceptors(Collections.singletonList(new CachingClientHttpRequestInterceptor()));
       
      // this response is cacheable and has appropriate headers
      ResponseEntity<Book> response = restTemplate.getForEntity("http://example.org/resource", Book.class);
      // it is now cached
      String etag = response.getHeaders().getEtag();
       
      // if the response is still fresh, then no network call should happen and the response should be reused
      Book book = restTemplate.getForObject("http://example.org/resource", Book.class);
      

      1. CachingHttpRequestFactory.java
        9 kB
        Arjen Poutsma
      2. ETagCachingRestTemplate.java
        8 kB
        Arjen Poutsma

        Issue Links

          Activity

          Hide
          marcopotento Marc Weinberger added a comment -

          Thanks Arjen,

          SPR-7308 looks very promising. I'll look into it, once the high level cache API is available.

          Show
          marcopotento Marc Weinberger added a comment - Thanks Arjen, SPR-7308 looks very promising. I'll look into it, once the high level cache API is available.
          Hide
          arjen.poutsma Arjen Poutsma added a comment -

          Postponing till after 3.1.

          Show
          arjen.poutsma Arjen Poutsma added a comment - Postponing till after 3.1.
          Hide
          michelz Michel Zanini added a comment -

          This will be good to be included in Android Rest Template also because ETag support is very useful on a mobile environment.

          Show
          michelz Michel Zanini added a comment - This will be good to be included in Android Rest Template also because ETag support is very useful on a mobile environment.
          Hide
          olivergierke Oliver Gierke added a comment -

          After a recent team discussion we decided to slightly shift the scope of this ticket. Caching is not the right word for what we're trying to achieve here. The core idea is to use the headers sent by the server in response to an original request in subsequent requests to the very same resource. Thus it's rather using HTTP means to optimize the request (using If-None-Match, If-Modified-Since) depending on what the server indicates to understand.

          Show
          olivergierke Oliver Gierke added a comment - After a recent team discussion we decided to slightly shift the scope of this ticket. Caching is not the right word for what we're trying to achieve here. The core idea is to use the headers sent by the server in response to an original request in subsequent requests to the very same resource. Thus it's rather using HTTP means to optimize the request (using If-None-Match , If-Modified-Since ) depending on what the server indicates to understand.
          Hide
          bclozel Brian Clozel added a comment -

          I've given this some thoughts after our discussion and found several problems with that approach.

          Leaky design

          So with that approach, we'd like to remain at the request/response level and let the framework generate conditional requests for us. Something (very imperfect) like this:

          String requestUrl = "http://example.org/users/12"
           
          ResponseEntity<User> cachedResponse = cache.get(requestUrl, User.class);
          if(cachedResponse == null) {
           
            ResponseEntity<User> response = restTemplate.getForEntity(requestUrl, User.class);
            cache.put(requestUrl, response);
          }
          else {
            ConditionalRequest<User> cRequest = cachedResponse.conditional(restTemplate);
            User user;
            if(cRequest.checkNotModified()) { // issue a conditional GET request and returns true if 304
              user = cRequest.getCachedResponse().getBody();
            }
            else {
              user = cRequest.getResponse().getBody();
              cache.put("http://example.org/users/12", cRequest.getResponse());
            }
          }        

          This API can be improved in many ways, but I think that leaving the abstraction at that level means we'd need to:

          • ask the user to cache the full ResponseEntity
          • store the original request in the ResponseEntity
          • create conditional requests from responses
          • expose both the response to the conditional request, and the cached one

          Not simplifying things

          This approach generates conditional requests (with proper Etag or If- headers), but leave to the user the following problems:

          • should you cache or not a response (given its status, headers)?
          • is a response eligible for a conditional request? (does it say "no-cache"?)?
          • is the request eligible for a conditional request? (does it say "max-age=0")?
          • how to manage cache expiration given HTTP headers values?
          • and many other questions, which are far harder to solve than setting those conditional request headers

          As soon as we're trying to tackle one of those issues, we're beginning to implement a client http cache.

          Implementing a client http cache

          Many frameworks/libraries went down that path with more or less complete or elegant implementations. And I'm wondering if there would be interest in this for Spring Framework given that:

          1. even with a great implementation, we'd probably miss some part of the spec or some specific browser/server quirks. It looks like this is the kind of feature that is driven by endless issues and feature requests that can contradict each others.
          2. Spring's HTTP client does not belong to a separate module which make it harder to reuse in other contexts. Such a feature is probably more useful if the http client is somehow standalone
          3. a lot of applications probably implement very simple, ad-hoc clients with @Cacheable annotations for their own use case (like this one

          Jakub Jirutka implemented a RequestInterceptor for this - see his project. Some parts can be improved, like conditional requests support and cache keys generation (currently the cache key is the URL itself and does not take into account HTTP headers such as Vary, or encoding...). But it's still in a good shape IMO.

          I've sketched something based on Jakub's work and I'm trying to find ways to come up with clean interfaces that would allow custom strategies for caching, conditional requests, etc. But I'm still struggling with the 3 points listed above.

          Actually Jakub Jirutka, what do you think about those points?
          Can you share your experience and tell us why you created this project in the first place and how it's being used?

          Show
          bclozel Brian Clozel added a comment - I've given this some thoughts after our discussion and found several problems with that approach. Leaky design So with that approach, we'd like to remain at the request/response level and let the framework generate conditional requests for us. Something (very imperfect) like this: String requestUrl = "http://example.org/users/12"   ResponseEntity<User> cachedResponse = cache.get(requestUrl, User. class ); if (cachedResponse == null ) {   ResponseEntity<User> response = restTemplate.getForEntity(requestUrl, User. class ); cache.put(requestUrl, response); } else { ConditionalRequest<User> cRequest = cachedResponse.conditional(restTemplate); User user; if (cRequest.checkNotModified()) { // issue a conditional GET request and returns true if 304 user = cRequest.getCachedResponse().getBody(); } else { user = cRequest.getResponse().getBody(); cache.put( "http://example.org/users/12" , cRequest.getResponse()); } } This API can be improved in many ways, but I think that leaving the abstraction at that level means we'd need to: ask the user to cache the full ResponseEntity store the original request in the ResponseEntity create conditional requests from responses expose both the response to the conditional request, and the cached one Not simplifying things This approach generates conditional requests (with proper Etag or If- headers), but leave to the user the following problems: should you cache or not a response (given its status, headers)? is a response eligible for a conditional request? (does it say "no-cache"?)? is the request eligible for a conditional request? (does it say "max-age=0")? how to manage cache expiration given HTTP headers values? and many other questions, which are far harder to solve than setting those conditional request headers As soon as we're trying to tackle one of those issues, we're beginning to implement a client http cache. Implementing a client http cache Many frameworks/libraries went down that path with more or less complete or elegant implementations. And I'm wondering if there would be interest in this for Spring Framework given that: even with a great implementation, we'd probably miss some part of the spec or some specific browser/server quirks. It looks like this is the kind of feature that is driven by endless issues and feature requests that can contradict each others. Spring's HTTP client does not belong to a separate module which make it harder to reuse in other contexts. Such a feature is probably more useful if the http client is somehow standalone a lot of applications probably implement very simple, ad-hoc clients with @Cacheable annotations for their own use case (like this one Jakub Jirutka implemented a RequestInterceptor for this - see his project . Some parts can be improved, like conditional requests support and cache keys generation (currently the cache key is the URL itself and does not take into account HTTP headers such as Vary, or encoding...). But it's still in a good shape IMO. I've sketched something based on Jakub's work and I'm trying to find ways to come up with clean interfaces that would allow custom strategies for caching, conditional requests, etc. But I'm still struggling with the 3 points listed above. Actually Jakub Jirutka , what do you think about those points? Can you share your experience and tell us why you created this project in the first place and how it's being used?

            People

            • Assignee:
              bclozel Brian Clozel
              Reporter:
              olivergierke Oliver Gierke
              Last updater:
              Juergen Hoeller
            • Votes:
              9 Vote for this issue
              Watchers:
              13 Start watching this issue

              Dates

              • Created:
                Updated:
                Days since last comment:
                1 year, 51 weeks, 4 days ago