HTTPCLIENT-987: cache module does not recognize equivalent URIs

Contributed by Jonathan Moore <jonathan_moore at comcast.com>


git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@993161 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2010-09-06 22:00:58 +00:00
parent eb64e7d3b8
commit ec40554d4d
4 changed files with 180 additions and 22 deletions

View File

@ -115,8 +115,9 @@ class CacheInvalidator {
}
protected void flushUriIfSameHost(URL requestURL, URL targetURL) throws IOException {
if (targetURL.getAuthority().equalsIgnoreCase(requestURL.getAuthority())) {
storage.removeEntry(targetURL.toString());
URL canonicalTarget = new URL(uriExtractor.canonicalizeUri(targetURL.toString()));
if (canonicalTarget.getAuthority().equalsIgnoreCase(requestURL.getAuthority())) {
storage.removeEntry(canonicalTarget.toString());
}
}

View File

@ -27,6 +27,11 @@
package org.apache.http.impl.client.cache;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
@ -56,7 +61,51 @@ class URIExtractor {
* @return String the extracted URI
*/
public String getURI(HttpHost host, HttpRequest req) {
return String.format("%s%s", host.toString(), req.getRequestLine().getUri());
if (isRelativeRequest(req)) {
return canonicalizeUri(String.format("%s%s", host.toString(), req.getRequestLine().getUri()));
}
return canonicalizeUri(req.getRequestLine().getUri());
}
public String canonicalizeUri(String uri) {
try {
URL u = new URL(uri);
String protocol = u.getProtocol().toLowerCase();
String hostname = u.getHost().toLowerCase();
int port = canonicalizePort(u.getPort(), protocol);
String path = canonicalizePath(u.getPath());
if ("".equals(path)) path = "/";
String query = u.getQuery();
String file = (query != null) ? (path + "?" + query) : path;
URL out = new URL(protocol, hostname, port, file);
return out.toString();
} catch (MalformedURLException e) {
return uri;
}
}
private String canonicalizePath(String path) {
try {
String decoded = URLDecoder.decode(path, "UTF-8");
return (new URI(decoded)).getPath();
} catch (UnsupportedEncodingException e) {
} catch (URISyntaxException e) {
}
return path;
}
private int canonicalizePort(int port, String protocol) {
if (port == -1 && "http".equalsIgnoreCase(protocol)) {
return 80;
} else if (port == -1 && "https".equalsIgnoreCase(protocol)) {
return 443;
}
return port;
}
private boolean isRelativeRequest(HttpRequest req) {
String requestUri = req.getRequestLine().getUri();
return ("*".equals(requestUri) || requestUri.startsWith("/"));
}
protected String getFullHeaderValue(Header[] headers) {

View File

@ -76,7 +76,7 @@ public class TestCacheInvalidator {
public void testInvalidatesRequestsThatArentGETorHEAD() throws Exception {
HttpRequest request = new BasicHttpRequest("POST","/path", HTTP_1_1);
final String theUri = "http://foo.example.com/path";
final String theUri = "http://foo.example.com:80/path";
Set<String> variantURIs = new HashSet<String>();
cacheEntryHasVariantURIs(variantURIs);
@ -96,15 +96,15 @@ public class TestCacheInvalidator {
request.setHeader("Content-Length","128");
String contentLocation = "http://foo.example.com/content";
request.setHeader("Content-Location",contentLocation);
request.setHeader("Content-Location", contentLocation);
final String theUri = "http://foo.example.com/";
final String theUri = "http://foo.example.com:80/";
Set<String> variantURIs = new HashSet<String>();
cacheEntryHasVariantURIs(variantURIs);
cacheReturnsEntryForUri(theUri);
entryIsRemoved(theUri);
entryIsRemoved(contentLocation);
entryIsRemoved("http://foo.example.com:80/content");
replayMocks();
@ -122,13 +122,13 @@ public class TestCacheInvalidator {
String contentLocation = "http://foo.example.com/content";
request.setHeader("Location",contentLocation);
final String theUri = "http://foo.example.com/";
final String theUri = "http://foo.example.com:80/";
Set<String> variantURIs = new HashSet<String>();
cacheEntryHasVariantURIs(variantURIs);
cacheReturnsEntryForUri(theUri);
entryIsRemoved(theUri);
entryIsRemoved(contentLocation);
entryIsRemoved(extractor.canonicalizeUri(contentLocation));
replayMocks();
@ -143,17 +143,16 @@ public class TestCacheInvalidator {
request.setEntity(HttpTestUtils.makeBody(128));
request.setHeader("Content-Length","128");
String contentLocation = "http://foo.example.com/content";
String relativePath = "/content";
request.setHeader("Content-Location",relativePath);
final String theUri = "http://foo.example.com/";
final String theUri = "http://foo.example.com:80/";
Set<String> variantURIs = new HashSet<String>();
cacheEntryHasVariantURIs(variantURIs);
cacheReturnsEntryForUri(theUri);
entryIsRemoved(theUri);
entryIsRemoved(contentLocation);
entryIsRemoved("http://foo.example.com:80/content");
replayMocks();
@ -171,7 +170,7 @@ public class TestCacheInvalidator {
String contentLocation = "http://bar.example.com/content";
request.setHeader("Content-Location",contentLocation);
final String theUri = "http://foo.example.com/";
final String theUri = "http://foo.example.com:80/";
Set<String> variantURIs = new HashSet<String>();
cacheEntryHasVariantURIs(variantURIs);
@ -212,7 +211,7 @@ public class TestCacheInvalidator {
HttpRequest request = new BasicHttpRequest("GET","/",HTTP_1_1);
request.setHeader("Cache-Control","no-cache");
final String theUri = "http://foo.example.com/";
final String theUri = "http://foo.example.com:80/";
cacheReturnsEntryForUri(theUri);
Set<String> variantURIs = new HashSet<String>();
cacheEntryHasVariantURIs(variantURIs);
@ -230,7 +229,7 @@ public class TestCacheInvalidator {
HttpRequest request = new BasicHttpRequest("GET","/",HTTP_1_1);
request.setHeader("Pragma","no-cache");
final String theUri = "http://foo.example.com/";
final String theUri = "http://foo.example.com:80/";
cacheReturnsEntryForUri(theUri);
Set<String> variantURIs = new HashSet<String>();
cacheEntryHasVariantURIs(variantURIs);
@ -247,7 +246,7 @@ public class TestCacheInvalidator {
public void testVariantURIsAreFlushedAlso() throws Exception {
HttpRequest request = new BasicHttpRequest("POST","/",HTTP_1_1);
final String theUri = "http://foo.example.com/";
final String theUri = "http://foo.example.com:80/";
final String variantUri = "theVariantURI";
Set<String> listOfURIs = new HashSet<String>();
@ -267,7 +266,7 @@ public class TestCacheInvalidator {
@Test(expected=IOException.class)
public void testCacheFlushException() throws Exception {
HttpRequest request = new BasicHttpRequest("POST","/",HTTP_1_1);
String theURI = "http://foo.example.com/";
String theURI = "http://foo.example.com:80/";
cacheReturnsExceptionForUri(theURI);

View File

@ -29,6 +29,8 @@ package org.apache.http.impl.client.cache;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpVersion;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpRequest;
import org.easymock.classextension.EasyMock;
@ -65,18 +67,25 @@ public class TestURIExtractor {
EasyMock.verify(mockRequest);
}
@Test
public void testExtractsUriFromAbsoluteUriInRequest() {
HttpHost host = new HttpHost("bar.example.com");
HttpRequest req = new HttpGet("http://foo.example.com/");
Assert.assertEquals("http://foo.example.com:80/", extractor.getURI(host, req));
}
@Test
public void testGetURIWithDefaultPortAndScheme() {
Assert.assertEquals("http://www.comcast.net/", extractor.getURI(new HttpHost(
Assert.assertEquals("http://www.comcast.net:80/", extractor.getURI(new HttpHost(
"www.comcast.net"), REQUEST_ROOT));
Assert.assertEquals("http://www.fancast.com/full_episodes", extractor.getURI(new HttpHost(
Assert.assertEquals("http://www.fancast.com:80/full_episodes", extractor.getURI(new HttpHost(
"www.fancast.com"), REQUEST_FULL_EPISODES));
}
@Test
public void testGetURIWithDifferentScheme() {
Assert.assertEquals("https://www.comcast.net/", extractor.getURI(new HttpHost(
Assert.assertEquals("https://www.comcast.net:443/", extractor.getURI(new HttpHost(
"www.comcast.net", -1, "https"), REQUEST_ROOT));
Assert.assertEquals("myhttp://www.fancast.com/full_episodes", extractor.getURI(
@ -103,9 +112,9 @@ public class TestURIExtractor {
@Test
public void testGetURIWithQueryParameters() {
Assert.assertEquals("http://www.comcast.net/?foo=bar", extractor.getURI(new HttpHost(
Assert.assertEquals("http://www.comcast.net:80/?foo=bar", extractor.getURI(new HttpHost(
"www.comcast.net", -1, "http"), new BasicHttpRequest("GET", "/?foo=bar")));
Assert.assertEquals("http://www.fancast.com/full_episodes?foo=bar", extractor.getURI(
Assert.assertEquals("http://www.fancast.com:80/full_episodes?foo=bar", extractor.getURI(
new HttpHost("www.fancast.com", -1, "http"), new BasicHttpRequest("GET",
"/full_episodes?foo=bar")));
}
@ -258,4 +267,104 @@ public class TestURIExtractor {
.assertEquals("{Accept-Encoding=gzip%2C+deflate&User-Agent=browser}" + theURI,
result);
}
/*
* "When comparing two URIs to decide if they match or not, a client
* SHOULD use a case-sensitive octet-by-octet comparison of the entire
* URIs, with these exceptions:
* - A port that is empty or not given is equivalent to the default
* port for that URI-reference;
* - Comparisons of host names MUST be case-insensitive;
* - Comparisons of scheme names MUST be case-insensitive;
* - An empty abs_path is equivalent to an abs_path of "/".
* Characters other than those in the 'reserved' and 'unsafe' sets
* (see RFC 2396 [42]) are equivalent to their '"%" HEX HEX' encoding."
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2.3
*/
@Test
public void testEmptyPortEquivalentToDefaultPortForHttp() {
HttpHost host1 = new HttpHost("foo.example.com:");
HttpHost host2 = new HttpHost("foo.example.com:80");
HttpRequest req = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
Assert.assertEquals(extractor.getURI(host1, req), extractor.getURI(host2, req));
}
@Test
public void testEmptyPortEquivalentToDefaultPortForHttps() {
HttpHost host1 = new HttpHost("foo.example.com", -1, "https");
HttpHost host2 = new HttpHost("foo.example.com", 443, "https");
HttpRequest req = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
final String uri1 = extractor.getURI(host1, req);
final String uri2 = extractor.getURI(host2, req);
Assert.assertEquals(uri1, uri2);
}
@Test
public void testEmptyPortEquivalentToDefaultPortForHttpsAbsoluteURI() {
HttpHost host = new HttpHost("foo.example.com", -1, "https");
HttpGet get1 = new HttpGet("https://bar.example.com:/");
HttpGet get2 = new HttpGet("https://bar.example.com:443/");
final String uri1 = extractor.getURI(host, get1);
final String uri2 = extractor.getURI(host, get2);
Assert.assertEquals(uri1, uri2);
}
@Test
public void testNotProvidedPortEquivalentToDefaultPortForHttpsAbsoluteURI() {
HttpHost host = new HttpHost("foo.example.com", -1, "https");
HttpGet get1 = new HttpGet("https://bar.example.com/");
HttpGet get2 = new HttpGet("https://bar.example.com:443/");
final String uri1 = extractor.getURI(host, get1);
final String uri2 = extractor.getURI(host, get2);
Assert.assertEquals(uri1, uri2);
}
@Test
public void testNotProvidedPortEquivalentToDefaultPortForHttp() {
HttpHost host1 = new HttpHost("foo.example.com");
HttpHost host2 = new HttpHost("foo.example.com:80");
HttpRequest req = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
Assert.assertEquals(extractor.getURI(host1, req), extractor.getURI(host2, req));
}
@Test
public void testHostNameComparisonsAreCaseInsensitive() {
HttpHost host1 = new HttpHost("foo.example.com");
HttpHost host2 = new HttpHost("FOO.EXAMPLE.COM");
HttpRequest req = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
Assert.assertEquals(extractor.getURI(host1, req), extractor.getURI(host2, req));
}
@Test
public void testSchemeNameComparisonsAreCaseInsensitive() {
HttpHost host1 = new HttpHost("foo.example.com", -1, "http");
HttpHost host2 = new HttpHost("foo.example.com", -1, "HTTP");
HttpRequest req = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
Assert.assertEquals(extractor.getURI(host1, req), extractor.getURI(host2, req));
}
@Test
public void testEmptyAbsPathIsEquivalentToSlash() {
HttpHost host = new HttpHost("foo.example.com");
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
HttpRequest req2 = new HttpGet("http://foo.example.com");
Assert.assertEquals(extractor.getURI(host, req1), extractor.getURI(host, req2));
}
@Test
public void testEquivalentPathEncodingsAreEquivalent() {
HttpHost host = new HttpHost("foo.example.com");
HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home.html", HttpVersion.HTTP_1_1);
HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith/home.html", HttpVersion.HTTP_1_1);
Assert.assertEquals(extractor.getURI(host, req1), extractor.getURI(host, req2));
}
@Test
public void testEquivalentExtraPathEncodingsAreEquivalent() {
HttpHost host = new HttpHost("foo.example.com");
HttpRequest req1 = new BasicHttpRequest("GET", "/~smith/home.html", HttpVersion.HTTP_1_1);
HttpRequest req2 = new BasicHttpRequest("GET", "/%7Esmith%2Fhome.html", HttpVersion.HTTP_1_1);
Assert.assertEquals(extractor.getURI(host, req1), extractor.getURI(host, req2));
}
}