HttpCacheEntry to cache parsed DATE, EXPIRES and LAST_MODIFIED values; avoid parsing DATE header of cache entries and HTTP messages multiple times

This commit is contained in:
Oleg Kalnichevski 2023-10-20 18:49:08 +02:00
parent e7ee13701e
commit 3ff5496ffb
12 changed files with 179 additions and 279 deletions

View File

@ -36,6 +36,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.hc.client5.http.utils.DateUtils;
@ -74,6 +75,9 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
private final HeaderGroup responseHeaders;
private final Resource resource;
private final Set<String> variants;
private final AtomicReference<Instant> dateRef;
private final AtomicReference<Instant> expiresRef;
private final AtomicReference<Instant> lastModifiedRef;
/**
* Internal constructor that makes no validation of the input parameters and makes
@ -100,6 +104,9 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
this.responseHeaders = responseHeaders;
this.resource = resource;
this.variants = variants != null ? Collections.unmodifiableSet(new HashSet<>(variants)) : null;
this.dateRef = new AtomicReference<>();
this.expiresRef = new AtomicReference<>();
this.lastModifiedRef = new AtomicReference<>();
}
/**
@ -169,6 +176,9 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
this.responseHeaders.setHeaders(responseHeaders);
this.resource = resource;
this.variants = variantMap != null ? Collections.unmodifiableSet(new HashSet<>(variantMap.keySet())) : null;
this.dateRef = new AtomicReference<>();
this.expiresRef = new AtomicReference<>();
this.lastModifiedRef = new AtomicReference<>();
}
/**
@ -342,8 +352,41 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
return DateUtils.toDate(getInstant());
}
private static final Instant NON_VALUE = Instant.ofEpochSecond(Instant.MIN.getEpochSecond(), 0);
private Instant getInstant(final AtomicReference<Instant> ref, final String headerName) {
Instant instant = ref.get();
if (instant == null) {
instant = DateUtils.parseStandardDate(this, headerName);
if (instant == null) {
instant = NON_VALUE;
}
if (!ref.compareAndSet(null, instant)) {
instant = ref.get();
}
}
return instant != null && instant != NON_VALUE ? instant : null;
}
/**
* @since 5.2
*/
public Instant getInstant() {
return DateUtils.parseStandardDate(this, HttpHeaders.DATE);
return getInstant(dateRef, HttpHeaders.DATE);
}
/**
* @since 5.4
*/
public Instant getExpires() {
return getInstant(expiresRef, HttpHeaders.EXPIRES);
}
/**
* @since 5.4
*/
public Instant getLastModified() {
return getInstant(lastModifiedRef, HttpHeaders.LAST_MODIFIED);
}
/**
@ -404,6 +447,28 @@ public class HttpCacheEntry implements MessageHeaders, Serializable {
return requestHeaders.headerIterator();
}
/**
* Tests if the given {@link HttpCacheEntry} is newer than the given {@link MessageHeaders}
* by comparing values of their {@literal DATE} header. In case the given entry, or the message,
* or their {@literal DATE} headers are null, this method returns {@code false}.
*
* @since 5.3
*/
public static boolean isNewer(final HttpCacheEntry entry, final MessageHeaders message) {
if (entry == null || message == null) {
return false;
}
final Instant cacheDate = entry.getInstant();
if (cacheDate == null) {
return false;
}
final Instant messageDate = DateUtils.parseStandardDate(message, HttpHeaders.DATE);
if (messageDate == null) {
return false;
}
return cacheDate.compareTo(messageDate) > 0;
}
/**
* Provides a string representation of this instance suitable for
* human consumption.

View File

@ -37,7 +37,6 @@ import java.util.Set;
import java.util.TreeSet;
import org.apache.hc.client5.http.impl.cache.CacheSupport;
import org.apache.hc.client5.http.impl.cache.DateSupport;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
@ -252,7 +251,7 @@ public class HttpCacheEntryFactory {
Args.check(response.getCode() == HttpStatus.SC_NOT_MODIFIED,
"Response must have 304 status code");
Args.notNull(entry, "Cache entry");
if (DateSupport.isAfter(entry, response, HttpHeaders.DATE)) {
if (HttpCacheEntry.isNewer(entry, response)) {
return entry;
}
final HeaderGroup mergedHeaders = mergeHeaders(entry, response);

View File

@ -46,6 +46,7 @@ import org.apache.hc.client5.http.async.methods.SimpleBody;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.cache.CacheResponseStatus;
import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.ResourceFactory;
import org.apache.hc.client5.http.cache.ResourceIOException;
import org.apache.hc.client5.http.impl.ExecSupport;
@ -531,7 +532,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
@Override
public void completed(final CacheMatch result) {
final CacheHit hit = result != null ? result.hit : null;
if (DateSupport.isAfter(hit != null ? hit.entry : null, backendResponse, HttpHeaders.DATE)) {
if (HttpCacheEntry.isNewer(hit != null ? hit.entry : null, backendResponse)) {
LOG.debug("Backend already contains fresher cache entry");
try {
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);

View File

@ -520,7 +520,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
final Header newETag = response.getFirstHeader(HttpHeaders.ETAG);
if (existingETag != null && newETag != null &&
!Objects.equals(existingETag.getValue(), newETag.getValue()) &&
!DateSupport.isBefore(response, root, HttpHeaders.DATE)) {
!HttpCacheEntry.isNewer(root, response)) {
evictAll(root, rootKey);
}
}

View File

@ -329,7 +329,7 @@ class BasicHttpCache implements HttpCache {
final Header newETag = response.getFirstHeader(HttpHeaders.ETAG);
if (existingETag != null && newETag != null &&
!Objects.equals(existingETag.getValue(), newETag.getValue()) &&
!DateSupport.isBefore(response, root, HttpHeaders.DATE)) {
!HttpCacheEntry.isNewer(root, response)) {
evictAll(root, rootKey);
}
}

View File

@ -31,7 +31,6 @@ import java.time.Instant;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.util.TimeValue;
@ -105,7 +104,7 @@ class CacheValidityPolicy {
// If the Expires response header field is present, use its value minus the value of the Date response header field
final Instant dateValue = entry.getInstant();
if (dateValue != null) {
final Instant expiry = DateUtils.parseStandardDate(entry, HttpHeaders.EXPIRES);
final Instant expiry = entry.getExpires();
if (expiry != null) {
final Duration diff = Duration.between(dateValue, expiry);
if (diff.isNegative()) {
@ -148,7 +147,7 @@ class CacheValidityPolicy {
public TimeValue getHeuristicFreshnessLifetime(final HttpCacheEntry entry) {
final Instant dateValue = entry.getInstant();
final Instant lastModifiedValue = DateUtils.parseStandardDate(entry, HttpHeaders.LAST_MODIFIED);
final Instant lastModifiedValue = entry.getLastModified();
if (dateValue != null && lastModifiedValue != null) {
final Duration diff = Duration.between(lastModifiedValue, dateValue);
@ -163,8 +162,7 @@ class CacheValidityPolicy {
}
public boolean isRevalidatable(final HttpCacheEntry entry) {
return entry.getFirstHeader(HttpHeaders.ETAG) != null
|| entry.getFirstHeader(HttpHeaders.LAST_MODIFIED) != null;
return entry.containsHeader(HttpHeaders.ETAG) || entry.containsHeader(HttpHeaders.LAST_MODIFIED);
}
public boolean mayReturnStaleWhileRevalidating(final ResponseCacheControl responseCacheControl,

View File

@ -286,7 +286,7 @@ class CachedResponseSuitabilityChecker {
* @return boolean Does the last modified header match
*/
private boolean lastModifiedValidatorMatches(final HttpRequest request, final HttpCacheEntry entry, final Instant now) {
final Instant lastModified = DateUtils.parseStandardDate(entry, HttpHeaders.LAST_MODIFIED);
final Instant lastModified = entry.getLastModified();
if (lastModified == null) {
return false;
}

View File

@ -39,6 +39,7 @@ import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.async.methods.SimpleBody;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.cache.CacheResponseStatus;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheStorage;
import org.apache.hc.client5.http.cache.ResourceIOException;
import org.apache.hc.client5.http.classic.ExecChain;
@ -428,7 +429,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
if (cacheConfig.isFreshnessCheckEnabled()) {
final CacheMatch result = responseCache.match(target ,request);
hit = result != null ? result.hit : null;
if (DateSupport.isAfter(hit != null ? hit.entry : null, backendResponse, HttpHeaders.DATE)) {
if (HttpCacheEntry.isNewer(hit != null ? hit.entry : null, backendResponse)) {
LOG.debug("Backend already contains fresher cache entry");
} else {
hit = responseCache.store(target, request, backendResponse, buf, requestSent, responseReceived);

View File

@ -250,7 +250,7 @@ public class CachingExecBase {
// Date header, so we can't tell if they are out of order
// according to the origin clock; thus we can skip the
// unconditional retry.
return DateSupport.isBefore(backendResponse, cacheEntry, HttpHeaders.DATE);
return HttpCacheEntry.isNewer(cacheEntry, backendResponse);
}
boolean shouldSendNotModifiedResponse(final HttpRequest request, final HttpCacheEntry responseEntry) {

View File

@ -1,116 +0,0 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import java.time.Instant;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.MessageHeaders;
/**
* HTTP cache date support utilities.
*
* @since 5.2
*/
@Internal
public final class DateSupport {
/**
* Tests if the first message is after (newer) than second one
* using the given message header for comparison.
*
* @param message1 the first message
* @param message2 the second message
* @param headerName header name
*
* @return {@code true} if both messages contain a header with the given name
* and the value of the header from the first message is newer that of
* the second message.
*
* @since 5.0
*/
public static boolean isAfter(
final MessageHeaders message1,
final MessageHeaders message2,
final String headerName) {
if (message1 != null && message2 != null) {
final Header dateHeader1 = message1.getFirstHeader(headerName);
if (dateHeader1 != null) {
final Header dateHeader2 = message2.getFirstHeader(headerName);
if (dateHeader2 != null) {
final Instant date1 = DateUtils.parseStandardDate(dateHeader1.getValue());
if (date1 != null) {
final Instant date2 = DateUtils.parseStandardDate(dateHeader2.getValue());
if (date2 != null) {
return date1.isAfter(date2);
}
}
}
}
}
return false;
}
/**
* Tests if the first message is before (older) than the second one
* using the given message header for comparison.
*
* @param message1 the first message
* @param message2 the second message
* @param headerName header name
*
* @return {@code true} if both messages contain a header with the given name
* and the value of the header from the first message is older that of
* the second message.
*
* @since 5.0
*/
public static boolean isBefore(
final MessageHeaders message1,
final MessageHeaders message2,
final String headerName) {
if (message1 != null && message2 != null) {
final Header dateHeader1 = message1.getFirstHeader(headerName);
if (dateHeader1 != null) {
final Header dateHeader2 = message2.getFirstHeader(headerName);
if (dateHeader2 != null) {
final Instant date1 = DateUtils.parseStandardDate(dateHeader1.getValue());
if (date1 != null) {
final Instant date2 = DateUtils.parseStandardDate(dateHeader2.getValue());
if (date2 != null) {
return date1.isBefore(date2);
}
}
}
}
}
return false;
}
}

View File

@ -36,18 +36,22 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;
import java.time.temporal.ChronoField;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.apache.hc.client5.http.impl.cache.HttpTestUtils;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.HeaderGroup;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -84,79 +88,82 @@ public class TestHttpCacheEntry {
"GET", "/", HttpTestUtils.headers(),
status, HttpTestUtils.headers(headers), resource, variants);
}
private HttpCacheEntry makeEntry(final Instant requestDate,
final Instant responseDate,
final int status,
final Header... headers) {
return new HttpCacheEntry(requestDate, responseDate,
"GET", "/", HttpTestUtils.headers(),
status, HttpTestUtils.headers(headers), mockResource, null);
}
@Test
public void testGetHeadersReturnsCorrectHeaders() {
final Header[] headers = { new BasicHeader("foo", "fooValue"),
entry = makeEntry(
new BasicHeader("bar", "barValue1"),
new BasicHeader("bar", "barValue2")
};
entry = makeEntry(headers);
new BasicHeader("bar", "barValue2"));
assertEquals(2, entry.getHeaders("bar").length);
}
@Test
public void testGetFirstHeaderReturnsCorrectHeader() {
final Header[] headers = { new BasicHeader("foo", "fooValue"),
entry = makeEntry(
new BasicHeader("bar", "barValue1"),
new BasicHeader("bar", "barValue2")
};
entry = makeEntry(headers);
new BasicHeader("bar", "barValue2"));
assertEquals("barValue1", entry.getFirstHeader("bar").getValue());
}
@Test
public void testGetHeadersReturnsEmptyArrayIfNoneMatch() {
final Header[] headers = { new BasicHeader("foo", "fooValue"),
entry = makeEntry(
new BasicHeader("foo", "fooValue"),
new BasicHeader("bar", "barValue1"),
new BasicHeader("bar", "barValue2")
};
entry = makeEntry(headers);
new BasicHeader("bar", "barValue2"));
assertEquals(0, entry.getHeaders("baz").length);
}
@Test
public void testGetFirstHeaderReturnsNullIfNoneMatch() {
final Header[] headers = { new BasicHeader("foo", "fooValue"),
entry = makeEntry(
new BasicHeader("foo", "fooValue"),
new BasicHeader("bar", "barValue1"),
new BasicHeader("bar", "barValue2")
};
entry = makeEntry(headers);
new BasicHeader("bar", "barValue2"));
assertNull(entry.getFirstHeader("quux"));
}
@Test
public void testGetMethodReturnsCorrectRequestMethod() {
final Header[] headers = { new BasicHeader("foo", "fooValue"),
entry = makeEntry(
new BasicHeader("foo", "fooValue"),
new BasicHeader("bar", "barValue1"),
new BasicHeader("bar", "barValue2")
};
entry = makeEntry(headers);
new BasicHeader("bar", "barValue2"));
assertEquals(Method.GET.name(), entry.getRequestMethod());
}
@Test
public void statusCodeComesFromOriginalStatusLine() {
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK, new Header[]{}, mockResource, null);
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK);
assertEquals(HttpStatus.SC_OK, entry.getStatus());
}
@Test
public void canGetOriginalRequestDate() {
final Instant requestDate = Instant.now();
entry = makeEntry(requestDate, Instant.now(), HttpStatus.SC_OK, new Header[]{}, mockResource, null);
entry = makeEntry(requestDate, Instant.now(), HttpStatus.SC_OK);
assertEquals(requestDate, entry.getRequestInstant());
}
@Test
public void canGetOriginalResponseDate() {
final Instant responseDate = Instant.now();
entry = makeEntry(Instant.now(), responseDate, HttpStatus.SC_OK, new Header[]{}, mockResource, null);
entry = makeEntry(Instant.now(), responseDate, HttpStatus.SC_OK);
assertEquals(responseDate, entry.getResponseInstant());
}
@Test
public void canGetOriginalResource() {
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK, new Header[]{}, mockResource, null);
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK);
assertSame(mockResource, entry.getResource());
}
@ -174,18 +181,6 @@ public class TestHttpCacheEntry {
}
}
@Test
public void canConstructWithoutVariants() {
makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK, new Header[]{}, mockResource, null);
}
@Test
public void canProvideVariantMap() {
makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
new Header[]{}, mockResource,
null);
}
@Test
public void canRetrieveOriginalVariantMap() {
final Set<String> variants = new HashSet<>();
@ -219,33 +214,91 @@ public class TestHttpCacheEntry {
@Test
public void canConvertToString() {
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK, new Header[]{}, mockResource, null);
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK);
assertNotNull(entry.toString());
assertNotEquals("", entry.toString());
}
@Test
public void testMissingDateHeaderIsIgnored() {
final Header[] headers = new Header[] {};
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK, headers, mockResource, null);
assertNull(entry.getDate());
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK);
assertNull(entry.getInstant());
}
@Test
public void testMalformedDateHeaderIsIgnored() {
final Header[] headers = new Header[] { new BasicHeader("Date", "asdf") };
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK, headers, mockResource, null);
assertNull(entry.getDate());
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
new BasicHeader("Date", "asdf"));
assertNull(entry.getInstant());
}
@Test
public void testValidDateHeaderIsParsed() {
final Instant date = Instant.now().with(ChronoField.MILLI_OF_SECOND, 0);
final Header[] headers = new Header[] { new BasicHeader("Date", DateUtils.formatStandardDate(date)) };
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK, headers, mockResource, null);
final Date dateHeaderValue = entry.getDate();
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
new BasicHeader("Date", DateUtils.formatStandardDate(date)));
final Instant dateHeaderValue = entry.getInstant();
assertNotNull(dateHeaderValue);
assertEquals(DateUtils.toDate(date), dateHeaderValue);
assertEquals(date, dateHeaderValue);
}
@Test
public void testEpochDateHeaderIsParsed() {
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
new BasicHeader("Date", DateUtils.formatStandardDate(Instant.EPOCH)));
final Instant dateHeaderValue = entry.getInstant();
assertNotNull(dateHeaderValue);
assertEquals(Instant.EPOCH, dateHeaderValue);
}
@Test
public void testDateParsedOnce() {
final Instant date = Instant.now().with(ChronoField.MILLI_OF_SECOND, 0);
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
new BasicHeader("Date", DateUtils.formatStandardDate(date)));
final Instant dateHeaderValue = entry.getInstant();
assertNotNull(dateHeaderValue);
assertSame(dateHeaderValue, entry.getInstant());
assertSame(dateHeaderValue, entry.getInstant());
}
@Test
public void testExpiresParsedOnce() {
final Instant date = Instant.now().with(ChronoField.MILLI_OF_SECOND, 0);
entry = makeEntry(Instant.now(), Instant.now(), HttpStatus.SC_OK,
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(date)));
final Instant lastModifiedHeaderValue = entry.getLastModified();
assertNotNull(lastModifiedHeaderValue);
assertSame(lastModifiedHeaderValue, entry.getLastModified());
assertSame(lastModifiedHeaderValue, entry.getLastModified());
}
private static Instant createInstant(final int year, final Month month, final int day) {
return LocalDate.of(year, month, day).atStartOfDay(ZoneId.of("GMT")).toInstant();
}
@Test
public void testIsCacheEntryNewer() throws Exception {
assertFalse(HttpCacheEntry.isNewer(null, null));
entry = makeEntry();
final HeaderGroup message = new HeaderGroup();
assertFalse(HttpCacheEntry.isNewer(entry, message));
entry = makeEntry(new BasicHeader(HttpHeaders.DATE, "huh?"));
message.setHeader(new BasicHeader(HttpHeaders.DATE, "eh?"));
assertFalse(HttpCacheEntry.isNewer(entry, message));
entry = makeEntry(new BasicHeader(HttpHeaders.DATE, "huh?"));
message.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
assertFalse(HttpCacheEntry.isNewer(entry, message));
entry = makeEntry(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 27))));
message.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
assertTrue(HttpCacheEntry.isNewer(entry, message));
entry = makeEntry(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 25))));
message.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
assertFalse(HttpCacheEntry.isNewer(entry, message));
}
}

View File

@ -1,101 +0,0 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import static org.hamcrest.MatcherAssert.assertThat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.HeaderGroup;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link DateSupport}.
*/
public class TestDateSupport {
private static Instant createInstant(final int year, final Month month, final int day) {
return LocalDate.of(year, month, day).atStartOfDay(ZoneId.of("GMT")).toInstant();
}
@Test
public void testIsBefore() throws Exception {
final HeaderGroup message1 = new HeaderGroup();
final HeaderGroup message2 = new HeaderGroup();
assertThat(DateSupport.isBefore(null, null, HttpHeaders.DATE), CoreMatchers.equalTo(false));
assertThat(DateSupport.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
message2.setHeader(new BasicHeader(HttpHeaders.DATE, "eh?"));
assertThat(DateSupport.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
assertThat(DateSupport.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
message1.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 25))));
message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
assertThat(DateSupport.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(true));
message1.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 27))));
message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
assertThat(DateSupport.isBefore(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
}
@Test
public void testIsAfter() throws Exception {
final HeaderGroup message1 = new HeaderGroup();
final HeaderGroup message2 = new HeaderGroup();
assertThat(DateSupport.isAfter(null, null, HttpHeaders.DATE), CoreMatchers.equalTo(false));
assertThat(DateSupport.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
message2.setHeader(new BasicHeader(HttpHeaders.DATE, "eh?"));
assertThat(DateSupport.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
message1.setHeader(new BasicHeader(HttpHeaders.DATE, "huh?"));
message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
assertThat(DateSupport.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
message1.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 27))));
message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
assertThat(DateSupport.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(true));
message1.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 25))));
message2.setHeader(new BasicHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(createInstant(2017, Month.DECEMBER, 26))));
assertThat(DateSupport.isAfter(message1, message2, HttpHeaders.DATE), CoreMatchers.equalTo(false));
}
}