Allow certain responses from a POST to be cacheable.

Update ResponseCachingPolicy to allow caching of responses to POST requests under certain circumstances, as specified in RFC 2616 and explained in more detail in draft-ietf-httpbis-p2-semantics-20#section-2.3.4. This change extends the cacheability of responses beyond GET and HEAD methods, improving the cache's efficiency and reducing network traffic.
This commit is contained in:
Arturo Bernal 2023-03-04 19:55:40 +01:00 committed by Oleg Kalnichevski
parent 019cf460ec
commit cfcdd11cb6
7 changed files with 630 additions and 9 deletions

View File

@ -39,6 +39,7 @@ public class HeaderConstants {
public static final String PUT_METHOD = "PUT";
public static final String DELETE_METHOD = "DELETE";
public static final String TRACE_METHOD = "TRACE";
public static final String POST_METHOD = "POST";
public static final String LAST_MODIFIED = "Last-Modified";
public static final String IF_MATCH = "If-Match";

View File

@ -0,0 +1,111 @@
/*
* ====================================================================
* 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 org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;
/**
* Represents the values of the Cache-Control header in an HTTP response, which indicate whether and for how long
* the response can be cached by the client and intermediary proxies.
* <p>
* The class provides methods to retrieve the maximum age of the response and the maximum age that applies to shared
* caches. The values are expressed in seconds, with -1 indicating that the value was not specified in the header.
* <p>
* Instances of this class are immutable, meaning that their values cannot be changed once they are set. To create an
* instance, use one of the constructors that take the desired values as arguments. Alternatively, use the default
* constructor to create an instance with both values set to -1, indicating that the header was not present in the
* response.
* <p>
* Example usage:
* <pre>
* HttpResponse response = httpClient.execute(httpGet);
* CacheControlHeader cacheControlHeader = CacheControlHeaderParser.INSTANCE.parse(response.getHeaders("Cache-Control"));
* long maxAge = cacheControlHeader.getMaxAge();
* long sharedMaxAge = cacheControlHeader.getSharedMaxAge();
* </pre>
* @since 5.3
*/
@Internal
@Contract(threading = ThreadingBehavior.IMMUTABLE)
final class CacheControl {
/**
* The max-age directive value.
*/
private final long maxAge;
/**
* The shared-max-age directive value.
*/
private final long sharedMaxAge;
/**
* Creates a new instance of {@code CacheControlHeader} with the specified max-age and shared-max-age values.
*
* @param maxAge The max-age value from the Cache-Control header.
* @param sharedMaxAge The shared-max-age value from the Cache-Control header.
*/
public CacheControl(final long maxAge, final long sharedMaxAge) {
this.maxAge = maxAge;
this.sharedMaxAge = sharedMaxAge;
}
/**
* Returns the max-age value from the Cache-Control header.
*
* @return The max-age value.
*/
public long getMaxAge() {
return maxAge;
}
/**
* Returns the shared-max-age value from the Cache-Control header.
*
* @return The shared-max-age value.
*/
public long getSharedMaxAge() {
return sharedMaxAge;
}
/**
* Returns a string representation of the {@code CacheControlHeader} object, including the max-age and shared-max-age values.
*
* @return A string representation of the object.
*/
@Override
public String toString() {
return "CacheControl{" +
"maxAge=" + maxAge +
", sharedMaxAge=" + sharedMaxAge +
'}';
}
}

View File

@ -0,0 +1,173 @@
/*
* ====================================================================
* 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.util.BitSet;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.FormattedHeader;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.CharArrayBuffer;
import org.apache.hc.core5.util.Tokenizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A parser for the HTTP Cache-Control header that can be used to extract information about caching directives.
* <p>
* This class is thread-safe and has a singleton instance ({@link #INSTANCE}).
* </p>
* <p>
* The {@link #parse(Header)} method takes an HTTP header and returns a {@link CacheControl} object containing
* the relevant caching directives. The header can be either a {@link FormattedHeader} object, which contains a
* pre-parsed {@link CharArrayBuffer}, or a plain {@link Header} object, in which case the value will be parsed and
* stored in a new {@link CharArrayBuffer}.
* </p>
* <p>
* This parser only supports two directives: "max-age" and "s-maxage". If either of these directives are present in the
* header, their values will be parsed and stored in the {@link CacheControl} object. If both directives are
* present, the value of "s-maxage" takes precedence.
* </p>
*/
@Internal
@Contract(threading = ThreadingBehavior.SAFE)
class CacheControlHeaderParser {
/**
* The singleton instance of this parser.
*/
public static final CacheControlHeaderParser INSTANCE = new CacheControlHeaderParser();
/**
* The logger for this class.
*/
private static final Logger LOG = LoggerFactory.getLogger(CacheControlHeaderParser.class);
/**
* The character used to indicate a parameter's value in the Cache-Control header.
*/
private final static char EQUAL_CHAR = '=';
/**
* The set of characters that can delimit a token in the header.
*/
private static final BitSet TOKEN_DELIMS = Tokenizer.INIT_BITSET(EQUAL_CHAR, ',');
/**
* The set of characters that can delimit a value in the header.
*/
private static final BitSet VALUE_DELIMS = Tokenizer.INIT_BITSET(EQUAL_CHAR, ',');
/**
* The token parser used to extract values from the header.
*/
private final Tokenizer tokenParser;
/**
* Constructs a new instance of this parser.
*/
protected CacheControlHeaderParser() {
super();
this.tokenParser = Tokenizer.INSTANCE;
}
/**
* Parses the specified header and returns a new {@link CacheControl} instance containing the relevant caching
* <p>
* directives.
*
* <p>The returned {@link CacheControl} instance will contain the values for "max-age" and "s-maxage" caching
* directives parsed from the input header. If the input header does not contain any caching directives or if the
* <p>
* directives are malformed, the returned {@link CacheControl} instance will have default values for "max-age" and
* <p>
* "s-maxage" (-1).</p>
*
* @param header the header to parse, cannot be {@code null}
* @return a new {@link CacheControl} instance containing the relevant caching directives parsed from the header
* @throws IllegalArgumentException if the input header is {@code null}
*/
public final CacheControl parse(final Header header) {
Args.notNull(header, "Header");
long maxAge = -1;
long sharedMaxAge = -1;
final CharArrayBuffer buffer;
final Tokenizer.Cursor cursor;
if (header instanceof FormattedHeader) {
buffer = ((FormattedHeader) header).getBuffer();
cursor = new Tokenizer.Cursor(((FormattedHeader) header).getValuePos(), buffer.length());
} else {
final String s = header.getValue();
if (s == null) {
return new CacheControl(maxAge, sharedMaxAge);
}
buffer = new CharArrayBuffer(s.length());
buffer.append(s);
cursor = new Tokenizer.Cursor(0, buffer.length());
}
while (!cursor.atEnd()) {
final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
if (cursor.atEnd()) {
return new CacheControl(maxAge, sharedMaxAge);
}
final int valueDelim = buffer.charAt(cursor.getPos());
cursor.updatePos(cursor.getPos() + 1);
if (valueDelim != EQUAL_CHAR) {
continue;
}
final String value = tokenParser.parseValue(buffer, cursor, VALUE_DELIMS);
if (!cursor.atEnd()) {
cursor.updatePos(cursor.getPos() + 1);
}
try {
if (name.equalsIgnoreCase("s-maxage")) {
sharedMaxAge = Long.parseLong(value);
} else if (name.equalsIgnoreCase("max-age")) {
maxAge = Long.parseLong(value);
}
} catch (final NumberFormatException e) {
// skip malformed directive
if (LOG.isDebugEnabled()) {
LOG.debug("Header {} was malformed: {}", name, value);
}
}
}
return new CacheControl(maxAge, sharedMaxAge);
}
}

View File

@ -26,7 +26,9 @@
*/
package org.apache.hc.client5.http.impl.cache;
import java.time.Duration;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@ -50,6 +52,25 @@ import org.slf4j.LoggerFactory;
class ResponseCachingPolicy {
/**
* The default freshness duration for a cached object, in seconds.
*
* <p>This constant is used to set the default value for the freshness lifetime of a cached object.
* When a new object is added to the cache, it will be assigned this duration if no other duration
* is specified.</p>
*
* <p>By default, this value is set to 300 seconds (5 minutes). Applications can customize this
* value as needed.</p>
*/
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 String[] AUTH_CACHEABLE_PARAMS = {
"s-maxage", HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE, HeaderConstants.PUBLIC
};
@ -108,7 +129,8 @@ class ResponseCachingPolicy {
public boolean isResponseCacheable(final String httpMethod, final HttpResponse response) {
boolean cacheable = false;
if (!HeaderConstants.GET_METHOD.equals(httpMethod) && !HeaderConstants.HEAD_METHOD.equals(httpMethod)) {
if (!HeaderConstants.GET_METHOD.equals(httpMethod) && !HeaderConstants.HEAD_METHOD.equals(httpMethod)
&& !HeaderConstants.POST_METHOD.equals(httpMethod)) {
if (LOG.isDebugEnabled()) {
LOG.debug("{} method response is not cacheable", httpMethod);
}
@ -181,6 +203,15 @@ class ResponseCachingPolicy {
return false;
}
// calculate freshness lifetime
final Duration freshnessLifetime = calculateFreshnessLifetime(response);
if (freshnessLifetime.isNegative() || freshnessLifetime.isZero()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Freshness lifetime is invalid");
}
return false;
}
return cacheable || isExplicitlyCacheable(response);
}
@ -320,4 +351,74 @@ class ResponseCachingPolicy {
return HttpVersion.HTTP_1_0.equals(version);
}
/**
* Calculates the freshness lifetime of a response, based on the headers in the response.
* <p>
* This method follows the algorithm for calculating the freshness lifetime.
* The freshness lifetime represents the time interval in seconds during which the response can be served without
* being considered stale. The freshness lifetime calculation takes into account the s-maxage, max-age, Expires, and
* Date headers as follows:
* <ul>
* <li>If the s-maxage directive is present in the Cache-Control header of the response, its value is used as the
* freshness lifetime for shared caches, which typically serve multiple users or clients.</li>
* <li>If the max-age directive is present in the Cache-Control header of the response, its value is used as the
* freshness lifetime for private caches, which serve a single user or client.</li>
* <li>If the Expires header is present in the response, its value is used as the expiration time of the response.
* The freshness lifetime is calculated as the difference between the expiration time and the time specified in the
* Date header of the response.</li>
* <li>If none of the above headers are present or if the calculated freshness lifetime is invalid, a default value of
* 5 minutes is returned.</li>
* </ul>
*
* <p>
* Note that caching is a complex topic and cache control directives may interact with each other in non-trivial ways.
* This method provides a basic implementation of the freshness lifetime calculation algorithm and may not be suitable
* 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 HttpResponse response) {
// Check if s-maxage is present and use its value if it is
final Header cacheControl = response.getFirstHeader(HttpHeaders.CACHE_CONTROL);
if (cacheControl == null) {
// If no cache-control header is present, assume no caching directives and return a default value
return DEFAULT_FRESHNESS_DURATION; // 5 minutes
}
final String cacheControlValue = cacheControl.getValue();
if (cacheControlValue == null) {
// If cache-control header has no value, assume no caching directives and return a default value
return DEFAULT_FRESHNESS_DURATION; // 5 minutes
}
final CacheControl cacheControlHeader = CacheControlHeaderParser.INSTANCE.parse(cacheControl);
if (cacheControlHeader.getSharedMaxAge() != -1) {
return Duration.ofSeconds(cacheControlHeader.getSharedMaxAge());
} else if (cacheControlHeader.getMaxAge() != -1) {
return Duration.ofSeconds(cacheControlHeader.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 none of the above conditions are met, a heuristic freshness lifetime might be applicable
return DEFAULT_FRESHNESS_DURATION; // 5 minutes
}
}

View File

@ -0,0 +1,109 @@
/*
* ====================================================================
* 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 org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.message.BasicHeader;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class CacheControlParserTest {
private final CacheControlHeaderParser parser = CacheControlHeaderParser.INSTANCE;
@Test
public void testParseMaxAgeZero() {
final Header header = new BasicHeader("Cache-Control", "max-age=0 , this = stuff;");
final CacheControl cacheControl = parser.parse(header);
assertEquals(0L, cacheControl.getMaxAge());
}
@Test
public void testParseSMaxAge() {
final Header header = new BasicHeader("Cache-Control", "s-maxage=3600");
final CacheControl cacheControl = parser.parse(header);
assertEquals(3600L, cacheControl.getSharedMaxAge());
}
@Test
public void testParseInvalidCacheValue() {
final Header header = new BasicHeader("Cache-Control", "max-age=invalid");
final CacheControl cacheControl = parser.parse(header);
assertEquals(-1L, cacheControl.getMaxAge());
}
@Test
public void testParseInvalidHeader() {
final Header header = new BasicHeader("Cache-Control", "max-age");
final CacheControl cacheControl = parser.parse(header);
assertEquals(-1L, cacheControl.getMaxAge());
}
@Test
public void testParseNullHeader() {
final Header header = null;
assertThrows(NullPointerException.class, () -> parser.parse(header));
}
@Test
public void testParseEmptyHeader() {
final Header header = new BasicHeader("Cache-Control", "");
final CacheControl cacheControl = parser.parse(header);
assertEquals(-1L, cacheControl.getMaxAge());
}
@Test
public void testParseCookieEmptyValue() {
final Header header = new BasicHeader("Cache-Control", "max-age=;");
final CacheControl cacheControl = parser.parse(header);
assertEquals(-1L, cacheControl.getMaxAge());
}
@Test
public void testParseNoCache() {
final Header header = new BasicHeader(" Cache-Control", "no-cache");
final CacheControl cacheControl = parser.parse(header);
assertEquals(-1L, cacheControl.getMaxAge());
}
@Test
public void testParseNoDirective() {
final Header header = new BasicHeader(" Cache-Control", "");
final CacheControl cacheControl = parser.parse(header);
assertEquals(-1L, cacheControl.getMaxAge());
}
@Test
public void testGarbage() {
final Header header = new BasicHeader("Cache-Control", ",,= blah,");
final CacheControl cacheControl = parser.parse(header);
assertEquals(-1L, cacheControl.getMaxAge());
}
}

View File

@ -1165,12 +1165,12 @@ public class TestCachingExecChain {
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
resp1.setHeader("Date", DateUtils.formatStandardDate(now));
resp1.setHeader("Cache-Control", "max-age=0");
resp1.setHeader("Cache-Control", "max-age=1");
resp1.setHeader("Etag", "etag");
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
resp2.setHeader("Cache-Control", "max-age=0");
resp2.setHeader("Cache-Control", "max-age=1");
resp1.setHeader("Etag", "etag");
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
@ -1199,13 +1199,13 @@ public class TestCachingExecChain {
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
resp1.setHeader("Date", DateUtils.formatStandardDate(now));
resp1.setHeader("Cache-Control", "max-age=0");
resp1.setHeader("Cache-Control", "max-age=1");
resp1.setHeader("Etag", "etag");
resp1.setHeader("Vary", "Accept-Encoding");
final ClassicHttpResponse resp2 = HttpTestUtils.make304Response();
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
resp2.setHeader("Cache-Control", "max-age=0");
resp2.setHeader("Cache-Control", "max-age=1");
resp1.setHeader("Etag", "etag");
resp1.setHeader("Vary", "Accept-Encoding");

View File

@ -27,11 +27,13 @@
package org.apache.hc.client5.http.impl.cache;
import java.time.Instant;
import java.util.Date;
import java.util.Random;
import org.apache.hc.client5.http.auth.StandardAuthScheme;
import org.apache.hc.client5.http.classic.methods.HttpOptions;
import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
@ -216,15 +218,49 @@ public class TestResponseCachingPolicy {
final int status = getRandomStatus();
response.setCode(status);
response.setHeader("Cache-Control", "max-age=0");
Assertions.assertFalse(policy.isResponseCacheable("GET", response));
}
@Test
public void testMalformedHeaderValue() {
final int status = getRandomStatus();
response.setCode(status);
response.setHeader("Cache-Control", "max-age=boom");
Assertions.assertTrue(policy.isResponseCacheable("GET", response));
}
@Test
public void testNullHeaderValue() {
final int status = getRandomStatus();
response.setCode(status);
response.setHeader("Cache-Control", null);
Assertions.assertTrue(policy.isResponseCacheable("GET", response));
}
@Test
public void testMultipleHeaderValue() {
final int status = getRandomStatus();
response.setCode(status);
response.setHeader("Cache-Control", "s-maxage=12,must-revalidate=true,max-age=23");
Assertions.assertTrue(policy.isResponseCacheable("GET", response));
}
@Test
public void testMissingCacheControlHeader() {
final int status = getRandomStatus();
response.setCode(status);
response.removeHeaders(HttpHeaders.CACHE_CONTROL);
final boolean isCacheable = policy.isResponseCacheable("GET", response);
Assertions.assertTrue(isCacheable);
}
@Test
public void testNon206WithSMaxAgeIsCacheable() {
final int status = getRandomStatus();
response.setCode(status);
response.setHeader("Cache-Control", "s-maxage=0");
Assertions.assertTrue(policy.isResponseCacheable("GET", response));
Assertions.assertFalse(policy.isResponseCacheable("GET", response));
}
@Test
@ -870,9 +906,99 @@ public class TestResponseCachingPolicy {
response.setCode(HttpStatus.SC_NOT_FOUND);
response.setHeader("Date", DateUtils.formatStandardDate(now));
response.setHeader("Cache-Control","max-age=300");
Assertions.assertTrue(policy.isResponseCacheable(request, response));
assertTrue(policy.isResponseCacheable(request, response));
}
@Test
void testIsResponseCacheableNullCacheControl() {
// Set up test data
final long now = System.currentTimeMillis();
final long tenSecondsFromNow = now + 10000;
response = new BasicHttpResponse(HttpStatus.SC_OK, "");
response.setHeader(HttpHeaders.DATE, DateUtils.formatDate(new Date(now), DateUtils.PATTERN_RFC1123));
response.setHeader(HttpHeaders.EXPIRES, DateUtils.formatDate(new Date(tenSecondsFromNow), DateUtils.PATTERN_RFC1123));
// Create ResponseCachingPolicy instance and test the method
policy = new ResponseCachingPolicy(0, true, false, false, false);
request = new BasicHttpRequest("POST", "/foo");
final boolean isCacheable = policy.isResponseCacheable(request, response);
// Verify the result
assertTrue(isCacheable);
}
@Test
void testIsResponseCacheableNotNullCacheControlSmaxAge60() {
// Set up test data
final long now = System.currentTimeMillis();
final long tenSecondsFromNow = now + 10000;
response = new BasicHttpResponse(HttpStatus.SC_OK, "");
response.setHeader(HttpHeaders.DATE, DateUtils.formatDate(new Date(now), DateUtils.PATTERN_RFC1123));
response.setHeader(HttpHeaders.EXPIRES, DateUtils.formatDate(new Date(tenSecondsFromNow), DateUtils.PATTERN_RFC1123));
// Create ResponseCachingPolicy instance and test the method
policy = new ResponseCachingPolicy(0, true, false, false, false);
request = new BasicHttpRequest("POST", "/foo");
response.addHeader(HttpHeaders.CACHE_CONTROL, "s-maxage=60");
final boolean isCacheable = policy.isResponseCacheable(request, response);
// Verify the result
assertTrue(isCacheable);
}
@Test
void testIsResponseCacheableNotNullCacheControlMaxAge60() {
// Set up test data
final long now = System.currentTimeMillis();
final long tenSecondsFromNow = now + 10000;
response = new BasicHttpResponse(HttpStatus.SC_OK, "");
response.setHeader(HttpHeaders.DATE, DateUtils.formatDate(new Date(now), DateUtils.PATTERN_RFC1123));
response.setHeader(HttpHeaders.EXPIRES, DateUtils.formatDate(new Date(tenSecondsFromNow), DateUtils.PATTERN_RFC1123));
// Create ResponseCachingPolicy instance and test the method
policy = new ResponseCachingPolicy(0, true, false, false,false);
request = new BasicHttpRequest("POST", "/foo");
response.addHeader(HttpHeaders.CACHE_CONTROL, "max-age=60");
final boolean isCacheable = policy.isResponseCacheable(request, response);
// Verify the result
assertTrue(isCacheable);
}
@Test
void testIsResponseCacheableNotExsiresAndDate() {
// Set up test data
final long now = System.currentTimeMillis();
final long tenSecondsFromNow = now + 10000;
response = new BasicHttpResponse(HttpStatus.SC_OK, "");
response.setHeader(HttpHeaders.DATE, DateUtils.formatDate(new Date(now), DateUtils.PATTERN_RFC1123));
response.setHeader(HttpHeaders.EXPIRES, DateUtils.formatDate(new Date(tenSecondsFromNow), DateUtils.PATTERN_RFC1123));
// Create ResponseCachingPolicy instance and test the method
policy = new ResponseCachingPolicy(0, true, false, false,false);
request = new BasicHttpRequest("POST", "/foo");
response.addHeader(HttpHeaders.CACHE_CONTROL, "something");
final boolean isCacheable = policy.isResponseCacheable(request, response);
// Verify the result
assertTrue(isCacheable);
}
private int getRandomStatus() {
final int rnd = new Random().nextInt(acceptableCodes.length);