All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cz.jirutka.spring.http.client.cache.DefaultCachingPolicy Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 Jakub Jirutka .
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cz.jirutka.spring.http.client.cache;

import cz.jirutka.spring.http.client.cache.internal.CacheControl;
import net.jcip.annotations.Immutable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpResponse;

import java.io.IOException;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;

import static cz.jirutka.spring.http.client.cache.internal.CacheControl.parseCacheControl;
import static java.util.Arrays.asList;

/**
 * Policy that determines if a request can be served from cache or a response
 * can be cached.
 *
 * 

This implementation currently supports HTTP/1.1 Cache-Control * header only, no Expires etc.

*/ @Immutable public class DefaultCachingPolicy implements CachingPolicy { private static final Logger log = LoggerFactory.getLogger(DefaultCachingPolicy.class); private static final Set CACHEABLE_METHODS = EnumSet.of(HttpMethod.GET); /** * @see HTTP/1.1 section 13.4 */ private static final Set CACHEABLE_STATUSES = new HashSet<>(asList(200, 203, 300, 301, 410)); private static final Set UNCACHEABLE_STATUSES = new HashSet<>(asList(206, 303)); private final long maxBodySizeBytes; private final boolean sharedCache; /** * Creates a new instance of {@code DefaultCachingPolicy} without the * size limit. * * @param sharedCache Whether to behave as a shared cache (true) or a * non-shared/private cache (false). * @see #DefaultCachingPolicy(boolean, long) */ public DefaultCachingPolicy(boolean sharedCache) { this(sharedCache, Long.MAX_VALUE); } /** * Creates a new instance of {@code DefaultCachingPolicy} with defined * size limit of responses that should be stored in the cache. * *

A private cache will not, for example, cache responses to requests * with Authorization headers or responses marked with Cache-Control: * private. If, however, the cache is only going to be used by one * logical "user" (behaving similarly to a browser cache), then you will * want to turn off the shared cache setting.

* * @param maxBodySizeBytes The maximum content length. * @param sharedCache Whether to behave as a shared cache (true) or a * non-shared/private cache (false). */ public DefaultCachingPolicy(boolean sharedCache, long maxBodySizeBytes) { this.sharedCache = sharedCache; this.maxBodySizeBytes = maxBodySizeBytes > 0 ? maxBodySizeBytes : Long.MAX_VALUE; } public boolean isResponseCacheable(HttpRequest request, ClientHttpResponse response) { HttpHeaders reqHeaders = request.getHeaders(); HttpHeaders respHeaders = response.getHeaders(); if (!isCacheableMethod(request.getMethod())) { log.trace("Not cacheable: method {}", request.getMethod()); return false; } if (parseCacheControl(reqHeaders).isNoStore()) { log.trace("Not cacheable: request has Cache-Control: no-store"); return false; } if (sharedCache) { if (reqHeaders.getFirst("Authorization") != null) { CacheControl cc = parseCacheControl(respHeaders); if (!cc.isPublic() && cc.getSMaxAge() <= 0) { log.trace("Not cacheable: this cache is shared and request contains " + "Authorization header, but no Cache-Control: public"); return false; } } } return isResponseCacheable(response); } public boolean isServableFromCache(HttpRequest request) { if (!isCacheableMethod(request.getMethod())) { log.trace("Request with method {} is not serveable from cache", request.getMethod()); return false; } CacheControl cc = parseCacheControl(request.getHeaders()); if (cc.isNoStore()) { log.trace("Request with no-store is not serveable from cache"); return false; } if (cc.isNoCache()) { log.trace("Request with no-cache is not serveable from cache"); return false; } return true; } protected boolean isResponseCacheable(ClientHttpResponse response) { boolean cacheable = false; HttpHeaders headers = response.getHeaders(); try { int status = response.getRawStatusCode(); if (isImplicitlyCacheableStatus(status)) { cacheable = true; //MAY be cached } else if (isUncacheableStatus(status)) { log.trace("Response with status code {} is not cacheable", status); return false; } } catch (IOException ex) { throw new IllegalStateException(ex); } if (isExplicitlyNonCacheable(response)) { log.trace("Response with Cache-Control: '{}' is not cacheable", headers.getCacheControl()); return false; } if (headers.getContentLength() > maxBodySizeBytes) { log.debug("Response with Content-Lenght {} > {} is not cacheable", headers.getContentLength(), maxBodySizeBytes); return false; } try { if (response.getHeaders().getDate() < 0) { log.debug("Response without a valid Date header is not cacheable"); return false; } } catch (IllegalArgumentException ex) { return false; } // dunno how to properly handle Vary if (headers.containsKey("Vary")) { log.trace("Response with Vary header is not cacheable"); return false; } return (cacheable || isExplicitlyCacheable(response)); } /** * Whether the given status code can be cached implicitly, i.e. even when * no cache header is specified. * * @param status HTTP status code */ protected boolean isImplicitlyCacheableStatus(int status) { return CACHEABLE_STATUSES.contains(status); } /** * Whether the given status code must not be cached, even when any cache * header is specified. * * @param status HTTP status code */ protected boolean isUncacheableStatus(int status) { return UNCACHEABLE_STATUSES.contains(status) || isUnknownStatus(status); } /** * Whether the given status code is considered to unknown and thus must not * be cached. * * The unknown statuses list is based on Apache HTTP Components. * * @param status HTTP status code */ protected boolean isUnknownStatus(int status) { return ! (status >= 100 && status <= 101 || status >= 200 && status <= 206 || status >= 300 && status <= 307 || status >= 400 && status <= 417 || status >= 500 && status <= 505); } protected boolean isCacheableMethod(HttpMethod method) { return CACHEABLE_METHODS.contains(method); } /** * Whether the given response must not be cached. */ protected boolean isExplicitlyNonCacheable(ClientHttpResponse response) { CacheControl cc = parseCacheControl(response.getHeaders()); return cc.isNoStore() || cc.isNoCache() || (sharedCache && cc.isPrivate()) || cc.getMaxAge(sharedCache) == 0; } /** * Whether the given response should be cached. */ protected boolean isExplicitlyCacheable(ClientHttpResponse response) { CacheControl cc = parseCacheControl(response.getHeaders()); return cc.isPublic() || cc.isMustRevalidate() || cc.isProxyRevalidate() || cc.getMaxAge(sharedCache) > 0; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy