Optimize `#isResponseCacheable` by parsing DATE and EXPIRES headers only once
This commit is contained in:
parent
762028805e
commit
e7ee13701e
|
@ -28,7 +28,6 @@ package org.apache.hc.client5.http.impl.cache;
|
|||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -62,13 +61,6 @@ class ResponseCachingPolicy {
|
|||
*/
|
||||
private static final Duration DEFAULT_FRESHNESS_DURATION = Duration.ofMinutes(5);
|
||||
|
||||
/**
|
||||
* This {@link DateTimeFormatter} is used to format and parse date-time objects in a specific format commonly
|
||||
* used in HTTP protocol messages. The format includes the day of the week, day of the month, month, year, and time
|
||||
* of day, all represented in GMT time. An example of a date-time string in this format is "Tue, 15 Nov 1994 08:12:31 GMT".
|
||||
*/
|
||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.RFC_1123_DATE_TIME;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResponseCachingPolicy.class);
|
||||
|
||||
private final long maxObjectSizeBytes;
|
||||
|
@ -112,12 +104,25 @@ class ResponseCachingPolicy {
|
|||
this.staleIfErrorEnabled = staleIfErrorEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an HttpResponse can be cached.
|
||||
*
|
||||
* @return {@code true} if response is cacheable
|
||||
*/
|
||||
public boolean isResponseCacheable(final ResponseCacheControl cacheControl, final String httpMethod, final HttpResponse response) {
|
||||
boolean isResponseCacheable(final ResponseCacheControl cacheControl, final String httpMethod, final HttpResponse response) {
|
||||
if (response.countHeaders(HttpHeaders.EXPIRES) > 1) {
|
||||
LOG.debug("Multiple Expires headers");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.countHeaders(HttpHeaders.DATE) > 1) {
|
||||
LOG.debug("Multiple Date headers");
|
||||
return false;
|
||||
}
|
||||
|
||||
final Instant responseDate = DateUtils.parseStandardDate(response, HttpHeaders.DATE);
|
||||
final Instant responseExpires = DateUtils.parseStandardDate(response, HttpHeaders.EXPIRES);
|
||||
|
||||
if (expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(cacheControl, responseDate, responseExpires)) {
|
||||
LOG.debug("Expires header less or equal to Date header and no cache control directives");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean cacheable = false;
|
||||
|
||||
if (!Method.GET.isSame(httpMethod) && !Method.HEAD.isSame(httpMethod) && !Method.POST.isSame((httpMethod))) {
|
||||
|
@ -156,16 +161,6 @@ class ResponseCachingPolicy {
|
|||
}
|
||||
}
|
||||
|
||||
if (response.countHeaders(HttpHeaders.EXPIRES) > 1) {
|
||||
LOG.debug("Multiple Expires headers");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.countHeaders(HttpHeaders.DATE) > 1) {
|
||||
LOG.debug("Multiple Date headers");
|
||||
return false;
|
||||
}
|
||||
|
||||
final Iterator<HeaderElement> it = MessageSupport.iterate(response, HttpHeaders.VARY);
|
||||
while (it.hasNext()) {
|
||||
final HeaderElement elem = it.next();
|
||||
|
@ -181,11 +176,11 @@ class ResponseCachingPolicy {
|
|||
return false;
|
||||
}
|
||||
|
||||
final Duration freshnessLifetime = calculateFreshnessLifetime(cacheControl, response);
|
||||
final Duration freshnessLifetime = calculateFreshnessLifetime(cacheControl, responseDate, responseExpires);
|
||||
|
||||
// If the 'immutable' directive is present and the response is still fresh,
|
||||
// then the response is considered cacheable without further validation
|
||||
if (cacheControl.isImmutable() && responseIsStillFresh(response, freshnessLifetime)) {
|
||||
if (cacheControl.isImmutable() && responseIsStillFresh(responseDate, freshnessLifetime)) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Response is immutable and fresh, considered cacheable without further validation");
|
||||
}
|
||||
|
@ -258,7 +253,7 @@ class ResponseCachingPolicy {
|
|||
}
|
||||
|
||||
protected boolean isExplicitlyCacheable(final ResponseCacheControl cacheControl, final HttpResponse response) {
|
||||
if (response.getFirstHeader(HttpHeaders.EXPIRES) != null) {
|
||||
if (response.containsHeader(HttpHeaders.EXPIRES)) {
|
||||
return true;
|
||||
}
|
||||
return cacheControl.getMaxAge() > 0 || cacheControl.getSharedMaxAge()>0 ||
|
||||
|
@ -302,11 +297,6 @@ class ResponseCachingPolicy {
|
|||
}
|
||||
}
|
||||
|
||||
if (expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(cacheControl, response)) {
|
||||
LOG.debug("Expires header less or equal to Date header and no cache control directives");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sharedCache) {
|
||||
if (request.countHeaders(HttpHeaders.AUTHORIZATION) > 0
|
||||
&& !(cacheControl.getSharedMaxAge() > -1 || cacheControl.isMustRevalidate() || cacheControl.isPublic())) {
|
||||
|
@ -319,21 +309,14 @@ class ResponseCachingPolicy {
|
|||
return isResponseCacheable(cacheControl, method, response);
|
||||
}
|
||||
|
||||
private boolean expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(final ResponseCacheControl cacheControl, final HttpResponse response) {
|
||||
private boolean expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(final ResponseCacheControl cacheControl, final Instant responseDate, final Instant expires) {
|
||||
if (!cacheControl.isUndefined()) {
|
||||
return false;
|
||||
}
|
||||
final Header expiresHdr = response.getFirstHeader(HttpHeaders.EXPIRES);
|
||||
final Header dateHdr = response.getFirstHeader(HttpHeaders.DATE);
|
||||
if (expiresHdr == null || dateHdr == null) {
|
||||
if (expires == null || responseDate == null) {
|
||||
return false;
|
||||
}
|
||||
final Instant expires = DateUtils.parseStandardDate(expiresHdr.getValue());
|
||||
final Instant date = DateUtils.parseStandardDate(dateHdr.getValue());
|
||||
if (expires == null || date == null) {
|
||||
return false;
|
||||
}
|
||||
return expires.equals(date) || expires.isBefore(date);
|
||||
return expires.equals(responseDate) || expires.isBefore(responseDate);
|
||||
}
|
||||
|
||||
private boolean from1_0Origin(final HttpResponse response) {
|
||||
|
@ -371,11 +354,8 @@ class ResponseCachingPolicy {
|
|||
* for all use cases. Developers should consult the HTTP caching specifications for more information and consider
|
||||
* implementing additional caching mechanisms as needed.
|
||||
* </p>
|
||||
*
|
||||
* @param response the HTTP response for which to calculate the freshness lifetime
|
||||
* @return the freshness lifetime of the response, in seconds
|
||||
*/
|
||||
private Duration calculateFreshnessLifetime(final ResponseCacheControl cacheControl, final HttpResponse response) {
|
||||
private Duration calculateFreshnessLifetime(final ResponseCacheControl cacheControl, final Instant responseDate, final Instant responseExpires) {
|
||||
|
||||
if (cacheControl.isUndefined()) {
|
||||
// If no cache-control header is present, assume no caching directives and return a default value
|
||||
|
@ -389,21 +369,8 @@ class ResponseCachingPolicy {
|
|||
return Duration.ofSeconds(cacheControl.getMaxAge());
|
||||
}
|
||||
|
||||
// Check if Expires is present and use its value minus the value of the Date header
|
||||
Instant expiresInstant = null;
|
||||
Instant dateInstant = null;
|
||||
final Header expire = response.getFirstHeader(HttpHeaders.EXPIRES);
|
||||
if (expire != null) {
|
||||
final String expiresHeaderValue = expire.getValue();
|
||||
expiresInstant = FORMATTER.parse(expiresHeaderValue, Instant::from);
|
||||
}
|
||||
final Header date = response.getFirstHeader(HttpHeaders.DATE);
|
||||
if (date != null) {
|
||||
final String dateHeaderValue = date.getValue();
|
||||
dateInstant = FORMATTER.parse(dateHeaderValue, Instant::from);
|
||||
}
|
||||
if (expiresInstant != null && dateInstant != null) {
|
||||
return Duration.ofSeconds(expiresInstant.getEpochSecond() - dateInstant.getEpochSecond());
|
||||
if (responseDate != null && responseExpires != null) {
|
||||
return Duration.ofSeconds(responseExpires.getEpochSecond() - responseDate.getEpochSecond());
|
||||
}
|
||||
|
||||
// If none of the above conditions are met, a heuristic freshness lifetime might be applicable
|
||||
|
@ -499,17 +466,16 @@ class ResponseCachingPolicy {
|
|||
* Note: If the Date header is missing or invalid, this method assumes the response is not fresh.
|
||||
* </p>
|
||||
*
|
||||
* @param response The HttpResponse whose freshness is being checked.
|
||||
* @param responseDate The response date.
|
||||
* @param freshnessLifetime The calculated freshness lifetime of the HttpResponse.
|
||||
* @return {@code true} if the response age is less than its freshness lifetime, {@code false} otherwise.
|
||||
*/
|
||||
private boolean responseIsStillFresh(final HttpResponse response, final Duration freshnessLifetime) {
|
||||
final Instant date = DateUtils.parseStandardDate(response, HttpHeaders.DATE);
|
||||
if (date == null) {
|
||||
private boolean responseIsStillFresh(final Instant responseDate, final Duration freshnessLifetime) {
|
||||
if (responseDate == null) {
|
||||
// The Date header is missing or invalid. Assuming the response is not fresh.
|
||||
return false;
|
||||
}
|
||||
final Duration age = Duration.between(date, Instant.now());
|
||||
final Duration age = Duration.between(responseDate, Instant.now());
|
||||
return age.compareTo(freshnessLifetime) < 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.hc.client5.http.auth.StandardAuthScheme;
|
||||
|
@ -217,7 +218,7 @@ public class TestResponseCachingPolicy {
|
|||
public void testNon206WithExplicitExpiresIsCacheable() {
|
||||
final int status = getRandomStatus();
|
||||
response.setCode(status);
|
||||
response.setHeader("Expires", DateUtils.formatStandardDate(Instant.now()));
|
||||
response.setHeader("Expires", DateUtils.formatStandardDate(Instant.now().plus(1, ChronoUnit.HOURS)));
|
||||
Assertions.assertTrue(policy.isResponseCacheable(responseCacheControl, "GET", response));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue