HTTPCLIENT-986: cache module does not completely handle upstream Warning headers correctly
Contributed by Jonathan Moore <jonathan_moore at comcast.com> git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@993139 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
b368f90913
commit
eb64e7d3b8
|
@ -107,9 +107,8 @@ class CacheEntryUpdater {
|
|||
}
|
||||
|
||||
removeCacheHeadersThatMatchResponse(cacheEntryHeaderList, response);
|
||||
|
||||
cacheEntryHeaderList.addAll(Arrays.asList(response.getAllHeaders()));
|
||||
removeCacheEntry1xxWarnings(cacheEntryHeaderList, entry);
|
||||
cacheEntryHeaderList.addAll(Arrays.asList(response.getAllHeaders()));
|
||||
|
||||
return cacheEntryHeaderList.toArray(new Header[cacheEntryHeaderList.size()]);
|
||||
}
|
||||
|
|
|
@ -26,8 +26,11 @@
|
|||
*/
|
||||
package org.apache.http.impl.client.cache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
|
@ -38,7 +41,9 @@ import org.apache.http.annotation.Immutable;
|
|||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.cache.HeaderConstants;
|
||||
import org.apache.http.impl.client.RequestWrapper;
|
||||
import org.apache.http.impl.cookie.DateParseException;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.protocol.HTTP;
|
||||
|
||||
/**
|
||||
|
@ -77,6 +82,38 @@ class ResponseProtocolCompliance {
|
|||
ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(request, response);
|
||||
|
||||
ensure206ContainsDateHeader(response);
|
||||
|
||||
warningsWithNonMatchingWarnDatesAreRemoved(response);
|
||||
}
|
||||
|
||||
private void warningsWithNonMatchingWarnDatesAreRemoved(
|
||||
HttpResponse response) {
|
||||
Date responseDate = null;
|
||||
try {
|
||||
responseDate = DateUtils.parseDate(response.getFirstHeader("Date").getValue());
|
||||
} catch (DateParseException e) {
|
||||
}
|
||||
if (responseDate == null) return;
|
||||
Header[] warningHeaders = response.getHeaders("Warning");
|
||||
if (warningHeaders == null || warningHeaders.length == 0) return;
|
||||
List<Header> newWarningHeaders = new ArrayList<Header>();
|
||||
boolean modified = false;
|
||||
for(Header h : warningHeaders) {
|
||||
for(WarningValue wv : WarningValue.getWarningValues(h)) {
|
||||
Date warnDate = wv.getWarnDate();
|
||||
if (warnDate == null || warnDate.equals(responseDate)) {
|
||||
newWarningHeaders.add(new BasicHeader("Warning",wv.toString()));
|
||||
} else {
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (modified) {
|
||||
response.removeHeaders("Warning");
|
||||
for(Header h : newWarningHeaders) {
|
||||
response.addHeader(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void authenticationRequiredDidNotHaveAProxyAuthenticationHeader(HttpRequest request,
|
||||
|
|
358
httpclient-cache/src/main/java/org/apache/http/impl/client/cache/WarningValue.java
vendored
Normal file
358
httpclient-cache/src/main/java/org/apache/http/impl/client/cache/WarningValue.java
vendored
Normal file
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.http.impl.client.cache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.impl.cookie.DateParseException;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
|
||||
/** 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#getElements()} call to access the values.
|
||||
*/
|
||||
class WarningValue {
|
||||
|
||||
private int offs;
|
||||
private int init_offs;
|
||||
private String src;
|
||||
private int warnCode;
|
||||
private String warnAgent;
|
||||
private String warnText;
|
||||
private Date warnDate;
|
||||
|
||||
WarningValue(String s) {
|
||||
this(s, 0);
|
||||
}
|
||||
|
||||
WarningValue(String s, int offs) {
|
||||
this.offs = this.init_offs = offs;
|
||||
this.src = s;
|
||||
consumeWarnValue();
|
||||
}
|
||||
|
||||
/** Returns an array of the parseable 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</code> objects
|
||||
*/
|
||||
public static WarningValue[] getWarningValues(Header h) {
|
||||
List<WarningValue> out = new ArrayList<WarningValue>();
|
||||
String src = h.getValue();
|
||||
int offs = 0;
|
||||
while(offs < src.length()) {
|
||||
try {
|
||||
WarningValue wv = new WarningValue(src, offs);
|
||||
out.add(wv);
|
||||
offs = wv.offs;
|
||||
} catch (IllegalArgumentException e) {
|
||||
final int nextComma = src.indexOf(',', offs);
|
||||
if (nextComma == -1) break;
|
||||
offs = nextComma + 1;
|
||||
}
|
||||
}
|
||||
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(char c) {
|
||||
int i = (int)c;
|
||||
return (i >= 0 && i <= 127);
|
||||
}
|
||||
|
||||
/*
|
||||
* CTL = <any US-ASCII control character
|
||||
(octets 0 - 31) and DEL (127)>
|
||||
*/
|
||||
private boolean isControl(char c) {
|
||||
int i = (int)c;
|
||||
return (i == 127 || (i >=0 && i <= 31));
|
||||
}
|
||||
|
||||
/*
|
||||
* separators = "(" | ")" | "<" | ">" | "@"
|
||||
* | "," | ";" | ":" | "\" | <">
|
||||
* | "/" | "[" | "]" | "?" | "="
|
||||
* | "{" | "}" | SP | HT
|
||||
*/
|
||||
private boolean isSeparator(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(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() {
|
||||
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() {
|
||||
int curr_offs = offs;
|
||||
try {
|
||||
consumeHostPort();
|
||||
warnAgent = src.substring(curr_offs, offs);
|
||||
consumeCharacter(' ');
|
||||
return;
|
||||
} catch (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) {
|
||||
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 (c != '\"' && !isControl(c)) {
|
||||
offs++;
|
||||
} else {
|
||||
parseError();
|
||||
}
|
||||
}
|
||||
if (!foundEnd) parseError();
|
||||
}
|
||||
|
||||
/*
|
||||
* warn-text = quoted-string
|
||||
*/
|
||||
protected void consumeWarnText() {
|
||||
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() {
|
||||
int curr = offs;
|
||||
Matcher m = WARN_DATE_PATTERN.matcher(src.substring(offs));
|
||||
if (!m.lookingAt()) parseError();
|
||||
offs += m.end();
|
||||
try {
|
||||
warnDate = DateUtils.parseDate(src.substring(curr+1,offs-1));
|
||||
} catch (DateParseException e) {
|
||||
throw new IllegalStateException("couldn't parse a parseable date");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(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() {
|
||||
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</code>
|
||||
*/
|
||||
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\""</code>
|
||||
* (surrounding quotes included).
|
||||
* @return {@link String}
|
||||
*/
|
||||
public String getWarnText() { return warnText; }
|
||||
|
||||
/** Returns the date and time when this warning was added, or
|
||||
* <code>null</code> if a warning date was not supplied in the
|
||||
* header.
|
||||
* @return {@link Date}
|
||||
*/
|
||||
public Date getWarnDate() { return warnDate; }
|
||||
|
||||
/** Formats a <code>WarningValue</code> 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}
|
||||
*/
|
||||
public String toString() {
|
||||
if (warnDate != null) {
|
||||
return String.format("%d %s %s \"%s\"", warnCode,
|
||||
warnAgent, warnText, DateUtils.formatDate(warnDate));
|
||||
} else {
|
||||
return String.format("%d %s %s", warnCode, warnAgent, warnText);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -2291,6 +2291,12 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
|
|||
* 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()
|
||||
|
@ -2538,6 +2544,12 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
|
|||
* 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 {
|
||||
|
@ -4705,6 +4717,11 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
|
|||
* 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()
|
||||
|
@ -5478,7 +5495,7 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
|
|||
request.removeHeaders("Via");
|
||||
EasyMock.expect(mockBackend.execute(EasyMock.isA(HttpHost.class),
|
||||
EasyMock.capture(cap), (HttpContext)EasyMock.isNull()))
|
||||
.andReturn(originResponse);
|
||||
.andReturn(originResponse);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host, request);
|
||||
|
@ -5564,7 +5581,7 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
|
|||
*/
|
||||
@Test
|
||||
public void testViaHeaderOnRequestProperlyRecordsClientProtocol()
|
||||
throws Exception {
|
||||
throws Exception {
|
||||
request = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_0);
|
||||
request.removeHeaders("Via");
|
||||
Capture<HttpRequest> cap = new Capture<HttpRequest>();
|
||||
|
@ -5588,7 +5605,7 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
|
|||
|
||||
@Test
|
||||
public void testViaHeaderOnResponseProperlyRecordsOriginProtocol()
|
||||
throws Exception {
|
||||
throws Exception {
|
||||
|
||||
originResponse = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_NO_CONTENT, "No Content");
|
||||
|
||||
|
@ -5608,4 +5625,184 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
|
|||
}
|
||||
Assert.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);
|
||||
backendExpectsAnyRequest().andReturn(originResponse);
|
||||
|
||||
replayMocks();
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
verifyMocks();
|
||||
Assert.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 {
|
||||
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
|
||||
|
||||
Date now = new Date();
|
||||
Date twentySecondsAgo = new Date(now.getTime() - 20 * 1000L);
|
||||
HttpResponse resp1 = make200Response();
|
||||
resp1.setHeader("Date", DateUtils.formatDate(twentySecondsAgo));
|
||||
resp1.setHeader("Cache-Control","public,max-age=5");
|
||||
resp1.setHeader("ETag", "\"etag1\"");
|
||||
final String oldWarning = "113 wilma \"stale\"";
|
||||
resp1.setHeader("Warning", oldWarning);
|
||||
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, "Not Modified");
|
||||
resp2.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
|
||||
resp2.setHeader("ETag", "\"etag1\"");
|
||||
final String newWarning = "113 betty \"stale too\"";
|
||||
resp2.setHeader("Warning", newWarning);
|
||||
|
||||
backendExpectsAnyRequest().andReturn(resp1);
|
||||
backendExpectsAnyRequest().andReturn(resp2);
|
||||
|
||||
replayMocks();
|
||||
impl.execute(host, req1);
|
||||
HttpResponse result = impl.execute(host, req2);
|
||||
verifyMocks();
|
||||
|
||||
boolean oldWarningFound = false;
|
||||
boolean newWarningFound = false;
|
||||
for(Header h : result.getHeaders("Warning")) {
|
||||
for(String warnValue : h.getValue().split("\\s*,\\s*")) {
|
||||
if (oldWarning.equals(warnValue)) {
|
||||
oldWarningFound = true;
|
||||
} else if (newWarning.equals(warnValue)) {
|
||||
newWarningFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.assertFalse(oldWarningFound);
|
||||
Assert.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.formatDate(new Date());
|
||||
final String origWarning = "110 fred \"stale\"";
|
||||
originResponse.setStatusLine(HttpVersion.HTTP_1_0, HttpStatus.SC_OK);
|
||||
originResponse.addHeader("Warning", origWarning);
|
||||
originResponse.setHeader("Date", dateHdr);
|
||||
backendExpectsAnyRequest().andReturn(originResponse);
|
||||
replayMocks();
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
verifyMocks();
|
||||
// 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.getProtocolVersion())) {
|
||||
Assert.assertEquals(dateHdr, result.getFirstHeader("Date").getValue());
|
||||
boolean warningFound = false;
|
||||
String targetWarning = origWarning + " \"" + dateHdr + "\"";
|
||||
for(Header h : result.getHeaders("Warning")) {
|
||||
for(String warning : h.getValue().split("\\s*,\\s*")) {
|
||||
if (targetWarning.equals(warning)) {
|
||||
warningFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.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 {
|
||||
Date now = new Date();
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
originResponse.setHeader("Date", DateUtils.formatDate(now));
|
||||
originResponse.addHeader("Warning", "110 fred \"stale\", 110 wilma \"stale\" \""
|
||||
+ DateUtils.formatDate(tenSecondsAgo) + "\"");
|
||||
originResponse.setHeader("Cache-Control","no-cache,no-store");
|
||||
backendExpectsAnyRequest().andReturn(originResponse);
|
||||
|
||||
replayMocks();
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
verifyMocks();
|
||||
|
||||
for(Header h : result.getHeaders("Warning")) {
|
||||
Assert.assertFalse(h.getValue().contains("wilma"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStripsBadlyDatedWarningsFromStoredResponses()
|
||||
throws Exception {
|
||||
Date now = new Date();
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
originResponse.setHeader("Date", DateUtils.formatDate(now));
|
||||
originResponse.addHeader("Warning", "110 fred \"stale\", 110 wilma \"stale\" \""
|
||||
+ DateUtils.formatDate(tenSecondsAgo) + "\"");
|
||||
originResponse.setHeader("Cache-Control","public,max-age=3600");
|
||||
backendExpectsAnyRequest().andReturn(originResponse);
|
||||
|
||||
replayMocks();
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
verifyMocks();
|
||||
|
||||
for(Header h : result.getHeaders("Warning")) {
|
||||
Assert.assertFalse(h.getValue().contains("wilma"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovesWarningHeaderIfAllWarnValuesAreBadlyDated()
|
||||
throws Exception {
|
||||
Date now = new Date();
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
originResponse.setHeader("Date", DateUtils.formatDate(now));
|
||||
originResponse.addHeader("Warning", "110 wilma \"stale\" \""
|
||||
+ DateUtils.formatDate(tenSecondsAgo) + "\"");
|
||||
backendExpectsAnyRequest().andReturn(originResponse);
|
||||
|
||||
replayMocks();
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
verifyMocks();
|
||||
|
||||
Header[] warningHeaders = result.getHeaders("Warning");
|
||||
Assert.assertTrue(warningHeaders == null || warningHeaders.length == 0);
|
||||
}
|
||||
|
||||
}
|
230
httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestWarningValue.java
vendored
Normal file
230
httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestWarningValue.java
vendored
Normal file
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.http.impl.client.cache;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestWarningValue {
|
||||
|
||||
@Test
|
||||
public void testParseSingleWarnValue() {
|
||||
Header h = new BasicHeader("Warning","110 fred \"stale\"");
|
||||
WarningValue[] result = WarningValue.getWarningValues(h);
|
||||
Assert.assertEquals(1, result.length);
|
||||
WarningValue wv = result[0];
|
||||
Assert.assertEquals(110, wv.getWarnCode());
|
||||
Assert.assertEquals("fred", wv.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", wv.getWarnText());
|
||||
Assert.assertNull(wv.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseMultipleWarnValues() {
|
||||
Header h = new BasicHeader("Warning","110 fred \"stale\", 111 wilma \"other\"");
|
||||
WarningValue[] result = WarningValue.getWarningValues(h);
|
||||
Assert.assertEquals(2, result.length);
|
||||
WarningValue wv = result[0];
|
||||
Assert.assertEquals(110, wv.getWarnCode());
|
||||
Assert.assertEquals("fred", wv.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", wv.getWarnText());
|
||||
Assert.assertNull(wv.getWarnDate());
|
||||
wv = result[1];
|
||||
Assert.assertEquals(111, wv.getWarnCode());
|
||||
Assert.assertEquals("wilma", wv.getWarnAgent());
|
||||
Assert.assertEquals("\"other\"", wv.getWarnText());
|
||||
Assert.assertNull(wv.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMidHeaderParseErrorRecovery() {
|
||||
Header h = new BasicHeader("Warning","110 fred \"stale\", bogus, 111 wilma \"other\"");
|
||||
WarningValue[] result = WarningValue.getWarningValues(h);
|
||||
Assert.assertEquals(2, result.length);
|
||||
WarningValue wv = result[0];
|
||||
Assert.assertEquals(110, wv.getWarnCode());
|
||||
Assert.assertEquals("fred", wv.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", wv.getWarnText());
|
||||
Assert.assertNull(wv.getWarnDate());
|
||||
wv = result[1];
|
||||
Assert.assertEquals(111, wv.getWarnCode());
|
||||
Assert.assertEquals("wilma", wv.getWarnAgent());
|
||||
Assert.assertEquals("\"other\"", wv.getWarnText());
|
||||
Assert.assertNull(wv.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrickyCommaMidHeaderParseErrorRecovery() {
|
||||
Header h = new BasicHeader("Warning","110 fred \"stale\", \"bogus, dude\", 111 wilma \"other\"");
|
||||
WarningValue[] result = WarningValue.getWarningValues(h);
|
||||
Assert.assertEquals(2, result.length);
|
||||
WarningValue wv = result[0];
|
||||
Assert.assertEquals(110, wv.getWarnCode());
|
||||
Assert.assertEquals("fred", wv.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", wv.getWarnText());
|
||||
Assert.assertNull(wv.getWarnDate());
|
||||
wv = result[1];
|
||||
Assert.assertEquals(111, wv.getWarnCode());
|
||||
Assert.assertEquals("wilma", wv.getWarnAgent());
|
||||
Assert.assertEquals("\"other\"", wv.getWarnText());
|
||||
Assert.assertNull(wv.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseErrorRecoveryAtEndOfHeader() {
|
||||
Header h = new BasicHeader("Warning","110 fred \"stale\", 111 wilma \"other\", \"bogus, dude\"");
|
||||
WarningValue[] result = WarningValue.getWarningValues(h);
|
||||
Assert.assertEquals(2, result.length);
|
||||
WarningValue wv = result[0];
|
||||
Assert.assertEquals(110, wv.getWarnCode());
|
||||
Assert.assertEquals("fred", wv.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", wv.getWarnText());
|
||||
Assert.assertNull(wv.getWarnDate());
|
||||
wv = result[1];
|
||||
Assert.assertEquals(111, wv.getWarnCode());
|
||||
Assert.assertEquals("wilma", wv.getWarnAgent());
|
||||
Assert.assertEquals("\"other\"", wv.getWarnText());
|
||||
Assert.assertNull(wv.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructSingleWarnValue() {
|
||||
WarningValue impl = new WarningValue("110 fred \"stale\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("fred", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assert.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithIPv4Address() {
|
||||
WarningValue impl = new WarningValue("110 192.168.1.1 \"stale\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("192.168.1.1", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assert.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithHostname() {
|
||||
WarningValue impl = new WarningValue("110 foo.example.com \"stale\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("foo.example.com", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assert.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithHostnameAndPort() {
|
||||
WarningValue impl = new WarningValue("110 foo.example.com:8080 \"stale\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("foo.example.com:8080", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assert.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithIPv4AddressAndPort() {
|
||||
WarningValue impl = new WarningValue("110 192.168.1.1:8080 \"stale\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("192.168.1.1:8080", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assert.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithPseudonym() {
|
||||
WarningValue impl = new WarningValue("110 ca$hm0ney \"stale\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("ca$hm0ney", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Assert.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithTextWithSpaces() {
|
||||
WarningValue impl = new WarningValue("110 fred \"stale stuff\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("fred", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale stuff\"", impl.getWarnText());
|
||||
Assert.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithTextWithCommas() {
|
||||
WarningValue impl = new WarningValue("110 fred \"stale, stuff\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("fred", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale, stuff\"", impl.getWarnText());
|
||||
Assert.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithTextWithEscapedQuotes() {
|
||||
WarningValue impl = new WarningValue("110 fred \"stale\\\" stuff\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("fred", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\\\" stuff\"", impl.getWarnText());
|
||||
Assert.assertNull(impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithAscTimeWarnDate() throws Exception {
|
||||
WarningValue impl = new WarningValue("110 fred \"stale\" \"Sun Nov 6 08:49:37 1994\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("fred", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Date target = DateUtils.parseDate("Sun Nov 6 08:49:37 1994");
|
||||
Assert.assertEquals(target, impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithRFC850WarnDate() throws Exception {
|
||||
WarningValue impl = new WarningValue("110 fred \"stale\" \"Sunday, 06-Nov-94 08:49:37 GMT\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("fred", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Date target = DateUtils.parseDate("Sunday, 06-Nov-94 08:49:37 GMT");
|
||||
Assert.assertEquals(target, impl.getWarnDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructWarnValueWithRFC1123WarnDate() throws Exception {
|
||||
WarningValue impl = new WarningValue("110 fred \"stale\" \"Sun, 06 Nov 1994 08:49:37 GMT\"");
|
||||
Assert.assertEquals(110, impl.getWarnCode());
|
||||
Assert.assertEquals("fred", impl.getWarnAgent());
|
||||
Assert.assertEquals("\"stale\"", impl.getWarnText());
|
||||
Date target = DateUtils.parseDate("Sun, 06 Nov 1994 08:49:37 GMT");
|
||||
Assert.assertEquals(target, impl.getWarnDate());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue