Support the following protocol recommendation:

"If a request includes the no-cache directive, it SHOULD NOT
include min-fresh, max-stale, or max-age."

http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4

We address this by looking for no-cache and then filtering the
above directives out if present.


git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1058280 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jonathan Moore 2011-01-12 19:14:37 +00:00
parent 3daa07005e
commit b4d6dee028
3 changed files with 181 additions and 5 deletions

View File

@ -27,6 +27,7 @@
package org.apache.http.impl.client.cache;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.http.Header;
@ -54,6 +55,9 @@ import org.apache.http.protocol.HTTP;
@Immutable
class RequestProtocolCompliance {
private static final List<String> disallowedWithNoCache =
Arrays.asList("min-fresh", "max-stale", "max-age");
/**
* Test to see if the {@link HttpRequest} is HTTP1.1 compliant or not
* and if not, we can not continue.
@ -64,11 +68,6 @@ class RequestProtocolCompliance {
public List<RequestProtocolError> requestIsFatallyNonCompliant(HttpRequest request) {
List<RequestProtocolError> theErrors = new ArrayList<RequestProtocolError>();
//RequestProtocolError anError = requestContainsBodyButNoLength(request);
//if (anError != null) {
// theErrors.add(anError);
//}
RequestProtocolError anError = requestHasWeakETagAndRange(request);
if (anError != null) {
theErrors.add(anError);
@ -105,6 +104,7 @@ class RequestProtocolCompliance {
verifyRequestWithExpectContinueFlagHas100continueHeader(request);
verifyOPTIONSRequestWithBodyHasContentType(request);
decrementOPTIONSMaxForwardsIfGreaterThen0(request);
stripOtherFreshnessDirectivesWithNoCache(request);
if (requestVersionIsTooLow(request)) {
return upgradeRequestTo(request, HttpVersion.HTTP_1_1);
@ -116,6 +116,38 @@ class RequestProtocolCompliance {
return request;
}
private void stripOtherFreshnessDirectivesWithNoCache(HttpRequest request) {
List<HeaderElement> outElts = new ArrayList<HeaderElement>();
boolean shouldStrip = false;
for(Header h : request.getHeaders("Cache-Control")) {
for(HeaderElement elt : h.getElements()) {
if (!disallowedWithNoCache.contains(elt.getName())) {
outElts.add(elt);
}
if ("no-cache".equals(elt.getName())) {
shouldStrip = true;
}
}
}
if (!shouldStrip) return;
request.removeHeaders("Cache-Control");
request.setHeader("Cache-Control", buildHeaderFromElements(outElts));
}
private String buildHeaderFromElements(List<HeaderElement> outElts) {
StringBuilder newHdr = new StringBuilder("");
boolean first = true;
for(HeaderElement elt : outElts) {
if (!first) {
newHdr.append(",");
} else {
first = false;
}
newHdr.append(elt.toString());
}
return newHdr.toString();
}
private boolean requestMustNotHaveEntity(HttpRequest request) {
return HeaderConstants.TRACE_METHOD.equals(request.getRequestLine().getMethod())

View File

@ -30,7 +30,9 @@ import static org.easymock.classextension.EasyMock.*;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
@ -1333,4 +1335,43 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
}
/*
* "If a request includes the no-cache directive, it SHOULD NOT
* include min-fresh, max-stale, or max-age."
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4
*/
@Test
public void otherFreshnessRequestDirectivesNotAllowedWithNoCache()
throws Exception {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
req1.setHeader("Cache-Control", "min-fresh=10, no-cache");
req1.addHeader("Cache-Control", "max-stale=0, max-age=0");
Capture<HttpRequest> cap = new Capture<HttpRequest>();
expect(mockBackend.execute(same(host), capture(cap), (HttpContext)isNull()))
.andReturn(HttpTestUtils.make200Response());
replayMocks();
impl.execute(host, req1);
verifyMocks();
HttpRequest captured = cap.getValue();
boolean foundNoCache = false;
boolean foundDisallowedDirective = false;
List<String> disallowed =
Arrays.asList("min-fresh", "max-stale", "max-age");
for(Header h : captured.getHeaders("Cache-Control")) {
for(HeaderElement elt : h.getElements()) {
if (disallowed.contains(elt.getName())) {
foundDisallowedDirective = true;
}
if ("no-cache".equals(elt.getName())) {
foundNoCache = true;
}
}
}
assertTrue(foundNoCache);
assertFalse(foundDisallowedDirective);
}
}

View File

@ -0,0 +1,103 @@
package org.apache.http.impl.client.cache;
import static org.junit.Assert.*;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpVersion;
import org.apache.http.ProtocolVersion;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest;
import org.junit.Before;
import org.junit.Test;
public class TestRequestProtocolCompliance {
private RequestProtocolCompliance impl;
private HttpRequest req;
private HttpRequest result;
@Before
public void setUp() {
req = HttpTestUtils.makeDefaultRequest();
impl = new RequestProtocolCompliance();
}
@Test
public void doesNotModifyACompliantRequest() throws Exception {
result = impl.makeRequestCompliant(req);
assertTrue(HttpTestUtils.equivalent(req, result));
}
@Test
public void removesEntityFromTRACERequest() throws Exception {
HttpEntityEnclosingRequest req =
new BasicHttpEntityEnclosingRequest("TRACE", "/", HttpVersion.HTTP_1_1);
req.setEntity(HttpTestUtils.makeBody(50));
result = impl.makeRequestCompliant(req);
if (result instanceof HttpEntityEnclosingRequest) {
assertNull(((HttpEntityEnclosingRequest)result).getEntity());
}
}
@Test
public void upgrades1_0RequestTo1_1() throws Exception {
req = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_0);
result = impl.makeRequestCompliant(req);
assertEquals(HttpVersion.HTTP_1_1, result.getProtocolVersion());
}
@Test
public void downgrades1_2RequestTo1_1() throws Exception {
ProtocolVersion HTTP_1_2 = new ProtocolVersion("HTTP", 1, 2);
req = new BasicHttpRequest("GET", "/", HTTP_1_2);
result = impl.makeRequestCompliant(req);
assertEquals(HttpVersion.HTTP_1_1, result.getProtocolVersion());
}
@Test
public void stripsMinFreshFromRequestIfNoCachePresent()
throws Exception {
req.setHeader("Cache-Control", "no-cache, min-fresh=10");
result = impl.makeRequestCompliant(req);
assertEquals("no-cache",
result.getFirstHeader("Cache-Control").getValue());
}
@Test
public void stripsMaxFreshFromRequestIfNoCachePresent()
throws Exception {
req.setHeader("Cache-Control", "no-cache, max-stale=10");
result = impl.makeRequestCompliant(req);
assertEquals("no-cache",
result.getFirstHeader("Cache-Control").getValue());
}
@Test
public void stripsMaxAgeFromRequestIfNoCachePresent()
throws Exception {
req.setHeader("Cache-Control", "no-cache, max-age=10");
result = impl.makeRequestCompliant(req);
assertEquals("no-cache",
result.getFirstHeader("Cache-Control").getValue());
}
@Test
public void doesNotStripMinFreshFromRequestWithoutNoCache()
throws Exception {
req.setHeader("Cache-Control", "min-fresh=10");
result = impl.makeRequestCompliant(req);
assertEquals("min-fresh=10",
result.getFirstHeader("Cache-Control").getValue());
}
@Test
public void correctlyStripsMinFreshFromMiddleIfNoCache()
throws Exception {
req.setHeader("Cache-Control", "no-cache,min-fresh=10,no-store");
result = impl.makeRequestCompliant(req);
assertEquals("no-cache,no-store",
result.getFirstHeader("Cache-Control").getValue());
}
}