HTTPCLIENT-2277: Removal of deprecated `Warning` header support
This commit is contained in:
parent
561c949f78
commit
d1d6eec7dd
|
@ -110,12 +110,6 @@ public class HttpCacheEntryFactory {
|
|||
// Since we do not expect a content in a 304 response, should retain the original Content-Encoding header
|
||||
if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_ENCODING)) {
|
||||
headerGroup.addHeader(entryHeader);
|
||||
} else if (headerName.equalsIgnoreCase(HttpHeaders.WARNING)) {
|
||||
// remove cache entry 1xx warnings
|
||||
final String warningValue = entryHeader.getValue();
|
||||
if (warningValue != null && !warningValue.startsWith("1")) {
|
||||
headerGroup.addHeader(entryHeader);
|
||||
}
|
||||
} else {
|
||||
if (!response.containsHeader(headerName)) {
|
||||
headerGroup.addHeader(entryHeader);
|
||||
|
|
|
@ -617,7 +617,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
}
|
||||
LOG.debug("Cache hit");
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context);
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} catch (final ResourceIOException ex) {
|
||||
recordCacheFailure(target, request);
|
||||
|
@ -645,7 +645,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
&& (validityPolicy.mayReturnStaleWhileRevalidating(responseCacheControl, hit.entry, now) || staleIfErrorEnabled)) {
|
||||
LOG.debug("Serving stale with asynchronous revalidation");
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context);
|
||||
final String exchangeId = ExecSupport.getNextExchangeId();
|
||||
context.setExchangeId(exchangeId);
|
||||
final AsyncExecChain.Scope fork = new AsyncExecChain.Scope(
|
||||
|
@ -669,7 +669,7 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
LOG.debug("Serving stale response due to IOException and stale-if-error enabled");
|
||||
}
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
|
||||
final SimpleHttpResponse cacheResponse = generateCachedResponse(responseCacheControl, hit.entry, request, context);
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} catch (final ResourceIOException ex2) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
|
@ -752,7 +752,6 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
|
|||
void triggerResponseStaleCacheEntry() {
|
||||
try {
|
||||
final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, hit.entry);
|
||||
cacheResponse.addHeader(HttpHeaders.WARNING, "110 localhost \"Response is stale\"");
|
||||
triggerResponse(cacheResponse, scope, asyncExecCallback);
|
||||
} catch (final ResourceIOException ex) {
|
||||
asyncExecCallback.failed(ex);
|
||||
|
|
|
@ -29,7 +29,6 @@ package org.apache.hc.client5.http.impl.cache;
|
|||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
@ -39,9 +38,6 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|||
|
||||
import org.apache.hc.client5.http.schedule.ConcurrentCountMap;
|
||||
import org.apache.hc.client5.http.schedule.SchedulingStrategy;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
import org.apache.hc.core5.util.TimeValue;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
|
@ -167,25 +163,4 @@ class CacheRevalidatorBase implements Closeable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given response is generated from a stale cache entry.
|
||||
* @param httpResponse the response to be checked
|
||||
* @return whether the response is stale or not
|
||||
*/
|
||||
boolean isStale(final HttpResponse httpResponse) {
|
||||
for (final Iterator<Header> it = httpResponse.headerIterator(HttpHeaders.WARNING); it.hasNext(); ) {
|
||||
/*
|
||||
* warn-codes
|
||||
* 110 = Response is stale
|
||||
* 111 = Revalidation failed
|
||||
*/
|
||||
final Header warning = it.next();
|
||||
final String warningValue = warning.getValue();
|
||||
if (warningValue.startsWith("110") || warningValue.startsWith("111")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -265,7 +265,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
}
|
||||
LOG.debug("Cache hit");
|
||||
try {
|
||||
return convert(generateCachedResponse(responseCacheControl, hit.entry, request, context, now), scope);
|
||||
return convert(generateCachedResponse(responseCacheControl, hit.entry, request, context), scope);
|
||||
} catch (final ResourceIOException ex) {
|
||||
recordCacheFailure(target, request);
|
||||
if (!mayCallBackend(requestCacheControl)) {
|
||||
|
@ -293,7 +293,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
scope.originalRequest,
|
||||
scope.execRuntime.fork(null),
|
||||
HttpClientContext.create());
|
||||
final SimpleHttpResponse response = generateCachedResponse(responseCacheControl, hit.entry, request, context, now);
|
||||
final SimpleHttpResponse response = generateCachedResponse(responseCacheControl, hit.entry, request, context);
|
||||
cacheRevalidator.revalidateCacheEntry(
|
||||
hit.getEntryKey(),
|
||||
() -> revalidateCacheEntry(requestCacheControl, responseCacheControl, hit, target, request, fork, chain));
|
||||
|
@ -305,7 +305,7 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Serving stale response due to IOException and stale-if-error enabled");
|
||||
}
|
||||
return convert(generateCachedResponse(responseCacheControl, hit.entry, request, context, now), scope);
|
||||
return convert(generateCachedResponse(responseCacheControl, hit.entry, request, context), scope);
|
||||
}
|
||||
return convert(handleRevalidationFailure(requestCacheControl, responseCacheControl, hit.entry, request, context, now), scope);
|
||||
}
|
||||
|
@ -361,7 +361,6 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
|
|||
&& validityPolicy.mayReturnStaleIfError(requestCacheControl, responseCacheControl, hit.entry, responseDate)) {
|
||||
try {
|
||||
final SimpleHttpResponse cachedResponse = responseGenerator.generateResponse(request, hit.entry);
|
||||
cachedResponse.addHeader(HttpHeaders.WARNING, "110 localhost \"Response is stale\"");
|
||||
return convert(cachedResponse, scope);
|
||||
} finally {
|
||||
backendResponse.close();
|
||||
|
|
|
@ -38,7 +38,6 @@ import org.apache.hc.client5.http.cache.CacheResponseStatus;
|
|||
import org.apache.hc.client5.http.cache.HttpCacheContext;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
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.HttpHost;
|
||||
|
@ -187,8 +186,7 @@ public class CachingExecBase {
|
|||
final ResponseCacheControl responseCacheControl,
|
||||
final HttpCacheEntry entry,
|
||||
final HttpRequest request,
|
||||
final HttpContext context,
|
||||
final Instant now) throws ResourceIOException {
|
||||
final HttpContext context) throws ResourceIOException {
|
||||
final SimpleHttpResponse cachedResponse;
|
||||
if (request.containsHeader(HttpHeaders.IF_NONE_MATCH)
|
||||
|| request.containsHeader(HttpHeaders.IF_MODIFIED_SINCE)) {
|
||||
|
@ -197,26 +195,6 @@ public class CachingExecBase {
|
|||
cachedResponse = responseGenerator.generateResponse(request, entry);
|
||||
}
|
||||
setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
|
||||
if (TimeValue.isPositive(validityPolicy.getStaleness(responseCacheControl, entry, now))) {
|
||||
cachedResponse.addHeader(HttpHeaders.WARNING,"110 localhost \"Response is stale\"");
|
||||
}
|
||||
|
||||
// Adding Warning: 113 - "Heuristic Expiration"
|
||||
if (!entry.containsHeader(HttpHeaders.WARNING)) {
|
||||
final Header header = entry.getFirstHeader(HttpHeaders.DATE);
|
||||
if (header != null) {
|
||||
final Instant responseDate = DateUtils.parseStandardDate(header.getValue());
|
||||
final TimeValue freshnessLifetime = validityPolicy.getFreshnessLifetime(responseCacheControl, entry);
|
||||
final TimeValue currentAge = validityPolicy.getCurrentAge(entry, responseDate);
|
||||
if (freshnessLifetime.compareTo(ONE_DAY) > 0 && currentAge.compareTo(ONE_DAY) > 0) {
|
||||
cachedResponse.addHeader(HttpHeaders.WARNING,"113 localhost \"Heuristic expiration\"");
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Added Warning 113 - Heuristic expiration to the response header.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
|
@ -246,7 +224,6 @@ public class CachingExecBase {
|
|||
final HttpCacheEntry entry) throws IOException {
|
||||
final SimpleHttpResponse cachedResponse = responseGenerator.generateResponse(request, entry);
|
||||
setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
|
||||
cachedResponse.addHeader(HttpHeaders.WARNING, "111 localhost \"Revalidation failed\"");
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
|
|
|
@ -135,7 +135,7 @@ class DefaultAsyncCacheRevalidator extends CacheRevalidatorBase {
|
|||
@Override
|
||||
public void completed() {
|
||||
final HttpResponse httpResponse = responseRef.getAndSet(null);
|
||||
if (httpResponse != null && httpResponse.getCode() < HttpStatus.SC_SERVER_ERROR && !isStale(httpResponse)) {
|
||||
if (httpResponse != null && httpResponse.getCode() < HttpStatus.SC_SERVER_ERROR) {
|
||||
jobSuccessful(cacheKey);
|
||||
} else {
|
||||
jobFailed(cacheKey);
|
||||
|
|
|
@ -77,7 +77,7 @@ class DefaultCacheRevalidator extends CacheRevalidatorBase {
|
|||
final RevalidationCall call) {
|
||||
scheduleRevalidation(cacheKey, () -> {
|
||||
try (ClassicHttpResponse httpResponse = call.execute()) {
|
||||
if (httpResponse.getCode() < HttpStatus.SC_SERVER_ERROR && !isStale(httpResponse)) {
|
||||
if (httpResponse.getCode() < HttpStatus.SC_SERVER_ERROR) {
|
||||
jobSuccessful(cacheKey);
|
||||
} else {
|
||||
jobFailed(cacheKey);
|
||||
|
|
|
@ -79,41 +79,6 @@ class ResponseProtocolCompliance {
|
|||
ensure304DoesNotContainExtraEntityHeaders(response);
|
||||
|
||||
identityIsNotUsedInContentEncoding(response);
|
||||
|
||||
warningsWithNonMatchingWarnDatesAreRemoved(response);
|
||||
}
|
||||
|
||||
private void warningsWithNonMatchingWarnDatesAreRemoved(
|
||||
final HttpResponse response) {
|
||||
final Instant responseDate = DateUtils.parseStandardDate(response, HttpHeaders.DATE);
|
||||
if (responseDate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Header[] warningHeaders = response.getHeaders(HttpHeaders.WARNING);
|
||||
|
||||
if (warningHeaders == null || warningHeaders.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Header> newWarningHeaders = new ArrayList<>();
|
||||
boolean modified = false;
|
||||
for(final Header h : warningHeaders) {
|
||||
for(final WarningValue wv : WarningValue.getWarningValues(h)) {
|
||||
final Instant warnInstant = wv.getWarnDate();
|
||||
if (warnInstant == null || warnInstant.equals(responseDate)) {
|
||||
newWarningHeaders.add(new BasicHeader(HttpHeaders.WARNING,wv.toString()));
|
||||
} else {
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (modified) {
|
||||
response.removeHeaders(HttpHeaders.WARNING);
|
||||
for(final Header h : newWarningHeaders) {
|
||||
response.addHeader(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void identityIsNotUsedInContentEncoding(final HttpResponse response) {
|
||||
|
|
|
@ -1,368 +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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.hc.client5.http.utils.DateUtils;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
|
||||
/** This class provides for parsing and understanding Warning headers. As
|
||||
* the Warning header can be multi-valued, but the values can contain
|
||||
* separators like commas inside quoted strings, we cannot use the regular
|
||||
* {@link Header#getValue()} } call to access the values.
|
||||
*/
|
||||
class WarningValue {
|
||||
|
||||
private int offs;
|
||||
private final int init_offs;
|
||||
private final String src;
|
||||
private int warnCode;
|
||||
private String warnAgent;
|
||||
private String warnText;
|
||||
private Instant warnDate;
|
||||
|
||||
WarningValue(final String s) {
|
||||
this(s, 0);
|
||||
}
|
||||
|
||||
WarningValue(final String s, final int offs) {
|
||||
this.offs = this.init_offs = offs;
|
||||
this.src = s;
|
||||
consumeWarnValue();
|
||||
}
|
||||
|
||||
/** Returns an array of the parsable warning values contained
|
||||
* in the given header value, which is assumed to be a
|
||||
* Warning header. Improperly formatted warning values will be
|
||||
* skipped, in keeping with the philosophy of "ignore what you
|
||||
* cannot understand."
|
||||
* @param h Warning {@link Header} to parse
|
||||
* @return array of {@code WarnValue} objects
|
||||
*/
|
||||
public static WarningValue[] getWarningValues(final Header h) {
|
||||
final List<WarningValue> out = new ArrayList<>();
|
||||
final String src = h.getValue();
|
||||
int offs = 0;
|
||||
while(offs < src.length()) {
|
||||
try {
|
||||
final WarningValue wv = new WarningValue(src, offs);
|
||||
out.add(wv);
|
||||
offs = wv.offs;
|
||||
} catch (final IllegalArgumentException e) {
|
||||
final int nextComma = src.indexOf(',', offs);
|
||||
if (nextComma == -1) {
|
||||
break;
|
||||
}
|
||||
offs = nextComma + 1;
|
||||
}
|
||||
}
|
||||
final WarningValue[] wvs = {};
|
||||
return out.toArray(wvs);
|
||||
}
|
||||
|
||||
/*
|
||||
* LWS = [CRLF] 1*( SP | HT )
|
||||
* CRLF = CR LF
|
||||
*/
|
||||
protected void consumeLinearWhitespace() {
|
||||
while(offs < src.length()) {
|
||||
switch(src.charAt(offs)) {
|
||||
case '\r':
|
||||
if (offs+2 >= src.length()
|
||||
|| src.charAt(offs+1) != '\n'
|
||||
|| (src.charAt(offs+2) != ' '
|
||||
&& src.charAt(offs+2) != '\t')) {
|
||||
return;
|
||||
}
|
||||
offs += 2;
|
||||
break;
|
||||
case ' ':
|
||||
case '\t':
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
offs++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
*/
|
||||
private boolean isChar(final char c) {
|
||||
return ((int) c >= 0 && (int) c <= 127);
|
||||
}
|
||||
|
||||
/*
|
||||
* CTL = <any US-ASCII control character
|
||||
(octets 0 - 31) and DEL (127)>
|
||||
*/
|
||||
private boolean isControl(final char c) {
|
||||
return ((int) c == 127 || ((int) c >=0 && (int) c <= 31));
|
||||
}
|
||||
|
||||
/*
|
||||
* separators = "(" | ")" | "<" | ">" | "@"
|
||||
* | "," | ";" | ":" | "\" | <">
|
||||
* | "/" | "[" | "]" | "?" | "="
|
||||
* | "{" | "}" | SP | HT
|
||||
*/
|
||||
private boolean isSeparator(final char c) {
|
||||
return (c == '(' || c == ')' || c == '<' || c == '>'
|
||||
|| c == '@' || c == ',' || c == ';' || c == ':'
|
||||
|| c == '\\' || c == '\"' || c == '/'
|
||||
|| c == '[' || c == ']' || c == '?' || c == '='
|
||||
|| c == '{' || c == '}' || c == ' ' || c == '\t');
|
||||
}
|
||||
|
||||
/*
|
||||
* token = 1*<any CHAR except CTLs or separators>
|
||||
*/
|
||||
protected void consumeToken() {
|
||||
if (!isTokenChar(src.charAt(offs))) {
|
||||
parseError();
|
||||
}
|
||||
while(offs < src.length()) {
|
||||
if (!isTokenChar(src.charAt(offs))) {
|
||||
break;
|
||||
}
|
||||
offs++;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTokenChar(final char c) {
|
||||
return (isChar(c) && !isControl(c) && !isSeparator(c));
|
||||
}
|
||||
|
||||
private static final String TOPLABEL = "\\p{Alpha}([\\p{Alnum}-]*\\p{Alnum})?";
|
||||
private static final String DOMAINLABEL = "\\p{Alnum}([\\p{Alnum}-]*\\p{Alnum})?";
|
||||
private static final String HOSTNAME = "(" + DOMAINLABEL + "\\.)*" + TOPLABEL + "\\.?";
|
||||
private static final String IPV4ADDRESS = "\\d+\\.\\d+\\.\\d+\\.\\d+";
|
||||
private static final String HOST = "(" + HOSTNAME + ")|(" + IPV4ADDRESS + ")";
|
||||
private static final String PORT = "\\d*";
|
||||
private static final String HOSTPORT = "(" + HOST + ")(\\:" + PORT + ")?";
|
||||
private static final Pattern HOSTPORT_PATTERN = Pattern.compile(HOSTPORT);
|
||||
|
||||
protected void consumeHostPort() {
|
||||
final Matcher m = HOSTPORT_PATTERN.matcher(src.substring(offs));
|
||||
if (!m.find()) {
|
||||
parseError();
|
||||
}
|
||||
if (m.start() != 0) {
|
||||
parseError();
|
||||
}
|
||||
offs += m.end();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* warn-agent = ( host [ ":" port ] ) | pseudonym
|
||||
* pseudonym = token
|
||||
*/
|
||||
protected void consumeWarnAgent() {
|
||||
final int curr_offs = offs;
|
||||
try {
|
||||
consumeHostPort();
|
||||
warnAgent = src.substring(curr_offs, offs);
|
||||
consumeCharacter(' ');
|
||||
return;
|
||||
} catch (final IllegalArgumentException e) {
|
||||
offs = curr_offs;
|
||||
}
|
||||
consumeToken();
|
||||
warnAgent = src.substring(curr_offs, offs);
|
||||
consumeCharacter(' ');
|
||||
}
|
||||
|
||||
/*
|
||||
* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
|
||||
* qdtext = <any TEXT except <">>
|
||||
*/
|
||||
protected void consumeQuotedString() {
|
||||
if (src.charAt(offs) != '\"') {
|
||||
parseError();
|
||||
}
|
||||
offs++;
|
||||
boolean foundEnd = false;
|
||||
while(offs < src.length() && !foundEnd) {
|
||||
final char c = src.charAt(offs);
|
||||
if (offs + 1 < src.length() && c == '\\'
|
||||
&& isChar(src.charAt(offs+1))) {
|
||||
offs += 2; // consume quoted-pair
|
||||
} else if (c == '\"') {
|
||||
foundEnd = true;
|
||||
offs++;
|
||||
} else if (!isControl(c)) {
|
||||
offs++;
|
||||
} else {
|
||||
parseError();
|
||||
}
|
||||
}
|
||||
if (!foundEnd) {
|
||||
parseError();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* warn-text = quoted-string
|
||||
*/
|
||||
protected void consumeWarnText() {
|
||||
final int curr = offs;
|
||||
consumeQuotedString();
|
||||
warnText = src.substring(curr, offs);
|
||||
}
|
||||
|
||||
private static final String MONTH = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec";
|
||||
private static final String WEEKDAY = "Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday";
|
||||
private static final String WKDAY = "Mon|Tue|Wed|Thu|Fri|Sat|Sun";
|
||||
private static final String TIME = "\\d{2}:\\d{2}:\\d{2}";
|
||||
private static final String DATE3 = "(" + MONTH + ") ( |\\d)\\d";
|
||||
private static final String DATE2 = "\\d{2}-(" + MONTH + ")-\\d{2}";
|
||||
private static final String DATE1 = "\\d{2} (" + MONTH + ") \\d{4}";
|
||||
private static final String ASCTIME_DATE = "(" + WKDAY + ") (" + DATE3 + ") (" + TIME + ") \\d{4}";
|
||||
private static final String RFC850_DATE = "(" + WEEKDAY + "), (" + DATE2 + ") (" + TIME + ") GMT";
|
||||
private static final String RFC1123_DATE = "(" + WKDAY + "), (" + DATE1 + ") (" + TIME + ") GMT";
|
||||
private static final String HTTP_DATE = "(" + RFC1123_DATE + ")|(" + RFC850_DATE + ")|(" + ASCTIME_DATE + ")";
|
||||
private static final String WARN_DATE = "\"(" + HTTP_DATE + ")\"";
|
||||
private static final Pattern WARN_DATE_PATTERN = Pattern.compile(WARN_DATE);
|
||||
|
||||
/*
|
||||
* warn-date = <"> HTTP-date <">
|
||||
*/
|
||||
protected void consumeWarnDate() {
|
||||
final int curr = offs;
|
||||
final Matcher m = WARN_DATE_PATTERN.matcher(src.substring(offs));
|
||||
if (!m.lookingAt()) {
|
||||
parseError();
|
||||
}
|
||||
offs += m.end();
|
||||
warnDate = DateUtils.parseStandardDate(src.substring(curr+1,offs-1));
|
||||
}
|
||||
|
||||
/*
|
||||
* warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
|
||||
*/
|
||||
protected void consumeWarnValue() {
|
||||
consumeLinearWhitespace();
|
||||
consumeWarnCode();
|
||||
consumeWarnAgent();
|
||||
consumeWarnText();
|
||||
if (offs + 1 < src.length() && src.charAt(offs) == ' ' && src.charAt(offs+1) == '\"') {
|
||||
consumeCharacter(' ');
|
||||
consumeWarnDate();
|
||||
}
|
||||
consumeLinearWhitespace();
|
||||
if (offs != src.length()) {
|
||||
consumeCharacter(',');
|
||||
}
|
||||
}
|
||||
|
||||
protected void consumeCharacter(final char c) {
|
||||
if (offs + 1 > src.length()
|
||||
|| c != src.charAt(offs)) {
|
||||
parseError();
|
||||
}
|
||||
offs++;
|
||||
}
|
||||
|
||||
/*
|
||||
* warn-code = 3DIGIT
|
||||
*/
|
||||
protected void consumeWarnCode() {
|
||||
if (offs + 4 > src.length()
|
||||
|| !Character.isDigit(src.charAt(offs))
|
||||
|| !Character.isDigit(src.charAt(offs + 1))
|
||||
|| !Character.isDigit(src.charAt(offs + 2))
|
||||
|| src.charAt(offs + 3) != ' ') {
|
||||
parseError();
|
||||
}
|
||||
warnCode = Integer.parseInt(src.substring(offs,offs+3));
|
||||
offs += 4;
|
||||
}
|
||||
|
||||
private void parseError() {
|
||||
final String s = src.substring(init_offs);
|
||||
throw new IllegalArgumentException("Bad warn code \"" + s + "\"");
|
||||
}
|
||||
|
||||
/** Returns the 3-digit code associated with this warning.
|
||||
* @return {@code int}
|
||||
*/
|
||||
public int getWarnCode() { return warnCode; }
|
||||
|
||||
/** Returns the "warn-agent" string associated with this warning,
|
||||
* which is either the name or pseudonym of the server that added
|
||||
* this particular Warning header.
|
||||
* @return {@link String}
|
||||
*/
|
||||
public String getWarnAgent() { return warnAgent; }
|
||||
|
||||
/** Returns the human-readable warning text for this warning. Note
|
||||
* that the original quoted-string is returned here, including
|
||||
* escaping for any contained characters. In other words, if the
|
||||
* header was:
|
||||
* <pre>
|
||||
* Warning: 110 fred "Response is stale"
|
||||
* </pre>
|
||||
* then this method will return {@code "\"Response is stale\""}
|
||||
* (surrounding quotes included).
|
||||
* @return {@link String}
|
||||
*/
|
||||
public String getWarnText() { return warnText; }
|
||||
|
||||
/** Returns the date and time when this warning was added, or
|
||||
* {@code null} if a warning date was not supplied in the
|
||||
* header.
|
||||
* @return {@link Instant}
|
||||
*/
|
||||
public Instant getWarnDate() { return warnDate; }
|
||||
|
||||
/** Formats a {@code WarningValue} as a {@link String}
|
||||
* suitable for including in a header. For example, you can:
|
||||
* <pre>
|
||||
* WarningValue wv = ...;
|
||||
* HttpResponse resp = ...;
|
||||
* resp.addHeader("Warning", wv.toString());
|
||||
* </pre>
|
||||
* @return {@link String}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (warnDate != null) {
|
||||
return String.format("%d %s %s \"%s\"", warnCode,
|
||||
warnAgent, warnText, DateUtils.formatStandardDate(warnDate));
|
||||
} else {
|
||||
return String.format("%d %s %s", warnCode, warnAgent, warnText);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -181,20 +181,6 @@ public class TestHttpCacheEntryFactory {
|
|||
MatcherAssert.assertThat(mergedHeaders, ContainsHeaderMatcher.contains("Cache-Control", "public"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entry1xxWarningsAreRemovedOnMerge() {
|
||||
entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo,
|
||||
new BasicHeader("Warning", "110 fred \"Response is stale\""),
|
||||
new BasicHeader("ETag", "\"old\""),
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(eightSecondsAgo)));
|
||||
response.setHeader("ETag", "\"new\"");
|
||||
response.setHeader("Date", DateUtils.formatStandardDate(twoSecondsAgo));
|
||||
|
||||
final HeaderGroup mergedHeaders = impl.mergeHeaders(entry, response);
|
||||
|
||||
Assertions.assertEquals(0, mergedHeaders.countHeaders("Warning"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entryWithMalformedDateIsStillUpdated() throws Exception {
|
||||
entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo,
|
||||
|
|
|
@ -29,7 +29,6 @@ package org.apache.hc.client5.http.impl.cache;
|
|||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
|
@ -43,9 +42,7 @@ import org.apache.hc.core5.concurrent.FutureCallback;
|
|||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HeaderElement;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpMessage;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
|
@ -58,7 +55,6 @@ import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
|
|||
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.apache.hc.core5.http.message.HeaderGroup;
|
||||
import org.apache.hc.core5.http.message.MessageSupport;
|
||||
import org.apache.hc.core5.util.ByteArrayBuffer;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
|
@ -364,20 +360,6 @@ public class HttpTestUtils {
|
|||
return new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not modified");
|
||||
}
|
||||
|
||||
public static final void assert110WarningFound(final HttpResponse response) {
|
||||
boolean found110Warning = false;
|
||||
final Iterator<HeaderElement> it = MessageSupport.iterate(response, HttpHeaders.WARNING);
|
||||
while (it.hasNext()) {
|
||||
final HeaderElement elt = it.next();
|
||||
final String[] parts = elt.getName().split("\\s");
|
||||
if ("110".equals(parts[0])) {
|
||||
found110Warning = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assertions.assertTrue(found110Warning);
|
||||
}
|
||||
|
||||
public static ClassicHttpRequest makeDefaultRequest() {
|
||||
return new BasicClassicHttpRequest(Method.GET.toString(), "/");
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
@ -34,13 +33,8 @@ import static org.mockito.Mockito.when;
|
|||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import org.apache.hc.client5.http.schedule.SchedulingStrategy;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.message.BasicHttpResponse;
|
||||
import org.apache.hc.core5.util.TimeValue;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -124,24 +118,6 @@ public class TestCacheRevalidatorBase {
|
|||
Assertions.assertEquals(1, impl.getScheduledIdentifiers().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleResponse() {
|
||||
final HttpResponse response1 = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response1.addHeader(HttpHeaders.WARNING, "110 localhost \"Response is stale\"");
|
||||
assertThat(impl.isStale(response1), CoreMatchers.equalTo(true));
|
||||
|
||||
final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response2.addHeader(HttpHeaders.WARNING, "111 localhost \"Revalidation failed\"");
|
||||
assertThat(impl.isStale(response2), CoreMatchers.equalTo(true));
|
||||
|
||||
final HttpResponse response3 = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
response3.addHeader(HttpHeaders.WARNING, "xxx localhost \"Huh?\"");
|
||||
assertThat(impl.isStale(response3), CoreMatchers.equalTo(false));
|
||||
|
||||
final HttpResponse response4 = new BasicHttpResponse(HttpStatus.SC_OK);
|
||||
assertThat(impl.isStale(response4), CoreMatchers.equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShutdown() throws Exception {
|
||||
impl.close();
|
||||
|
|
|
@ -38,11 +38,6 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.hc.client5.http.HttpRoute;
|
||||
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
|
||||
|
@ -52,7 +47,6 @@ import org.apache.hc.client5.http.cache.HttpCacheContext;
|
|||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntryFactory;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheStorage;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.client5.http.classic.ExecChain;
|
||||
import org.apache.hc.client5.http.classic.ExecRuntime;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpDelete;
|
||||
|
@ -76,7 +70,6 @@ import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
|
|||
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.apache.hc.core5.net.URIAuthority;
|
||||
import org.apache.hc.core5.util.TimeValue;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -1472,74 +1465,6 @@ public class TestCachingExecChain {
|
|||
Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, cachedResponse.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleIfErrorEnabledWithIOException() throws Exception {
|
||||
|
||||
impl = new CachingExec(responseCache, validityPolicy, responseCachingPolicy,
|
||||
responseGenerator, cacheableRequestPolicy, suitabilityChecker,
|
||||
responseCompliance, requestCompliance, cacheRevalidator,
|
||||
conditionalRequestBuilder, customConfig);
|
||||
// Create the first request and response
|
||||
final BasicClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "http://foo.example.com/");
|
||||
final BasicClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "http://foo.example.com/");
|
||||
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "OK");
|
||||
resp1.setEntity(HttpTestUtils.makeBody(128));
|
||||
resp1.setHeader("Content-Length", "128");
|
||||
resp1.setHeader("ETag", "\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(Instant.now().minus(Duration.ofHours(10))));
|
||||
resp1.setHeader("Cache-Control", "public, max-age=-1, stale-while-revalidate=1");
|
||||
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
|
||||
|
||||
// Set up the mock response chain
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
Mockito.when(responseCachingPolicy.isStaleIfErrorEnabled(Mockito.any(), Mockito.any())).thenReturn(true);
|
||||
Mockito.when(cacheableRequestPolicy.isServableFromCache(Mockito.any(), Mockito.any())).thenReturn(true);
|
||||
Mockito.when(validityPolicy.getStaleness(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(TimeValue.MAX_VALUE);
|
||||
|
||||
// Assuming validityPolicy is a Mockito mock
|
||||
Mockito.when(validityPolicy.getCurrentAge(Mockito.any(), Mockito.any()))
|
||||
.thenReturn(TimeValue.ofMilliseconds(0));
|
||||
|
||||
// Assuming validityPolicy is a Mockito mock
|
||||
Mockito.when(validityPolicy.getFreshnessLifetime(Mockito.any(), Mockito.any()))
|
||||
.thenReturn(TimeValue.ofMilliseconds(0));
|
||||
|
||||
final SimpleHttpResponse response = SimpleHttpResponse.create(HttpStatus.SC_OK);
|
||||
final AtomicInteger callCount = new AtomicInteger(0);
|
||||
|
||||
Mockito.doAnswer(invocation -> {
|
||||
if (callCount.getAndIncrement() == 0) {
|
||||
throw new ResourceIOException("ResourceIOException");
|
||||
} else {
|
||||
// Replace this with the actual return value for the second call
|
||||
return response;
|
||||
}
|
||||
}).when(responseGenerator).generateResponse(Mockito.any(), Mockito.any());
|
||||
|
||||
// Execute the first request and assert the response
|
||||
final ClassicHttpResponse response1 = execute(req1);
|
||||
final HttpCacheEntry httpCacheEntry = HttpTestUtils.makeCacheEntry();
|
||||
Mockito.when(responseCache.match(Mockito.any(), Mockito.any())).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", httpCacheEntry), null));
|
||||
Assertions.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, response1.getCode());
|
||||
|
||||
Mockito.when(mockExecRuntime.fork(Mockito.any())).thenReturn(mockExecRuntime);
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
final ClassicHttpResponse response2 = execute(req2);
|
||||
// Verify that the response has the expected status code (e.g., 200)
|
||||
Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode());
|
||||
|
||||
// Verify that the response has the expected headers or other properties
|
||||
// For example, you can check for the "Warning" header indicating a stale response:
|
||||
final Optional<Header> warningHeader = Arrays.stream(response.getHeaders())
|
||||
.filter(header -> header.getName().equalsIgnoreCase("Warning"))
|
||||
.findFirst();
|
||||
|
||||
Assertions.assertTrue(warningHeader.isPresent());
|
||||
Assertions.assertTrue(warningHeader.get().getValue().contains("110"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCacheFieldsRevalidation() throws Exception {
|
||||
final Instant now = Instant.now();
|
||||
|
@ -1575,79 +1500,6 @@ public class TestCachingExecChain {
|
|||
Mockito.verify(mockExecChain, Mockito.times(5)).proceed(Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeuristicExpirationWarning() throws Exception {
|
||||
impl = new CachingExec(responseCache, validityPolicy, responseCachingPolicy,
|
||||
responseGenerator, cacheableRequestPolicy, suitabilityChecker,
|
||||
responseCompliance, requestCompliance, cacheRevalidator,
|
||||
conditionalRequestBuilder, customConfig);
|
||||
// Create the first request and response
|
||||
final BasicClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "http://foo.example.com/");
|
||||
final BasicClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "http://foo.example.com/");
|
||||
final ClassicHttpResponse resp1 = new BasicClassicHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "OK");
|
||||
resp1.setEntity(HttpTestUtils.makeBody(128));
|
||||
resp1.setHeader("Content-Length", "128");
|
||||
resp1.setHeader("ETag", "\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(Instant.now().minus(Duration.ofHours(10))));
|
||||
resp1.setHeader("Cache-Control", "public, max-age=-1, stale-while-revalidate=1");
|
||||
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
|
||||
|
||||
// Set up the mock response chain
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
Mockito.when(responseCachingPolicy.isStaleIfErrorEnabled(Mockito.any(), Mockito.any())).thenReturn(true);
|
||||
Mockito.when(cacheableRequestPolicy.isServableFromCache(Mockito.any(), Mockito.any())).thenReturn(true);
|
||||
Mockito.when(validityPolicy.getStaleness(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(TimeValue.MAX_VALUE);
|
||||
|
||||
// Assuming validityPolicy is a Mockito mock
|
||||
Mockito.when(validityPolicy.getCurrentAge(Mockito.any(), Mockito.any()))
|
||||
.thenReturn(TimeValue.ofDays(2));
|
||||
|
||||
// Assuming validityPolicy is a Mockito mock
|
||||
Mockito.when(validityPolicy.getFreshnessLifetime(Mockito.any(), Mockito.any()))
|
||||
.thenReturn(TimeValue.ofDays(2));
|
||||
|
||||
final SimpleHttpResponse response = SimpleHttpResponse.create(HttpStatus.SC_OK);
|
||||
final AtomicInteger callCount = new AtomicInteger(0);
|
||||
|
||||
Mockito.doAnswer(invocation -> {
|
||||
if (callCount.getAndIncrement() == 0) {
|
||||
throw new ResourceIOException("ResourceIOException");
|
||||
} else {
|
||||
// Replace this with the actual return value for the second call
|
||||
return response;
|
||||
}
|
||||
}).when(responseGenerator).generateResponse(Mockito.any(), Mockito.any());
|
||||
|
||||
// Execute the first request and assert the response
|
||||
final ClassicHttpResponse response1 = execute(req1);
|
||||
final HttpCacheEntry httpCacheEntry = HttpTestUtils.makeCacheEntry();
|
||||
Mockito.when(responseCache.match(Mockito.any(), Mockito.any())).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", httpCacheEntry), null));
|
||||
Assertions.assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT, response1.getCode());
|
||||
|
||||
Mockito.when(mockExecRuntime.fork(Mockito.any())).thenReturn(mockExecRuntime);
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
final ClassicHttpResponse response2 = execute(req2);
|
||||
// Verify that the response has the expected status code (e.g., 200)
|
||||
Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode());
|
||||
|
||||
// For example, you can check for the "Warning" header indicating a stale response:
|
||||
final List<Header> warningHeaders = Arrays.stream(response.getHeaders())
|
||||
.filter(header -> header.getName().equalsIgnoreCase("Warning"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
Assertions.assertFalse(warningHeaders.isEmpty());
|
||||
|
||||
final boolean found113 = warningHeaders.stream().anyMatch(header -> header.getValue().contains("113 localhost \"Heuristic expiration\""));
|
||||
Assertions.assertTrue(found113);
|
||||
|
||||
final boolean found110 = warningHeaders.stream().anyMatch(header -> header.getValue().contains("110 localhost \"Response is stale\""));
|
||||
Assertions.assertTrue(found110);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRequestWithWeakETagAndRange() throws Exception {
|
||||
final ClassicHttpRequest req1 = new HttpGet("http://foo1.example.com/");
|
||||
|
|
|
@ -502,118 +502,10 @@ public class TestProtocolRecommendations {
|
|||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, result.getCode());
|
||||
assertTrue(result.containsHeader("Warning"));
|
||||
|
||||
Mockito.verify(mockExecChain, Mockito.atMost(1)).proceed(Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
/*
|
||||
* "A correct cache MUST respond to a request with the most up-to-date
|
||||
* response held by the cache that is appropriate to the request
|
||||
* (see sections 13.2.5, 13.2.6, and 13.12) which meets one of the
|
||||
* following conditions:
|
||||
*
|
||||
* 1. It has been checked for equivalence with what the origin server
|
||||
* would have returned by revalidating the response with the
|
||||
* origin server (section 13.3);
|
||||
*
|
||||
* 2. It is "fresh enough" (see section 13.2). In the default case,
|
||||
* this means it meets the least restrictive freshness requirement
|
||||
* of the client, origin server, and cache (see section 14.9); if
|
||||
* the origin server so specifies, it is the freshness requirement
|
||||
* of the origin server alone.
|
||||
*
|
||||
* If a stored response is not "fresh enough" by the most
|
||||
* restrictive freshness requirement of both the client and the
|
||||
* origin server, in carefully considered circumstances the cache
|
||||
* MAY still return the response with the appropriate Warning
|
||||
* header (see section 13.1.5 and 14.46), unless such a response
|
||||
* is prohibited (e.g., by a "no-store" cache-directive, or by a
|
||||
* "no-cache" cache-request-directive; see section 14.9).
|
||||
*
|
||||
* 3. It is an appropriate 304 (Not Modified), 305 (Proxy Redirect),
|
||||
* or error (4xx or 5xx) response message.
|
||||
*
|
||||
* If the cache can not communicate with the origin server, then a
|
||||
* correct cache SHOULD respond as above if the response can be
|
||||
* correctly served from the cache..."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.1.1
|
||||
*/
|
||||
@Test
|
||||
public void testReturnsCachedResponsesAppropriatelyWhenNoOriginCommunication() throws Exception {
|
||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Cache-Control", "public, max-age=5");
|
||||
resp1.setHeader("ETag","\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
|
||||
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(new IOException());
|
||||
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
Mockito.verify(mockExecChain, Mockito.times(2)).proceed(Mockito.any(), Mockito.any());
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, result.getCode());
|
||||
boolean warning111Found = false;
|
||||
for(final Header h : result.getHeaders("Warning")) {
|
||||
for(final WarningValue wv : WarningValue.getWarningValues(h)) {
|
||||
if (wv.getWarnCode() == 111) {
|
||||
warning111Found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertTrue(warning111Found);
|
||||
}
|
||||
|
||||
/*
|
||||
* "If a cache receives a response (either an entire response, or a
|
||||
* 304 (Not Modified) response) that it would normally forward to the
|
||||
* requesting client, and the received response is no longer fresh,
|
||||
* the cache SHOULD forward it to the requesting client without adding
|
||||
* a new Warning (but without removing any existing Warning headers).
|
||||
* A cache SHOULD NOT attempt to revalidate a response simply because
|
||||
* that response became stale in transit; this might lead to an
|
||||
* infinite loop."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.1.1
|
||||
*/
|
||||
@Test
|
||||
public void testDoesNotAddNewWarningHeaderIfResponseArrivesStale() throws Exception {
|
||||
originResponse.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
originResponse.setHeader("Cache-Control","public, max-age=5");
|
||||
originResponse.setHeader("ETag","\"etag\"");
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
|
||||
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
|
||||
assertFalse(result.containsHeader("Warning"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForwardsExistingWarningHeadersOnResponseThatArrivesStale() throws Exception {
|
||||
originResponse.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
originResponse.setHeader("Cache-Control","public, max-age=5");
|
||||
originResponse.setHeader("ETag","\"etag\"");
|
||||
originResponse.addHeader("Age","10");
|
||||
final String warning = "110 fred \"Response is stale\"";
|
||||
originResponse.addHeader("Warning",warning);
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
|
||||
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
|
||||
MatcherAssert.assertThat(result, ContainsHeaderMatcher.contains("Warning", warning));
|
||||
}
|
||||
|
||||
/*
|
||||
* "A transparent proxy SHOULD NOT modify an end-to-end header unless
|
||||
* the definition of that header requires or specifically allows that."
|
||||
|
|
|
@ -411,13 +411,6 @@ public class TestProtocolRequirements {
|
|||
testOrderOfMultipleHeadersIsPreservedOnRequests(HttpHeaders.VIA, request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrderOfMultipleWarningHeadersIsPreservedOnRequests() throws Exception {
|
||||
request.addHeader("Warning", "199 fred \"bargle\"");
|
||||
request.addHeader("Warning", "199 barney \"bungle\"");
|
||||
testOrderOfMultipleHeadersIsPreservedOnRequests("Warning", request);
|
||||
}
|
||||
|
||||
private void testOrderOfMultipleHeadersIsPreservedOnResponses(final String h) throws Exception {
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
|
||||
|
||||
|
@ -1899,199 +1892,6 @@ public class TestProtocolRequirements {
|
|||
Assertions.assertEquals(200, result.getCode());
|
||||
}
|
||||
|
||||
/*
|
||||
* "If the cache can not communicate with the origin server, then a correct
|
||||
* cache SHOULD respond as above if the response can be correctly served
|
||||
* from the cache; if not it MUST return an error or warning indicating that
|
||||
* there was a communication failure."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.1.1
|
||||
*
|
||||
* "111 Revalidation failed MUST be included if a cache returns a stale
|
||||
* response because an attempt to revalidate the response failed, due to an
|
||||
* inability to reach the server."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.46
|
||||
*/
|
||||
@Test
|
||||
public void testMustServeAppropriateErrorOrWarningIfNoOriginCommunicationPossible() throws Exception {
|
||||
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
final Instant nineSecondsAgo = now.plusSeconds(9);
|
||||
final Instant eightSecondsAgo = now.plusSeconds(8);
|
||||
|
||||
final Header[] hdrs = new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(nineSecondsAgo)),
|
||||
new BasicHeader("Cache-Control", "max-age=0"),
|
||||
new BasicHeader("Content-Length", "128"),
|
||||
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(tenSecondsAgo))
|
||||
};
|
||||
|
||||
final byte[] bytes = new byte[128];
|
||||
new Random().nextBytes(bytes);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(tenSecondsAgo, eightSecondsAgo, hdrs, bytes);
|
||||
|
||||
impl = new CachingExec(mockCache, null, config);
|
||||
request = new BasicClassicHttpRequest("GET", "/thing");
|
||||
|
||||
Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", entry), null));
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenThrow(
|
||||
new IOException("can't talk to origin!"));
|
||||
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
|
||||
final int status = result.getCode();
|
||||
Assertions.assertEquals(200, result.getCode());
|
||||
boolean foundWarning = false;
|
||||
for (final Header h : result.getHeaders("Warning")) {
|
||||
if (h.getValue().split(" ")[0].equals("111")) {
|
||||
foundWarning = true;
|
||||
}
|
||||
}
|
||||
Assertions.assertTrue(foundWarning);
|
||||
}
|
||||
|
||||
/*
|
||||
* "Whenever a cache returns a response that is neither first-hand nor
|
||||
* "fresh enough" (in the sense of condition 2 in section 13.1.1), it MUST
|
||||
* attach a warning to that effect, using a Warning general-header."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.1.2
|
||||
*/
|
||||
@Test
|
||||
public void testAttachesWarningHeaderWhenGeneratingStaleResponse() throws Exception {
|
||||
// covered by previous test
|
||||
}
|
||||
|
||||
/*
|
||||
* "1xx Warnings that describe the freshness or revalidation status of the
|
||||
* response, and so MUST be deleted after a successful revalidation."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.1.2
|
||||
*/
|
||||
@Test
|
||||
public void test1xxWarningsAreDeletedAfterSuccessfulRevalidation() throws Exception {
|
||||
|
||||
final Instant now = Instant.now();
|
||||
final Instant twentyFiveSecondsAgo = now.minusSeconds(25);
|
||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(twentyFiveSecondsAgo));
|
||||
resp1.setHeader("ETag", "\"etag\"");
|
||||
resp1.setHeader("Cache-Control", "max-age=5");
|
||||
resp1.setHeader("Warning", "110 squid \"stale stuff\"");
|
||||
resp1.setHeader(HttpHeaders.VIA, "1.1 fred");
|
||||
|
||||
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
|
||||
|
||||
final ClassicHttpRequest validate = new BasicClassicHttpRequest("GET", "/");
|
||||
validate.setHeader("If-None-Match", "\"etag\"");
|
||||
|
||||
final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED,
|
||||
"Not Modified");
|
||||
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
resp2.setHeader("Server", "MockServer/1.0");
|
||||
resp2.setHeader("ETag", "\"etag\"");
|
||||
resp2.setHeader(HttpHeaders.VIA, "1.1 fred");
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(validate), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
|
||||
|
||||
|
||||
final ClassicHttpResponse stale = execute(req1);
|
||||
Assertions.assertNotNull(stale.getFirstHeader("Warning"));
|
||||
|
||||
final ClassicHttpResponse result1 = execute(req2);
|
||||
final ClassicHttpResponse result2 = execute(req3);
|
||||
|
||||
boolean found1xxWarning = false;
|
||||
final Iterator<HeaderElement> it = MessageSupport.iterate(result1, HttpHeaders.WARNING);
|
||||
while (it.hasNext()) {
|
||||
final HeaderElement elt = it.next();
|
||||
if (elt.getName().startsWith("1")) {
|
||||
found1xxWarning = true;
|
||||
}
|
||||
}
|
||||
final Iterator<HeaderElement> it2 = MessageSupport.iterate(result2, HttpHeaders.WARNING);
|
||||
while (it2.hasNext()) {
|
||||
final HeaderElement elt = it2.next();
|
||||
if (elt.getName().startsWith("1")) {
|
||||
found1xxWarning = true;
|
||||
}
|
||||
}
|
||||
Assertions.assertFalse(found1xxWarning);
|
||||
}
|
||||
|
||||
/*
|
||||
* "2xx Warnings that describe some aspect of the entity body or entity
|
||||
* headers that is not rectified by a revalidation (for example, a lossy
|
||||
* compression of the entity bodies) and which MUST NOT be deleted after a
|
||||
* successful revalidation."
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.1.2
|
||||
*/
|
||||
@Test
|
||||
public void test2xxWarningsAreNotDeletedAfterSuccessfulRevalidation() throws Exception {
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
resp1.setHeader("ETag", "\"etag\"");
|
||||
resp1.setHeader("Cache-Control", "max-age=5");
|
||||
resp1.setHeader(HttpHeaders.VIA, "1.1 xproxy");
|
||||
resp1.setHeader("Warning", "214 xproxy \"transformed stuff\"");
|
||||
|
||||
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
|
||||
|
||||
final ClassicHttpRequest validate = new BasicClassicHttpRequest("GET", "/");
|
||||
validate.setHeader("If-None-Match", "\"etag\"");
|
||||
|
||||
final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED,
|
||||
"Not Modified");
|
||||
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
resp2.setHeader("Server", "MockServer/1.0");
|
||||
resp2.setHeader("ETag", "\"etag\"");
|
||||
resp1.setHeader(HttpHeaders.VIA, "1.1 xproxy");
|
||||
|
||||
final ClassicHttpRequest req3 = new BasicClassicHttpRequest("GET", "/");
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
Mockito.when(mockExecChain.proceed(RequestEquivalent.eq(validate), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
final ClassicHttpResponse stale = execute(req1);
|
||||
Assertions.assertNotNull(stale.getFirstHeader("Warning"));
|
||||
|
||||
final ClassicHttpResponse result1 = execute(req2);
|
||||
final ClassicHttpResponse result2 = execute(req3);
|
||||
|
||||
boolean found214Warning = false;
|
||||
final Iterator<HeaderElement> it = MessageSupport.iterate(result1, HttpHeaders.WARNING);
|
||||
while (it.hasNext()) {
|
||||
final HeaderElement elt = it.next();
|
||||
final String[] parts = elt.getName().split(" ");
|
||||
if ("214".equals(parts[0])) {
|
||||
found214Warning = true;
|
||||
}
|
||||
}
|
||||
Assertions.assertTrue(found214Warning);
|
||||
|
||||
found214Warning = false;
|
||||
final Iterator<HeaderElement> it2 = MessageSupport.iterate(result2, HttpHeaders.WARNING);
|
||||
while (it2.hasNext()) {
|
||||
final HeaderElement elt = it2.next();
|
||||
final String[] parts = elt.getName().split(" ");
|
||||
if ("214".equals(parts[0])) {
|
||||
found214Warning = true;
|
||||
}
|
||||
}
|
||||
Assertions.assertTrue(found214Warning);
|
||||
}
|
||||
|
||||
/*
|
||||
* "When a response is generated from a cache entry, the cache MUST include
|
||||
* a single Age header field in the response with a value equal to the cache
|
||||
|
@ -2136,96 +1936,6 @@ public class TestProtocolRequirements {
|
|||
assertThat(result, ContainsHeaderMatcher.contains("Age", "10"));
|
||||
}
|
||||
|
||||
/*
|
||||
* "If none of Expires, Cache-Control: max-age, or Cache-Control: s-maxage
|
||||
* (see section 14.9.3) appears in the response, and the response does not
|
||||
* include other restrictions on caching, the cache MAY compute a freshness
|
||||
* lifetime using a heuristic. The cache MUST attach Warning 113 to any
|
||||
* response whose age is more than 24 hours if such warning has not already
|
||||
* been added."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.4
|
||||
*
|
||||
* "113 Heuristic expiration MUST be included if the cache heuristically
|
||||
* chose a freshness lifetime greater than 24 hours and the response's age
|
||||
* is greater than 24 hours."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.46
|
||||
*/
|
||||
@Test
|
||||
public void testHeuristicCacheOlderThan24HoursHasWarningAttached() throws Exception {
|
||||
|
||||
final Instant now = Instant.now();
|
||||
final Instant thirtySixHoursAgo = now.minus(26, ChronoUnit.HOURS);
|
||||
final Instant oneYearAgo = now.minus(1, ChronoUnit.HOURS);
|
||||
final Instant requestTime = thirtySixHoursAgo.minusSeconds(1);
|
||||
final Instant responseTime = thirtySixHoursAgo.plusSeconds(1);
|
||||
|
||||
final Header[] hdrs = new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatStandardDate(thirtySixHoursAgo)),
|
||||
new BasicHeader("Cache-Control", "public"),
|
||||
new BasicHeader("Last-Modified", DateUtils.formatStandardDate(oneYearAgo)),
|
||||
new BasicHeader("Content-Length", "128")
|
||||
};
|
||||
|
||||
final byte[] bytes = new byte[128];
|
||||
new Random().nextBytes(bytes);
|
||||
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(requestTime, responseTime, hdrs, bytes);
|
||||
|
||||
impl = new CachingExec(mockCache, null, config);
|
||||
|
||||
request = new BasicClassicHttpRequest("GET", "/thing");
|
||||
|
||||
final ClassicHttpResponse validated = HttpTestUtils.make200Response();
|
||||
validated.setHeader("Cache-Control", "public");
|
||||
validated.setHeader("Last-Modified", DateUtils.formatStandardDate(oneYearAgo));
|
||||
validated.setHeader("Content-Length", "128");
|
||||
validated.setEntity(new ByteArrayEntity(bytes, null));
|
||||
|
||||
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
Mockito.when(mockCache.match(Mockito.eq(host), RequestEquivalent.eq(request))).thenReturn(
|
||||
new CacheMatch(new CacheHit("key", entry), null));
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(validated);
|
||||
Mockito.when(mockCache.store(
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
ResponseEquivalent.eq(validated),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any())).thenReturn(new CacheHit("key", cacheEntry));
|
||||
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
|
||||
Assertions.assertEquals(200, result.getCode());
|
||||
|
||||
final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
|
||||
Mockito.verify(mockExecChain, Mockito.atMostOnce()).proceed(reqCapture.capture(), Mockito.any());
|
||||
final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
|
||||
if (allRequests.isEmpty()) {
|
||||
// heuristic cache hit
|
||||
boolean found113Warning = false;
|
||||
final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.WARNING);
|
||||
while (it.hasNext()) {
|
||||
final HeaderElement elt = it.next();
|
||||
final String[] parts = elt.getName().split(" ");
|
||||
if ("113".equals(parts[0])) {
|
||||
found113Warning = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assertions.assertTrue(found113Warning);
|
||||
}
|
||||
Mockito.verify(mockCache).store(
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any(),
|
||||
Mockito.any());
|
||||
}
|
||||
|
||||
/*
|
||||
* "If a cache has two fresh responses for the same representation with
|
||||
* different validators, it MUST use the one with the more recent Date
|
||||
|
@ -4183,57 +3893,6 @@ public class TestProtocolRequirements {
|
|||
testSharedCacheMustUseNewRequestHeadersWhenRevalidatingAuthorizedResponse(resp1);
|
||||
}
|
||||
|
||||
/* "If a cache returns a stale response, either because of a max-stale
|
||||
* directive on a request, or because the cache is configured to
|
||||
* override the expiration time of a response, the cache MUST attach a
|
||||
* Warning header to the stale response, using Warning 110 (Response
|
||||
* is stale).
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3
|
||||
*
|
||||
* "110 Response is stale MUST be included whenever the returned
|
||||
* response is stale."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.46
|
||||
*/
|
||||
@Test
|
||||
public void testWarning110IsAddedToStaleResponses() throws Exception {
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
resp1.setHeader("Cache-Control","max-age=5");
|
||||
resp1.setHeader("Etag","\"etag\"");
|
||||
|
||||
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
|
||||
req2.setHeader("Cache-Control","max-stale=60");
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
final ArgumentCaptor<ClassicHttpRequest> reqCapture = ArgumentCaptor.forClass(ClassicHttpRequest.class);
|
||||
Mockito.verify(mockExecChain, Mockito.atMostOnce()).proceed(reqCapture.capture(), Mockito.any());
|
||||
|
||||
final List<ClassicHttpRequest> allRequests = reqCapture.getAllValues();
|
||||
if (allRequests.isEmpty()) {
|
||||
boolean found110Warning = false;
|
||||
final Iterator<HeaderElement> it = MessageSupport.iterate(result, HttpHeaders.WARNING);
|
||||
while (it.hasNext()) {
|
||||
final HeaderElement elt = it.next();
|
||||
final String[] parts = elt.getName().split("\\s");
|
||||
if ("110".equals(parts[0])) {
|
||||
found110Warning = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assertions.assertTrue(found110Warning);
|
||||
}
|
||||
}
|
||||
|
||||
/* "The request includes a "no-cache" cache-control directive or, for
|
||||
* compatibility with HTTP/1.0 clients, "Pragma: no-cache".... The
|
||||
* server MUST NOT use a cached copy when responding to such a request."
|
||||
|
@ -4940,166 +4599,4 @@ public class TestProtocolRequirements {
|
|||
Assertions.assertEquals("1.0", protoParts[protoParts.length - 1]);
|
||||
}
|
||||
|
||||
/* "A cache MUST NOT delete any Warning header that it received with
|
||||
* a message."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.46
|
||||
*/
|
||||
@Test
|
||||
public void testRetainsWarningHeadersReceivedFromUpstream() throws Exception {
|
||||
originResponse.removeHeaders("Warning");
|
||||
final String warning = "199 fred \"misc\"";
|
||||
originResponse.addHeader("Warning", warning);
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
|
||||
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
Assertions.assertEquals(warning,
|
||||
result.getFirstHeader("Warning").getValue());
|
||||
}
|
||||
|
||||
/* "However, if a cache successfully validates a cache entry, it
|
||||
* SHOULD remove any Warning headers previously attached to that
|
||||
* entry except as specified for specific Warning codes. It MUST
|
||||
* then add any Warning headers received in the validating response."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.46
|
||||
*/
|
||||
@Test
|
||||
public void testUpdatesWarningHeadersOnValidation() throws Exception {
|
||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
|
||||
|
||||
final Instant now = Instant.now();
|
||||
final Instant twentySecondsAgo = now.plusSeconds(20);
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(twentySecondsAgo));
|
||||
resp1.setHeader("Cache-Control","public,max-age=5");
|
||||
resp1.setHeader("ETag", "\"etag1\"");
|
||||
final String oldWarning = "113 wilma \"stale\"";
|
||||
resp1.setHeader("Warning", oldWarning);
|
||||
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
final ClassicHttpResponse resp2 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not Modified");
|
||||
resp2.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
resp2.setHeader("ETag", "\"etag1\"");
|
||||
final String newWarning = "113 betty \"stale too\"";
|
||||
resp2.setHeader("Warning", newWarning);
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
execute(req1);
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
boolean oldWarningFound = false;
|
||||
boolean newWarningFound = false;
|
||||
for(final Header h : result.getHeaders("Warning")) {
|
||||
for(final String warnValue : h.getValue().split("\\s*,\\s*")) {
|
||||
if (oldWarning.equals(warnValue)) {
|
||||
oldWarningFound = true;
|
||||
} else if (newWarning.equals(warnValue)) {
|
||||
newWarningFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Assertions.assertFalse(oldWarningFound);
|
||||
Assertions.assertTrue(newWarningFound);
|
||||
}
|
||||
|
||||
/* "If an implementation sends a message with one or more Warning
|
||||
* headers whose version is HTTP/1.0 or lower, then the sender MUST
|
||||
* include in each warning-value a warn-date that matches the date
|
||||
* in the response."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.46
|
||||
*/
|
||||
@Test
|
||||
public void testWarnDatesAreAddedToWarningsOnLowerProtocolVersions() throws Exception {
|
||||
final String dateHdr = DateUtils.formatStandardDate(Instant.now());
|
||||
final String origWarning = "110 fred \"stale\"";
|
||||
originResponse.setCode(HttpStatus.SC_OK);
|
||||
originResponse.setVersion(HttpVersion.HTTP_1_0);
|
||||
originResponse.addHeader("Warning", origWarning);
|
||||
originResponse.setHeader("Date", dateHdr);
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
// note that currently the implementation acts as an HTTP/1.1 proxy,
|
||||
// which means that all the responses from the caching module should
|
||||
// be HTTP/1.1, so we won't actually be testing anything here until
|
||||
// that changes.
|
||||
if (HttpVersion.HTTP_1_0.greaterEquals(result.getVersion())) {
|
||||
Assertions.assertEquals(dateHdr, result.getFirstHeader("Date").getValue());
|
||||
boolean warningFound = false;
|
||||
final String targetWarning = origWarning + " \"" + dateHdr + "\"";
|
||||
for(final Header h : result.getHeaders("Warning")) {
|
||||
for(final String warning : h.getValue().split("\\s*,\\s*")) {
|
||||
if (targetWarning.equals(warning)) {
|
||||
warningFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Assertions.assertTrue(warningFound);
|
||||
}
|
||||
}
|
||||
|
||||
/* "If an implementation receives a message with a warning-value that
|
||||
* includes a warn-date, and that warn-date is different from the Date
|
||||
* value in the response, then that warning-value MUST be deleted from
|
||||
* the message before storing, forwarding, or using it. (This prevents
|
||||
* bad consequences of naive caching of Warning header fields.) If all
|
||||
* of the warning-values are deleted for this reason, the Warning
|
||||
* header MUST be deleted as well."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.46
|
||||
*/
|
||||
@Test
|
||||
public void testStripsBadlyDatedWarningsFromForwardedResponses() throws Exception {
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
originResponse.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
originResponse.addHeader("Warning", "110 fred \"stale\", 110 wilma \"stale\" \""
|
||||
+ DateUtils.formatStandardDate(tenSecondsAgo) + "\"");
|
||||
originResponse.setHeader("Cache-Control","no-cache,no-store");
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
|
||||
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
|
||||
for(final Header h : result.getHeaders("Warning")) {
|
||||
Assertions.assertFalse(h.getValue().contains("wilma"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStripsBadlyDatedWarningsFromStoredResponses() throws Exception {
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
originResponse.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
originResponse.addHeader("Warning", "110 fred \"stale\", 110 wilma \"stale\" \""
|
||||
+ DateUtils.formatStandardDate(tenSecondsAgo) + "\"");
|
||||
originResponse.setHeader("Cache-Control","public,max-age=3600");
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
|
||||
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
|
||||
for(final Header h : result.getHeaders("Warning")) {
|
||||
Assertions.assertFalse(h.getValue().contains("wilma"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovesWarningHeaderIfAllWarnValuesAreBadlyDated() throws Exception {
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
originResponse.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
originResponse.addHeader("Warning", "110 wilma \"stale\" \""
|
||||
+ DateUtils.formatStandardDate(tenSecondsAgo) + "\"");
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(originResponse);
|
||||
|
||||
final ClassicHttpResponse result = execute(request);
|
||||
|
||||
final Header[] warningHeaders = result.getHeaders("Warning");
|
||||
Assertions.assertTrue(warningHeaders == null || warningHeaders.length == 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ package org.apache.hc.client5.http.impl.cache;
|
|||
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
@ -40,12 +39,9 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|||
import org.apache.hc.client5.http.HttpRoute;
|
||||
import org.apache.hc.client5.http.classic.ExecChain;
|
||||
import org.apache.hc.client5.http.classic.ExecRuntime;
|
||||
import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.client5.http.utils.DateUtils;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.http.HttpException;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
|
@ -128,43 +124,6 @@ public class TestRFC5861Compliance {
|
|||
mockExecChain);
|
||||
}
|
||||
|
||||
/*
|
||||
* "The stale-if-error Cache-Control extension indicates that when an
|
||||
* error is encountered, a cached stale response MAY be used to satisfy
|
||||
* the request, regardless of other freshness information.When used as a
|
||||
* request Cache-Control extension, its scope of application is the request
|
||||
* it appears in; when used as a response Cache-Control extension, its
|
||||
* scope is any request applicable to the cached response in which it
|
||||
* occurs.Its value indicates the upper limit to staleness; when the cached
|
||||
* response is more stale than the indicated amount, the cached response
|
||||
* SHOULD NOT be used to satisfy the request, absent other information.
|
||||
* In this context, an error is any situation that would result in a
|
||||
* 500, 502, 503, or 504 HTTP response status code being returned."
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc5861
|
||||
*/
|
||||
@Test
|
||||
public void testStaleIfErrorInResponseIsTrueReturnsStaleEntryWithWarning()
|
||||
throws Exception{
|
||||
final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
|
||||
final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5, stale-if-error=60");
|
||||
|
||||
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
HttpTestUtils.assert110WarningFound(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConsumesErrorResponseWhenServingStale()
|
||||
throws Exception{
|
||||
|
@ -237,32 +196,6 @@ public class TestRFC5861Compliance {
|
|||
assertTrue(HttpStatus.SC_OK != result.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleIfErrorInResponseNeedNotYieldToProxyRevalidateForPrivateCache()
|
||||
throws Exception{
|
||||
final CacheConfig configUnshared = CacheConfig.custom()
|
||||
.setSharedCache(false).build();
|
||||
impl = new CachingExec(new BasicHttpCache(configUnshared), null, configUnshared);
|
||||
|
||||
final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
|
||||
final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5, stale-if-error=60, proxy-revalidate");
|
||||
|
||||
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
HttpTestUtils.assert110WarningFound(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleIfErrorInResponseYieldsToExplicitFreshnessRequest()
|
||||
throws Exception{
|
||||
|
@ -286,53 +219,6 @@ public class TestRFC5861Compliance {
|
|||
assertTrue(HttpStatus.SC_OK != result.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleIfErrorInRequestIsTrueReturnsStaleEntryWithWarning()
|
||||
throws Exception{
|
||||
final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
|
||||
final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
|
||||
"public, max-age=5");
|
||||
|
||||
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
req2.setHeader("Cache-Control","public, stale-if-error=60");
|
||||
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
HttpTestUtils.assert110WarningFound(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleIfErrorInRequestIsTrueReturnsStaleNonRevalidatableEntryWithWarning()
|
||||
throws Exception {
|
||||
final Instant tenSecondsAgo = Instant.now().minusSeconds(10);
|
||||
final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
resp1.setHeader("Cache-Control", "public, max-age=5");
|
||||
|
||||
final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
|
||||
req2.setHeader("Cache-Control", "public, stale-if-error=60");
|
||||
final ClassicHttpResponse resp2 = HttpTestUtils.make500Response();
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
HttpTestUtils.assert110WarningFound(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleIfErrorInResponseIsFalseReturnsError()
|
||||
throws Exception{
|
||||
|
@ -381,280 +267,4 @@ public class TestRFC5861Compliance {
|
|||
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, result.getCode());
|
||||
}
|
||||
|
||||
/*
|
||||
* When present in an HTTP response, the stale-while-revalidate Cache-
|
||||
* Control extension indicates that caches MAY serve the response in
|
||||
* which it appears after it becomes stale, up to the indicated number
|
||||
* of seconds.
|
||||
*
|
||||
* http://tools.ietf.org/html/rfc5861
|
||||
*/
|
||||
@Test
|
||||
public void testStaleWhileRevalidateReturnsStaleEntryWithWarning()
|
||||
throws Exception {
|
||||
config = CacheConfig.custom()
|
||||
.setMaxCacheEntries(MAX_ENTRIES)
|
||||
.setMaxObjectSize(MAX_BYTES)
|
||||
.setAsynchronousWorkers(1)
|
||||
.build();
|
||||
|
||||
impl = new CachingExec(cache, executorService, ImmediateSchedulingStrategy.INSTANCE, config);
|
||||
|
||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
|
||||
resp1.setHeader("ETag","\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
|
||||
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, result.getCode());
|
||||
boolean warning110Found = false;
|
||||
for(final Header h : result.getHeaders("Warning")) {
|
||||
for(final WarningValue wv : WarningValue.getWarningValues(h)) {
|
||||
if (wv.getWarnCode() == 110) {
|
||||
warning110Found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertTrue(warning110Found);
|
||||
|
||||
Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
|
||||
Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleWhileRevalidateReturnsStaleNonRevalidatableEntryWithWarning()
|
||||
throws Exception {
|
||||
config = CacheConfig.custom().setMaxCacheEntries(MAX_ENTRIES).setMaxObjectSize(MAX_BYTES)
|
||||
.setAsynchronousWorkers(1).build();
|
||||
|
||||
impl = new CachingExec(cache, executorService, ImmediateSchedulingStrategy.INSTANCE, config);
|
||||
|
||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
|
||||
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, result.getCode());
|
||||
boolean warning110Found = false;
|
||||
for (final Header h : result.getHeaders("Warning")) {
|
||||
for (final WarningValue wv : WarningValue.getWarningValues(h)) {
|
||||
if (wv.getWarnCode() == 110) {
|
||||
warning110Found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertTrue(warning110Found);
|
||||
|
||||
Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
|
||||
Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanAlsoServeStale304sWhileRevalidating()
|
||||
throws Exception {
|
||||
|
||||
config = CacheConfig.custom()
|
||||
.setMaxCacheEntries(MAX_ENTRIES)
|
||||
.setMaxObjectSize(MAX_BYTES)
|
||||
.setAsynchronousWorkers(1)
|
||||
.setSharedCache(false)
|
||||
.build();
|
||||
impl = new CachingExec(cache, executorService, ImmediateSchedulingStrategy.INSTANCE, config);
|
||||
|
||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
resp1.setHeader("Cache-Control", "private, stale-while-revalidate=15");
|
||||
resp1.setHeader("ETag","\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
|
||||
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
|
||||
req2.setHeader("If-None-Match","\"etag\"");
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
|
||||
boolean warning110Found = false;
|
||||
for(final Header h : result.getHeaders("Warning")) {
|
||||
for(final WarningValue wv : WarningValue.getWarningValues(h)) {
|
||||
if (wv.getWarnCode() == 110) {
|
||||
warning110Found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertTrue(warning110Found);
|
||||
|
||||
Mockito.verify(mockExecChain, Mockito.atLeastOnce()).proceed(Mockito.any(), Mockito.any());
|
||||
Mockito.verify(mockExecChain, Mockito.atMost(2)).proceed(Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleWhileRevalidateYieldsToMustRevalidate()
|
||||
throws Exception {
|
||||
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
|
||||
config = CacheConfig.custom()
|
||||
.setMaxCacheEntries(MAX_ENTRIES)
|
||||
.setMaxObjectSize(MAX_BYTES)
|
||||
.setAsynchronousWorkers(1)
|
||||
.build();
|
||||
impl = new CachingExec(cache, null, config);
|
||||
|
||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate");
|
||||
resp1.setHeader("ETag","\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
|
||||
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
|
||||
resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate");
|
||||
resp2.setHeader("ETag","\"etag\"");
|
||||
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, result.getCode());
|
||||
boolean warning110Found = false;
|
||||
for(final Header h : result.getHeaders("Warning")) {
|
||||
for(final WarningValue wv : WarningValue.getWarningValues(h)) {
|
||||
if (wv.getWarnCode() == 110) {
|
||||
warning110Found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertFalse(warning110Found);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleWhileRevalidateYieldsToProxyRevalidateForSharedCache()
|
||||
throws Exception {
|
||||
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
|
||||
config = CacheConfig.custom()
|
||||
.setMaxCacheEntries(MAX_ENTRIES)
|
||||
.setMaxObjectSize(MAX_BYTES)
|
||||
.setAsynchronousWorkers(1)
|
||||
.setSharedCache(true)
|
||||
.build();
|
||||
impl = new CachingExec(cache, null, config);
|
||||
|
||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate");
|
||||
resp1.setHeader("ETag","\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
|
||||
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
|
||||
resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate");
|
||||
resp2.setHeader("ETag","\"etag\"");
|
||||
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, result.getCode());
|
||||
boolean warning110Found = false;
|
||||
for(final Header h : result.getHeaders("Warning")) {
|
||||
for(final WarningValue wv : WarningValue.getWarningValues(h)) {
|
||||
if (wv.getWarnCode() == 110) {
|
||||
warning110Found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertFalse(warning110Found);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaleWhileRevalidateYieldsToExplicitFreshnessRequest()
|
||||
throws Exception {
|
||||
|
||||
final Instant now = Instant.now();
|
||||
final Instant tenSecondsAgo = now.minusSeconds(10);
|
||||
|
||||
config = CacheConfig.custom()
|
||||
.setMaxCacheEntries(MAX_ENTRIES)
|
||||
.setMaxObjectSize(MAX_BYTES)
|
||||
.setAsynchronousWorkers(1)
|
||||
.setSharedCache(true)
|
||||
.build();
|
||||
impl = new CachingExec(cache, null, config);
|
||||
|
||||
final ClassicHttpRequest req1 = new BasicClassicHttpRequest("GET", "/");
|
||||
final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
|
||||
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
|
||||
resp1.setHeader("ETag","\"etag\"");
|
||||
resp1.setHeader("Date", DateUtils.formatStandardDate(tenSecondsAgo));
|
||||
|
||||
final ClassicHttpRequest req2 = new BasicClassicHttpRequest("GET", "/");
|
||||
req2.setHeader("Cache-Control","min-fresh=2");
|
||||
final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
|
||||
resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
|
||||
resp2.setHeader("ETag","\"etag\"");
|
||||
resp2.setHeader("Date", DateUtils.formatStandardDate(now));
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
|
||||
|
||||
execute(req1);
|
||||
|
||||
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
|
||||
|
||||
final ClassicHttpResponse result = execute(req2);
|
||||
|
||||
assertEquals(HttpStatus.SC_OK, result.getCode());
|
||||
boolean warning110Found = false;
|
||||
for(final Header h : result.getHeaders("Warning")) {
|
||||
for(final WarningValue wv : WarningValue.getWarningValues(h)) {
|
||||
if (wv.getWarnCode() == 110) {
|
||||
warning110Found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertFalse(warning110Found);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,230 +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.http.Header;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class TestWarningValue {
|
||||
|
||||
@Test
|
||||
public void testParseSingleWarnValue() {
|
||||
final Header h = new BasicHeader("Warning","110 fred \"stale\"");
|
||||
final WarningValue[] result = WarningValue.getWarningValues(h);
|
||||
Assertions.assertEquals(1, result.length);
|
||||
final WarningValue wv = result[0];
|
||||
Assertions.assertEquals(110, wv.getWarnCode());
|
||||
Assertions.assertEquals("fred", wv.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", wv.getWarnText());
|
||||
Assertions.assertNull(wv.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseMultipleWarnValues() {
|
||||
final Header h = new BasicHeader("Warning","110 fred \"stale\", 111 wilma \"other\"");
|
||||
final WarningValue[] result = WarningValue.getWarningValues(h);
|
||||
Assertions.assertEquals(2, result.length);
|
||||
WarningValue wv = result[0];
|
||||
Assertions.assertEquals(110, wv.getWarnCode());
|
||||
Assertions.assertEquals("fred", wv.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", wv.getWarnText());
|
||||
Assertions.assertNull(wv.getWarnDate());
|
||||
wv = result[1];
|
||||
Assertions.assertEquals(111, wv.getWarnCode());
|
||||
Assertions.assertEquals("wilma", wv.getWarnAgent());
|
||||
Assertions.assertEquals("\"other\"", wv.getWarnText());
|
||||
Assertions.assertNull(wv.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMidHeaderParseErrorRecovery() {
|
||||
final Header h = new BasicHeader("Warning","110 fred \"stale\", bogus, 111 wilma \"other\"");
|
||||
final WarningValue[] result = WarningValue.getWarningValues(h);
|
||||
Assertions.assertEquals(2, result.length);
|
||||
WarningValue wv = result[0];
|
||||
Assertions.assertEquals(110, wv.getWarnCode());
|
||||
Assertions.assertEquals("fred", wv.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", wv.getWarnText());
|
||||
Assertions.assertNull(wv.getWarnDate());
|
||||
wv = result[1];
|
||||
Assertions.assertEquals(111, wv.getWarnCode());
|
||||
Assertions.assertEquals("wilma", wv.getWarnAgent());
|
||||
Assertions.assertEquals("\"other\"", wv.getWarnText());
|
||||
Assertions.assertNull(wv.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrickyCommaMidHeaderParseErrorRecovery() {
|
||||
final Header h = new BasicHeader("Warning","110 fred \"stale\", \"bogus, dude\", 111 wilma \"other\"");
|
||||
final WarningValue[] result = WarningValue.getWarningValues(h);
|
||||
Assertions.assertEquals(2, result.length);
|
||||
WarningValue wv = result[0];
|
||||
Assertions.assertEquals(110, wv.getWarnCode());
|
||||
Assertions.assertEquals("fred", wv.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", wv.getWarnText());
|
||||
Assertions.assertNull(wv.getWarnDate());
|
||||
wv = result[1];
|
||||
Assertions.assertEquals(111, wv.getWarnCode());
|
||||
Assertions.assertEquals("wilma", wv.getWarnAgent());
|
||||
Assertions.assertEquals("\"other\"", wv.getWarnText());
|
||||
Assertions.assertNull(wv.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseErrorRecoveryAtEndOfHeader() {
|
||||
final Header h = new BasicHeader("Warning","110 fred \"stale\", 111 wilma \"other\", \"bogus, dude\"");
|
||||
final WarningValue[] result = WarningValue.getWarningValues(h);
|
||||
Assertions.assertEquals(2, result.length);
|
||||
WarningValue wv = result[0];
|
||||
Assertions.assertEquals(110, wv.getWarnCode());
|
||||
Assertions.assertEquals("fred", wv.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", wv.getWarnText());
|
||||
Assertions.assertNull(wv.getWarnDate());
|
||||
wv = result[1];
|
||||
Assertions.assertEquals(111, wv.getWarnCode());
|
||||
Assertions.assertEquals("wilma", wv.getWarnAgent());
|
||||
Assertions.assertEquals("\"other\"", wv.getWarnText());
|
||||
Assertions.assertNull(wv.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructSingleWarnValue() {
|
||||
final WarningValue impl = new WarningValue("110 fred \"stale\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("fred", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assertions.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithIPv4Address() {
|
||||
final WarningValue impl = new WarningValue("110 192.168.1.1 \"stale\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("192.168.1.1", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assertions.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithHostname() {
|
||||
final WarningValue impl = new WarningValue("110 foo.example.com \"stale\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("foo.example.com", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assertions.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithHostnameAndPort() {
|
||||
final WarningValue impl = new WarningValue("110 foo.example.com:8080 \"stale\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("foo.example.com:8080", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assertions.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithIPv4AddressAndPort() {
|
||||
final WarningValue impl = new WarningValue("110 192.168.1.1:8080 \"stale\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("192.168.1.1:8080", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assertions.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithPseudonym() {
|
||||
final WarningValue impl = new WarningValue("110 ca$hm0ney \"stale\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("ca$hm0ney", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assertions.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithTextWithSpaces() {
|
||||
final WarningValue impl = new WarningValue("110 fred \"stale stuff\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("fred", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale stuff\"", impl.getWarnText());
|
||||
Assertions.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithTextWithCommas() {
|
||||
final WarningValue impl = new WarningValue("110 fred \"stale, stuff\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("fred", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale, stuff\"", impl.getWarnText());
|
||||
Assertions.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithTextWithEscapedQuotes() {
|
||||
final WarningValue impl = new WarningValue("110 fred \"stale\\\" stuff\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("fred", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\\\" stuff\"", impl.getWarnText());
|
||||
Assertions.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithAscTimeWarnDate() throws Exception {
|
||||
final WarningValue impl = new WarningValue("110 fred \"stale\" \"Sun Nov 6 08:49:37 1994\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("fred", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", impl.getWarnText());
|
||||
final Instant target = DateUtils.parseStandardDate("Sun Nov 6 08:49:37 1994");
|
||||
Assertions.assertEquals(target, impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithRFC850WarnDate() throws Exception {
|
||||
final WarningValue impl = new WarningValue("110 fred \"stale\" \"Sunday, 06-Nov-94 08:49:37 GMT\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("fred", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", impl.getWarnText());
|
||||
final Instant target = DateUtils.parseStandardDate("Sunday, 06-Nov-94 08:49:37 GMT");
|
||||
Assertions.assertEquals(target, impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithRFC1123WarnDate() throws Exception {
|
||||
final WarningValue impl = new WarningValue("110 fred \"stale\" \"Sun, 06 Nov 1994 08:49:37 GMT\"");
|
||||
Assertions.assertEquals(110, impl.getWarnCode());
|
||||
Assertions.assertEquals("fred", impl.getWarnAgent());
|
||||
Assertions.assertEquals("\"stale\"", impl.getWarnText());
|
||||
final Instant target = DateUtils.parseStandardDate("Sun, 06 Nov 1994 08:49:37 GMT");
|
||||
Assertions.assertEquals(target, impl.getWarnDate());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue