HTTPCLIENT-427: HTTP caching support
Contributed by Joe Campbell, David Cleaver, David Mays, Jon Moore, Brad Spenla (Comcast Corporation) git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@939814 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
160af55cb1
commit
283bba855f
|
@ -1,5 +1,7 @@
|
|||
Release 4.1 ALPHA2
|
||||
-------------------
|
||||
* [HTTPCLIENT-427] HTTP caching support
|
||||
Contributed by Joe Campbell, David Cleaver, David Mays, Jon Moore, Brad Spenla
|
||||
|
||||
* Dropped dependency on Mime4j for HttpMime.
|
||||
Contributed by Oleg Kalnichevski <olegk at apache.org>
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
====================================================================
|
||||
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 />.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcomponents-client</artifactId>
|
||||
<version>4.1-alpha2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>httpclient-cache</artifactId>
|
||||
<name>HttpClient Cache</name>
|
||||
<description>
|
||||
HttpComponents HttpClient - Cache
|
||||
</description>
|
||||
<url>http://hc.apache.org/httpcomponents-client</url>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License</name>
|
||||
<url>../LICENSE.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<version>${commons-logging.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.easymock</groupId>
|
||||
<artifactId>easymock</artifactId>
|
||||
<version>${easymock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.easymock</groupId>
|
||||
<artifactId>easymockclassextension</artifactId>
|
||||
<version>${easymock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<maven.compile.source>1.5</maven.compile.source>
|
||||
<maven.compile.target>1.5</maven.compile.target>
|
||||
<maven.compile.optimize>true</maven.compile.optimize>
|
||||
<maven.compile.deprecation>true</maven.compile.deprecation>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
<includes>
|
||||
<include>META-INF/*</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>**/*.properties</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>..</directory>
|
||||
<targetPath>META-INF</targetPath>
|
||||
<includes>
|
||||
<include>LICENSE.txt</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>../src/main/resources</directory>
|
||||
<targetPath>META-INF</targetPath>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>NOTICE.txt</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compile.source}</source>
|
||||
<target>${maven.compile.target}</target>
|
||||
<optimize>${maven.compile.optimize}</optimize>
|
||||
<showDeprecations>${maven.compile.deprecation}</showDeprecations>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Specification-Title>HttpComponents HttpMime</Specification-Title>
|
||||
<Specification-Version>${project.version}</Specification-Version>
|
||||
<Specification-Vendor>The Apache Software Foundation</Specification-Vendor>
|
||||
<Implementation-Title>HttpComponents HttpMime</Implementation-Title>
|
||||
<Implementation-Version>${project.version}</Implementation-Version>
|
||||
<Implementation-Vendor>The Apache Software Foundation</Implementation-Vendor>
|
||||
<Implementation-Vendor-Id>org.apache</Implementation-Vendor-Id>
|
||||
<url>${project.url}</url>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.atlassian.maven.plugins</groupId>
|
||||
<artifactId>maven-clover2-plugin</artifactId>
|
||||
<configuration>
|
||||
<flushPolicy>threaded</flushPolicy>
|
||||
<flushInterval>100</flushInterval>
|
||||
<targetPercentage>50%</targetPercentage>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>site</id>
|
||||
<phase>pre-site</phase>
|
||||
<goals>
|
||||
<goal>instrument</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<reporting>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.5</source>
|
||||
<links>
|
||||
<link>http://java.sun.com/j2se/1.5.0/docs/api/</link>
|
||||
<link>http://hc.apache.org/httpcomponents-core/httpcore/apidocs/</link>
|
||||
<link>http://hc.apache.org/httpcomponents-client/httpclient/apidocs/</link>
|
||||
</links>
|
||||
</configuration>
|
||||
<reportSets>
|
||||
<reportSet>
|
||||
<reports>
|
||||
<report>javadoc</report>
|
||||
</reports>
|
||||
</reportSet>
|
||||
</reportSets>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.atlassian.maven.plugins</groupId>
|
||||
<artifactId>maven-clover2-plugin</artifactId>
|
||||
<configuration>
|
||||
<jdk>1.5</jdk>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-jxr-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-report-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</reporting>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public interface HttpCache<E> {
|
||||
|
||||
void putEntry(String url, E entry) throws HttpCacheOperationException;
|
||||
|
||||
E getEntry(String url) throws HttpCacheOperationException;
|
||||
|
||||
void removeEntry(String url) throws HttpCacheOperationException;
|
||||
|
||||
void updateCacheEntry(
|
||||
String url, HttpCacheUpdateCallback<E> callback) throws HttpCacheOperationException;
|
||||
|
||||
}
|
44
httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntrySerializer.java
vendored
Normal file
44
httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntrySerializer.java
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Common operations for cache entry serialization and deserialization.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public interface HttpCacheEntrySerializer<E> {
|
||||
|
||||
public void writeTo(E entry, OutputStream os) throws IOException;
|
||||
|
||||
public E readFrom(InputStream is) throws IOException;
|
||||
|
||||
}
|
56
httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheOperationException.java
vendored
Normal file
56
httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheOperationException.java
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache;
|
||||
|
||||
|
||||
/**
|
||||
* Exception to be thrown when an {@link HttpCache} encounters an error performing
|
||||
* an caching operation.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public class HttpCacheOperationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 823573584868632876L;
|
||||
|
||||
public HttpCacheOperationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public HttpCacheOperationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public HttpCacheOperationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public HttpCacheOperationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
46
httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheUpdateCallback.java
vendored
Normal file
46
httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheUpdateCallback.java
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache;
|
||||
|
||||
public interface HttpCacheUpdateCallback<E> {
|
||||
|
||||
/**
|
||||
* Returns the new cache entry that should replace an existing one.
|
||||
*
|
||||
* @param existing
|
||||
* the cache entry current in-place in the cache, possibly
|
||||
* <code>null</code> if nonexistent
|
||||
* @return CacheEntry the cache entry that should replace it, again,
|
||||
* possible <code>null</code>
|
||||
* @throws HttpCacheOperationException
|
||||
* exception containing information about a failure in the cache
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
E getUpdatedEntry(E existing) throws HttpCacheOperationException;
|
||||
|
||||
}
|
103
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/BasicHttpCache.java
vendored
Normal file
103
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/BasicHttpCache.java
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.http.client.cache.HttpCacheOperationException;
|
||||
import org.apache.http.client.cache.HttpCacheUpdateCallback;
|
||||
import org.apache.http.client.cache.HttpCache;
|
||||
|
||||
/**
|
||||
* Implements {@link HttpCache} using LinkedHashMap for backing store
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public class BasicHttpCache implements HttpCache<CacheEntry> {
|
||||
|
||||
private LinkedHashMap<String, CacheEntry> baseMap = new LinkedHashMap<String, CacheEntry>(20,
|
||||
0.75f, true) {
|
||||
|
||||
private static final long serialVersionUID = -7750025207539768511L;
|
||||
|
||||
protected boolean removeEldestEntry(Map.Entry<String, CacheEntry> eldest) {
|
||||
return size() > maxEntries;
|
||||
}
|
||||
};
|
||||
|
||||
private Map<String, CacheEntry> syncMap;
|
||||
|
||||
private int maxEntries;
|
||||
|
||||
public BasicHttpCache(int maxEntries) {
|
||||
this.maxEntries = maxEntries;
|
||||
syncMap = Collections.synchronizedMap(baseMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a CacheEntry in the cache
|
||||
*
|
||||
* @param url
|
||||
* Url to use as the cache key
|
||||
* @param entry
|
||||
* CacheEntry to place in the cache
|
||||
*/
|
||||
public void putEntry(String url, CacheEntry entry) {
|
||||
syncMap.put(url, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an entry from the cache, if it exists
|
||||
*
|
||||
* @param url
|
||||
* Url that is the cache key
|
||||
* @return CacheEntry if one exists, or null for cache miss
|
||||
*/
|
||||
public CacheEntry getEntry(String url) {
|
||||
return syncMap.get(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a CacheEntry from the cache
|
||||
*
|
||||
* @param url
|
||||
* Url that is the cache key
|
||||
*/
|
||||
public void removeEntry(String url) {
|
||||
syncMap.remove(url);
|
||||
}
|
||||
|
||||
public synchronized void updateCacheEntry(
|
||||
String url,
|
||||
HttpCacheUpdateCallback<CacheEntry> callback) throws HttpCacheOperationException {
|
||||
CacheEntry existingEntry = syncMap.get(url);
|
||||
CacheEntry updated = callback.getUpdatedEntry(existingEntry);
|
||||
syncMap.put(url, updated);
|
||||
}
|
||||
}
|
416
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntry.java
vendored
Normal file
416
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntry.java
vendored
Normal file
|
@ -0,0 +1,416 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.impl.cookie.DateParseException;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
|
||||
/**
|
||||
* Structure used to store an {@link HttpResponse} in a cache
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public class CacheEntry implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6300496422359477413L;
|
||||
|
||||
public static final long MAX_AGE = 2147483648L;
|
||||
|
||||
private transient Header[] responseHeaders;
|
||||
private byte[] body;
|
||||
private ProtocolVersion version;
|
||||
private int status;
|
||||
private String reason;
|
||||
private Date requestDate;
|
||||
private Date responseDate;
|
||||
private Set<String> variantURIs = new HashSet<String>();
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public CacheEntry() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param requestDate
|
||||
* Date/time when the request was made (Used for age
|
||||
* calculations)
|
||||
* @param responseDate
|
||||
* Date/time that the response came back (Used for age
|
||||
* calculations)
|
||||
* @param response
|
||||
* original {@link HttpResponse}
|
||||
* @param responseBytes
|
||||
* Byte array containing the body of the response
|
||||
* @throws IOException
|
||||
* Does not attempt to handle IOExceptions
|
||||
*/
|
||||
public CacheEntry(Date requestDate, Date responseDate, HttpResponse response,
|
||||
byte[] responseBytes) throws IOException {
|
||||
this.requestDate = requestDate;
|
||||
this.responseDate = responseDate;
|
||||
version = response.getProtocolVersion();
|
||||
responseHeaders = response.getAllHeaders();
|
||||
StatusLine sl = response.getStatusLine();
|
||||
status = sl.getStatusCode();
|
||||
reason = sl.getReasonPhrase();
|
||||
|
||||
body = responseBytes;
|
||||
}
|
||||
|
||||
public void setProtocolVersion(ProtocolVersion version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public ProtocolVersion getProtocolVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String getReasonPhrase() {
|
||||
return this.reason;
|
||||
}
|
||||
|
||||
public int getStatusCode() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public void setRequestDate(Date requestDate) {
|
||||
this.requestDate = requestDate;
|
||||
}
|
||||
|
||||
public Date getRequestDate() {
|
||||
return requestDate;
|
||||
}
|
||||
|
||||
public void setResponseDate(Date responseDate) {
|
||||
this.responseDate = responseDate;
|
||||
}
|
||||
|
||||
public Date getResponseDate() {
|
||||
return this.responseDate;
|
||||
}
|
||||
|
||||
public void setBody(byte[] body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public Header[] getAllHeaders() {
|
||||
return responseHeaders;
|
||||
}
|
||||
|
||||
public void setResponseHeaders(Header[] responseHeaders) {
|
||||
this.responseHeaders = responseHeaders;
|
||||
}
|
||||
|
||||
public Header getFirstHeader(String name) {
|
||||
for (Header h : responseHeaders) {
|
||||
if (h.getName().equals(name))
|
||||
return h;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Header[] getHeaders(String name) {
|
||||
|
||||
ArrayList<Header> headers = new ArrayList<Header>();
|
||||
|
||||
for (Header h : this.responseHeaders) {
|
||||
if (h.getName().equals(name))
|
||||
headers.add(h);
|
||||
}
|
||||
|
||||
Header[] headerArray = new Header[headers.size()];
|
||||
|
||||
headers.toArray(headerArray);
|
||||
|
||||
return headerArray;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Response Date header value
|
||||
*/
|
||||
protected Date getDateValue() {
|
||||
Header dateHdr = getFirstHeader(HeaderConstants.DATE);
|
||||
if (dateHdr == null)
|
||||
return null;
|
||||
try {
|
||||
return DateUtils.parseDate(dateHdr.getValue());
|
||||
} catch (DateParseException dpe) {
|
||||
// ignore malformed date
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected long getContentLengthValue() {
|
||||
Header cl = getFirstHeader(HeaderConstants.CONTENT_LENGTH);
|
||||
if (cl == null)
|
||||
return -1;
|
||||
|
||||
try {
|
||||
return Long.parseLong(cl.getValue());
|
||||
} catch (NumberFormatException ex) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This matters for deciding whether the cache entry is valid to serve as a
|
||||
* response. If these values do not match, we might have a partial response
|
||||
*
|
||||
* @return boolean indicating whether actual length matches Content-Length
|
||||
*/
|
||||
protected boolean contentLengthHeaderMatchesActualLength() {
|
||||
return getContentLengthValue() == body.length;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Apparent age of the response
|
||||
*/
|
||||
protected long getApparentAgeSecs() {
|
||||
Date dateValue = getDateValue();
|
||||
if (dateValue == null)
|
||||
return MAX_AGE;
|
||||
long diff = responseDate.getTime() - dateValue.getTime();
|
||||
if (diff < 0L)
|
||||
return 0;
|
||||
return (diff / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Response Age header value
|
||||
*/
|
||||
protected long getAgeValue() {
|
||||
long ageValue = 0;
|
||||
for (Header hdr : getHeaders(HeaderConstants.AGE)) {
|
||||
long hdrAge;
|
||||
try {
|
||||
hdrAge = Long.parseLong(hdr.getValue());
|
||||
if (hdrAge < 0) {
|
||||
hdrAge = MAX_AGE;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
hdrAge = MAX_AGE;
|
||||
}
|
||||
ageValue = (hdrAge > ageValue) ? hdrAge : ageValue;
|
||||
}
|
||||
return ageValue;
|
||||
}
|
||||
|
||||
protected long getCorrectedReceivedAgeSecs() {
|
||||
long apparentAge = getApparentAgeSecs();
|
||||
long ageValue = getAgeValue();
|
||||
return (apparentAge > ageValue) ? apparentAge : ageValue;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Delay between request and response
|
||||
*/
|
||||
protected long getResponseDelaySecs() {
|
||||
long diff = responseDate.getTime() - requestDate.getTime();
|
||||
return (diff / 1000L);
|
||||
}
|
||||
|
||||
protected long getCorrectedInitialAgeSecs() {
|
||||
return getCorrectedReceivedAgeSecs() + getResponseDelaySecs();
|
||||
}
|
||||
|
||||
protected Date getCurrentDate() {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
protected long getResidentTimeSecs() {
|
||||
long diff = getCurrentDate().getTime() - responseDate.getTime();
|
||||
return (diff / 1000L);
|
||||
}
|
||||
|
||||
public long getCurrentAgeSecs() {
|
||||
return getCorrectedInitialAgeSecs() + getResidentTimeSecs();
|
||||
}
|
||||
|
||||
protected long getMaxAge() {
|
||||
long maxage = -1;
|
||||
for (Header hdr : getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for (HeaderElement elt : hdr.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())
|
||||
|| "s-maxage".equals(elt.getName())) {
|
||||
try {
|
||||
long currMaxAge = Long.parseLong(elt.getValue());
|
||||
if (maxage == -1 || currMaxAge < maxage) {
|
||||
maxage = currMaxAge;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
// be conservative if can't parse
|
||||
maxage = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxage;
|
||||
}
|
||||
|
||||
protected Date getExpirationDate() {
|
||||
Header expiresHeader = getFirstHeader(HeaderConstants.EXPIRES);
|
||||
if (expiresHeader == null)
|
||||
return null;
|
||||
try {
|
||||
return DateUtils.parseDate(expiresHeader.getValue());
|
||||
} catch (DateParseException dpe) {
|
||||
// malformed expires header
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getFreshnessLifetimeSecs() {
|
||||
long maxage = getMaxAge();
|
||||
if (maxage > -1)
|
||||
return maxage;
|
||||
|
||||
Date dateValue = getDateValue();
|
||||
if (dateValue == null)
|
||||
return 0L;
|
||||
|
||||
Date expiry = getExpirationDate();
|
||||
if (expiry == null)
|
||||
return 0;
|
||||
long diff = expiry.getTime() - dateValue.getTime();
|
||||
return (diff / 1000);
|
||||
}
|
||||
|
||||
public boolean isResponseFresh() {
|
||||
return (getCurrentAgeSecs() < getFreshnessLifetimeSecs());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return boolean indicating whether ETag or Last-Modified responseHeaders
|
||||
* are present
|
||||
*/
|
||||
public boolean isRevalidatable() {
|
||||
return getFirstHeader(HeaderConstants.ETAG) != null
|
||||
|| getFirstHeader(HeaderConstants.LAST_MODIFIED) != null;
|
||||
|
||||
}
|
||||
|
||||
public boolean modifiedSince(HttpRequest request) {
|
||||
Header unmodHeader = request.getFirstHeader(HeaderConstants.IF_UNMODIFIED_SINCE);
|
||||
|
||||
if (unmodHeader == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Date unmodifiedSinceDate = DateUtils.parseDate(unmodHeader.getValue());
|
||||
Date lastModifiedDate = DateUtils.parseDate(getFirstHeader(
|
||||
HeaderConstants.LAST_MODIFIED).getValue());
|
||||
|
||||
if (unmodifiedSinceDate.before(lastModifiedDate)) {
|
||||
return true;
|
||||
}
|
||||
} catch (DateParseException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return boolean indicating whether any Vary responseHeaders are present
|
||||
*/
|
||||
public boolean hasVariants() {
|
||||
return (getFirstHeader(HeaderConstants.VARY) != null);
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||
|
||||
// write CacheEntry
|
||||
out.defaultWriteObject();
|
||||
|
||||
// write (non-serializable) responseHeaders
|
||||
if (null == responseHeaders || responseHeaders.length < 1)
|
||||
return;
|
||||
String[][] sheaders = new String[responseHeaders.length][2];
|
||||
for (int i = 0; i < responseHeaders.length; i++) {
|
||||
sheaders[i][0] = responseHeaders[i].getName();
|
||||
sheaders[i][1] = responseHeaders[i].getValue();
|
||||
}
|
||||
out.writeObject(sheaders);
|
||||
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
|
||||
// read CacheEntry
|
||||
in.defaultReadObject();
|
||||
|
||||
// read (non-serializable) responseHeaders
|
||||
String[][] sheaders = (String[][]) in.readObject();
|
||||
if (null == sheaders || sheaders.length < 1)
|
||||
return;
|
||||
BasicHeader[] headers = new BasicHeader[sheaders.length];
|
||||
for (int i = 0; i < sheaders.length; i++) {
|
||||
String[] sheader = sheaders[i];
|
||||
headers[i] = new BasicHeader(sheader[0], sheader[1]);
|
||||
}
|
||||
this.responseHeaders = headers;
|
||||
|
||||
}
|
||||
|
||||
public void addVariantURI(String URI) {
|
||||
this.variantURIs.add(URI);
|
||||
}
|
||||
|
||||
public Set<String> getVariantURIs() {
|
||||
return Collections.unmodifiableSet(this.variantURIs);
|
||||
}
|
||||
}
|
45
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntryGenerator.java
vendored
Normal file
45
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntryGenerator.java
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public class CacheEntryGenerator {
|
||||
|
||||
public CacheEntry generateEntry(Date requestDate, Date responseDate, HttpResponse response,
|
||||
byte[] responseBytes) throws IOException {
|
||||
|
||||
return new CacheEntry(requestDate, responseDate, response, responseBytes);
|
||||
|
||||
}
|
||||
}
|
130
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntryUpdater.java
vendored
Normal file
130
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheEntryUpdater.java
vendored
Normal file
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.impl.cookie.DateParseException;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public class CacheEntryUpdater {
|
||||
|
||||
public void updateCacheEntry(CacheEntry entry, Date requestDate, Date responseDate,
|
||||
HttpResponse response) {
|
||||
entry.setRequestDate(requestDate);
|
||||
entry.setResponseDate(responseDate);
|
||||
mergeHeaders(entry, response);
|
||||
}
|
||||
|
||||
protected void mergeHeaders(CacheEntry entry, HttpResponse response) {
|
||||
List<Header> cacheEntryHeaderList = new ArrayList<Header>(Arrays.asList(entry
|
||||
.getAllHeaders()));
|
||||
|
||||
if (entryAndResponseHaveDateHeader(entry, response)
|
||||
&& entryDateHeaderNewerThenResponse(entry, response)) {
|
||||
// Don't merge Headers, keep the entries headers as they are newer.
|
||||
removeCacheEntry1xxWarnings(cacheEntryHeaderList, entry);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
removeCacheHeadersThatMatchResponse(cacheEntryHeaderList, response);
|
||||
|
||||
cacheEntryHeaderList.addAll(Arrays.asList(response.getAllHeaders()));
|
||||
removeCacheEntry1xxWarnings(cacheEntryHeaderList, entry);
|
||||
|
||||
entry.setResponseHeaders(cacheEntryHeaderList.toArray(new Header[cacheEntryHeaderList
|
||||
.size()]));
|
||||
}
|
||||
|
||||
private void removeCacheHeadersThatMatchResponse(List<Header> cacheEntryHeaderList,
|
||||
HttpResponse response) {
|
||||
for (Header responseHeader : response.getAllHeaders()) {
|
||||
ListIterator<Header> cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator();
|
||||
|
||||
while (cacheEntryHeaderListIter.hasNext()) {
|
||||
String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName();
|
||||
|
||||
if (cacheEntryHeaderName.equals(responseHeader.getName())) {
|
||||
cacheEntryHeaderListIter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeCacheEntry1xxWarnings(List<Header> cacheEntryHeaderList, CacheEntry entry) {
|
||||
ListIterator<Header> cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator();
|
||||
|
||||
while (cacheEntryHeaderListIter.hasNext()) {
|
||||
String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName();
|
||||
|
||||
if (HeaderConstants.WARNING.equals(cacheEntryHeaderName)) {
|
||||
for (Header cacheEntryWarning : entry.getHeaders(HeaderConstants.WARNING)) {
|
||||
if (cacheEntryWarning.getValue().startsWith("1")) {
|
||||
cacheEntryHeaderListIter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean entryDateHeaderNewerThenResponse(CacheEntry entry, HttpResponse response) {
|
||||
try {
|
||||
Date entryDate = DateUtils.parseDate(entry.getFirstHeader(HeaderConstants.DATE)
|
||||
.getValue());
|
||||
Date responseDate = DateUtils.parseDate(response.getFirstHeader(HeaderConstants.DATE)
|
||||
.getValue());
|
||||
|
||||
if (!entryDate.after(responseDate)) {
|
||||
return false;
|
||||
}
|
||||
} catch (DateParseException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean entryAndResponseHaveDateHeader(CacheEntry entry, HttpResponse response) {
|
||||
if (entry.getFirstHeader(HeaderConstants.DATE) != null
|
||||
&& response.getFirstHeader(HeaderConstants.DATE) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
125
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheInvalidator.java
vendored
Normal file
125
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheInvalidator.java
vendored
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.client.cache.HttpCacheOperationException;
|
||||
import org.apache.http.client.cache.HttpCache;
|
||||
|
||||
/**
|
||||
* Given a particular HttpRequest, flush any cache entries that this request
|
||||
* would invalidate.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public class CacheInvalidator {
|
||||
|
||||
private HttpCache<CacheEntry> cache;
|
||||
private URIExtractor uriExtractor;
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(CacheInvalidator.class);
|
||||
|
||||
public CacheInvalidator(URIExtractor uriExtractor, HttpCache<CacheEntry> cache) {
|
||||
this.uriExtractor = uriExtractor;
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
public void flushInvalidatedCacheEntries(HttpHost host, HttpRequest req) {
|
||||
LOG.debug("CacheInvalidator: flushInvalidatedCacheEntries, BEGIN");
|
||||
|
||||
if (requestShouldNotBeCached(req)) {
|
||||
LOG
|
||||
.debug("CacheInvalidator: flushInvalidatedCacheEntries, Request should not be cached");
|
||||
|
||||
try {
|
||||
String theUri = uriExtractor.getURI(host, req);
|
||||
|
||||
CacheEntry parent = cache.getEntry(theUri);
|
||||
|
||||
LOG.debug("CacheInvalidator: flushInvalidatedCacheEntries: " + parent);
|
||||
|
||||
if (parent != null) {
|
||||
for (String variantURI : parent.getVariantURIs()) {
|
||||
cache.removeEntry(variantURI);
|
||||
}
|
||||
cache.removeEntry(theUri);
|
||||
}
|
||||
} catch (HttpCacheOperationException coe) {
|
||||
// TODO: track failed state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean requestShouldNotBeCached(HttpRequest req) {
|
||||
String method = req.getRequestLine().getMethod();
|
||||
return notGetOrHeadRequest(method) || containsCacheControlHeader(req)
|
||||
|| containsPragmaHeader(req);
|
||||
}
|
||||
|
||||
private boolean notGetOrHeadRequest(String method) {
|
||||
return !(HeaderConstants.GET_METHOD.equals(method) || HeaderConstants.HEAD_METHOD
|
||||
.equals(method));
|
||||
}
|
||||
|
||||
private boolean containsPragmaHeader(HttpRequest req) {
|
||||
return req.getFirstHeader(HeaderConstants.PRAGMA) != null;
|
||||
}
|
||||
|
||||
private boolean containsCacheControlHeader(HttpRequest request) {
|
||||
Header[] cacheControlHeaders = request.getHeaders(HeaderConstants.CACHE_CONTROL);
|
||||
|
||||
if (cacheControlHeaders == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Header cacheControl : cacheControlHeaders) {
|
||||
HeaderElement[] cacheControlElements = cacheControl.getElements();
|
||||
if (cacheControlElements == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (HeaderElement cacheControlElement : cacheControlElements) {
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(cacheControlElement
|
||||
.getName())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_STORE.equalsIgnoreCase(cacheControlElement
|
||||
.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
92
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheableRequestPolicy.java
vendored
Normal file
92
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CacheableRequestPolicy.java
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
|
||||
/**
|
||||
* Determines if an HttpRequest is allowed to be served from the cache.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public class CacheableRequestPolicy {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(CacheableRequestPolicy.class);
|
||||
|
||||
/**
|
||||
* Determines if an HttpRequest can be served from the cache.
|
||||
*
|
||||
* @param request
|
||||
* an HttpRequest
|
||||
* @return boolean Is it possible to serve this request from cache
|
||||
*/
|
||||
public boolean isServableFromCache(HttpRequest request) {
|
||||
String method = request.getRequestLine().getMethod();
|
||||
|
||||
ProtocolVersion pv = request.getRequestLine().getProtocolVersion();
|
||||
if (CachingHttpClient.HTTP_1_1.compareToVersion(pv) != 0) {
|
||||
LOG.debug("CacheableRequestPolicy: Request WAS NOT serveable from Cache.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!method.equals(HeaderConstants.GET_METHOD)) {
|
||||
LOG.debug("CacheableRequestPolicy: Request WAS NOT serveable from Cache.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.getHeaders(HeaderConstants.PRAGMA).length > 0) {
|
||||
LOG.debug("CacheableRequestPolicy: Request WAS NOT serveable from Cache.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Header[] cacheControlHeaders = request.getHeaders(HeaderConstants.CACHE_CONTROL);
|
||||
for (Header cacheControl : cacheControlHeaders) {
|
||||
for (HeaderElement cacheControlElement : cacheControl.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_STORE.equalsIgnoreCase(cacheControlElement
|
||||
.getName())) {
|
||||
LOG.debug("CacheableRequestPolicy: Request WAS NOT serveable from Cache.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(cacheControlElement
|
||||
.getName())) {
|
||||
LOG.debug("CacheableRequestPolicy: Request WAS NOT serveable from Cache.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("CacheableRequestPolicy: Request WAS serveable from Cache.");
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
|
||||
/**
|
||||
* Rebuilds an {@link HttpResponse} from a {@link CacheEntry}
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public class CachedHttpResponseGenerator {
|
||||
|
||||
/**
|
||||
* @param entry
|
||||
* {@link CacheEntry} to transform into an {@link HttpResponse}
|
||||
* @return {@link HttpResponse} that was constructed
|
||||
*/
|
||||
HttpResponse generateResponse(CacheEntry entry) {
|
||||
|
||||
HttpResponse response = new BasicHttpResponse(CachingHttpClient.HTTP_1_1, entry
|
||||
.getStatusCode(), entry.getReasonPhrase());
|
||||
|
||||
if (entry.getStatusCode() != HttpStatus.SC_NOT_MODIFIED) {
|
||||
HttpEntity entity = new ByteArrayEntity(entry.getBody());
|
||||
response.setEntity(entity);
|
||||
response.setHeaders(entry.getAllHeaders());
|
||||
addMissingContentLengthHeader(response, entity);
|
||||
}
|
||||
|
||||
long age = entry.getCurrentAgeSecs();
|
||||
if (age > 0) {
|
||||
if (age >= (long) Integer.MAX_VALUE) {
|
||||
response.setHeader(HeaderConstants.AGE, "2147483648");
|
||||
} else {
|
||||
response.setHeader(HeaderConstants.AGE, "" + ((int) age));
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void addMissingContentLengthHeader(HttpResponse response, HttpEntity entity) {
|
||||
if (transferEncodingIsPresent(response))
|
||||
return;
|
||||
|
||||
Header contentLength = response.getFirstHeader(HeaderConstants.CONTENT_LENGTH);
|
||||
if (contentLength == null) {
|
||||
contentLength = new BasicHeader(HeaderConstants.CONTENT_LENGTH, Long.toString(entity
|
||||
.getContentLength()));
|
||||
response.setHeader(contentLength);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean transferEncodingIsPresent(HttpResponse response) {
|
||||
Header hdr = response.getFirstHeader(HeaderConstants.TRANSFER_ENCODING);
|
||||
return hdr != null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
|
||||
/**
|
||||
* Determines whether a given response can be cached.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public class CachedResponseSuitabilityChecker {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(CachedResponseSuitabilityChecker.class);
|
||||
|
||||
/**
|
||||
* @param host
|
||||
* {@link HttpHost}
|
||||
* @param request
|
||||
* {@link HttpRequest}
|
||||
* @param entry
|
||||
* {@link CacheEntry}
|
||||
* @return boolean yes/no answer
|
||||
*/
|
||||
public boolean canCachedResponseBeUsed(HttpHost host, HttpRequest request, CacheEntry entry) {
|
||||
if (!entry.isResponseFresh()) {
|
||||
LOG.debug("CachedResponseSuitabilityChecker: Cache Entry was NOT fresh enough");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entry.contentLengthHeaderMatchesActualLength()) {
|
||||
LOG
|
||||
.debug("CachedResponseSuitabilityChecker: Cache Entry Content Length and header information DO NOT match.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.modifiedSince(request)) {
|
||||
LOG
|
||||
.debug("CachedResponseSuitabilityChecker: Cache Entry modified times didn't line up. Cache Entry should NOT be used.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Header ccHdr : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for (HeaderElement elt : ccHdr.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) {
|
||||
LOG
|
||||
.debug("CachedResponseSuitabilityChecker: Response contained NO CACHE directive, cache was NOT suitable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elt.getName())) {
|
||||
LOG
|
||||
.debug("CachedResponseSuitabilityChecker: Response contained NO SORE directive, cache was NOT suitable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) {
|
||||
try {
|
||||
int maxage = Integer.parseInt(elt.getValue());
|
||||
if (entry.getCurrentAgeSecs() > maxage) {
|
||||
LOG
|
||||
.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
|
||||
return false;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
// err conservatively
|
||||
LOG
|
||||
.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) {
|
||||
try {
|
||||
int maxstale = Integer.parseInt(elt.getValue());
|
||||
if (entry.getFreshnessLifetimeSecs() > maxstale) {
|
||||
LOG
|
||||
.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
|
||||
return false;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
// err conservatively
|
||||
LOG
|
||||
.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName())) {
|
||||
try {
|
||||
int minfresh = Integer.parseInt(elt.getValue());
|
||||
if (entry.getFreshnessLifetimeSecs() < minfresh) {
|
||||
LOG
|
||||
.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
|
||||
return false;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
// err conservatively
|
||||
LOG
|
||||
.debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("CachedResponseSuitabilityChecker: Response from cache was suitable.");
|
||||
return true;
|
||||
}
|
||||
}
|
444
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachingHttpClient.java
vendored
Normal file
444
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachingHttpClient.java
vendored
Normal file
|
@ -0,0 +1,444 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.ProtocolException;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.RequestLine;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.ResponseHandler;
|
||||
import org.apache.http.client.cache.HttpCacheOperationException;
|
||||
import org.apache.http.client.cache.HttpCacheUpdateCallback;
|
||||
import org.apache.http.client.cache.HttpCache;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.conn.ClientConnectionManager;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
import org.apache.http.message.BasicStatusLine;
|
||||
import org.apache.http.params.HttpParams;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public class CachingHttpClient implements HttpClient {
|
||||
|
||||
private final static int MAX_CACHE_ENTRIES = 1000;
|
||||
private final static int DEFAULT_MAX_OBJECT_SIZE_BYTES = 8192;
|
||||
|
||||
public static final ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1);
|
||||
|
||||
private final static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false;
|
||||
|
||||
private HttpClient backend;
|
||||
private ResponseCachingPolicy responseCachingPolicy;
|
||||
private CacheEntryGenerator cacheEntryGenerator;
|
||||
private URIExtractor uriExtractor;
|
||||
private HttpCache<CacheEntry> responseCache;
|
||||
private CachedHttpResponseGenerator responseGenerator;
|
||||
private CacheInvalidator cacheInvalidator;
|
||||
private CacheableRequestPolicy cacheableRequestPolicy;
|
||||
private CachedResponseSuitabilityChecker suitabilityChecker;
|
||||
|
||||
private ConditionalRequestBuilder conditionalRequestBuilder;
|
||||
private int maxObjectSizeBytes = DEFAULT_MAX_OBJECT_SIZE_BYTES;
|
||||
private CacheEntryUpdater cacheEntryUpdater;
|
||||
|
||||
private volatile long cacheHits;
|
||||
private volatile long cacheMisses;
|
||||
private volatile long cacheUpdates;
|
||||
private ResponseProtocolCompliance responseCompliance;
|
||||
private RequestProtocolCompliance requestCompliance;
|
||||
private static final Log LOG = LogFactory.getLog(CachingHttpClient.class);
|
||||
|
||||
public CachingHttpClient() {
|
||||
this.backend = new DefaultHttpClient();
|
||||
this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes);
|
||||
this.cacheEntryGenerator = new CacheEntryGenerator();
|
||||
this.uriExtractor = new URIExtractor();
|
||||
this.responseCache = new BasicHttpCache(MAX_CACHE_ENTRIES);
|
||||
this.responseGenerator = new CachedHttpResponseGenerator();
|
||||
this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.responseCache);
|
||||
this.cacheableRequestPolicy = new CacheableRequestPolicy();
|
||||
this.suitabilityChecker = new CachedResponseSuitabilityChecker();
|
||||
this.conditionalRequestBuilder = new ConditionalRequestBuilder();
|
||||
this.cacheEntryUpdater = new CacheEntryUpdater();
|
||||
this.responseCompliance = new ResponseProtocolCompliance();
|
||||
this.requestCompliance = new RequestProtocolCompliance();
|
||||
}
|
||||
|
||||
public CachingHttpClient(HttpCache<CacheEntry> cache, int maxObjectSizeBytes) {
|
||||
this.responseCache = cache;
|
||||
|
||||
this.backend = new DefaultHttpClient();
|
||||
this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes);
|
||||
this.cacheEntryGenerator = new CacheEntryGenerator();
|
||||
this.uriExtractor = new URIExtractor();
|
||||
this.responseGenerator = new CachedHttpResponseGenerator();
|
||||
this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.responseCache);
|
||||
this.cacheableRequestPolicy = new CacheableRequestPolicy();
|
||||
this.suitabilityChecker = new CachedResponseSuitabilityChecker();
|
||||
this.conditionalRequestBuilder = new ConditionalRequestBuilder();
|
||||
this.cacheEntryUpdater = new CacheEntryUpdater();
|
||||
this.responseCompliance = new ResponseProtocolCompliance();
|
||||
this.requestCompliance = new RequestProtocolCompliance();
|
||||
}
|
||||
|
||||
public CachingHttpClient(HttpClient client, HttpCache<CacheEntry> cache, int maxObjectSizeBytes) {
|
||||
this.responseCache = cache;
|
||||
|
||||
this.backend = client;
|
||||
this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes);
|
||||
this.cacheEntryGenerator = new CacheEntryGenerator();
|
||||
this.uriExtractor = new URIExtractor();
|
||||
this.responseGenerator = new CachedHttpResponseGenerator();
|
||||
this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.responseCache);
|
||||
this.cacheableRequestPolicy = new CacheableRequestPolicy();
|
||||
this.suitabilityChecker = new CachedResponseSuitabilityChecker();
|
||||
this.conditionalRequestBuilder = new ConditionalRequestBuilder();
|
||||
this.cacheEntryUpdater = new CacheEntryUpdater();
|
||||
this.maxObjectSizeBytes = maxObjectSizeBytes;
|
||||
this.responseCompliance = new ResponseProtocolCompliance();
|
||||
this.requestCompliance = new RequestProtocolCompliance();
|
||||
}
|
||||
|
||||
public CachingHttpClient(HttpClient backend, ResponseCachingPolicy responseCachingPolicy,
|
||||
CacheEntryGenerator cacheEntryGenerator, URIExtractor uriExtractor,
|
||||
HttpCache<CacheEntry> responseCache, CachedHttpResponseGenerator responseGenerator,
|
||||
CacheInvalidator cacheInvalidator, CacheableRequestPolicy cacheableRequestPolicy,
|
||||
CachedResponseSuitabilityChecker suitabilityChecker,
|
||||
ConditionalRequestBuilder conditionalRequestBuilder, CacheEntryUpdater entryUpdater,
|
||||
ResponseProtocolCompliance responseCompliance,
|
||||
RequestProtocolCompliance requestCompliance) {
|
||||
this.backend = backend;
|
||||
this.responseCachingPolicy = responseCachingPolicy;
|
||||
this.cacheEntryGenerator = cacheEntryGenerator;
|
||||
this.uriExtractor = uriExtractor;
|
||||
this.responseCache = responseCache;
|
||||
this.responseGenerator = responseGenerator;
|
||||
this.cacheInvalidator = cacheInvalidator;
|
||||
this.cacheableRequestPolicy = cacheableRequestPolicy;
|
||||
this.suitabilityChecker = suitabilityChecker;
|
||||
this.conditionalRequestBuilder = conditionalRequestBuilder;
|
||||
this.cacheEntryUpdater = entryUpdater;
|
||||
this.responseCompliance = responseCompliance;
|
||||
this.requestCompliance = requestCompliance;
|
||||
}
|
||||
|
||||
public long getCacheHits() {
|
||||
return cacheHits;
|
||||
}
|
||||
|
||||
public long getCacheMisses() {
|
||||
return cacheMisses;
|
||||
}
|
||||
|
||||
public long getCacheUpdates() {
|
||||
return cacheUpdates;
|
||||
}
|
||||
|
||||
public HttpResponse execute(HttpHost target, HttpRequest request) throws IOException {
|
||||
HttpContext defaultContext = null;
|
||||
return execute(target, request, defaultContext);
|
||||
}
|
||||
|
||||
public <T> T execute(HttpHost target, HttpRequest request,
|
||||
ResponseHandler<? extends T> responseHandler) throws IOException {
|
||||
return execute(target, request, responseHandler, null);
|
||||
}
|
||||
|
||||
public <T> T execute(HttpHost target, HttpRequest request,
|
||||
ResponseHandler<? extends T> responseHandler, HttpContext context) throws IOException {
|
||||
HttpResponse resp = execute(target, request, context);
|
||||
return responseHandler.handleResponse(resp);
|
||||
}
|
||||
|
||||
public HttpResponse execute(HttpUriRequest request) throws IOException {
|
||||
HttpContext context = null;
|
||||
return execute(request, context);
|
||||
}
|
||||
|
||||
public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException {
|
||||
URI uri = request.getURI();
|
||||
HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
|
||||
return execute(httpHost, request, context);
|
||||
}
|
||||
|
||||
public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler)
|
||||
throws IOException {
|
||||
return execute(request, responseHandler, null);
|
||||
}
|
||||
|
||||
public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler,
|
||||
HttpContext context) throws IOException {
|
||||
HttpResponse resp = execute(request, context);
|
||||
return responseHandler.handleResponse(resp);
|
||||
}
|
||||
|
||||
public ClientConnectionManager getConnectionManager() {
|
||||
return backend.getConnectionManager();
|
||||
}
|
||||
|
||||
public HttpParams getParams() {
|
||||
return backend.getParams();
|
||||
}
|
||||
|
||||
protected Date getCurrentDate() {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
protected CacheEntry getCacheEntry(HttpHost target, HttpRequest request) {
|
||||
String uri = uriExtractor.getURI(target, request);
|
||||
CacheEntry entry = null;
|
||||
try {
|
||||
entry = responseCache.getEntry(uri);
|
||||
} catch (HttpCacheOperationException probablyIgnore) {
|
||||
// TODO: do something useful with this exception
|
||||
}
|
||||
|
||||
if (entry == null || !entry.hasVariants())
|
||||
return entry;
|
||||
|
||||
String variantUri = uriExtractor.getVariantURI(target, request, entry);
|
||||
try {
|
||||
return responseCache.getEntry(variantUri);
|
||||
} catch (HttpCacheOperationException probablyIgnore) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context)
|
||||
throws IOException {
|
||||
|
||||
if (clientRequestsOurOptions(request)) {
|
||||
return new OptionsHttp11Response();
|
||||
}
|
||||
|
||||
List<RequestProtocolError> fatalError = requestCompliance
|
||||
.requestIsFatallyNonCompliant(request);
|
||||
|
||||
for (RequestProtocolError error : fatalError) {
|
||||
return requestCompliance.getErrorForRequest(error);
|
||||
}
|
||||
|
||||
try {
|
||||
request = requestCompliance.makeRequestCompliant(request);
|
||||
} catch (ProtocolException e) {
|
||||
throw new ClientProtocolException(e);
|
||||
}
|
||||
|
||||
cacheInvalidator.flushInvalidatedCacheEntries(target, request);
|
||||
|
||||
if (!cacheableRequestPolicy.isServableFromCache(request)) {
|
||||
return callBackend(target, request, context);
|
||||
}
|
||||
|
||||
CacheEntry entry = getCacheEntry(target, request);
|
||||
if (entry == null) {
|
||||
cacheMisses++;
|
||||
LOG.debug("CLIENT: Cache Miss.");
|
||||
return callBackend(target, request, context);
|
||||
}
|
||||
|
||||
LOG.debug("CLIENT: Cache HIT.");
|
||||
cacheHits++;
|
||||
|
||||
if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry)) {
|
||||
return responseGenerator.generateResponse(entry);
|
||||
}
|
||||
|
||||
if (entry.isRevalidatable()) {
|
||||
LOG.debug("CLIENT: Revalidate the entry.");
|
||||
|
||||
try {
|
||||
return revalidateCacheEntry(target, request, context, entry);
|
||||
} catch (IOException ioex) {
|
||||
HttpResponse response = responseGenerator.generateResponse(entry);
|
||||
response.addHeader(HeaderConstants.WARNING, "111 Revalidation Failed - "
|
||||
+ ioex.getMessage());
|
||||
return response;
|
||||
} catch (ProtocolException e) {
|
||||
throw new ClientProtocolException(e);
|
||||
}
|
||||
}
|
||||
return callBackend(target, request, context);
|
||||
}
|
||||
|
||||
private boolean clientRequestsOurOptions(HttpRequest request) {
|
||||
RequestLine line = request.getRequestLine();
|
||||
|
||||
if (!HeaderConstants.OPTIONS_METHOD.equals(line.getMethod()))
|
||||
return false;
|
||||
|
||||
if (!"*".equals(line.getUri()))
|
||||
return false;
|
||||
|
||||
if (!"0".equals(request.getFirstHeader(HeaderConstants.MAX_FORWARDS).getValue()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected HttpResponse callBackend(HttpHost target, HttpRequest request, HttpContext context)
|
||||
throws IOException {
|
||||
|
||||
Date requestDate = getCurrentDate();
|
||||
|
||||
try {
|
||||
LOG.debug("CLIENT: Calling the backend.");
|
||||
HttpResponse backendResponse = backend.execute(target, request, context);
|
||||
return handleBackendResponse(target, request, requestDate, getCurrentDate(),
|
||||
backendResponse);
|
||||
} catch (ClientProtocolException cpex) {
|
||||
throw cpex;
|
||||
} catch (IOException ex) {
|
||||
StatusLine status = new BasicStatusLine(HTTP_1_1, HttpStatus.SC_SERVICE_UNAVAILABLE, ex
|
||||
.getMessage());
|
||||
return new BasicHttpResponse(status);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected HttpResponse revalidateCacheEntry(HttpHost target, HttpRequest request,
|
||||
HttpContext context, CacheEntry cacheEntry) throws IOException, ProtocolException {
|
||||
HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(request,
|
||||
cacheEntry);
|
||||
Date requestDate = getCurrentDate();
|
||||
|
||||
HttpResponse backendResponse = backend.execute(target, conditionalRequest, context);
|
||||
|
||||
Date responseDate = getCurrentDate();
|
||||
|
||||
int statusCode = backendResponse.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
|
||||
cacheUpdates++;
|
||||
cacheEntryUpdater.updateCacheEntry(cacheEntry, requestDate, responseDate,
|
||||
backendResponse);
|
||||
storeInCache(target, request, cacheEntry);
|
||||
return responseGenerator.generateResponse(cacheEntry);
|
||||
}
|
||||
|
||||
return handleBackendResponse(target, conditionalRequest, requestDate, responseDate,
|
||||
backendResponse);
|
||||
}
|
||||
|
||||
protected void storeInCache(HttpHost target, HttpRequest request, CacheEntry entry) {
|
||||
if (entry.hasVariants()) {
|
||||
try {
|
||||
String uri = uriExtractor.getURI(target, request);
|
||||
HttpCacheUpdateCallback<CacheEntry> callback = storeVariantEntry(target, request, entry);
|
||||
responseCache.updateCacheEntry(uri, callback);
|
||||
} catch (HttpCacheOperationException probablyIgnore) {
|
||||
// TODO: do something useful with this exception
|
||||
}
|
||||
} else {
|
||||
storeNonVariantEntry(target, request, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void storeNonVariantEntry(HttpHost target, HttpRequest req, CacheEntry entry) {
|
||||
String uri = uriExtractor.getURI(target, req);
|
||||
try {
|
||||
responseCache.putEntry(uri, entry);
|
||||
} catch (HttpCacheOperationException probablyIgnore) {
|
||||
// TODO: do something useful with this exception
|
||||
}
|
||||
}
|
||||
|
||||
protected HttpCacheUpdateCallback<CacheEntry> storeVariantEntry(final HttpHost target, final HttpRequest req,
|
||||
final CacheEntry entry) {
|
||||
return new HttpCacheUpdateCallback<CacheEntry>() {
|
||||
public CacheEntry getUpdatedEntry(CacheEntry existing) throws HttpCacheOperationException {
|
||||
|
||||
String variantURI = uriExtractor.getVariantURI(target, req, entry);
|
||||
responseCache.putEntry(variantURI, entry);
|
||||
|
||||
if (existing != null) {
|
||||
existing.addVariantURI(variantURI);
|
||||
return existing;
|
||||
} else {
|
||||
entry.addVariantURI(variantURI);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected HttpResponse handleBackendResponse(HttpHost target, HttpRequest request,
|
||||
Date requestDate, Date responseDate, HttpResponse backendResponse) throws IOException {
|
||||
|
||||
LOG.debug("CLIENT: Handling Backend response.");
|
||||
responseCompliance.ensureProtocolCompliance(request, backendResponse);
|
||||
|
||||
boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
|
||||
|
||||
if (cacheable) {
|
||||
|
||||
SizeLimitedResponseReader responseReader = getResponseReader(backendResponse);
|
||||
|
||||
if (responseReader.isResponseTooLarge()) {
|
||||
return responseReader.getReconstructedResponse();
|
||||
}
|
||||
|
||||
CacheEntry entry = cacheEntryGenerator.generateEntry(requestDate, responseDate,
|
||||
backendResponse, responseReader.getResponseBytes());
|
||||
storeInCache(target, request, entry);
|
||||
return responseGenerator.generateResponse(entry);
|
||||
}
|
||||
|
||||
String uri = uriExtractor.getURI(target, request);
|
||||
try {
|
||||
responseCache.removeEntry(uri);
|
||||
} catch (HttpCacheOperationException coe) {
|
||||
// TODO: track failed state
|
||||
}
|
||||
return backendResponse;
|
||||
}
|
||||
|
||||
protected SizeLimitedResponseReader getResponseReader(HttpResponse backEndResponse)
|
||||
throws IOException {
|
||||
return new SizeLimitedResponseReader(maxObjectSizeBytes, backEndResponse);
|
||||
}
|
||||
|
||||
public boolean supportsRangeAndContentRangeHeaders() {
|
||||
return SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS;
|
||||
}
|
||||
|
||||
}
|
73
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CombinedInputStream.java
vendored
Normal file
73
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CombinedInputStream.java
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* A class that presents two inputstreams as a single stream
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
class CombinedInputStream extends InputStream {
|
||||
|
||||
private InputStream inputStream1;
|
||||
private InputStream inputStream2;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param inputStream1
|
||||
* First stream to read
|
||||
* @param inputStream2
|
||||
* Second stream to read
|
||||
*/
|
||||
public CombinedInputStream(InputStream inputStream1, InputStream inputStream2) {
|
||||
if (inputStream1 == null)
|
||||
throw new IllegalArgumentException("inputStream1 cannot be null");
|
||||
if (inputStream2 == null)
|
||||
throw new IllegalArgumentException("inputStream2 cannot be null");
|
||||
|
||||
this.inputStream1 = inputStream1;
|
||||
this.inputStream2 = inputStream2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return inputStream1.available() + inputStream2.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int result = inputStream1.read();
|
||||
|
||||
if (result == -1)
|
||||
result = inputStream2.read();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
54
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ConditionalRequestBuilder.java
vendored
Normal file
54
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ConditionalRequestBuilder.java
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.ProtocolException;
|
||||
import org.apache.http.impl.client.RequestWrapper;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public class ConditionalRequestBuilder {
|
||||
|
||||
public HttpRequest buildConditionalRequest(HttpRequest request, CacheEntry cacheEntry)
|
||||
throws ProtocolException {
|
||||
RequestWrapper wrapperRequest = new RequestWrapper(request);
|
||||
wrapperRequest.resetHeaders();
|
||||
Header eTag = cacheEntry.getFirstHeader(HeaderConstants.ETAG);
|
||||
if (eTag != null) {
|
||||
wrapperRequest.setHeader("If-None-Match", eTag.getValue());
|
||||
} else {
|
||||
Header lastModified = cacheEntry.getFirstHeader(HeaderConstants.LAST_MODIFIED);
|
||||
wrapperRequest.setHeader("If-Modified-Since", lastModified.getValue());
|
||||
}
|
||||
return wrapperRequest;
|
||||
|
||||
}
|
||||
|
||||
}
|
109
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/DefaultCacheEntrySerializer.java
vendored
Normal file
109
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/DefaultCacheEntrySerializer.java
vendored
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.http.client.cache.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.http.client.cache.HttpCacheEntrySerializer;
|
||||
|
||||
/**
|
||||
* {@link HttpCacheEntrySerializer} implementation that uses the default (native)
|
||||
* serialization.
|
||||
*
|
||||
* @see java.io.Serializable
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public class DefaultCacheEntrySerializer implements HttpCacheEntrySerializer<CacheEntry> {
|
||||
|
||||
public void writeTo(CacheEntry cacheEntry, OutputStream os) throws IOException {
|
||||
|
||||
ObjectOutputStream oos = null;
|
||||
try {
|
||||
oos = new ObjectOutputStream(os);
|
||||
// write CacheEntry
|
||||
oos.writeObject(cacheEntry);
|
||||
// write headers as a String [][]
|
||||
// Header [] headers = cacheEntry.getAllHeaders();
|
||||
// if(null == headers || headers.length < 1) return;
|
||||
// String [][] sheaders = new String[headers.length][2];
|
||||
// for(int i=0; i < headers.length; i++) {
|
||||
// sheaders[i][0] = headers[i].getName();
|
||||
// sheaders[i][1] = headers[i].getValue();
|
||||
// }
|
||||
// oos.writeObject(sheaders);
|
||||
} finally {
|
||||
try {
|
||||
oos.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
try {
|
||||
os.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public CacheEntry readFrom(InputStream is) throws IOException {
|
||||
|
||||
ObjectInputStream ois = null;
|
||||
try {
|
||||
ois = new ObjectInputStream(is);
|
||||
// read CacheEntry
|
||||
CacheEntry cacheEntry = (CacheEntry) ois.readObject();
|
||||
// read headers as a String [][]
|
||||
// String [][] sheaders = (String[][])ois.readObject();
|
||||
// if(null == sheaders || sheaders.length < 1) return cacheEntry;
|
||||
// BasicHeader [] headers = new BasicHeader[sheaders.length];
|
||||
// for(int i=0; i < sheaders.length; i++) {
|
||||
// String [] sheader = sheaders[i];
|
||||
// headers[i] = new BasicHeader(sheader[0], sheader[1]);
|
||||
// }
|
||||
// cacheEntry.setResponseHeaders(headers);
|
||||
return cacheEntry;
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
// CacheEntry should be known, it not we have a runtime issue
|
||||
throw new RuntimeException(cnfe);
|
||||
} finally {
|
||||
try {
|
||||
ois.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
68
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/HeaderConstants.java
vendored
Normal file
68
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/HeaderConstants.java
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public class HeaderConstants {
|
||||
|
||||
public static final String GET_METHOD = "GET";
|
||||
public static final String HEAD_METHOD = "HEAD";
|
||||
public static final String OPTIONS_METHOD = "OPTIONS";
|
||||
public static final String PUT_METHOD = "PUT";
|
||||
public static final String DELETE_METHOD = "DELETE";
|
||||
public static final String TRACE_METHOD = "TRACE";
|
||||
|
||||
public static final String LAST_MODIFIED = "Last-Modified";
|
||||
public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
|
||||
public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
|
||||
public static final String IF_NONE_MATCH = "If-None-Match";
|
||||
|
||||
public static final String PRAGMA = "Pragma";
|
||||
public static final String MAX_FORWARDS = "Max-Forwards";
|
||||
public static final String ETAG = "ETag";
|
||||
public static final String EXPIRES = "Expires";
|
||||
public static final String AGE = "Age";
|
||||
public static final String CONTENT_LENGTH = "Content-Length";
|
||||
public static final String DATE = "Date";
|
||||
public static final String VARY = "Vary";
|
||||
|
||||
public static final String CACHE_CONTROL = "Cache-Control";
|
||||
public static final String CACHE_CONTROL_NO_STORE = "no-store";
|
||||
public static final String CACHE_CONTROL_NO_CACHE = "no-cache";
|
||||
public static final String CACHE_CONTROL_MAX_AGE = "max-age";
|
||||
public static final String CACHE_CONTROL_MAX_STALE = "max-stale";
|
||||
public static final String CACHE_CONTROL_MIN_FRESH = "min-fresh";
|
||||
|
||||
public static final String TRANSFER_ENCODING = "Transfer-Encoding";
|
||||
public static final String WARNING = "Warning";
|
||||
public static final String EXPECT = "Expect";
|
||||
public static final String RANGE = "Range";
|
||||
public static final String CONTENT_RANGE = "Content-Range";
|
||||
|
||||
}
|
162
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/OptionsHttp11Response.java
vendored
Normal file
162
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/OptionsHttp11Response.java
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderIterator;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.message.AbstractHttpMessage;
|
||||
import org.apache.http.message.BasicStatusLine;
|
||||
import org.apache.http.params.BasicHttpParams;
|
||||
import org.apache.http.params.HttpParams;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public final class OptionsHttp11Response extends AbstractHttpMessage implements HttpResponse {
|
||||
|
||||
StatusLine statusLine = new BasicStatusLine(CachingHttpClient.HTTP_1_1,
|
||||
HttpStatus.SC_NOT_IMPLEMENTED, "");
|
||||
ProtocolVersion version = CachingHttpClient.HTTP_1_1;
|
||||
|
||||
public StatusLine getStatusLine() {
|
||||
return statusLine;
|
||||
}
|
||||
|
||||
public void setStatusLine(StatusLine statusline) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void setStatusLine(ProtocolVersion ver, int code) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void setStatusLine(ProtocolVersion ver, int code, String reason) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void setStatusCode(int code) throws IllegalStateException {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void setReasonPhrase(String reason) throws IllegalStateException {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public HttpEntity getEntity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setEntity(HttpEntity entity) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setLocale(Locale loc) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public ProtocolVersion getProtocolVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public boolean containsHeader(String name) {
|
||||
return this.headergroup.containsHeader(name);
|
||||
}
|
||||
|
||||
public Header[] getHeaders(String name) {
|
||||
return this.headergroup.getHeaders(name);
|
||||
}
|
||||
|
||||
public Header getFirstHeader(String name) {
|
||||
return this.headergroup.getFirstHeader(name);
|
||||
}
|
||||
|
||||
public Header getLastHeader(String name) {
|
||||
return this.headergroup.getLastHeader(name);
|
||||
}
|
||||
|
||||
public Header[] getAllHeaders() {
|
||||
return this.headergroup.getAllHeaders();
|
||||
}
|
||||
|
||||
public void addHeader(Header header) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void addHeader(String name, String value) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void setHeader(Header header) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void setHeader(String name, String value) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void setHeaders(Header[] headers) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void removeHeader(Header header) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void removeHeaders(String name) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public HeaderIterator headerIterator() {
|
||||
return this.headergroup.iterator();
|
||||
}
|
||||
|
||||
public HeaderIterator headerIterator(String name) {
|
||||
return this.headergroup.iterator(name);
|
||||
}
|
||||
|
||||
public HttpParams getParams() {
|
||||
if (this.params == null) {
|
||||
this.params = new BasicHttpParams();
|
||||
}
|
||||
return this.params;
|
||||
}
|
||||
|
||||
public void setParams(HttpParams params) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
}
|
308
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolCompliance.java
vendored
Normal file
308
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolCompliance.java
vendored
Normal file
|
@ -0,0 +1,308 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.ProtocolException;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.entity.AbstractHttpEntity;
|
||||
import org.apache.http.impl.client.RequestWrapper;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
import org.apache.http.message.BasicStatusLine;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public class RequestProtocolCompliance {
|
||||
|
||||
public List<RequestProtocolError> requestIsFatallyNonCompliant(HttpRequest request) {
|
||||
List<RequestProtocolError> theErrors = new ArrayList<RequestProtocolError>();
|
||||
|
||||
RequestProtocolError anError = requestContainsBodyButNoLength(request);
|
||||
if (anError != null) {
|
||||
theErrors.add(anError);
|
||||
}
|
||||
|
||||
anError = requestHasWeakETagAndRange(request);
|
||||
if (anError != null) {
|
||||
theErrors.add(anError);
|
||||
}
|
||||
|
||||
anError = requestHasWeekETagForPUTOrDELETEIfMatch(request);
|
||||
if (anError != null) {
|
||||
theErrors.add(anError);
|
||||
}
|
||||
|
||||
return theErrors;
|
||||
}
|
||||
|
||||
public HttpRequest makeRequestCompliant(HttpRequest request) throws ProtocolException {
|
||||
if (requestMustNotHaveEntity(request)) {
|
||||
((HttpEntityEnclosingRequest) request).setEntity(null);
|
||||
}
|
||||
|
||||
verifyRequestWithExpectContinueFlagHas100continueHeader(request);
|
||||
verifyOPTIONSRequestWithBodyHasContentType(request);
|
||||
decrementOPTIONSMaxForwardsIfGreaterThen0(request);
|
||||
|
||||
if (requestVersionIsTooLow(request)) {
|
||||
return upgradeRequestTo(request, CachingHttpClient.HTTP_1_1);
|
||||
}
|
||||
|
||||
if (requestMinorVersionIsTooHighMajorVersionsMatch(request)) {
|
||||
return downgradeRequestTo(request, CachingHttpClient.HTTP_1_1);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private boolean requestMustNotHaveEntity(HttpRequest request) {
|
||||
return HeaderConstants.TRACE_METHOD.equals(request.getRequestLine().getMethod())
|
||||
&& request instanceof HttpEntityEnclosingRequest;
|
||||
}
|
||||
|
||||
private void decrementOPTIONSMaxForwardsIfGreaterThen0(HttpRequest request) {
|
||||
if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Header maxForwards = request.getFirstHeader(HeaderConstants.MAX_FORWARDS);
|
||||
if (maxForwards == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
request.removeHeaders(HeaderConstants.MAX_FORWARDS);
|
||||
Integer currentMaxForwards = Integer.valueOf(maxForwards.getValue());
|
||||
|
||||
request.setHeader(HeaderConstants.MAX_FORWARDS, Integer.toString(currentMaxForwards - 1));
|
||||
}
|
||||
|
||||
private void verifyOPTIONSRequestWithBodyHasContentType(HttpRequest request) {
|
||||
if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(request instanceof HttpEntityEnclosingRequest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
addContentTypeHeaderIfMissing((HttpEntityEnclosingRequest) request);
|
||||
}
|
||||
|
||||
private void addContentTypeHeaderIfMissing(HttpEntityEnclosingRequest request) {
|
||||
if (request.getEntity().getContentType() == null) {
|
||||
((AbstractHttpEntity) request.getEntity()).setContentType("application/octet-stream");
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyRequestWithExpectContinueFlagHas100continueHeader(HttpRequest request) {
|
||||
if (request instanceof HttpEntityEnclosingRequest) {
|
||||
|
||||
if (((HttpEntityEnclosingRequest) request).expectContinue()
|
||||
&& ((HttpEntityEnclosingRequest) request).getEntity() != null) {
|
||||
add100ContinueHeaderIfMissing(request);
|
||||
} else {
|
||||
remove100ContinueHeaderIfExists(request);
|
||||
}
|
||||
} else {
|
||||
remove100ContinueHeaderIfExists(request);
|
||||
}
|
||||
}
|
||||
|
||||
private void remove100ContinueHeaderIfExists(HttpRequest request) {
|
||||
boolean hasHeader = false;
|
||||
|
||||
Header[] expectHeaders = request.getHeaders(HeaderConstants.EXPECT);
|
||||
List<HeaderElement> expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>();
|
||||
|
||||
for (Header h : expectHeaders) {
|
||||
for (HeaderElement elt : h.getElements()) {
|
||||
if (!("100-continue".equalsIgnoreCase(elt.getName()))) {
|
||||
expectElementsThatAreNot100Continue.add(elt);
|
||||
} else {
|
||||
hasHeader = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasHeader) {
|
||||
request.removeHeader(h);
|
||||
for (HeaderElement elt : expectElementsThatAreNot100Continue) {
|
||||
BasicHeader newHeader = new BasicHeader(HeaderConstants.EXPECT, elt.getName());
|
||||
request.addHeader(newHeader);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void add100ContinueHeaderIfMissing(HttpRequest request) {
|
||||
boolean hasHeader = false;
|
||||
|
||||
for (Header h : request.getHeaders(HeaderConstants.EXPECT)) {
|
||||
for (HeaderElement elt : h.getElements()) {
|
||||
if ("100-continue".equalsIgnoreCase(elt.getName())) {
|
||||
hasHeader = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasHeader) {
|
||||
request.addHeader(HeaderConstants.EXPECT, "100-continue");
|
||||
}
|
||||
}
|
||||
|
||||
private HttpRequest upgradeRequestTo(HttpRequest request, ProtocolVersion version)
|
||||
throws ProtocolException {
|
||||
RequestWrapper newRequest = new RequestWrapper(request);
|
||||
newRequest.setProtocolVersion(version);
|
||||
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
private HttpRequest downgradeRequestTo(HttpRequest request, ProtocolVersion version)
|
||||
throws ProtocolException {
|
||||
RequestWrapper newRequest = new RequestWrapper(request);
|
||||
newRequest.setProtocolVersion(version);
|
||||
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
protected boolean requestMinorVersionIsTooHighMajorVersionsMatch(HttpRequest request) {
|
||||
ProtocolVersion requestProtocol = request.getProtocolVersion();
|
||||
if (requestProtocol.getMajor() != CachingHttpClient.HTTP_1_1.getMajor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (requestProtocol.getMinor() > CachingHttpClient.HTTP_1_1.getMinor()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean requestVersionIsTooLow(HttpRequest request) {
|
||||
return request.getProtocolVersion().compareToVersion(CachingHttpClient.HTTP_1_1) < 0;
|
||||
}
|
||||
|
||||
public HttpResponse getErrorForRequest(RequestProtocolError errorCheck) {
|
||||
switch (errorCheck) {
|
||||
case BODY_BUT_NO_LENGTH_ERROR:
|
||||
return new BasicHttpResponse(new BasicStatusLine(CachingHttpClient.HTTP_1_1,
|
||||
HttpStatus.SC_LENGTH_REQUIRED, ""));
|
||||
|
||||
case WEAK_ETAG_AND_RANGE_ERROR:
|
||||
return new BasicHttpResponse(new BasicStatusLine(CachingHttpClient.HTTP_1_1,
|
||||
HttpStatus.SC_BAD_REQUEST, "Weak eTag not compatible with byte range"));
|
||||
|
||||
case WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR:
|
||||
return new BasicHttpResponse(new BasicStatusLine(CachingHttpClient.HTTP_1_1,
|
||||
HttpStatus.SC_BAD_REQUEST,
|
||||
"Weak eTag not compatible with PUT or DELETE requests"));
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
"The request was compliant, therefore no error can be generated for it.");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private RequestProtocolError requestHasWeakETagAndRange(HttpRequest request) {
|
||||
// TODO: Should these be looking at all the headers marked as Range?
|
||||
String method = request.getRequestLine().getMethod();
|
||||
if (!(HeaderConstants.GET_METHOD.equals(method))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Header range = request.getFirstHeader("Range");
|
||||
if (range == null)
|
||||
return null;
|
||||
|
||||
Header ifRange = request.getFirstHeader("If-Range");
|
||||
if (ifRange == null)
|
||||
return null;
|
||||
|
||||
String val = ifRange.getValue();
|
||||
if (val.startsWith("W/")) {
|
||||
return RequestProtocolError.WEAK_ETAG_AND_RANGE_ERROR;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private RequestProtocolError requestHasWeekETagForPUTOrDELETEIfMatch(HttpRequest request) {
|
||||
// TODO: Should these be looking at all the headers marked as
|
||||
// If-Match/If-None-Match?
|
||||
|
||||
String method = request.getRequestLine().getMethod();
|
||||
if (!(HeaderConstants.PUT_METHOD.equals(method) || HeaderConstants.DELETE_METHOD
|
||||
.equals(method))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Header ifMatch = request.getFirstHeader("If-Match");
|
||||
if (ifMatch != null) {
|
||||
String val = ifMatch.getValue();
|
||||
if (val.startsWith("W/")) {
|
||||
return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
|
||||
}
|
||||
} else {
|
||||
Header ifNoneMatch = request.getFirstHeader("If-None-Match");
|
||||
if (ifNoneMatch == null)
|
||||
return null;
|
||||
|
||||
String val2 = ifNoneMatch.getValue();
|
||||
if (val2.startsWith("W/")) {
|
||||
return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private RequestProtocolError requestContainsBodyButNoLength(HttpRequest request) {
|
||||
if (!(request instanceof HttpEntityEnclosingRequest)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (request.getFirstHeader(HeaderConstants.CONTENT_LENGTH) != null
|
||||
&& ((HttpEntityEnclosingRequest) request).getEntity() != null)
|
||||
return null;
|
||||
|
||||
return RequestProtocolError.BODY_BUT_NO_LENGTH_ERROR;
|
||||
}
|
||||
}
|
39
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolError.java
vendored
Normal file
39
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolError.java
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public enum RequestProtocolError {
|
||||
|
||||
UNKNOWN,
|
||||
BODY_BUT_NO_LENGTH_ERROR,
|
||||
WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR,
|
||||
WEAK_ETAG_AND_RANGE_ERROR
|
||||
|
||||
}
|
189
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseCachingPolicy.java
vendored
Normal file
189
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseCachingPolicy.java
vendored
Normal file
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.impl.cookie.DateParseException;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
|
||||
/**
|
||||
* Determines if an HttpResponse can be cached.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public class ResponseCachingPolicy {
|
||||
|
||||
private int maxObjectSizeBytes;
|
||||
private static final Log LOG = LogFactory.getLog(ResponseCachingPolicy.class);
|
||||
|
||||
public ResponseCachingPolicy(int maxObjectSizeBytes) {
|
||||
this.maxObjectSizeBytes = maxObjectSizeBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an HttpResponse can be cached.
|
||||
*
|
||||
* @param httpMethod
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
public boolean isResponseCacheable(String httpMethod, HttpResponse response) {
|
||||
boolean cacheable = false;
|
||||
|
||||
if (!HeaderConstants.GET_METHOD.equals(httpMethod)) {
|
||||
LOG.debug("ResponseCachingPolicy: response was not cacheable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (response.getStatusLine().getStatusCode()) {
|
||||
case HttpStatus.SC_OK:
|
||||
case HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION:
|
||||
case HttpStatus.SC_MULTIPLE_CHOICES:
|
||||
case HttpStatus.SC_MOVED_PERMANENTLY:
|
||||
case HttpStatus.SC_GONE:
|
||||
// these response codes MAY be cached
|
||||
cacheable = true;
|
||||
LOG.debug("ResponseCachingPolicy: response WAS cacheable.");
|
||||
break;
|
||||
case HttpStatus.SC_PARTIAL_CONTENT:
|
||||
// we don't implement Range requests and hence are not
|
||||
// allowed to cache partial content
|
||||
LOG.debug("ResponseCachingPolicy: response was not cacheable (Partial Content).");
|
||||
return cacheable;
|
||||
|
||||
default:
|
||||
// If the status code is not one of the recognized
|
||||
// available codes in HttpStatus Don't Cache
|
||||
LOG.debug("ResponseCachingPolicy: response was not cacheable (Unknown Status code).");
|
||||
return cacheable;
|
||||
}
|
||||
|
||||
Header contentLength = response.getFirstHeader(HeaderConstants.CONTENT_LENGTH);
|
||||
if (contentLength != null) {
|
||||
int contentLengthValue = Integer.parseInt(contentLength.getValue());
|
||||
if (contentLengthValue > this.maxObjectSizeBytes)
|
||||
return false;
|
||||
}
|
||||
|
||||
Header[] ageHeaders = response.getHeaders(HeaderConstants.AGE);
|
||||
|
||||
if (ageHeaders.length > 1)
|
||||
return false;
|
||||
|
||||
Header[] expiresHeaders = response.getHeaders(HeaderConstants.EXPIRES);
|
||||
|
||||
if (expiresHeaders.length > 1)
|
||||
return false;
|
||||
|
||||
Header[] dateHeaders = response.getHeaders(HeaderConstants.DATE);
|
||||
|
||||
if (dateHeaders.length != 1)
|
||||
return false;
|
||||
|
||||
try {
|
||||
DateUtils.parseDate(dateHeaders[0].getValue());
|
||||
} catch (DateParseException dpe) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Header varyHdr : response.getHeaders(HeaderConstants.VARY)) {
|
||||
for (HeaderElement elem : varyHdr.getElements()) {
|
||||
if ("*".equals(elem.getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isExplicitlyNonCacheable(response))
|
||||
return false;
|
||||
|
||||
return (cacheable || isExplicitlyCacheable(response));
|
||||
}
|
||||
|
||||
protected boolean isExplicitlyNonCacheable(HttpResponse response) {
|
||||
Header[] cacheControlHeaders = response.getHeaders(HeaderConstants.CACHE_CONTROL);
|
||||
for (Header header : cacheControlHeaders) {
|
||||
for (HeaderElement elem : header.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elem.getName())
|
||||
|| HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elem.getName())
|
||||
|| "private".equals(elem.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isExplicitlyCacheable(HttpResponse response) {
|
||||
if (response.getFirstHeader(HeaderConstants.EXPIRES) != null)
|
||||
return true;
|
||||
Header[] cacheControlHeaders = response.getHeaders(HeaderConstants.CACHE_CONTROL);
|
||||
for (Header header : cacheControlHeaders) {
|
||||
for (HeaderElement elem : header.getElements()) {
|
||||
if ("max-age".equals(elem.getName()) || "s-maxage".equals(elem.getName())
|
||||
|| "must-revalidate".equals(elem.getName())
|
||||
|| "proxy-revalidate".equals(elem.getName())
|
||||
|| "public".equals(elem.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
public boolean isResponseCacheable(HttpRequest request, HttpResponse response) {
|
||||
if (requestProtocolGreaterThanAccepted(request)) {
|
||||
LOG.debug("ResponseCachingPolicy: response was not cacheable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.getRequestLine().getUri().contains("?") && !isExplicitlyCacheable(response)) {
|
||||
LOG.debug("ResponseCachingPolicy: response was not cacheable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
String method = request.getRequestLine().getMethod();
|
||||
return isResponseCacheable(method, response);
|
||||
}
|
||||
|
||||
private boolean requestProtocolGreaterThanAccepted(HttpRequest req) {
|
||||
return req.getProtocolVersion().compareToVersion(CachingHttpClient.HTTP_1_1) > 0;
|
||||
}
|
||||
|
||||
}
|
201
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseProtocolCompliance.java
vendored
Normal file
201
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseProtocolCompliance.java
vendored
Normal file
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.impl.client.RequestWrapper;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public class ResponseProtocolCompliance {
|
||||
|
||||
public void ensureProtocolCompliance(HttpRequest request, HttpResponse response)
|
||||
throws ClientProtocolException {
|
||||
if (backendResponseMustNotHaveBody(request, response)) {
|
||||
response.setEntity(null);
|
||||
}
|
||||
|
||||
authenticationRequiredDidNotHaveAProxyAuthenticationHeader(request, response);
|
||||
|
||||
notAllowedResponseDidNotHaveAnAllowHeader(request, response);
|
||||
|
||||
unauthorizedResponseDidNotHaveAWWWAuthenticateHeader(request, response);
|
||||
|
||||
requestDidNotExpect100ContinueButResponseIsOne(request, response);
|
||||
|
||||
transferEncodingIsNotReturnedTo1_0Client(request, response);
|
||||
|
||||
ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(request, response);
|
||||
|
||||
ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(request, response);
|
||||
|
||||
ensure206ContainsDateHeader(response);
|
||||
}
|
||||
|
||||
private void authenticationRequiredDidNotHaveAProxyAuthenticationHeader(HttpRequest request,
|
||||
HttpResponse response) throws ClientProtocolException {
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED)
|
||||
return;
|
||||
|
||||
if (response.getFirstHeader("Proxy-Authenticate") == null)
|
||||
throw new ClientProtocolException(
|
||||
"407 Response did not contain a Proxy-Authentication header");
|
||||
}
|
||||
|
||||
private void notAllowedResponseDidNotHaveAnAllowHeader(HttpRequest request,
|
||||
HttpResponse response) throws ClientProtocolException {
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_METHOD_NOT_ALLOWED)
|
||||
return;
|
||||
|
||||
if (response.getFirstHeader("Allow") == null)
|
||||
throw new ClientProtocolException("405 Response did not contain an Allow header.");
|
||||
}
|
||||
|
||||
private void unauthorizedResponseDidNotHaveAWWWAuthenticateHeader(HttpRequest request,
|
||||
HttpResponse response) throws ClientProtocolException {
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_UNAUTHORIZED)
|
||||
return;
|
||||
|
||||
if (response.getFirstHeader("WWW-Authenticate") == null) {
|
||||
throw new ClientProtocolException(
|
||||
"401 Response did not contain required WWW-Authenticate challenge header");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensure206ContainsDateHeader(HttpResponse response) {
|
||||
if (response.getFirstHeader(HeaderConstants.DATE) == null) {
|
||||
response.addHeader(HeaderConstants.DATE, DateUtils.formatDate(new Date()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(HttpRequest request,
|
||||
HttpResponse response) throws ClientProtocolException {
|
||||
if (request.getFirstHeader(HeaderConstants.RANGE) != null)
|
||||
return;
|
||||
|
||||
if (response.getFirstHeader(HeaderConstants.CONTENT_RANGE) != null) {
|
||||
throw new ClientProtocolException(
|
||||
"Content-Range was returned for a request that did not ask for a Content-Range.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(HttpRequest request,
|
||||
HttpResponse response) {
|
||||
if (!request.getRequestLine().getMethod().equalsIgnoreCase(HeaderConstants.OPTIONS_METHOD)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.getFirstHeader(HeaderConstants.CONTENT_LENGTH) == null) {
|
||||
response.addHeader(HeaderConstants.CONTENT_LENGTH, "0");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean backendResponseMustNotHaveBody(HttpRequest request, HttpResponse backendResponse) {
|
||||
return HeaderConstants.HEAD_METHOD.equals(request.getRequestLine().getMethod())
|
||||
|| backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT
|
||||
|| backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_RESET_CONTENT
|
||||
|| backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED;
|
||||
}
|
||||
|
||||
private void requestDidNotExpect100ContinueButResponseIsOne(HttpRequest request,
|
||||
HttpResponse response) throws ClientProtocolException {
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!requestWasWrapped(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProtocolVersion originalProtocol = getOriginalRequestProtocol((RequestWrapper) request);
|
||||
|
||||
if (originalProtocol.compareToVersion(CachingHttpClient.HTTP_1_1) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (originalRequestDidNotExpectContinue((RequestWrapper) request)) {
|
||||
throw new ClientProtocolException("The incoming request did not contain a "
|
||||
+ "100-continue header, but the response was a Status 100, continue.");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void transferEncodingIsNotReturnedTo1_0Client(HttpRequest request, HttpResponse response) {
|
||||
if (!requestWasWrapped(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProtocolVersion originalProtocol = getOriginalRequestProtocol((RequestWrapper) request);
|
||||
|
||||
if (originalProtocol.compareToVersion(CachingHttpClient.HTTP_1_1) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeResponseTransferEncoding(response);
|
||||
}
|
||||
|
||||
private void removeResponseTransferEncoding(HttpResponse response) {
|
||||
response.removeHeaders("TE");
|
||||
response.removeHeaders(HeaderConstants.TRANSFER_ENCODING);
|
||||
}
|
||||
|
||||
private boolean originalRequestDidNotExpectContinue(RequestWrapper request) {
|
||||
|
||||
try {
|
||||
HttpEntityEnclosingRequest original = (HttpEntityEnclosingRequest) request
|
||||
.getOriginal();
|
||||
|
||||
return !original.expectContinue();
|
||||
} catch (ClassCastException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private ProtocolVersion getOriginalRequestProtocol(RequestWrapper request) {
|
||||
return request.getOriginal().getProtocolVersion();
|
||||
}
|
||||
|
||||
private boolean requestWasWrapped(HttpRequest request) {
|
||||
return request instanceof RequestWrapper;
|
||||
}
|
||||
|
||||
}
|
148
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/SizeLimitedResponseReader.java
vendored
Normal file
148
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/SizeLimitedResponseReader.java
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.entity.InputStreamEntity;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public class SizeLimitedResponseReader {
|
||||
|
||||
private int maxResponseSizeBytes;
|
||||
private HttpResponse response;
|
||||
ByteArrayOutputStream outputStream;
|
||||
InputStream contentInputStream;
|
||||
|
||||
private boolean isTooLarge;
|
||||
private boolean responseIsConsumed;
|
||||
private byte[] sizeLimitedContent;
|
||||
private boolean outputStreamConsumed;
|
||||
|
||||
public SizeLimitedResponseReader(int maxResponseSizeBytes, HttpResponse response)
|
||||
throws IOException {
|
||||
this.maxResponseSizeBytes = maxResponseSizeBytes;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public boolean isResponseTooLarge() throws IOException {
|
||||
if (!responseIsConsumed)
|
||||
isTooLarge = consumeResponse();
|
||||
|
||||
return isTooLarge;
|
||||
}
|
||||
|
||||
private synchronized boolean consumeResponse() throws IOException {
|
||||
|
||||
if (responseIsConsumed)
|
||||
throw new IllegalStateException(
|
||||
"You cannot call this method more than once, because it consumes an underlying stream");
|
||||
|
||||
responseIsConsumed = true;
|
||||
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (entity == null)
|
||||
return false;
|
||||
|
||||
contentInputStream = entity.getContent();
|
||||
int bytes = 0;
|
||||
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
|
||||
int current;
|
||||
|
||||
while (bytes < maxResponseSizeBytes && (current = contentInputStream.read()) != -1) {
|
||||
outputStream.write(current);
|
||||
bytes++;
|
||||
}
|
||||
|
||||
if ((current = contentInputStream.read()) != -1) {
|
||||
outputStream.write(current);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private synchronized void consumeOutputStream() {
|
||||
if (outputStreamConsumed)
|
||||
throw new IllegalStateException(
|
||||
"underlying output stream has already been written to byte[]");
|
||||
|
||||
if (!responseIsConsumed)
|
||||
throw new IllegalStateException("Must call consumeResponse first.");
|
||||
|
||||
sizeLimitedContent = outputStream.toByteArray();
|
||||
outputStreamConsumed = true;
|
||||
}
|
||||
|
||||
public byte[] getResponseBytes() {
|
||||
if (!outputStreamConsumed)
|
||||
consumeOutputStream();
|
||||
|
||||
return sizeLimitedContent;
|
||||
}
|
||||
|
||||
public HttpResponse getReconstructedResponse() throws IOException {
|
||||
|
||||
InputStream combinedStream = getCombinedInputStream();
|
||||
|
||||
return constructResponse(response, combinedStream);
|
||||
}
|
||||
|
||||
protected InputStream getCombinedInputStream() throws IOException {
|
||||
InputStream input1 = new ByteArrayInputStream(getResponseBytes());
|
||||
InputStream input2 = getContentInputStream();
|
||||
return new CombinedInputStream(input1, input2);
|
||||
}
|
||||
|
||||
protected InputStream getContentInputStream() {
|
||||
return contentInputStream;
|
||||
}
|
||||
|
||||
protected HttpResponse constructResponse(HttpResponse originalResponse,
|
||||
InputStream combinedStream) throws IOException {
|
||||
HttpResponse response = new BasicHttpResponse(originalResponse.getProtocolVersion(),
|
||||
HttpStatus.SC_OK, "Success");
|
||||
|
||||
HttpEntity entity = new InputStreamEntity(combinedStream, -1);
|
||||
response.setEntity(entity);
|
||||
response.setHeaders(originalResponse.getAllHeaders());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
101
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/URIExtractor.java
vendored
Normal file
101
httpclient-cache/src/main/java/org/apache/http/client/cache/impl/URIExtractor.java
vendored
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
public class URIExtractor {
|
||||
|
||||
public String getURI(HttpHost host, HttpRequest req) {
|
||||
return String.format("%s%s", host.toString(), req.getRequestLine().getUri());
|
||||
}
|
||||
|
||||
protected String getFullHeaderValue(Header[] headers) {
|
||||
if (headers == null)
|
||||
return "";
|
||||
|
||||
StringBuilder buf = new StringBuilder("");
|
||||
boolean first = true;
|
||||
for (Header hdr : headers) {
|
||||
if (!first) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(hdr.getValue().trim());
|
||||
first = false;
|
||||
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public String getVariantURI(HttpHost host, HttpRequest req, CacheEntry entry) {
|
||||
Header[] varyHdrs = entry.getHeaders(HeaderConstants.VARY);
|
||||
if (varyHdrs == null || varyHdrs.length == 0) {
|
||||
return getURI(host, req);
|
||||
}
|
||||
|
||||
List<String> variantHeaderNames = new ArrayList<String>();
|
||||
for (Header varyHdr : varyHdrs) {
|
||||
for (HeaderElement elt : varyHdr.getElements()) {
|
||||
variantHeaderNames.add(elt.getName());
|
||||
}
|
||||
}
|
||||
Collections.sort(variantHeaderNames);
|
||||
|
||||
try {
|
||||
StringBuilder buf = new StringBuilder("{");
|
||||
boolean first = true;
|
||||
for (String headerName : variantHeaderNames) {
|
||||
if (!first) {
|
||||
buf.append("&");
|
||||
}
|
||||
buf.append(URLEncoder.encode(headerName, "UTF-8"));
|
||||
buf.append("=");
|
||||
buf.append(URLEncoder.encode(getFullHeaderValue(req.getHeaders(headerName)),
|
||||
"UTF-8"));
|
||||
first = false;
|
||||
}
|
||||
buf.append("}");
|
||||
buf.append(getURI(host, req));
|
||||
return buf.toString();
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("couldn't encode to UTF-8", uee);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
41
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/Counter.java
vendored
Normal file
41
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/Counter.java
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
public class Counter {
|
||||
|
||||
private int count;
|
||||
|
||||
public void incr() {
|
||||
count++;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
187
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/DoNotTestProtocolRequirements.java
vendored
Normal file
187
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/DoNotTestProtocolRequirements.java
vendored
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.cache.HttpCache;
|
||||
import org.apache.http.client.cache.impl.BasicHttpCache;
|
||||
import org.apache.http.client.cache.impl.CachingHttpClient;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.easymock.classextension.EasyMock;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class DoNotTestProtocolRequirements {
|
||||
|
||||
private static ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1);
|
||||
|
||||
private static int MAX_BYTES = 1024;
|
||||
private static int MAX_ENTRIES = 100;
|
||||
|
||||
private HttpHost host;
|
||||
private HttpEntity mockEntity;
|
||||
private HttpClient mockBackend;
|
||||
private HttpCache<CacheEntry> mockCache;
|
||||
private HttpRequest request;
|
||||
private HttpResponse originResponse;
|
||||
|
||||
private CachingHttpClient impl;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Before
|
||||
public void setUp() {
|
||||
host = new HttpHost("foo.example.com");
|
||||
|
||||
request = new BasicHttpRequest("GET", "/foo", HTTP_1_1);
|
||||
|
||||
originResponse = make200Response();
|
||||
|
||||
HttpCache<CacheEntry> cache = new BasicHttpCache(MAX_ENTRIES);
|
||||
mockBackend = EasyMock.createMock(HttpClient.class);
|
||||
mockEntity = EasyMock.createMock(HttpEntity.class);
|
||||
mockCache = EasyMock.createMock(HttpCache.class);
|
||||
impl = new CachingHttpClient(mockBackend, cache, MAX_BYTES);
|
||||
}
|
||||
|
||||
private HttpResponse make200Response() {
|
||||
HttpResponse out = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_OK, "OK");
|
||||
out.setHeader("Date", DateUtils.formatDate(new Date()));
|
||||
out.setHeader("Server", "MockOrigin/1.0");
|
||||
out.setEntity(makeBody(128));
|
||||
return out;
|
||||
}
|
||||
|
||||
private void replayMocks() {
|
||||
EasyMock.replay(mockBackend);
|
||||
EasyMock.replay(mockCache);
|
||||
EasyMock.replay(mockEntity);
|
||||
}
|
||||
|
||||
private HttpEntity makeBody(int nbytes) {
|
||||
byte[] bytes = new byte[nbytes];
|
||||
(new Random()).nextBytes(bytes);
|
||||
return new ByteArrayEntity(bytes);
|
||||
}
|
||||
|
||||
/*
|
||||
* "10.2.7 206 Partial Content ... The response MUST include the following
|
||||
* header fields:
|
||||
*
|
||||
* - Either a Content-Range header field (section 14.16) indicating the
|
||||
* range included with this response, or a multipart/byteranges Content-Type
|
||||
* including Content-Range fields for each part. If a Content-Length header
|
||||
* field is present in the response, its value MUST match the actual number
|
||||
* of OCTETs transmitted in the message-body.
|
||||
*
|
||||
* - Date
|
||||
*
|
||||
* - ETag and/or Content-Location, if the header would have been sent in a
|
||||
* 200 response to the same request
|
||||
*
|
||||
* - Expires, Cache-Control, and/or Vary, if the field-value might differ
|
||||
* from that sent in any previous response for the same variant
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.7
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void test206ResponseReturnedToClientMustHaveContentRangeOrByteRangesContentType()
|
||||
throws Exception {
|
||||
request.addHeader("Range", "bytes 0-499/1234");
|
||||
originResponse = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_PARTIAL_CONTENT,
|
||||
"Partial Content");
|
||||
originResponse.setHeader("Date", DateUtils.formatDate(new Date()));
|
||||
originResponse.setHeader("Server", "MockOrigin/1.0");
|
||||
originResponse.setEntity(makeBody(500));
|
||||
|
||||
org.easymock.EasyMock.expect(
|
||||
mockBackend.execute(org.easymock.EasyMock.isA(HttpHost.class),
|
||||
org.easymock.EasyMock.isA(HttpRequest.class),
|
||||
(HttpContext) org.easymock.EasyMock.isNull())).andReturn(originResponse);
|
||||
|
||||
replayMocks();
|
||||
|
||||
try {
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
Header crHdr = result.getFirstHeader("Content-Range");
|
||||
Header ctHdr = result.getFirstHeader("Content-Type");
|
||||
if (result.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT) {
|
||||
if (crHdr == null) {
|
||||
Assert.assertNotNull(ctHdr);
|
||||
boolean foundMultipartByteRanges = false;
|
||||
for (HeaderElement elt : ctHdr.getElements()) {
|
||||
if ("multipart/byteranges".equalsIgnoreCase(elt.getName())) {
|
||||
NameValuePair param = elt.getParameterByName("boundary");
|
||||
Assert.assertNotNull(param);
|
||||
String boundary = param.getValue();
|
||||
Assert.assertNotNull(boundary);
|
||||
// perhaps eventually should parse out the
|
||||
// request body to check proper formatting
|
||||
// but that might be excessive; HttpClient
|
||||
// developers have indicated that
|
||||
// HttpClient's job does not involve
|
||||
// parsing a response body
|
||||
}
|
||||
}
|
||||
Assert.assertTrue(foundMultipartByteRanges);
|
||||
}
|
||||
}
|
||||
} catch (ClientProtocolException acceptableBehavior) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void test206ResponseReturnedToClientWithAContentLengthMustMatchActualOctetsTransmitted() {
|
||||
// We are explicitly saying that CachingHttpClient does not care about
|
||||
// this:
|
||||
// We do not attempt to cache 206, nor do we ever construct a 206. We
|
||||
// simply pass along a 206,
|
||||
// which could be malformed. But protocol compliance of a downstream
|
||||
// server is not our responsibility
|
||||
}
|
||||
|
||||
}
|
194
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/HttpTestUtils.java
vendored
Normal file
194
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/HttpTestUtils.java
vendored
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpMessage;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.RequestLine;
|
||||
import org.apache.http.StatusLine;
|
||||
|
||||
public class HttpTestUtils {
|
||||
|
||||
/*
|
||||
* "The following HTTP/1.1 headers are hop-by-hop headers..."
|
||||
*
|
||||
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
|
||||
*/
|
||||
public static String[] HOP_BY_HOP_HEADERS = { "Connection", "Keep-Alive", "Proxy-Authenticate",
|
||||
"Proxy-Authorization", "TE", "Trailers", "Transfer-Encoding", "Upgrade" };
|
||||
|
||||
/*
|
||||
* "Multiple message-header fields with the same field-name MAY be present
|
||||
* in a message if and only if the entire field-value for that header field
|
||||
* is defined as a comma-separated list [i.e., #(values)]."
|
||||
*
|
||||
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
||||
*/
|
||||
public static String[] MULTI_HEADERS = { "Accept", "Accept-Charset", "Accept-Encoding",
|
||||
"Accept-Language", "Allow", "Cache-Control", "Connection", "Content-Encoding",
|
||||
"Content-Language", "Expect", "Pragma", "Proxy-Authenticate", "TE", "Trailer",
|
||||
"Transfer-Encoding", "Upgrade", "Via", "Warning", "WWW-Authenticate" };
|
||||
public static String[] SINGLE_HEADERS = { "Accept-Ranges", "Age", "Authorization",
|
||||
"Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type",
|
||||
"Date", "ETag", "Expires", "From", "Host", "If-Match", "If-Modified-Since",
|
||||
"If-None-Match", "If-Range", "If-Unmodified-Since", "Last-Modified", "Location",
|
||||
"Max-Forwards", "Proxy-Authorization", "Range", "Referer", "Retry-After", "Server",
|
||||
"User-Agent", "Vary" };
|
||||
|
||||
/*
|
||||
* Determines whether the given header name is considered a hop-by-hop
|
||||
* header.
|
||||
*
|
||||
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
|
||||
*/
|
||||
public static boolean isHopByHopHeader(String name) {
|
||||
for (String s : HOP_BY_HOP_HEADERS) {
|
||||
if (s.equalsIgnoreCase(name))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determines whether a given header name may only appear once in a message.
|
||||
*/
|
||||
public static boolean isSingleHeader(String name) {
|
||||
for (String s : SINGLE_HEADERS) {
|
||||
if (s.equalsIgnoreCase(name))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Assert.asserts that two request or response bodies are byte-equivalent.
|
||||
*/
|
||||
public static boolean equivalent(HttpEntity e1, HttpEntity e2) throws Exception {
|
||||
InputStream i1 = e1.getContent();
|
||||
InputStream i2 = e2.getContent();
|
||||
if (i1 == null && i2 == null)
|
||||
return true;
|
||||
int b1 = -1;
|
||||
while ((b1 = i1.read()) != -1) {
|
||||
if (b1 != i2.read())
|
||||
return false;
|
||||
}
|
||||
return (-1 == i2.read());
|
||||
}
|
||||
|
||||
/*
|
||||
* Assert.asserts that the components of two status lines match in a way
|
||||
* that differs only by hop-by-hop information. "2.1 Proxy Behavior ...We
|
||||
* remind the reader that HTTP version numbers are hop-by-hop components of
|
||||
* HTTP meesages, and are not end-to-end."
|
||||
*
|
||||
* @see http://www.ietf.org/rfc/rfc2145.txt
|
||||
*/
|
||||
public static boolean semanticallyTransparent(StatusLine l1, StatusLine l2) {
|
||||
return (l1.getReasonPhrase().equals(l2.getReasonPhrase()) && l1.getStatusCode() == l2
|
||||
.getStatusCode());
|
||||
}
|
||||
|
||||
/* Assert.asserts that the components of two status lines match. */
|
||||
public static boolean equivalent(StatusLine l1, StatusLine l2) {
|
||||
return (l1.getProtocolVersion().equals(l2.getProtocolVersion()) && semanticallyTransparent(
|
||||
l1, l2));
|
||||
}
|
||||
|
||||
/* Assert.asserts that the components of two request lines match. */
|
||||
public static boolean equivalent(RequestLine l1, RequestLine l2) {
|
||||
return (l1.getMethod().equals(l2.getMethod())
|
||||
&& l1.getProtocolVersion().equals(l2.getProtocolVersion()) && l1.getUri().equals(
|
||||
l2.getUri()));
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieves the full header value by combining multiple headers and
|
||||
* separating with commas, canonicalizing whitespace along the way.
|
||||
*
|
||||
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
||||
*/
|
||||
public static String getCanonicalHeaderValue(HttpMessage r, String name) {
|
||||
if (isSingleHeader(name)) {
|
||||
Header h = r.getFirstHeader(name);
|
||||
return (h != null) ? h.getValue() : null;
|
||||
}
|
||||
StringBuilder buf = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (Header h : r.getHeaders(name)) {
|
||||
if (!first) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(h.getValue().trim());
|
||||
first = false;
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* Assert.asserts that all the headers appearing in r1 also appear in r2
|
||||
* with the same canonical header values.
|
||||
*/
|
||||
public static boolean isEndToEndHeaderSubset(HttpMessage r1, HttpMessage r2) {
|
||||
for (Header h : r1.getAllHeaders()) {
|
||||
if (!isHopByHopHeader(h.getName())) {
|
||||
String r1val = getCanonicalHeaderValue(r1, h.getName());
|
||||
String r2val = getCanonicalHeaderValue(r2, h.getName());
|
||||
if (!r1val.equals(r2val))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Assert.asserts that message <code>r2</code> represents exactly the same
|
||||
* message as <code>r1</code>, except for hop-by-hop headers. "When a cache
|
||||
* is semantically transparent, the client receives exactly the same
|
||||
* response (except for hop-by-hop headers) that it would have received had
|
||||
* its request been handled directly by the origin server."
|
||||
*
|
||||
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec1.html#sec1.3
|
||||
*/
|
||||
public static boolean semanticallyTransparent(HttpResponse r1, HttpResponse r2)
|
||||
throws Exception {
|
||||
return (equivalent(r1.getEntity(), r2.getEntity())
|
||||
&& semanticallyTransparent(r1.getStatusLine(), r2.getStatusLine()) && isEndToEndHeaderSubset(
|
||||
r1, r2));
|
||||
}
|
||||
|
||||
/* Assert.asserts that two requests are morally equivalent. */
|
||||
public static boolean equivalent(HttpRequest r1, HttpRequest r2) {
|
||||
return (equivalent(r1.getRequestLine(), r2.getRequestLine()) && isEndToEndHeaderSubset(r1,
|
||||
r2));
|
||||
}
|
||||
}
|
53
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/RequestEquivalent.java
vendored
Normal file
53
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/RequestEquivalent.java
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.easymock.IArgumentMatcher;
|
||||
|
||||
public class RequestEquivalent implements IArgumentMatcher {
|
||||
|
||||
private HttpRequest expected;
|
||||
|
||||
public RequestEquivalent(HttpRequest expected) {
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
public boolean matches(Object actual) {
|
||||
if (!(actual instanceof HttpRequest))
|
||||
return false;
|
||||
HttpRequest other = (HttpRequest) actual;
|
||||
return HttpTestUtils.equivalent(expected, other);
|
||||
}
|
||||
|
||||
public void appendTo(StringBuffer buf) {
|
||||
buf.append("eqRequest(");
|
||||
buf.append(expected);
|
||||
buf.append(")");
|
||||
}
|
||||
|
||||
}
|
51
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/Serializer.java
vendored
Normal file
51
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/Serializer.java
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
public class Serializer<T> {
|
||||
|
||||
public byte[] serialize(T object) throws Exception {
|
||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
ObjectOutputStream objectStream = new ObjectOutputStream(byteStream);
|
||||
objectStream.writeObject(object);
|
||||
return byteStream.toByteArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T deserialize(byte[] serialized) throws Exception {
|
||||
ByteArrayInputStream byteStream = new ByteArrayInputStream(serialized);
|
||||
ObjectInputStream objectStream = new ObjectInputStream(byteStream);
|
||||
T object = (T) objectStream.readObject();
|
||||
return object;
|
||||
}
|
||||
|
||||
}
|
494
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntry.java
vendored
Normal file
494
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntry.java
vendored
Normal file
|
@ -0,0 +1,494 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.client.cache.impl.CacheEntry;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestCacheEntry {
|
||||
|
||||
@Test
|
||||
public void testGetHeadersReturnsCorrectHeaders() {
|
||||
Header[] headers = new Header[] { new BasicHeader("foo", "fooValue"),
|
||||
new BasicHeader("bar", "barValue1"), new BasicHeader("bar", "barValue2") };
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertEquals(2, entry.getHeaders("bar").length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFirstHeaderReturnsCorrectHeader() {
|
||||
Header[] headers = new Header[] { new BasicHeader("foo", "fooValue"),
|
||||
new BasicHeader("bar", "barValue1"), new BasicHeader("bar", "barValue2") };
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertEquals("barValue1", entry.getFirstHeader("bar").getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHeadersReturnsEmptyArrayIfNoneMatch() {
|
||||
Header[] headers = new Header[] { new BasicHeader("foo", "fooValue"),
|
||||
new BasicHeader("bar", "barValue1"), new BasicHeader("bar", "barValue2") };
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
|
||||
Assert.assertEquals(0, entry.getHeaders("baz").length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFirstHeaderReturnsNullIfNoneMatch() {
|
||||
Header[] headers = new Header[] { new BasicHeader("foo", "fooValue"),
|
||||
new BasicHeader("bar", "barValue1"), new BasicHeader("bar", "barValue2") };
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
|
||||
Assert.assertEquals(null, entry.getFirstHeader("quux"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApparentAgeIsMaxIntIfDateHeaderNotPresent() {
|
||||
Header[] headers = new Header[0];
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertEquals(2147483648L, entry.getApparentAgeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApparentAgeIsResponseReceivedTimeLessDateHeader() {
|
||||
Date now = new Date();
|
||||
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
|
||||
Header[] headers = new Header[] { new BasicHeader("Date", DateUtils
|
||||
.formatDate(tenSecondsAgo)) };
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
entry.setResponseDate(sixSecondsAgo);
|
||||
|
||||
Assert.assertEquals(4, entry.getApparentAgeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeApparentAgeIsBroughtUpToZero() {
|
||||
Date now = new Date();
|
||||
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
|
||||
Header[] headers = new Header[] { new BasicHeader("Date", DateUtils
|
||||
.formatDate(sixSecondsAgo)) };
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
entry.setResponseDate(tenSecondsAgo);
|
||||
|
||||
Assert.assertEquals(0, entry.getApparentAgeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectedReceivedAgeIsAgeHeaderIfLarger() {
|
||||
Header[] headers = new Header[] { new BasicHeader("Age", "10"), };
|
||||
CacheEntry entry = new CacheEntry() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected long getApparentAgeSecs() {
|
||||
return 6;
|
||||
}
|
||||
};
|
||||
entry.setResponseHeaders(headers);
|
||||
|
||||
Assert.assertEquals(10, entry.getCorrectedReceivedAgeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectedReceivedAgeIsApparentAgeIfLarger() {
|
||||
Header[] headers = new Header[] { new BasicHeader("Age", "6"), };
|
||||
CacheEntry entry = new CacheEntry() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected long getApparentAgeSecs() {
|
||||
return 10;
|
||||
}
|
||||
};
|
||||
entry.setResponseHeaders(headers);
|
||||
|
||||
Assert.assertEquals(10, entry.getCorrectedReceivedAgeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseDelayIsDifferenceBetweenResponseAndRequestTimes() {
|
||||
Date now = new Date();
|
||||
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setRequestDate(tenSecondsAgo);
|
||||
entry.setResponseDate(sixSecondsAgo);
|
||||
|
||||
Assert.assertEquals(4, entry.getResponseDelaySecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectedInitialAgeIsCorrectedReceivedAgePlusResponseDelay() {
|
||||
CacheEntry entry = new CacheEntry() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected long getCorrectedReceivedAgeSecs() {
|
||||
return 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getResponseDelaySecs() {
|
||||
return 13;
|
||||
}
|
||||
};
|
||||
Assert.assertEquals(20, entry.getCorrectedInitialAgeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResidentTimeSecondsIsTimeSinceResponseTime() {
|
||||
final Date now = new Date();
|
||||
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
|
||||
|
||||
CacheEntry entry = new CacheEntry() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected Date getCurrentDate() {
|
||||
return now;
|
||||
}
|
||||
};
|
||||
entry.setResponseDate(sixSecondsAgo);
|
||||
|
||||
Assert.assertEquals(6, entry.getResidentTimeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentAgeIsCorrectedInitialAgePlusResidentTime() {
|
||||
CacheEntry entry = new CacheEntry() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected long getCorrectedInitialAgeSecs() {
|
||||
return 11;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getResidentTimeSecs() {
|
||||
return 17;
|
||||
}
|
||||
};
|
||||
Assert.assertEquals(28, entry.getCurrentAgeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFreshnessLifetimeIsSMaxAgeIfPresent() {
|
||||
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10") };
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertEquals(10, entry.getFreshnessLifetimeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFreshnessLifetimeIsMaxAgeIfPresent() {
|
||||
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10") };
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertEquals(10, entry.getFreshnessLifetimeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFreshnessLifetimeIsMostRestrictiveOfMaxAgeAndSMaxAge() {
|
||||
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"),
|
||||
new BasicHeader("Cache-Control", "s-maxage=20") };
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertEquals(10, entry.getFreshnessLifetimeSecs());
|
||||
|
||||
headers = new Header[] { new BasicHeader("Cache-Control", "max-age=20"),
|
||||
new BasicHeader("Cache-Control", "s-maxage=10") };
|
||||
entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertEquals(10, entry.getFreshnessLifetimeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFreshnessLifetimeIsMaxAgeEvenIfExpiresIsPresent() {
|
||||
Date now = new Date();
|
||||
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "max-age=10"),
|
||||
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
|
||||
new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertEquals(10, entry.getFreshnessLifetimeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFreshnessLifetimeIsSMaxAgeEvenIfExpiresIsPresent() {
|
||||
Date now = new Date();
|
||||
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
Header[] headers = new Header[] { new BasicHeader("Cache-Control", "s-maxage=10"),
|
||||
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
|
||||
new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertEquals(10, entry.getFreshnessLifetimeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFreshnessLifetimeIsFromExpiresHeaderIfNoMaxAge() {
|
||||
Date now = new Date();
|
||||
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
Header[] headers = new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatDate(tenSecondsAgo)),
|
||||
new BasicHeader("Expires", DateUtils.formatDate(sixSecondsAgo)) };
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertEquals(4, entry.getFreshnessLifetimeSecs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseIsFreshIfFreshnessLifetimeExceedsCurrentAge() {
|
||||
CacheEntry entry = new CacheEntry() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public long getCurrentAgeSecs() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFreshnessLifetimeSecs() {
|
||||
return 10;
|
||||
}
|
||||
};
|
||||
|
||||
Assert.assertTrue(entry.isResponseFresh());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseIsNotFreshIfFreshnessLifetimeEqualsCurrentAge() {
|
||||
CacheEntry entry = new CacheEntry() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public long getCurrentAgeSecs() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFreshnessLifetimeSecs() {
|
||||
return 6;
|
||||
}
|
||||
};
|
||||
|
||||
Assert.assertFalse(entry.isResponseFresh());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseIsNotFreshIfCurrentAgeExceedsFreshnessLifetime() {
|
||||
CacheEntry entry = new CacheEntry() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public long getCurrentAgeSecs() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFreshnessLifetimeSecs() {
|
||||
return 6;
|
||||
}
|
||||
};
|
||||
|
||||
Assert.assertFalse(entry.isResponseFresh());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheEntryIsRevalidatableIfHeadersIncludeETag() {
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(new Header[] {
|
||||
new BasicHeader("Expires", DateUtils.formatDate(new Date())),
|
||||
new BasicHeader("ETag", "somevalue") });
|
||||
|
||||
Assert.assertTrue(entry.isRevalidatable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheEntryIsRevalidatableIfHeadersIncludeLastModifiedDate() {
|
||||
CacheEntry entry = new CacheEntry();
|
||||
|
||||
entry.setResponseHeaders(new Header[] {
|
||||
new BasicHeader("Expires", DateUtils.formatDate(new Date())),
|
||||
new BasicHeader("Last-Modified", DateUtils.formatDate(new Date())) });
|
||||
|
||||
Assert.assertTrue(entry.isRevalidatable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheEntryIsNotRevalidatableIfNoAppropriateHeaders() {
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(new Header[] {
|
||||
new BasicHeader("Expires", DateUtils.formatDate(new Date())),
|
||||
new BasicHeader("Cache-Control", "public") });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheEntryWithNoVaryHeaderDoesNotHaveVariants() {
|
||||
Header[] headers = new Header[0];
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertFalse(entry.hasVariants());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheEntryWithOneVaryHeaderHasVariants() {
|
||||
Header[] headers = { new BasicHeader("Vary", "User-Agent") };
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertTrue(entry.hasVariants());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheEntryWithMultipleVaryHeadersHasVariants() {
|
||||
Header[] headers = { new BasicHeader("Vary", "User-Agent"),
|
||||
new BasicHeader("Vary", "Accept-Encoding") };
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.setResponseHeaders(headers);
|
||||
Assert.assertTrue(entry.hasVariants());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheEntryCanStoreMultipleVariantUris() {
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
|
||||
entry.addVariantURI("foo");
|
||||
entry.addVariantURI("bar");
|
||||
|
||||
Set<String> variants = entry.getVariantURIs();
|
||||
|
||||
Assert.assertTrue(variants.contains("foo"));
|
||||
Assert.assertTrue(variants.contains("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMalformedDateHeaderIsIgnored() {
|
||||
|
||||
Header[] h = new Header[] { new BasicHeader("Date", "asdf") };
|
||||
CacheEntry e = new CacheEntry();
|
||||
e.setResponseHeaders(h);
|
||||
|
||||
Date d = e.getDateValue();
|
||||
|
||||
Assert.assertNull(d);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMalformedContentLengthReturnsNegativeOne() {
|
||||
|
||||
Header[] h = new Header[] { new BasicHeader("Content-Length", "asdf") };
|
||||
CacheEntry e = new CacheEntry();
|
||||
e.setResponseHeaders(h);
|
||||
|
||||
long length = e.getContentLengthValue();
|
||||
|
||||
Assert.assertEquals(-1, length);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeAgeHeaderValueReturnsMaxAge() {
|
||||
|
||||
Header[] h = new Header[] { new BasicHeader("Age", "-100") };
|
||||
CacheEntry e = new CacheEntry();
|
||||
e.setResponseHeaders(h);
|
||||
|
||||
long length = e.getAgeValue();
|
||||
|
||||
Assert.assertEquals(CacheEntry.MAX_AGE, length);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMalformedAgeHeaderValueReturnsMaxAge() {
|
||||
|
||||
Header[] h = new Header[] { new BasicHeader("Age", "asdf") };
|
||||
CacheEntry e = new CacheEntry();
|
||||
e.setResponseHeaders(h);
|
||||
|
||||
long length = e.getAgeValue();
|
||||
|
||||
Assert.assertEquals(CacheEntry.MAX_AGE, length);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMalformedCacheControlMaxAgeHeaderReturnsZero() {
|
||||
|
||||
Header[] h = new Header[] { new BasicHeader("Cache-Control", "max-age=asdf") };
|
||||
CacheEntry e = new CacheEntry();
|
||||
e.setResponseHeaders(h);
|
||||
|
||||
long maxage = e.getMaxAge();
|
||||
|
||||
Assert.assertEquals(0, maxage);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMalformedExpirationDateReturnsNull() {
|
||||
Header[] h = new Header[] { new BasicHeader("Expires", "asdf") };
|
||||
CacheEntry e = new CacheEntry();
|
||||
e.setResponseHeaders(h);
|
||||
|
||||
Date expirationDate = e.getExpirationDate();
|
||||
|
||||
Assert.assertNull(expirationDate);
|
||||
}
|
||||
}
|
64
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntryGenerator.java
vendored
Normal file
64
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntryGenerator.java
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.client.cache.impl.CacheEntry;
|
||||
import org.apache.http.client.cache.impl.CacheEntryGenerator;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestCacheEntryGenerator {
|
||||
|
||||
@Test
|
||||
public void testEntryMatchesInputs() throws IOException {
|
||||
|
||||
CacheEntryGenerator gen = new CacheEntryGenerator();
|
||||
|
||||
HttpResponse response = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1),
|
||||
HttpStatus.SC_OK, "Success");
|
||||
HttpEntity entity = new ByteArrayEntity(new byte[] {});
|
||||
response.setEntity(entity);
|
||||
|
||||
response.setHeader("fooHeader", "fooHeaderValue");
|
||||
|
||||
CacheEntry entry = gen.generateEntry(new Date(), new Date(), response, new byte[] {});
|
||||
|
||||
Assert.assertEquals("HTTP", entry.getProtocolVersion().getProtocol());
|
||||
Assert.assertEquals(1, entry.getProtocolVersion().getMajor());
|
||||
Assert.assertEquals(1, entry.getProtocolVersion().getMinor());
|
||||
Assert.assertEquals("fooHeaderValue", entry.getFirstHeader("fooHeader").getValue());
|
||||
}
|
||||
}
|
187
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntryUpdater.java
vendored
Normal file
187
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheEntryUpdater.java
vendored
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.client.cache.impl.CacheEntry;
|
||||
import org.apache.http.client.cache.impl.CacheEntryUpdater;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
import org.apache.http.message.BasicStatusLine;
|
||||
import org.easymock.classextension.EasyMock;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestCacheEntryUpdater {
|
||||
|
||||
private HttpResponse mockResponse;
|
||||
private CacheEntry mockCacheEntry;
|
||||
private Date requestDate;
|
||||
private Date responseDate;
|
||||
|
||||
private boolean implMocked = false;
|
||||
private CacheEntryUpdater impl;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mockResponse = EasyMock.createMock(HttpResponse.class);
|
||||
mockCacheEntry = EasyMock.createMock(CacheEntry.class);
|
||||
|
||||
requestDate = new Date(System.currentTimeMillis() - 1000);
|
||||
responseDate = new Date();
|
||||
|
||||
impl = new CacheEntryUpdater();
|
||||
}
|
||||
|
||||
private void replayMocks() {
|
||||
EasyMock.replay(mockResponse);
|
||||
EasyMock.replay(mockCacheEntry);
|
||||
if (implMocked) {
|
||||
EasyMock.replay(impl);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyMocks() {
|
||||
EasyMock.verify(mockResponse);
|
||||
EasyMock.verify(mockCacheEntry);
|
||||
if (implMocked) {
|
||||
EasyMock.verify(impl);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateCacheEntry() {
|
||||
mockImplMethods("mergeHeaders");
|
||||
mockCacheEntry.setRequestDate(requestDate);
|
||||
mockCacheEntry.setResponseDate(responseDate);
|
||||
impl.mergeHeaders(mockCacheEntry, mockResponse);
|
||||
|
||||
replayMocks();
|
||||
|
||||
impl.updateCacheEntry(mockCacheEntry, requestDate, responseDate, mockResponse);
|
||||
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExistingHeadersNotInResponseDontChange() {
|
||||
|
||||
CacheEntry cacheEntry = new CacheEntry();
|
||||
cacheEntry.setResponseHeaders(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatDate(responseDate)),
|
||||
new BasicHeader("ETag", "eTag") });
|
||||
|
||||
HttpResponse response = new BasicHttpResponse(new BasicStatusLine(new ProtocolVersion(
|
||||
"http", 1, 1), HttpStatus.SC_NOT_MODIFIED, ""));
|
||||
response.setHeaders(new Header[] {});
|
||||
|
||||
impl.mergeHeaders(cacheEntry, response);
|
||||
|
||||
Assert.assertEquals(2, cacheEntry.getAllHeaders().length);
|
||||
|
||||
headersContain(cacheEntry.getAllHeaders(), "Date", DateUtils.formatDate(responseDate));
|
||||
headersContain(cacheEntry.getAllHeaders(), "ETag", "eTag");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewerHeadersReplaceExistingHeaders() {
|
||||
CacheEntry cacheEntry = new CacheEntry();
|
||||
cacheEntry.setResponseHeaders(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatDate(requestDate)),
|
||||
new BasicHeader("Cache-Control", "private"), new BasicHeader("ETag", "eTag"),
|
||||
new BasicHeader("Last-Modified", DateUtils.formatDate(requestDate)),
|
||||
new BasicHeader("Cache-Control", "max-age=0"), });
|
||||
|
||||
HttpResponse response = new BasicHttpResponse(new BasicStatusLine(new ProtocolVersion(
|
||||
"http", 1, 1), HttpStatus.SC_NOT_MODIFIED, ""));
|
||||
response.setHeaders(new Header[] {
|
||||
new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)),
|
||||
new BasicHeader("Cache-Control", "public"), });
|
||||
|
||||
impl.mergeHeaders(cacheEntry, response);
|
||||
|
||||
Assert.assertEquals(4, cacheEntry.getAllHeaders().length);
|
||||
|
||||
headersContain(cacheEntry.getAllHeaders(), "Date", DateUtils.formatDate(requestDate));
|
||||
headersContain(cacheEntry.getAllHeaders(), "ETag", "eTag");
|
||||
headersContain(cacheEntry.getAllHeaders(), "Last-Modified", DateUtils
|
||||
.formatDate(responseDate));
|
||||
headersContain(cacheEntry.getAllHeaders(), "Cache-Control", "public");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNewHeadersAreAddedByMerge() {
|
||||
|
||||
CacheEntry cacheEntry = new CacheEntry();
|
||||
cacheEntry.setResponseHeaders(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatDate(requestDate)),
|
||||
new BasicHeader("ETag", "eTag"), });
|
||||
|
||||
HttpResponse response = new BasicHttpResponse(new BasicStatusLine(new ProtocolVersion(
|
||||
"http", 1, 1), HttpStatus.SC_NOT_MODIFIED, ""));
|
||||
response.setHeaders(new Header[] {
|
||||
new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)),
|
||||
new BasicHeader("Cache-Control", "public"), });
|
||||
|
||||
impl.mergeHeaders(cacheEntry, response);
|
||||
|
||||
Assert.assertEquals(4, cacheEntry.getAllHeaders().length);
|
||||
|
||||
headersContain(cacheEntry.getAllHeaders(), "Date", DateUtils.formatDate(requestDate));
|
||||
headersContain(cacheEntry.getAllHeaders(), "ETag", "eTag");
|
||||
headersContain(cacheEntry.getAllHeaders(), "Last-Modified", DateUtils
|
||||
.formatDate(responseDate));
|
||||
headersContain(cacheEntry.getAllHeaders(), "Cache-Control", "public");
|
||||
|
||||
}
|
||||
|
||||
private void headersContain(Header[] headers, String name, String value) {
|
||||
for (Header header : headers) {
|
||||
if (header.getName().equals(name)) {
|
||||
if (header.getValue().equals(value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.fail("Header [" + name + ": " + value + "] not found in headers.");
|
||||
}
|
||||
|
||||
private void mockImplMethods(String... methods) {
|
||||
implMocked = true;
|
||||
impl = EasyMock.createMockBuilder(CacheEntryUpdater.class).addMockedMethods(methods)
|
||||
.createMock();
|
||||
}
|
||||
|
||||
}
|
276
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheInvalidator.java
vendored
Normal file
276
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheInvalidator.java
vendored
Normal file
|
@ -0,0 +1,276 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.RequestLine;
|
||||
import org.apache.http.client.cache.HttpCacheOperationException;
|
||||
import org.apache.http.client.cache.HttpCache;
|
||||
import org.apache.http.client.cache.impl.CacheEntry;
|
||||
import org.apache.http.client.cache.impl.CacheInvalidator;
|
||||
import org.apache.http.client.cache.impl.URIExtractor;
|
||||
import org.easymock.classextension.EasyMock;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestCacheInvalidator {
|
||||
|
||||
private CacheInvalidator impl;
|
||||
private HttpCache<CacheEntry> mockCache;
|
||||
private Header mockHeader;
|
||||
private Header[] mockHeaderArray = new Header[1];
|
||||
private HttpHost host;
|
||||
private HttpRequest mockRequest;
|
||||
private RequestLine mockRequestLine;
|
||||
private URIExtractor mockExtractor;
|
||||
private CacheEntry mockEntry;
|
||||
|
||||
private boolean mockedImpl;
|
||||
private HeaderElement mockElement;
|
||||
private HeaderElement[] mockElementArray = new HeaderElement[1];
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Before
|
||||
public void setUp() {
|
||||
host = new HttpHost("foo.example.com");
|
||||
mockCache = EasyMock.createMock(HttpCache.class);
|
||||
mockExtractor = EasyMock.createMock(URIExtractor.class);
|
||||
mockHeader = EasyMock.createMock(Header.class);
|
||||
mockElement = EasyMock.createMock(HeaderElement.class);
|
||||
mockRequest = EasyMock.createMock(HttpRequest.class);
|
||||
mockRequestLine = EasyMock.createMock(RequestLine.class);
|
||||
mockEntry = EasyMock.createMock(CacheEntry.class);
|
||||
mockHeaderArray[0] = mockHeader;
|
||||
mockElementArray[0] = mockElement;
|
||||
|
||||
impl = new CacheInvalidator(mockExtractor, mockCache);
|
||||
}
|
||||
|
||||
private void mockImplMethods(String... methods) {
|
||||
mockedImpl = true;
|
||||
impl = EasyMock.createMockBuilder(CacheInvalidator.class).withConstructor(mockExtractor,
|
||||
mockCache).addMockedMethods(methods).createMock();
|
||||
}
|
||||
|
||||
private void replayMocks() {
|
||||
EasyMock.replay(mockCache);
|
||||
EasyMock.replay(mockExtractor);
|
||||
EasyMock.replay(mockHeader);
|
||||
EasyMock.replay(mockRequest);
|
||||
EasyMock.replay(mockRequestLine);
|
||||
EasyMock.replay(mockEntry);
|
||||
EasyMock.replay(mockElement);
|
||||
|
||||
if (mockedImpl)
|
||||
EasyMock.replay(impl);
|
||||
}
|
||||
|
||||
private void verifyMocks() {
|
||||
EasyMock.verify(mockCache);
|
||||
EasyMock.verify(mockExtractor);
|
||||
EasyMock.verify(mockHeader);
|
||||
EasyMock.verify(mockRequest);
|
||||
EasyMock.verify(mockRequestLine);
|
||||
EasyMock.verify(mockEntry);
|
||||
EasyMock.verify(mockElement);
|
||||
|
||||
if (mockedImpl)
|
||||
EasyMock.verify(impl);
|
||||
}
|
||||
|
||||
// Tests
|
||||
@Test
|
||||
public void testInvalidatesRequestsThatArentGETorHEAD() throws Exception {
|
||||
final String theUri = "theUri";
|
||||
Set<String> variantURIs = new HashSet<String>();
|
||||
cacheEntryHasVariantURIs(variantURIs);
|
||||
|
||||
cacheReturnsEntryForUri(theUri);
|
||||
requestLineIsRead();
|
||||
requestMethodIs("POST");
|
||||
extractorReturns(theUri);
|
||||
entryIsRemoved(theUri);
|
||||
replayMocks();
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, mockRequest);
|
||||
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateGETRequest() throws HttpCacheOperationException {
|
||||
|
||||
requestLineIsRead();
|
||||
requestMethodIs("GET");
|
||||
requestContainsCacheControlHeader(null);
|
||||
requestContainsPragmaHeader(null);
|
||||
replayMocks();
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, mockRequest);
|
||||
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotInvalidateHEADRequest() throws HttpCacheOperationException {
|
||||
|
||||
requestLineIsRead();
|
||||
requestMethodIs("HEAD");
|
||||
requestContainsCacheControlHeader(null);
|
||||
requestContainsPragmaHeader(null);
|
||||
replayMocks();
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, mockRequest);
|
||||
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesRequestsWithClientCacheControlHeaders() throws Exception {
|
||||
final String theUri = "theUri";
|
||||
extractorReturns(theUri);
|
||||
cacheReturnsEntryForUri(theUri);
|
||||
Set<String> variantURIs = new HashSet<String>();
|
||||
cacheEntryHasVariantURIs(variantURIs);
|
||||
|
||||
requestLineIsRead();
|
||||
requestMethodIs("GET");
|
||||
requestContainsCacheControlHeader(mockHeaderArray);
|
||||
|
||||
org.easymock.EasyMock.expect(mockHeader.getElements()).andReturn(mockElementArray);
|
||||
org.easymock.EasyMock.expect(mockElement.getName()).andReturn("no-cache").anyTimes();
|
||||
|
||||
entryIsRemoved(theUri);
|
||||
replayMocks();
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, mockRequest);
|
||||
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidatesRequestsWithClientPragmaHeaders() throws Exception {
|
||||
final String theUri = "theUri";
|
||||
extractorReturns(theUri);
|
||||
cacheReturnsEntryForUri(theUri);
|
||||
Set<String> variantURIs = new HashSet<String>();
|
||||
cacheEntryHasVariantURIs(variantURIs);
|
||||
|
||||
requestLineIsRead();
|
||||
requestMethodIs("GET");
|
||||
requestContainsCacheControlHeader(null);
|
||||
requestContainsPragmaHeader(mockHeader);
|
||||
entryIsRemoved(theUri);
|
||||
replayMocks();
|
||||
|
||||
impl.flushInvalidatedCacheEntries(host, mockRequest);
|
||||
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariantURIsAreFlushedAlso() throws HttpCacheOperationException {
|
||||
final String theUri = "theUri";
|
||||
final String variantUri = "theVariantURI";
|
||||
|
||||
Set<String> listOfURIs = new HashSet<String>();
|
||||
listOfURIs.add(variantUri);
|
||||
|
||||
extractorReturns(theUri);
|
||||
cacheReturnsEntryForUri(theUri);
|
||||
cacheEntryHasVariantURIs(listOfURIs);
|
||||
|
||||
entryIsRemoved(variantUri);
|
||||
entryIsRemoved(theUri);
|
||||
|
||||
mockImplMethods("requestShouldNotBeCached");
|
||||
org.easymock.EasyMock.expect(impl.requestShouldNotBeCached(mockRequest)).andReturn(true);
|
||||
|
||||
replayMocks();
|
||||
impl.flushInvalidatedCacheEntries(host, mockRequest);
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheFlushException() throws Exception {
|
||||
String theURI = "theURI";
|
||||
|
||||
mockImplMethods("requestShouldNotBeCached");
|
||||
org.easymock.EasyMock.expect(impl.requestShouldNotBeCached(mockRequest)).andReturn(true);
|
||||
|
||||
extractorReturns(theURI);
|
||||
cacheReturnsExceptionForUri(theURI);
|
||||
|
||||
replayMocks();
|
||||
impl.flushInvalidatedCacheEntries(host, mockRequest);
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
// Expectations
|
||||
private void requestContainsPragmaHeader(Header header) {
|
||||
org.easymock.EasyMock.expect(mockRequest.getFirstHeader("Pragma")).andReturn(header);
|
||||
}
|
||||
|
||||
private void requestMethodIs(String s) {
|
||||
org.easymock.EasyMock.expect(mockRequestLine.getMethod()).andReturn(s);
|
||||
}
|
||||
|
||||
private void cacheEntryHasVariantURIs(Set<String> variantURIs) {
|
||||
org.easymock.EasyMock.expect(mockEntry.getVariantURIs()).andReturn(variantURIs);
|
||||
}
|
||||
|
||||
private void cacheReturnsEntryForUri(String theUri) throws HttpCacheOperationException {
|
||||
org.easymock.EasyMock.expect(mockCache.getEntry(theUri)).andReturn(mockEntry);
|
||||
}
|
||||
|
||||
private void cacheReturnsExceptionForUri(String theUri) throws HttpCacheOperationException {
|
||||
org.easymock.EasyMock.expect(mockCache.getEntry(theUri)).andThrow(
|
||||
new HttpCacheOperationException("TOTAL FAIL"));
|
||||
}
|
||||
|
||||
private void extractorReturns(String theUri) {
|
||||
org.easymock.EasyMock.expect(mockExtractor.getURI(host, mockRequest)).andReturn(theUri);
|
||||
}
|
||||
|
||||
private void entryIsRemoved(String theUri) throws HttpCacheOperationException {
|
||||
mockCache.removeEntry(theUri);
|
||||
}
|
||||
|
||||
private void requestLineIsRead() {
|
||||
org.easymock.EasyMock.expect(mockRequest.getRequestLine()).andReturn(mockRequestLine);
|
||||
}
|
||||
|
||||
private void requestContainsCacheControlHeader(Header[] header) {
|
||||
org.easymock.EasyMock.expect(mockRequest.getHeaders("Cache-Control")).andReturn(header);
|
||||
}
|
||||
}
|
99
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheableRequestPolicy.java
vendored
Normal file
99
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCacheableRequestPolicy.java
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import org.apache.http.client.cache.impl.CacheableRequestPolicy;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestCacheableRequestPolicy {
|
||||
|
||||
private CacheableRequestPolicy policy;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
policy = new CacheableRequestPolicy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetServableFromCache() {
|
||||
BasicHttpRequest request = new BasicHttpRequest("GET", "someUri");
|
||||
|
||||
Assert.assertTrue(policy.isServableFromCache(request));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetWithCacheControlServableFromCache() {
|
||||
BasicHttpRequest request = new BasicHttpRequest("GET", "someUri");
|
||||
request.addHeader("Cache-Control", "no-cache");
|
||||
|
||||
Assert.assertFalse(policy.isServableFromCache(request));
|
||||
|
||||
request = new BasicHttpRequest("GET", "someUri");
|
||||
request.addHeader("Cache-Control", "no-store");
|
||||
request.addHeader("Cache-Control", "max-age=20");
|
||||
|
||||
Assert.assertFalse(policy.isServableFromCache(request));
|
||||
|
||||
request = new BasicHttpRequest("GET", "someUri");
|
||||
request.addHeader("Cache-Control", "public");
|
||||
request.addHeader("Cache-Control", "no-store, max-age=20");
|
||||
|
||||
Assert.assertFalse(policy.isServableFromCache(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetWithPragmaServableFromCache() {
|
||||
BasicHttpRequest request = new BasicHttpRequest("GET", "someUri");
|
||||
request.addHeader("Pragma", "no-cache");
|
||||
|
||||
Assert.assertFalse(policy.isServableFromCache(request));
|
||||
|
||||
request = new BasicHttpRequest("GET", "someUri");
|
||||
request.addHeader("Pragma", "value1");
|
||||
request.addHeader("Pragma", "value2");
|
||||
|
||||
Assert.assertFalse(policy.isServableFromCache(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsArbitraryMethodServableFromCache() {
|
||||
|
||||
BasicHttpRequest request = new BasicHttpRequest("HEAD", "someUri");
|
||||
|
||||
Assert.assertFalse(policy.isServableFromCache(request));
|
||||
|
||||
request = new BasicHttpRequest("get", "someUri");
|
||||
|
||||
Assert.assertFalse(policy.isServableFromCache(request));
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.client.cache.impl.CacheEntry;
|
||||
import org.apache.http.client.cache.impl.CachedHttpResponseGenerator;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestCachedHttpResponseGenerator {
|
||||
|
||||
@Test
|
||||
public void testResponseHasContentLength() {
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
Header[] hdrs = new Header[] {};
|
||||
byte[] buf = new byte[] { 1, 2, 3, 4, 5 };
|
||||
entry.setResponseHeaders(hdrs);
|
||||
entry.setProtocolVersion(new ProtocolVersion("HTTP", 1, 1));
|
||||
entry.setBody(buf);
|
||||
entry.setResponseDate(new Date());
|
||||
entry.setRequestDate(new Date());
|
||||
|
||||
CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator();
|
||||
HttpResponse response = gen.generateResponse(entry);
|
||||
|
||||
Header length = response.getFirstHeader("Content-Length");
|
||||
Assert.assertNotNull("Content-Length Header is missing", length);
|
||||
|
||||
Assert.assertEquals("Content-Length does not match buffer length", buf.length, Integer
|
||||
.parseInt(length.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContentLengthIsNotAddedWhenTransferEncodingIsPresent() {
|
||||
CacheEntry entry = new CacheEntry();
|
||||
Header[] hdrs = new Header[] { new BasicHeader("Transfer-Encoding", "chunked") };
|
||||
byte[] buf = new byte[] { 1, 2, 3, 4, 5 };
|
||||
entry.setResponseHeaders(hdrs);
|
||||
entry.setProtocolVersion(new ProtocolVersion("HTTP", 1, 1));
|
||||
entry.setBody(buf);
|
||||
entry.setResponseDate(new Date());
|
||||
entry.setRequestDate(new Date());
|
||||
|
||||
CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator();
|
||||
HttpResponse response = gen.generateResponse(entry);
|
||||
|
||||
Header length = response.getFirstHeader("Content-Length");
|
||||
|
||||
Assert.assertNull(length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseMatchesCacheEntry() {
|
||||
CacheEntry entry = new CacheEntry();
|
||||
buildEntry(entry);
|
||||
|
||||
CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator();
|
||||
HttpResponse response = gen.generateResponse(entry);
|
||||
|
||||
Assert.assertTrue(response.containsHeader("Content-Length"));
|
||||
|
||||
Assert.assertSame("HTTP", response.getProtocolVersion().getProtocol());
|
||||
Assert.assertSame(1, response.getProtocolVersion().getMajor());
|
||||
Assert.assertSame(1, response.getProtocolVersion().getMinor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseStatusCodeMatchesCacheEntry() {
|
||||
CacheEntry entry = new CacheEntry();
|
||||
buildEntry(entry);
|
||||
|
||||
CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator();
|
||||
HttpResponse response = gen.generateResponse(entry);
|
||||
|
||||
Assert.assertEquals(entry.getStatusCode(), response.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAgeHeaderIsPopulatedWithCurrentAgeOfCacheEntryIfNonZero() {
|
||||
final long currAge = 10L;
|
||||
CacheEntry entry = new CacheEntry() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public long getCurrentAgeSecs() {
|
||||
return currAge;
|
||||
}
|
||||
};
|
||||
|
||||
buildEntry(entry);
|
||||
|
||||
CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator();
|
||||
HttpResponse response = gen.generateResponse(entry);
|
||||
|
||||
Header ageHdr = response.getFirstHeader("Age");
|
||||
Assert.assertNotNull(ageHdr);
|
||||
Assert.assertEquals(currAge, Long.parseLong(ageHdr.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAgeHeaderIsNotPopulatedIfCurrentAgeOfCacheEntryIsZero() {
|
||||
final long currAge = 0L;
|
||||
CacheEntry entry = new CacheEntry() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public long getCurrentAgeSecs() {
|
||||
return currAge;
|
||||
}
|
||||
};
|
||||
buildEntry(entry);
|
||||
|
||||
CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator();
|
||||
HttpResponse response = gen.generateResponse(entry);
|
||||
|
||||
Header ageHdr = response.getFirstHeader("Age");
|
||||
Assert.assertNull(ageHdr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAgeHeaderIsPopulatedWithMaxAgeIfCurrentAgeTooBig() {
|
||||
final long currAge = CacheEntry.MAX_AGE + 1L;
|
||||
CacheEntry entry = new CacheEntry() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public long getCurrentAgeSecs() {
|
||||
return currAge;
|
||||
}
|
||||
};
|
||||
buildEntry(entry);
|
||||
|
||||
CachedHttpResponseGenerator gen = new CachedHttpResponseGenerator();
|
||||
HttpResponse response = gen.generateResponse(entry);
|
||||
|
||||
Header ageHdr = response.getFirstHeader("Age");
|
||||
Assert.assertNotNull(ageHdr);
|
||||
Assert.assertEquals(CacheEntry.MAX_AGE, Long.parseLong(ageHdr.getValue()));
|
||||
}
|
||||
|
||||
private CacheEntry buildEntry(CacheEntry entry) {
|
||||
Date now = new Date();
|
||||
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
|
||||
Date eightSecondsAgo = new Date(now.getTime() - 8 * 1000L);
|
||||
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
|
||||
Date tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
|
||||
Header[] hdrs = { new BasicHeader("Date", DateUtils.formatDate(eightSecondsAgo)),
|
||||
new BasicHeader("Expires", DateUtils.formatDate(tenSecondsFromNow)),
|
||||
new BasicHeader("Content-Length", "150") };
|
||||
entry.setRequestDate(tenSecondsAgo);
|
||||
entry.setResponseDate(sixSecondsAgo);
|
||||
entry.setBody(new byte[] {});
|
||||
entry.setResponseHeaders(hdrs);
|
||||
|
||||
entry.setProtocolVersion(new ProtocolVersion("HTTP", 1, 1));
|
||||
|
||||
return entry;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.client.cache.impl.CacheEntry;
|
||||
import org.apache.http.client.cache.impl.CachedResponseSuitabilityChecker;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.easymock.classextension.EasyMock;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestCachedResponseSuitabilityChecker {
|
||||
|
||||
private CachedResponseSuitabilityChecker impl;
|
||||
private HttpHost host;
|
||||
private HttpRequest request;
|
||||
private CacheEntry mockEntry;
|
||||
private HttpRequest mockRequest;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
host = new HttpHost("foo.example.com");
|
||||
request = new BasicHttpRequest("GET", "/foo");
|
||||
mockEntry = EasyMock.createMock(CacheEntry.class);
|
||||
mockRequest = EasyMock.createMock(HttpRequest.class);
|
||||
|
||||
impl = new CachedResponseSuitabilityChecker();
|
||||
}
|
||||
|
||||
public void replayMocks() {
|
||||
EasyMock.replay(mockEntry, mockRequest);
|
||||
}
|
||||
|
||||
public void verifyMocks() {
|
||||
EasyMock.verify(mockEntry, mockRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotSuitableIfContentLengthHeaderIsWrong() {
|
||||
responseIsFresh(true);
|
||||
contentLengthMatchesActualLength(false);
|
||||
|
||||
replayMocks();
|
||||
boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
|
||||
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuitableIfContentLengthHeaderIsRight() {
|
||||
responseIsFresh(true);
|
||||
contentLengthMatchesActualLength(true);
|
||||
modifiedSince(false, request);
|
||||
|
||||
replayMocks();
|
||||
boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
|
||||
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuitableIfCacheEntryIsFresh() {
|
||||
responseIsFresh(true);
|
||||
contentLengthMatchesActualLength(true);
|
||||
modifiedSince(false, request);
|
||||
|
||||
replayMocks();
|
||||
|
||||
boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
|
||||
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotSuitableIfCacheEntryIsNotFresh() {
|
||||
responseIsFresh(false);
|
||||
replayMocks();
|
||||
|
||||
boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
|
||||
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotSuitableIfRequestHasNoCache() {
|
||||
request.addHeader("Cache-Control", "no-cache");
|
||||
responseIsFresh(true);
|
||||
contentLengthMatchesActualLength(true);
|
||||
modifiedSince(false, request);
|
||||
|
||||
replayMocks();
|
||||
|
||||
boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
|
||||
verifyMocks();
|
||||
Assert.assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotSuitableIfAgeExceedsRequestMaxAge() {
|
||||
request.addHeader("Cache-Control", "max-age=10");
|
||||
responseIsFresh(true);
|
||||
contentLengthMatchesActualLength(true);
|
||||
modifiedSince(false, request);
|
||||
|
||||
org.easymock.EasyMock.expect(mockEntry.getCurrentAgeSecs()).andReturn(20L);
|
||||
replayMocks();
|
||||
|
||||
boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
|
||||
verifyMocks();
|
||||
Assert.assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuitableIfFreshAndAgeIsUnderRequestMaxAge() {
|
||||
request.addHeader("Cache-Control", "max-age=10");
|
||||
responseIsFresh(true);
|
||||
contentLengthMatchesActualLength(true);
|
||||
modifiedSince(false, request);
|
||||
|
||||
org.easymock.EasyMock.expect(mockEntry.getCurrentAgeSecs()).andReturn(5L);
|
||||
replayMocks();
|
||||
|
||||
boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
|
||||
verifyMocks();
|
||||
Assert.assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuitableIfFreshAndFreshnessLifetimeGreaterThanRequestMinFresh() {
|
||||
request.addHeader("Cache-Control", "min-fresh=10");
|
||||
responseIsFresh(true);
|
||||
contentLengthMatchesActualLength(true);
|
||||
modifiedSince(false, request);
|
||||
|
||||
org.easymock.EasyMock.expect(mockEntry.getFreshnessLifetimeSecs()).andReturn(15L);
|
||||
replayMocks();
|
||||
|
||||
boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
|
||||
verifyMocks();
|
||||
Assert.assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotSuitableIfFreshnessLifetimeLessThanRequestMinFresh() {
|
||||
request.addHeader("Cache-Control", "min-fresh=10");
|
||||
responseIsFresh(true);
|
||||
contentLengthMatchesActualLength(true);
|
||||
modifiedSince(false, request);
|
||||
|
||||
org.easymock.EasyMock.expect(mockEntry.getFreshnessLifetimeSecs()).andReturn(5L);
|
||||
replayMocks();
|
||||
|
||||
boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
|
||||
verifyMocks();
|
||||
Assert.assertFalse(result);
|
||||
}
|
||||
|
||||
// this is compliant but possibly misses some cache hits; would
|
||||
// need to change logic to add Warning header if we allowed this
|
||||
@Test
|
||||
public void testNotSuitableEvenIfStaleButPermittedByRequestMaxStale() {
|
||||
request.addHeader("Cache-Control", "max-stale=10");
|
||||
responseIsFresh(false);
|
||||
|
||||
replayMocks();
|
||||
|
||||
boolean result = impl.canCachedResponseBeUsed(host, request, mockEntry);
|
||||
verifyMocks();
|
||||
Assert.assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMalformedCacheControlMaxAgeRequestHeaderCausesUnsuitableEntry() {
|
||||
|
||||
Header[] hdrs = new Header[] { new BasicHeader("Cache-Control", "max-age=foo") };
|
||||
responseIsFresh(true);
|
||||
contentLengthMatchesActualLength(true);
|
||||
modifiedSince(false, mockRequest);
|
||||
|
||||
org.easymock.EasyMock.expect(mockRequest.getHeaders("Cache-Control")).andReturn(hdrs);
|
||||
replayMocks();
|
||||
|
||||
boolean result = impl.canCachedResponseBeUsed(host, mockRequest, mockEntry);
|
||||
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMalformedCacheControlMinFreshRequestHeaderCausesUnsuitableEntry() {
|
||||
|
||||
Header[] hdrs = new Header[] { new BasicHeader("Cache-Control", "min-fresh=foo") };
|
||||
responseIsFresh(true);
|
||||
contentLengthMatchesActualLength(true);
|
||||
modifiedSince(false, mockRequest);
|
||||
|
||||
org.easymock.EasyMock.expect(mockRequest.getHeaders("Cache-Control")).andReturn(hdrs);
|
||||
replayMocks();
|
||||
|
||||
boolean result = impl.canCachedResponseBeUsed(host, mockRequest, mockEntry);
|
||||
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertFalse(result);
|
||||
}
|
||||
|
||||
private void responseIsFresh(boolean fresh) {
|
||||
org.easymock.EasyMock.expect(mockEntry.isResponseFresh()).andReturn(fresh);
|
||||
}
|
||||
|
||||
private void modifiedSince(boolean modified, HttpRequest request) {
|
||||
org.easymock.EasyMock.expect(mockEntry.modifiedSince(request)).andReturn(modified);
|
||||
}
|
||||
|
||||
private void contentLengthMatchesActualLength(boolean b) {
|
||||
org.easymock.EasyMock.expect(mockEntry.contentLengthHeaderMatchesActualLength()).andReturn(
|
||||
b);
|
||||
}
|
||||
}
|
1126
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachingHttpClient.java
vendored
Normal file
1126
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCachingHttpClient.java
vendored
Normal file
File diff suppressed because it is too large
Load Diff
119
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCombinedInputStream.java
vendored
Normal file
119
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestCombinedInputStream.java
vendored
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.http.client.cache.impl.CombinedInputStream;
|
||||
import org.easymock.classextension.EasyMock;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class TestCombinedInputStream {
|
||||
|
||||
private InputStream mockInputStream1;
|
||||
private InputStream mockInputStream2;
|
||||
private CombinedInputStream impl;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mockInputStream1 = EasyMock.createMock(InputStream.class);
|
||||
mockInputStream2 = EasyMock.createMock(InputStream.class);
|
||||
|
||||
impl = new CombinedInputStream(mockInputStream1, mockInputStream2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatingInputStreamWithNullInputFails() {
|
||||
|
||||
boolean gotex1 = false;
|
||||
boolean gotex2 = false;
|
||||
|
||||
try {
|
||||
impl = new CombinedInputStream(null, mockInputStream2);
|
||||
} catch (Exception ex) {
|
||||
gotex1 = true;
|
||||
}
|
||||
|
||||
try {
|
||||
impl = new CombinedInputStream(mockInputStream1, null);
|
||||
} catch (Exception ex) {
|
||||
gotex2 = true;
|
||||
}
|
||||
|
||||
Assert.assertTrue(gotex1);
|
||||
Assert.assertTrue(gotex2);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAvailableReturnsCorrectSize() throws Exception {
|
||||
ByteArrayInputStream s1 = new ByteArrayInputStream(new byte[] { 1, 1, 1, 1, 1 });
|
||||
ByteArrayInputStream s2 = new ByteArrayInputStream(new byte[] { 1, 1, 1, 1, 1 });
|
||||
|
||||
impl = new CombinedInputStream(s1, s2);
|
||||
int avail = impl.available();
|
||||
|
||||
Assert.assertEquals(10, avail);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFirstEmptyStreamReadsFromOtherStream() throws Exception {
|
||||
org.easymock.EasyMock.expect(mockInputStream1.read()).andReturn(-1);
|
||||
org.easymock.EasyMock.expect(mockInputStream2.read()).andReturn(500);
|
||||
|
||||
replayMocks();
|
||||
int result = impl.read();
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertEquals(500, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatWeReadTheFirstInputStream() throws Exception {
|
||||
org.easymock.EasyMock.expect(mockInputStream1.read()).andReturn(500);
|
||||
|
||||
replayMocks();
|
||||
int result = impl.read();
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertEquals(500, result);
|
||||
}
|
||||
|
||||
private void verifyMocks() {
|
||||
EasyMock.verify(mockInputStream1, mockInputStream2);
|
||||
}
|
||||
|
||||
private void replayMocks() {
|
||||
EasyMock.replay(mockInputStream1, mockInputStream2);
|
||||
}
|
||||
|
||||
}
|
116
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestConditionalRequestBuilder.java
vendored
Normal file
116
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestConditionalRequestBuilder.java
vendored
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.ProtocolException;
|
||||
import org.apache.http.client.cache.impl.CacheEntry;
|
||||
import org.apache.http.client.cache.impl.ConditionalRequestBuilder;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestConditionalRequestBuilder {
|
||||
|
||||
private ConditionalRequestBuilder impl;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
impl = new ConditionalRequestBuilder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildConditionalRequestWithLastModified() throws ProtocolException {
|
||||
String theMethod = "GET";
|
||||
String theUri = "/theuri";
|
||||
String lastModified = "this is my last modified date";
|
||||
|
||||
HttpRequest request = new BasicHttpRequest(theMethod, theUri);
|
||||
request.addHeader("Accept-Encoding", "gzip");
|
||||
|
||||
CacheEntry cacheEntry = new CacheEntry();
|
||||
cacheEntry.setResponseHeaders(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatDate(new Date())),
|
||||
new BasicHeader("Last-Modified", lastModified) });
|
||||
|
||||
HttpRequest newRequest = impl.buildConditionalRequest(request, cacheEntry);
|
||||
|
||||
Assert.assertNotSame(request, newRequest);
|
||||
|
||||
Assert.assertEquals(theMethod, newRequest.getRequestLine().getMethod());
|
||||
Assert.assertEquals(theUri, newRequest.getRequestLine().getUri());
|
||||
Assert.assertEquals(request.getRequestLine().getProtocolVersion(), newRequest
|
||||
.getRequestLine().getProtocolVersion());
|
||||
Assert.assertEquals(2, newRequest.getAllHeaders().length);
|
||||
|
||||
Assert.assertEquals("Accept-Encoding", newRequest.getAllHeaders()[0].getName());
|
||||
Assert.assertEquals("gzip", newRequest.getAllHeaders()[0].getValue());
|
||||
|
||||
Assert.assertEquals("If-Modified-Since", newRequest.getAllHeaders()[1].getName());
|
||||
Assert.assertEquals(lastModified, newRequest.getAllHeaders()[1].getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildConditionalRequestWithETag() throws ProtocolException {
|
||||
String theMethod = "GET";
|
||||
String theUri = "/theuri";
|
||||
String theETag = "this is my eTag";
|
||||
|
||||
HttpRequest request = new BasicHttpRequest(theMethod, theUri);
|
||||
request.addHeader("Accept-Encoding", "gzip");
|
||||
|
||||
CacheEntry cacheEntry = new CacheEntry();
|
||||
cacheEntry.setResponseHeaders(new Header[] {
|
||||
new BasicHeader("Date", DateUtils.formatDate(new Date())),
|
||||
new BasicHeader("Last-Modified", DateUtils.formatDate(new Date())),
|
||||
new BasicHeader("ETag", theETag) });
|
||||
|
||||
HttpRequest newRequest = impl.buildConditionalRequest(request, cacheEntry);
|
||||
|
||||
Assert.assertNotSame(request, newRequest);
|
||||
|
||||
Assert.assertEquals(theMethod, newRequest.getRequestLine().getMethod());
|
||||
Assert.assertEquals(theUri, newRequest.getRequestLine().getUri());
|
||||
Assert.assertEquals(request.getRequestLine().getProtocolVersion(), newRequest
|
||||
.getRequestLine().getProtocolVersion());
|
||||
|
||||
Assert.assertEquals(2, newRequest.getAllHeaders().length);
|
||||
|
||||
Assert.assertEquals("Accept-Encoding", newRequest.getAllHeaders()[0].getName());
|
||||
Assert.assertEquals("gzip", newRequest.getAllHeaders()[0].getValue());
|
||||
|
||||
Assert.assertEquals("If-None-Match", newRequest.getAllHeaders()[1].getName());
|
||||
Assert.assertEquals(theETag, newRequest.getAllHeaders()[1].getValue());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpVersion;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.client.cache.HttpCacheEntrySerializer;
|
||||
import org.apache.http.client.cache.impl.CacheEntry;
|
||||
import org.apache.http.client.cache.impl.DefaultCacheEntrySerializer;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestDefaultCacheEntrySerializer {
|
||||
|
||||
@Test
|
||||
public void testSerialization() throws Exception {
|
||||
|
||||
HttpCacheEntrySerializer<CacheEntry> serializer = new DefaultCacheEntrySerializer();
|
||||
|
||||
// write the entry
|
||||
CacheEntry writeEntry = newCacheEntry();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
serializer.writeTo(writeEntry, out);
|
||||
|
||||
// read the entry
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
CacheEntry readEntry = serializer.readFrom(in);
|
||||
|
||||
// compare
|
||||
Assert.assertTrue(areEqual(readEntry, writeEntry));
|
||||
|
||||
}
|
||||
|
||||
private CacheEntry newCacheEntry() {
|
||||
|
||||
CacheEntry cacheEntry = new CacheEntry();
|
||||
|
||||
Header[] headers = new Header[5];
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
headers[i] = new BasicHeader("header" + i, "value" + i);
|
||||
}
|
||||
ProtocolVersion version = new HttpVersion(1, 1);
|
||||
String body = "Lorem ipsum dolor sit amet";
|
||||
|
||||
cacheEntry.setResponseHeaders(headers);
|
||||
cacheEntry.setProtocolVersion(version);
|
||||
cacheEntry.setRequestDate(new Date());
|
||||
cacheEntry.setResponseDate(new Date());
|
||||
cacheEntry.setBody(body.getBytes());
|
||||
|
||||
return cacheEntry;
|
||||
|
||||
}
|
||||
|
||||
private boolean areEqual(CacheEntry one, CacheEntry two) {
|
||||
|
||||
if (!one.getRequestDate().equals(two.getRequestDate()))
|
||||
return false;
|
||||
if (!one.getResponseDate().equals(two.getResponseDate()))
|
||||
return false;
|
||||
if (!one.getProtocolVersion().equals(two.getProtocolVersion()))
|
||||
return false;
|
||||
if (!Arrays.equals(one.getBody(), two.getBody()))
|
||||
return false;
|
||||
|
||||
Header[] oneHeaders = one.getAllHeaders();
|
||||
Header[] twoHeaders = one.getAllHeaders();
|
||||
if (!(oneHeaders.length == twoHeaders.length))
|
||||
return false;
|
||||
for (int i = 0; i < oneHeaders.length; i++) {
|
||||
if (!oneHeaders[i].getName().equals(twoHeaders[i].getName()))
|
||||
return false;
|
||||
if (!oneHeaders[i].getValue().equals(twoHeaders[i].getValue()))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
378
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolDeviations.java
vendored
Normal file
378
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolDeviations.java
vendored
Normal file
|
@ -0,0 +1,378 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpEntityEnclosingRequest;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.cache.HttpCache;
|
||||
import org.apache.http.client.cache.impl.BasicHttpCache;
|
||||
import org.apache.http.client.cache.impl.CachingHttpClient;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.easymock.Capture;
|
||||
import org.easymock.classextension.EasyMock;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* We are a conditionally-compliant HTTP/1.1 client with a cache. However, a lot
|
||||
* of the rules for proxies apply to us, as far as proper operation of the
|
||||
* requests that pass through us. Generally speaking, we want to make sure that
|
||||
* any response returned from our HttpClient.execute() methods is conditionally
|
||||
* compliant with the rules for an HTTP/1.1 server, and that any requests we
|
||||
* pass downstream to the backend HttpClient are are conditionally compliant
|
||||
* with the rules for an HTTP/1.1 client.
|
||||
*
|
||||
* There are some cases where strictly behaving as a compliant caching proxy
|
||||
* would result in strange behavior, since we're attached as part of a client
|
||||
* and are expected to be a drop-in replacement. The test cases captured here
|
||||
* document the places where we differ from the HTTP RFC.
|
||||
*/
|
||||
public class TestProtocolDeviations {
|
||||
|
||||
private static ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1);
|
||||
|
||||
private static int MAX_BYTES = 1024;
|
||||
private static int MAX_ENTRIES = 100;
|
||||
private int entityLength = 128;
|
||||
|
||||
private HttpHost host;
|
||||
private HttpEntity body;
|
||||
private HttpEntity mockEntity;
|
||||
private HttpClient mockBackend;
|
||||
private HttpCache<CacheEntry> mockCache;
|
||||
private HttpRequest request;
|
||||
private HttpResponse originResponse;
|
||||
|
||||
private CachingHttpClient impl;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Before
|
||||
public void setUp() {
|
||||
host = new HttpHost("foo.example.com");
|
||||
|
||||
body = makeBody(entityLength);
|
||||
|
||||
request = new BasicHttpRequest("GET", "/foo", HTTP_1_1);
|
||||
|
||||
originResponse = make200Response();
|
||||
|
||||
HttpCache<CacheEntry> cache = new BasicHttpCache(MAX_ENTRIES);
|
||||
mockBackend = EasyMock.createMock(HttpClient.class);
|
||||
mockEntity = EasyMock.createMock(HttpEntity.class);
|
||||
mockCache = EasyMock.createMock(HttpCache.class);
|
||||
impl = new CachingHttpClient(mockBackend, cache, MAX_BYTES);
|
||||
}
|
||||
|
||||
private HttpResponse make200Response() {
|
||||
HttpResponse out = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_OK, "OK");
|
||||
out.setHeader("Date", DateUtils.formatDate(new Date()));
|
||||
out.setHeader("Server", "MockOrigin/1.0");
|
||||
out.setEntity(makeBody(128));
|
||||
return out;
|
||||
}
|
||||
|
||||
private void replayMocks() {
|
||||
EasyMock.replay(mockBackend);
|
||||
EasyMock.replay(mockCache);
|
||||
EasyMock.replay(mockEntity);
|
||||
}
|
||||
|
||||
private void verifyMocks() {
|
||||
EasyMock.verify(mockBackend);
|
||||
EasyMock.verify(mockCache);
|
||||
EasyMock.verify(mockEntity);
|
||||
}
|
||||
|
||||
private HttpEntity makeBody(int nbytes) {
|
||||
byte[] bytes = new byte[nbytes];
|
||||
(new Random()).nextBytes(bytes);
|
||||
return new ByteArrayEntity(bytes);
|
||||
}
|
||||
|
||||
public static HttpRequest eqRequest(HttpRequest in) {
|
||||
org.easymock.EasyMock.reportMatcher(new RequestEquivalent(in));
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* "For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
|
||||
* containing a message-body MUST include a valid Content-Length header
|
||||
* field unless the server is known to be HTTP/1.1 compliant. If a request
|
||||
* contains a message-body and a Content-Length is not given, the server
|
||||
* SHOULD respond with 400 (bad request) if it cannot determine the length
|
||||
* of the message, or with 411 (length required) if it wishes to insist on
|
||||
* receiving a valid Content-Length."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
|
||||
*/
|
||||
@Test
|
||||
public void testHTTP1_1RequestsWithBodiesOfKnownLengthMustHaveContentLength() throws Exception {
|
||||
BasicHttpEntityEnclosingRequest post = new BasicHttpEntityEnclosingRequest("POST", "/",
|
||||
HTTP_1_1);
|
||||
post.setEntity(mockEntity);
|
||||
|
||||
replayMocks();
|
||||
|
||||
HttpResponse response = impl.execute(host, post);
|
||||
|
||||
verifyMocks();
|
||||
|
||||
Assert
|
||||
.assertEquals(HttpStatus.SC_LENGTH_REQUIRED, response.getStatusLine()
|
||||
.getStatusCode());
|
||||
}
|
||||
|
||||
/*
|
||||
* Discussion: if an incoming request has a body, but the HttpEntity
|
||||
* attached has an unknown length (meaning entity.getContentLength() is
|
||||
* negative), we have two choices if we want to be conditionally compliant.
|
||||
* (1) we can slurp the whole body into a bytearray and compute its length
|
||||
* before sending; or (2) we can push responsibility for (1) back onto the
|
||||
* client by just generating a 411 response
|
||||
*
|
||||
* There is a third option, which is that we delegate responsibility for (1)
|
||||
* onto the backend HttpClient, but because that is an injected dependency,
|
||||
* we can't rely on it necessarily being conditionally compliant with
|
||||
* HTTP/1.1. Currently, option (2) seems like the safest bet, as this
|
||||
* exposes to the client application that the slurping required for (1)
|
||||
* needs to happen in order to compute the content length.
|
||||
*
|
||||
* In any event, this test just captures the behavior required.
|
||||
*/
|
||||
@Test
|
||||
public void testHTTP1_1RequestsWithUnknownBodyLengthAreRejectedOrHaveContentLengthAdded()
|
||||
throws Exception {
|
||||
BasicHttpEntityEnclosingRequest post = new BasicHttpEntityEnclosingRequest("POST", "/",
|
||||
HTTP_1_1);
|
||||
|
||||
byte[] bytes = new byte[128];
|
||||
(new Random()).nextBytes(bytes);
|
||||
|
||||
HttpEntity mockBody = EasyMock.createMockBuilder(ByteArrayEntity.class).withConstructor(
|
||||
new Object[] { bytes }).addMockedMethods("getContentLength").createMock();
|
||||
org.easymock.EasyMock.expect(mockBody.getContentLength()).andReturn(-1L).anyTimes();
|
||||
post.setEntity(mockBody);
|
||||
|
||||
Capture<HttpRequest> reqCap = new Capture<HttpRequest>();
|
||||
org.easymock.EasyMock.expect(
|
||||
mockBackend.execute(org.easymock.EasyMock.eq(host), org.easymock.EasyMock
|
||||
.capture(reqCap), (HttpContext) org.easymock.EasyMock.isNull())).andReturn(
|
||||
originResponse).times(0, 1);
|
||||
|
||||
replayMocks();
|
||||
EasyMock.replay(mockBody);
|
||||
|
||||
HttpResponse result = impl.execute(host, post);
|
||||
|
||||
verifyMocks();
|
||||
EasyMock.verify(mockBody);
|
||||
|
||||
if (reqCap.hasCaptured()) {
|
||||
// backend request was made
|
||||
HttpRequest forwarded = reqCap.getValue();
|
||||
Assert.assertNotNull(forwarded.getFirstHeader("Content-Length"));
|
||||
} else {
|
||||
int status = result.getStatusLine().getStatusCode();
|
||||
Assert.assertTrue(HttpStatus.SC_LENGTH_REQUIRED == status
|
||||
|| HttpStatus.SC_BAD_REQUEST == status);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* "If the OPTIONS request includes an entity-body (as indicated by the
|
||||
* presence of Content-Length or Transfer-Encoding), then the media type
|
||||
* MUST be indicated by a Content-Type field."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2
|
||||
*/
|
||||
@Test
|
||||
public void testOPTIONSRequestsWithBodiesAndNoContentTypeHaveOneSupplied() throws Exception {
|
||||
BasicHttpEntityEnclosingRequest options = new BasicHttpEntityEnclosingRequest("OPTIONS",
|
||||
"/", HTTP_1_1);
|
||||
options.setEntity(body);
|
||||
options.setHeader("Content-Length", "1");
|
||||
|
||||
Capture<HttpRequest> reqCap = new Capture<HttpRequest>();
|
||||
org.easymock.EasyMock.expect(
|
||||
mockBackend.execute(org.easymock.EasyMock.eq(host), org.easymock.EasyMock
|
||||
.capture(reqCap), (HttpContext) org.easymock.EasyMock.isNull())).andReturn(
|
||||
originResponse);
|
||||
replayMocks();
|
||||
|
||||
impl.execute(host, options);
|
||||
|
||||
verifyMocks();
|
||||
|
||||
HttpRequest forwarded = reqCap.getValue();
|
||||
Assert.assertTrue(forwarded instanceof HttpEntityEnclosingRequest);
|
||||
HttpEntityEnclosingRequest reqWithBody = (HttpEntityEnclosingRequest) forwarded;
|
||||
HttpEntity reqBody = reqWithBody.getEntity();
|
||||
Assert.assertNotNull(reqBody);
|
||||
Assert.assertNotNull(reqBody.getContentType());
|
||||
}
|
||||
|
||||
/*
|
||||
* "10.2.7 206 Partial Content ... The request MUST have included a Range
|
||||
* header field (section 14.35) indicating the desired range, and MAY have
|
||||
* included an If-Range header field (section 14.27) to make the request
|
||||
* conditional."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.7
|
||||
*/
|
||||
@Test
|
||||
public void testPartialContentIsNotReturnedToAClientThatDidNotAskForIt() throws Exception {
|
||||
|
||||
// tester's note: I don't know what the cache will *do* in
|
||||
// this situation, but it better not just pass the response
|
||||
// on.
|
||||
request.removeHeaders("Range");
|
||||
originResponse = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_PARTIAL_CONTENT,
|
||||
"Partial Content");
|
||||
originResponse.setHeader("Content-Range", "bytes 0-499/1234");
|
||||
originResponse.setEntity(makeBody(500));
|
||||
|
||||
org.easymock.EasyMock.expect(
|
||||
mockBackend.execute(org.easymock.EasyMock.isA(HttpHost.class),
|
||||
org.easymock.EasyMock.isA(HttpRequest.class),
|
||||
(HttpContext) org.easymock.EasyMock.isNull())).andReturn(originResponse);
|
||||
|
||||
replayMocks();
|
||||
try {
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
Assert.assertTrue(HttpStatus.SC_PARTIAL_CONTENT != result.getStatusLine()
|
||||
.getStatusCode());
|
||||
} catch (ClientProtocolException acceptableBehavior) {
|
||||
// this is probably ok
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* "10.4.2 401 Unauthorized ... The response MUST include a WWW-Authenticate
|
||||
* header field (section 14.47) containing a challenge applicable to the
|
||||
* requested resource."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
|
||||
*/
|
||||
@Test(expected = ClientProtocolException.class)
|
||||
public void testCantReturnOrigin401ResponseWithoutWWWAuthenticateHeader() throws Exception {
|
||||
|
||||
originResponse = new BasicHttpResponse(HTTP_1_1, 401, "Unauthorized");
|
||||
|
||||
org.easymock.EasyMock.expect(
|
||||
mockBackend.execute(org.easymock.EasyMock.isA(HttpHost.class),
|
||||
org.easymock.EasyMock.isA(HttpRequest.class),
|
||||
(HttpContext) org.easymock.EasyMock.isNull())).andReturn(originResponse);
|
||||
replayMocks();
|
||||
|
||||
// this is another case where we are caught in a sticky
|
||||
// situation, where the origin was not 1.1-compliant.
|
||||
try {
|
||||
impl.execute(host, request);
|
||||
} catch (ClientProtocolException possiblyAcceptableBehavior) {
|
||||
verifyMocks();
|
||||
throw possiblyAcceptableBehavior;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* "10.4.6 405 Method Not Allowed ... The response MUST include an Allow
|
||||
* header containing a list of valid methods for the requested resource.
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
|
||||
*/
|
||||
@Test(expected = ClientProtocolException.class)
|
||||
public void testCantReturnAnOrigin405WithoutAllowHeader() throws Exception {
|
||||
originResponse = new BasicHttpResponse(HTTP_1_1, 405, "Method Not Allowed");
|
||||
|
||||
org.easymock.EasyMock.expect(
|
||||
mockBackend.execute(org.easymock.EasyMock.isA(HttpHost.class),
|
||||
org.easymock.EasyMock.isA(HttpRequest.class),
|
||||
(HttpContext) org.easymock.EasyMock.isNull())).andReturn(originResponse);
|
||||
replayMocks();
|
||||
|
||||
// this is another case where we are caught in a sticky
|
||||
// situation, where the origin was not 1.1-compliant.
|
||||
try {
|
||||
impl.execute(host, request);
|
||||
} catch (ClientProtocolException possiblyAcceptableBehavior) {
|
||||
verifyMocks();
|
||||
throw possiblyAcceptableBehavior;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* "10.4.8 407 Proxy Authentication Required ... The proxy MUST return a
|
||||
* Proxy-Authenticate header field (section 14.33) containing a challenge
|
||||
* applicable to the proxy for the requested resource."
|
||||
*
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8
|
||||
*/
|
||||
@Test
|
||||
public void testCantReturnA407WithoutAProxyAuthenticateHeader() throws Exception {
|
||||
originResponse = new BasicHttpResponse(HTTP_1_1, 407, "Proxy Authentication Required");
|
||||
|
||||
org.easymock.EasyMock.expect(
|
||||
mockBackend.execute(org.easymock.EasyMock.isA(HttpHost.class),
|
||||
org.easymock.EasyMock.isA(HttpRequest.class),
|
||||
(HttpContext) org.easymock.EasyMock.isNull())).andReturn(originResponse);
|
||||
replayMocks();
|
||||
|
||||
boolean gotException = false;
|
||||
// this is another case where we are caught in a sticky
|
||||
// situation, where the origin was not 1.1-compliant.
|
||||
try {
|
||||
HttpResponse result = impl.execute(host, request);
|
||||
Assert.fail("should have gotten ClientProtocolException");
|
||||
|
||||
if (result.getStatusLine().getStatusCode() == 407) {
|
||||
Assert.assertNotNull(result.getFirstHeader("Proxy-Authentication"));
|
||||
}
|
||||
} catch (ClientProtocolException possiblyAcceptableBehavior) {
|
||||
gotException = true;
|
||||
}
|
||||
|
||||
verifyMocks();
|
||||
Assert.assertTrue(gotException);
|
||||
}
|
||||
|
||||
}
|
3147
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolRequirements.java
vendored
Normal file
3147
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolRequirements.java
vendored
Normal file
File diff suppressed because it is too large
Load Diff
163
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestResponseCache.java
vendored
Normal file
163
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestResponseCache.java
vendored
Normal file
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import org.apache.http.client.cache.HttpCacheOperationException;
|
||||
import org.apache.http.client.cache.HttpCacheUpdateCallback;
|
||||
import org.apache.http.client.cache.impl.BasicHttpCache;
|
||||
import org.apache.http.client.cache.impl.CacheEntry;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestResponseCache {
|
||||
|
||||
private BasicHttpCache cache;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cache = new BasicHttpCache(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryRemainsInCacheWhenPutThere() {
|
||||
CacheEntry entry = new CacheEntry();
|
||||
cache.putEntry("foo", entry);
|
||||
|
||||
CacheEntry cachedEntry = cache.getEntry("foo");
|
||||
|
||||
Assert.assertSame(entry, cachedEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovedEntriesDoNotExistAnymore() {
|
||||
CacheEntry entry = new CacheEntry();
|
||||
cache.putEntry("foo", entry);
|
||||
|
||||
cache.removeEntry("foo");
|
||||
|
||||
CacheEntry nullEntry = cache.getEntry("foo");
|
||||
|
||||
Assert.assertNull(nullEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheHoldsNoMoreThanSpecifiedMaxEntries() {
|
||||
BasicHttpCache cache = new BasicHttpCache(1);
|
||||
|
||||
CacheEntry entry1 = new CacheEntry();
|
||||
cache.putEntry("foo", entry1);
|
||||
|
||||
CacheEntry entry2 = new CacheEntry();
|
||||
cache.putEntry("bar", entry2);
|
||||
|
||||
CacheEntry entry3 = new CacheEntry();
|
||||
cache.putEntry("baz", entry3);
|
||||
|
||||
CacheEntry e1 = cache.getEntry("foo");
|
||||
Assert.assertNull("Got foo entry when we should not", e1);
|
||||
|
||||
CacheEntry e2 = cache.getEntry("bar");
|
||||
Assert.assertNull("Got bar entry when we should not", e2);
|
||||
|
||||
CacheEntry e3 = cache.getEntry("baz");
|
||||
Assert.assertNotNull("Did not get baz entry, but should have", e3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallCacheKeepsMostRecentlyUsedEntry() {
|
||||
|
||||
final int max_size = 3;
|
||||
BasicHttpCache cache = new BasicHttpCache(max_size);
|
||||
|
||||
// fill the cache with entries
|
||||
for (int i = 0; i < max_size; i++) {
|
||||
CacheEntry entry = new CacheEntry();
|
||||
cache.putEntry("entry" + i, entry);
|
||||
}
|
||||
|
||||
// read the eldest entry to make it the MRU entry
|
||||
cache.getEntry("entry0");
|
||||
|
||||
// add another entry, which kicks out the eldest (should be the 2nd one
|
||||
// created), and becomes the new MRU entry
|
||||
CacheEntry newMru = new CacheEntry();
|
||||
cache.putEntry("newMru", newMru);
|
||||
|
||||
// get the original second eldest
|
||||
CacheEntry gone = cache.getEntry("entry1");
|
||||
Assert.assertNull("entry1 should be gone", gone);
|
||||
|
||||
CacheEntry latest = cache.getEntry("newMru");
|
||||
Assert.assertNotNull("latest entry should still be there", latest);
|
||||
|
||||
CacheEntry originalEldest = cache.getEntry("entry0");
|
||||
Assert.assertNotNull("original eldest entry should still be there", originalEldest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZeroMaxSizeCacheDoesNotStoreAnything() {
|
||||
BasicHttpCache cache = new BasicHttpCache(0);
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
cache.putEntry("foo", entry);
|
||||
|
||||
CacheEntry gone = cache.getEntry("foo");
|
||||
|
||||
Assert.assertNull("This cache should not have anything in it!", gone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheEntryCallbackUpdatesCacheEntry() throws HttpCacheOperationException {
|
||||
|
||||
final byte[] expectedArray = new byte[] { 1, 2, 3, 4, 5 };
|
||||
|
||||
CacheEntry entry = new CacheEntry();
|
||||
CacheEntry entry2 = new CacheEntry();
|
||||
|
||||
cache.putEntry("foo", entry);
|
||||
cache.putEntry("bar", entry2);
|
||||
|
||||
cache.updateCacheEntry("foo", new HttpCacheUpdateCallback<CacheEntry>() {
|
||||
|
||||
public CacheEntry getUpdatedEntry(CacheEntry existing) {
|
||||
existing.setBody(expectedArray);
|
||||
|
||||
cache.removeEntry("bar");
|
||||
return existing;
|
||||
}
|
||||
});
|
||||
|
||||
CacheEntry afterUpdate = cache.getEntry("foo");
|
||||
CacheEntry bar = cache.getEntry("bar");
|
||||
|
||||
Assert.assertNull(bar);
|
||||
|
||||
Assert.assertArrayEquals(expectedArray, afterUpdate.getBody());
|
||||
}
|
||||
|
||||
}
|
331
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestResponseCachingPolicy.java
vendored
Normal file
331
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestResponseCachingPolicy.java
vendored
Normal file
|
@ -0,0 +1,331 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.client.cache.impl.ResponseCachingPolicy;
|
||||
import org.apache.http.impl.cookie.DateUtils;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.apache.http.message.BasicHttpResponse;
|
||||
import org.apache.http.message.BasicStatusLine;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestResponseCachingPolicy {
|
||||
|
||||
private static final ProtocolVersion PROTOCOL_VERSION = new ProtocolVersion("HTTP", 1, 1);
|
||||
private ResponseCachingPolicy policy;
|
||||
private HttpResponse response;
|
||||
private HttpRequest request;
|
||||
private int[] acceptableCodes = new int[] { HttpStatus.SC_OK,
|
||||
HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION, HttpStatus.SC_MULTIPLE_CHOICES,
|
||||
HttpStatus.SC_MOVED_PERMANENTLY, HttpStatus.SC_GONE };
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
policy = new ResponseCachingPolicy(0);
|
||||
response = new BasicHttpResponse(
|
||||
new BasicStatusLine(PROTOCOL_VERSION, HttpStatus.SC_OK, ""));
|
||||
response.setHeader("Date", DateUtils.formatDate(new Date()));
|
||||
response.setHeader("Content-Length", "0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetCacheable() {
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test203ResponseCodeIsCacheable() {
|
||||
response.setStatusCode(HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION);
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test206ResponseCodeIsNotCacheable() {
|
||||
response.setStatusCode(HttpStatus.SC_PARTIAL_CONTENT);
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test300ResponseCodeIsCacheable() {
|
||||
response.setStatusCode(HttpStatus.SC_MULTIPLE_CHOICES);
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test301ResponseCodeIsCacheable() {
|
||||
response.setStatusCode(HttpStatus.SC_MOVED_PERMANENTLY);
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test410ResponseCodeIsCacheable() {
|
||||
response.setStatusCode(HttpStatus.SC_GONE);
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlain302ResponseCodeIsNotCacheable() {
|
||||
response.setStatusCode(HttpStatus.SC_MOVED_TEMPORARILY);
|
||||
response.removeHeaders("Expires");
|
||||
response.removeHeaders("Cache-Control");
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlain307ResponseCodeIsNotCacheable() {
|
||||
response.setStatusCode(HttpStatus.SC_TEMPORARY_REDIRECT);
|
||||
response.removeHeaders("Expires");
|
||||
response.removeHeaders("Cache-Control");
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNon206WithExplicitExpiresIsCacheable() {
|
||||
int status = getRandomStatus();
|
||||
response.setStatusCode(status);
|
||||
response.setHeader("Expires", DateUtils.formatDate(new Date()));
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNon206WithMaxAgeIsCacheable() {
|
||||
int status = getRandomStatus();
|
||||
response.setStatusCode(status);
|
||||
response.setHeader("Cache-Control", "max-age=0");
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNon206WithSMaxAgeIsCacheable() {
|
||||
int status = getRandomStatus();
|
||||
response.setStatusCode(status);
|
||||
response.setHeader("Cache-Control", "s-maxage=0");
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNon206WithMustRevalidateIsCacheable() {
|
||||
int status = getRandomStatus();
|
||||
response.setStatusCode(status);
|
||||
response.setHeader("Cache-Control", "must-revalidate");
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNon206WithProxyRevalidateIsCacheable() {
|
||||
int status = getRandomStatus();
|
||||
response.setStatusCode(status);
|
||||
response.setHeader("Cache-Control", "proxy-revalidate");
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNon206WithPublicCacheControlIsCacheable() {
|
||||
int status = getRandomStatus();
|
||||
response.setStatusCode(status);
|
||||
response.setHeader("Cache-Control", "public");
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
// are we truly a non-shared cache? best be safe
|
||||
@Test
|
||||
public void testNon206WithPrivateCacheControlIsNotCacheable() {
|
||||
int status = getRandomStatus();
|
||||
response.setStatusCode(status);
|
||||
response.setHeader("Cache-Control", "private");
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetWithNoCacheCacheable() {
|
||||
response.addHeader("Cache-Control", "no-cache");
|
||||
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetWithNoStoreCacheable() {
|
||||
response.addHeader("Cache-Control", "no-store");
|
||||
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetWithNoStoreEmbeddedInListCacheable() {
|
||||
response.addHeader("Cache-Control", "public, no-store");
|
||||
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetWithNoCacheEmbeddedInListCacheable() {
|
||||
response.addHeader("Cache-Control", "public, no-cache");
|
||||
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetWithNoCacheEmbeddedInListAfterFirstHeaderCacheable() {
|
||||
response.addHeader("Cache-Control", "max-age=20");
|
||||
response.addHeader("Cache-Control", "public, no-cache");
|
||||
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetWithNoStoreEmbeddedInListAfterFirstHeaderCacheable() {
|
||||
response.addHeader("Cache-Control", "max-age=20");
|
||||
response.addHeader("Cache-Control", "public, no-store");
|
||||
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetWithAnyCacheControlCacheable() {
|
||||
response.addHeader("Cache-Control", "max=10");
|
||||
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
|
||||
response = new BasicHttpResponse(
|
||||
new BasicStatusLine(PROTOCOL_VERSION, HttpStatus.SC_OK, ""));
|
||||
response.setHeader("Date", DateUtils.formatDate(new Date()));
|
||||
response.addHeader("Cache-Control", "no-transform");
|
||||
response.setHeader("Content-Length", "0");
|
||||
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetWithout200Cacheable() {
|
||||
HttpResponse response = new BasicHttpResponse(new BasicStatusLine(PROTOCOL_VERSION,
|
||||
HttpStatus.SC_NOT_FOUND, ""));
|
||||
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
|
||||
response = new BasicHttpResponse(new BasicStatusLine(PROTOCOL_VERSION,
|
||||
HttpStatus.SC_GATEWAY_TIMEOUT, ""));
|
||||
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVaryStarIsNotCacheable() {
|
||||
response.setHeader("Vary", "*");
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGetWithVaryHeaderCacheable() {
|
||||
response.addHeader("Vary", "Accept-Encoding");
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsArbitraryMethodCacheable() {
|
||||
|
||||
Assert.assertFalse(policy.isResponseCacheable("PUT", response));
|
||||
|
||||
Assert.assertFalse(policy.isResponseCacheable("get", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponsesWithMultipleAgeHeadersAreNotCacheable() {
|
||||
response.addHeader("Age", "3");
|
||||
response.addHeader("Age", "5");
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponsesWithMultipleDateHeadersAreNotCacheable() {
|
||||
Date now = new Date();
|
||||
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
|
||||
response.addHeader("Date", DateUtils.formatDate(now));
|
||||
response.addHeader("Date", DateUtils.formatDate(sixSecondsAgo));
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponsesWithMalformedDateHeadersAreNotCacheable() {
|
||||
response.addHeader("Date", "garbage");
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponsesWithMultipleExpiresHeadersAreNotCacheable() {
|
||||
Date now = new Date();
|
||||
Date sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
|
||||
response.addHeader("Expires", DateUtils.formatDate(now));
|
||||
response.addHeader("Expires", DateUtils.formatDate(sixSecondsAgo));
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponsesWithoutDateHeadersAreNotCacheable() {
|
||||
response.removeHeaders("Date");
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseThatHasTooMuchContentIsNotCacheable() {
|
||||
response.setHeader("Content-Length", "9000");
|
||||
Assert.assertFalse(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponsesThatAreSmallEnoughAreCacheable() {
|
||||
response.setHeader("Content-Length", "0");
|
||||
Assert.assertTrue(policy.isResponseCacheable("GET", response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponsesToGETWithQueryParamsButNoExplicitCachingAreNotCacheable() {
|
||||
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
||||
Assert.assertFalse(policy.isResponseCacheable(request, response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheable() {
|
||||
request = new BasicHttpRequest("GET", "/foo?s=bar");
|
||||
response.setHeader("Expires", DateUtils.formatDate(new Date()));
|
||||
Assert.assertTrue(policy.isResponseCacheable(request, response));
|
||||
}
|
||||
|
||||
private int getRandomStatus() {
|
||||
int rnd = (new Random()).nextInt(acceptableCodes.length);
|
||||
|
||||
return acceptableCodes[rnd];
|
||||
}
|
||||
}
|
252
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestSizeLimitedResponseReader.java
vendored
Normal file
252
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestSizeLimitedResponseReader.java
vendored
Normal file
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.ProtocolVersion;
|
||||
import org.apache.http.client.cache.impl.SizeLimitedResponseReader;
|
||||
import org.easymock.classextension.EasyMock;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestSizeLimitedResponseReader {
|
||||
|
||||
private static final int MAX_SIZE = 4;
|
||||
|
||||
private SizeLimitedResponseReader impl;
|
||||
private HttpResponse mockResponse;
|
||||
private HttpEntity mockEntity;
|
||||
private InputStream mockInputStream;
|
||||
private ProtocolVersion mockVersion;
|
||||
|
||||
private boolean mockedImpl;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mockResponse = EasyMock.createMock(HttpResponse.class);
|
||||
mockEntity = EasyMock.createMock(HttpEntity.class);
|
||||
mockInputStream = EasyMock.createMock(InputStream.class);
|
||||
mockVersion = EasyMock.createMock(ProtocolVersion.class);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLargeResponseIsTooLarge() throws Exception {
|
||||
|
||||
responseHasValidEntity();
|
||||
entityHasValidContentStream();
|
||||
inputStreamReturnsValidBytes(5);
|
||||
|
||||
getReader();
|
||||
|
||||
replayMocks();
|
||||
boolean tooLarge = impl.isResponseTooLarge();
|
||||
byte[] result = impl.getResponseBytes();
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertTrue(tooLarge);
|
||||
|
||||
Assert.assertArrayEquals(new byte[] { 1, 1, 1, 1, 1 }, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExactSizeResponseIsNotTooLarge() throws Exception {
|
||||
responseHasValidEntity();
|
||||
entityHasValidContentStream();
|
||||
inputStreamReturnsValidBytes(4);
|
||||
inputStreamReturnsEndOfStream();
|
||||
|
||||
getReader();
|
||||
replayMocks();
|
||||
boolean tooLarge = impl.isResponseTooLarge();
|
||||
byte[] result = impl.getResponseBytes();
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertFalse(tooLarge);
|
||||
|
||||
Assert.assertArrayEquals(new byte[] { 1, 1, 1, 1 }, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallResponseIsNotTooLarge() throws Exception {
|
||||
responseHasValidEntity();
|
||||
entityHasValidContentStream();
|
||||
|
||||
org.easymock.EasyMock.expect(mockInputStream.read()).andReturn(1).times(3);
|
||||
|
||||
org.easymock.EasyMock.expect(mockInputStream.read()).andReturn(-1).times(2);
|
||||
|
||||
getReader();
|
||||
replayMocks();
|
||||
boolean tooLarge = impl.isResponseTooLarge();
|
||||
byte[] result = impl.getResponseBytes();
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertFalse(tooLarge);
|
||||
|
||||
Assert.assertArrayEquals(new byte[] { 1, 1, 1 }, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseWithNoEntityIsNotTooLarge() throws Exception {
|
||||
responseHasNullEntity();
|
||||
|
||||
getReader();
|
||||
replayMocks();
|
||||
boolean tooLarge = impl.isResponseTooLarge();
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertFalse(tooLarge);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReconstructedSmallResponseHasCorrectLength() throws Exception {
|
||||
|
||||
byte[] expectedArray = new byte[] { 1, 1, 1, 1 };
|
||||
|
||||
InputStream stream = new ByteArrayInputStream(new byte[] {});
|
||||
|
||||
responseReturnsHeaders();
|
||||
responseReturnsProtocolVersion();
|
||||
|
||||
getReader();
|
||||
mockImplMethods("getResponseBytes", "getContentInputStream");
|
||||
getContentInputStreamReturns(stream);
|
||||
getResponseBytesReturns(expectedArray);
|
||||
replayMocks();
|
||||
|
||||
HttpResponse response = impl.getReconstructedResponse();
|
||||
|
||||
verifyMocks();
|
||||
|
||||
Assert.assertNotNull("Response should not be null", response);
|
||||
InputStream resultStream = response.getEntity().getContent();
|
||||
|
||||
byte[] buffer = new byte[expectedArray.length];
|
||||
resultStream.read(buffer);
|
||||
|
||||
Assert.assertArrayEquals(expectedArray, buffer);
|
||||
}
|
||||
|
||||
private void getContentInputStreamReturns(InputStream inputStream) {
|
||||
org.easymock.EasyMock.expect(impl.getContentInputStream()).andReturn(inputStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReconstructedLargeResponseHasCorrectLength() throws Exception {
|
||||
|
||||
byte[] expectedArray = new byte[] { 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1 };
|
||||
byte[] arrayAfterConsumedBytes = new byte[] { 1, 1, 1, 1, 1, 1, 1 };
|
||||
byte[] smallArray = new byte[] { 2, 2, 2, 2, };
|
||||
InputStream is = new ByteArrayInputStream(arrayAfterConsumedBytes);
|
||||
|
||||
responseReturnsHeaders();
|
||||
responseReturnsProtocolVersion();
|
||||
|
||||
getReader();
|
||||
mockImplMethods("getResponseBytes", "getContentInputStream");
|
||||
getResponseBytesReturns(smallArray);
|
||||
getContentInputStreamReturns(is);
|
||||
|
||||
replayMocks();
|
||||
|
||||
HttpResponse response = impl.getReconstructedResponse();
|
||||
|
||||
verifyMocks();
|
||||
|
||||
InputStream resultStream = response.getEntity().getContent();
|
||||
|
||||
byte[] buffer = new byte[expectedArray.length];
|
||||
resultStream.read(buffer);
|
||||
|
||||
Assert.assertArrayEquals(expectedArray, buffer);
|
||||
}
|
||||
|
||||
private void getResponseBytesReturns(byte[] expectedArray) {
|
||||
org.easymock.EasyMock.expect(impl.getResponseBytes()).andReturn(expectedArray);
|
||||
}
|
||||
|
||||
private void responseReturnsHeaders() {
|
||||
org.easymock.EasyMock.expect(mockResponse.getAllHeaders()).andReturn(new Header[] {});
|
||||
}
|
||||
|
||||
private void entityHasValidContentStream() throws IOException {
|
||||
org.easymock.EasyMock.expect(mockEntity.getContent()).andReturn(mockInputStream);
|
||||
}
|
||||
|
||||
private void inputStreamReturnsEndOfStream() throws IOException {
|
||||
org.easymock.EasyMock.expect(mockInputStream.read()).andReturn(-1);
|
||||
}
|
||||
|
||||
private void responseHasValidEntity() {
|
||||
org.easymock.EasyMock.expect(mockResponse.getEntity()).andReturn(mockEntity);
|
||||
}
|
||||
|
||||
private void responseReturnsProtocolVersion() {
|
||||
org.easymock.EasyMock.expect(mockResponse.getProtocolVersion()).andReturn(mockVersion);
|
||||
}
|
||||
|
||||
private void inputStreamReturnsValidBytes(int times) throws IOException {
|
||||
org.easymock.EasyMock.expect(mockInputStream.read()).andReturn(1).times(times);
|
||||
}
|
||||
|
||||
private void responseHasNullEntity() {
|
||||
org.easymock.EasyMock.expect(mockResponse.getEntity()).andReturn(null);
|
||||
}
|
||||
|
||||
private void verifyMocks() {
|
||||
EasyMock.verify(mockResponse, mockEntity, mockInputStream, mockVersion);
|
||||
if (mockedImpl) {
|
||||
EasyMock.verify(impl);
|
||||
}
|
||||
}
|
||||
|
||||
private void replayMocks() {
|
||||
EasyMock.replay(mockResponse, mockEntity, mockInputStream, mockVersion);
|
||||
if (mockedImpl) {
|
||||
EasyMock.replay(impl);
|
||||
}
|
||||
}
|
||||
|
||||
private void getReader() throws IOException {
|
||||
impl = new SizeLimitedResponseReader(MAX_SIZE, mockResponse);
|
||||
}
|
||||
|
||||
private void mockImplMethods(String... methods) {
|
||||
mockedImpl = true;
|
||||
impl = EasyMock.createMockBuilder(SizeLimitedResponseReader.class).withConstructor(
|
||||
MAX_SIZE, mockResponse).addMockedMethods(methods).createMock();
|
||||
}
|
||||
|
||||
}
|
261
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestURIExtractor.java
vendored
Normal file
261
httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestURIExtractor.java
vendored
Normal file
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.client.cache.impl;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.client.cache.impl.CacheEntry;
|
||||
import org.apache.http.client.cache.impl.URIExtractor;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.easymock.classextension.EasyMock;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestURIExtractor {
|
||||
|
||||
private static final BasicHttpRequest REQUEST_FULL_EPISODES = new BasicHttpRequest("GET",
|
||||
"/full_episodes");
|
||||
private static final BasicHttpRequest REQUEST_ROOT = new BasicHttpRequest("GET", "/");
|
||||
|
||||
URIExtractor extractor;
|
||||
private HttpHost host;
|
||||
private CacheEntry mockEntry;
|
||||
private HttpRequest mockRequest;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
host = new HttpHost("foo.example.com");
|
||||
mockEntry = EasyMock.createMock(CacheEntry.class);
|
||||
mockRequest = EasyMock.createMock(HttpRequest.class);
|
||||
extractor = new URIExtractor();
|
||||
}
|
||||
|
||||
private void replayMocks() {
|
||||
EasyMock.replay(mockEntry);
|
||||
EasyMock.replay(mockRequest);
|
||||
}
|
||||
|
||||
private void verifyMocks() {
|
||||
EasyMock.verify(mockEntry);
|
||||
EasyMock.verify(mockRequest);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetURIWithDefaultPortAndScheme() {
|
||||
Assert.assertEquals("http://www.comcast.net/", extractor.getURI(new HttpHost(
|
||||
"www.comcast.net"), REQUEST_ROOT));
|
||||
|
||||
Assert.assertEquals("http://www.fancast.com/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(
|
||||
"www.comcast.net", -1, "https"), REQUEST_ROOT));
|
||||
|
||||
Assert.assertEquals("myhttp://www.fancast.com/full_episodes", extractor.getURI(
|
||||
new HttpHost("www.fancast.com", -1, "myhttp"), REQUEST_FULL_EPISODES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetURIWithDifferentPort() {
|
||||
Assert.assertEquals("http://www.comcast.net:8080/", extractor.getURI(new HttpHost(
|
||||
"www.comcast.net", 8080), REQUEST_ROOT));
|
||||
|
||||
Assert.assertEquals("http://www.fancast.com:9999/full_episodes", extractor.getURI(
|
||||
new HttpHost("www.fancast.com", 9999), REQUEST_FULL_EPISODES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetURIWithDifferentPortAndScheme() {
|
||||
Assert.assertEquals("https://www.comcast.net:8080/", extractor.getURI(new HttpHost(
|
||||
"www.comcast.net", 8080, "https"), REQUEST_ROOT));
|
||||
|
||||
Assert.assertEquals("myhttp://www.fancast.com:9999/full_episodes", extractor.getURI(
|
||||
new HttpHost("www.fancast.com", 9999, "myhttp"), REQUEST_FULL_EPISODES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetURIWithQueryParameters() {
|
||||
Assert.assertEquals("http://www.comcast.net/?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(
|
||||
new HttpHost("www.fancast.com", -1, "http"), new BasicHttpRequest("GET",
|
||||
"/full_episodes?foo=bar")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVariantURIWithNoVaryHeaderReturnsNormalURI() {
|
||||
final String theURI = "theURI";
|
||||
Header[] noHdrs = new Header[0];
|
||||
org.easymock.EasyMock.expect(mockEntry.getHeaders("Vary")).andReturn(noHdrs);
|
||||
extractor = new URIExtractor() {
|
||||
@Override
|
||||
public String getURI(HttpHost h, HttpRequest req) {
|
||||
Assert.assertSame(host, h);
|
||||
Assert.assertSame(mockRequest, req);
|
||||
return theURI;
|
||||
}
|
||||
};
|
||||
|
||||
replayMocks();
|
||||
String result = extractor.getVariantURI(host, mockRequest, mockEntry);
|
||||
verifyMocks();
|
||||
Assert.assertSame(theURI, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVariantURIWithSingleValueVaryHeaderPrepends() {
|
||||
final String theURI = "theURI";
|
||||
Header[] varyHeaders = { new BasicHeader("Vary", "Accept-Encoding") };
|
||||
Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip") };
|
||||
|
||||
extractor = new URIExtractor() {
|
||||
@Override
|
||||
public String getURI(HttpHost h, HttpRequest req) {
|
||||
Assert.assertSame(host, h);
|
||||
Assert.assertSame(mockRequest, req);
|
||||
return theURI;
|
||||
}
|
||||
};
|
||||
org.easymock.EasyMock.expect(mockEntry.getHeaders("Vary")).andReturn(varyHeaders);
|
||||
org.easymock.EasyMock.expect(mockRequest.getHeaders("Accept-Encoding")).andReturn(
|
||||
encHeaders);
|
||||
replayMocks();
|
||||
|
||||
String result = extractor.getVariantURI(host, mockRequest, mockEntry);
|
||||
|
||||
verifyMocks();
|
||||
Assert.assertEquals("{Accept-Encoding=gzip}" + theURI, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVariantURIWithMissingRequestHeader() {
|
||||
final String theURI = "theURI";
|
||||
Header[] noHeaders = new Header[0];
|
||||
Header[] varyHeaders = { new BasicHeader("Vary", "Accept-Encoding") };
|
||||
extractor = new URIExtractor() {
|
||||
@Override
|
||||
public String getURI(HttpHost h, HttpRequest req) {
|
||||
Assert.assertSame(host, h);
|
||||
Assert.assertSame(mockRequest, req);
|
||||
return theURI;
|
||||
}
|
||||
};
|
||||
org.easymock.EasyMock.expect(mockEntry.getHeaders("Vary")).andReturn(varyHeaders);
|
||||
org.easymock.EasyMock.expect(mockRequest.getHeaders("Accept-Encoding"))
|
||||
.andReturn(noHeaders);
|
||||
replayMocks();
|
||||
|
||||
String result = extractor.getVariantURI(host, mockRequest, mockEntry);
|
||||
|
||||
verifyMocks();
|
||||
Assert.assertEquals("{Accept-Encoding=}" + theURI, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVariantURIAlphabetizesWithMultipleVaryingHeaders() {
|
||||
final String theURI = "theURI";
|
||||
Header[] varyHeaders = { new BasicHeader("Vary", "User-Agent, Accept-Encoding") };
|
||||
Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip") };
|
||||
Header[] uaHeaders = { new BasicHeader("User-Agent", "browser") };
|
||||
extractor = new URIExtractor() {
|
||||
@Override
|
||||
public String getURI(HttpHost h, HttpRequest req) {
|
||||
Assert.assertSame(host, h);
|
||||
Assert.assertSame(mockRequest, req);
|
||||
return theURI;
|
||||
}
|
||||
};
|
||||
org.easymock.EasyMock.expect(mockEntry.getHeaders("Vary")).andReturn(varyHeaders);
|
||||
org.easymock.EasyMock.expect(mockRequest.getHeaders("Accept-Encoding")).andReturn(
|
||||
encHeaders);
|
||||
org.easymock.EasyMock.expect(mockRequest.getHeaders("User-Agent")).andReturn(uaHeaders);
|
||||
replayMocks();
|
||||
|
||||
String result = extractor.getVariantURI(host, mockRequest, mockEntry);
|
||||
|
||||
verifyMocks();
|
||||
Assert.assertEquals("{Accept-Encoding=gzip&User-Agent=browser}" + theURI, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVariantURIHandlesMultipleVaryHeaders() {
|
||||
final String theURI = "theURI";
|
||||
Header[] varyHeaders = { new BasicHeader("Vary", "User-Agent"),
|
||||
new BasicHeader("Vary", "Accept-Encoding") };
|
||||
Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip") };
|
||||
Header[] uaHeaders = { new BasicHeader("User-Agent", "browser") };
|
||||
extractor = new URIExtractor() {
|
||||
public String getURI(HttpHost h, HttpRequest req) {
|
||||
Assert.assertSame(host, h);
|
||||
Assert.assertSame(mockRequest, req);
|
||||
return theURI;
|
||||
}
|
||||
};
|
||||
EasyMock.expect(mockEntry.getHeaders("Vary")).andReturn(varyHeaders);
|
||||
EasyMock.expect(mockRequest.getHeaders("Accept-Encoding")).andReturn(encHeaders);
|
||||
EasyMock.expect(mockRequest.getHeaders("User-Agent")).andReturn(uaHeaders);
|
||||
replayMocks();
|
||||
|
||||
String result = extractor.getVariantURI(host, mockRequest, mockEntry);
|
||||
|
||||
verifyMocks();
|
||||
Assert.assertEquals("{Accept-Encoding=gzip&User-Agent=browser}" + theURI, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVariantURIHandlesMultipleLineRequestHeaders() {
|
||||
final String theURI = "theURI";
|
||||
Header[] varyHeaders = { new BasicHeader("Vary", "User-Agent, Accept-Encoding") };
|
||||
Header[] encHeaders = { new BasicHeader("Accept-Encoding", "gzip"),
|
||||
new BasicHeader("Accept-Encoding", "deflate") };
|
||||
Header[] uaHeaders = { new BasicHeader("User-Agent", "browser") };
|
||||
extractor = new URIExtractor() {
|
||||
public String getURI(HttpHost h, HttpRequest req) {
|
||||
Assert.assertSame(host, h);
|
||||
Assert.assertSame(mockRequest, req);
|
||||
return theURI;
|
||||
}
|
||||
};
|
||||
EasyMock.expect(mockEntry.getHeaders("Vary")).andReturn(varyHeaders);
|
||||
EasyMock.expect(mockRequest.getHeaders("Accept-Encoding")).andReturn(encHeaders);
|
||||
EasyMock.expect(mockRequest.getHeaders("User-Agent")).andReturn(uaHeaders);
|
||||
replayMocks();
|
||||
|
||||
String result = extractor.getVariantURI(host, mockRequest, mockEntry);
|
||||
|
||||
verifyMocks();
|
||||
Assert
|
||||
.assertEquals("{Accept-Encoding=gzip%2C+deflate&User-Agent=browser}" + theURI,
|
||||
result);
|
||||
}
|
||||
}
|
2
pom.xml
2
pom.xml
|
@ -73,11 +73,13 @@
|
|||
<commons-codec.version>1.4</commons-codec.version>
|
||||
<mime4j.version>0.6</mime4j.version>
|
||||
<junit.version>4.8.1</junit.version>
|
||||
<easymock.version>2.5.2</easymock.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>httpclient</module>
|
||||
<module>httpmime</module>
|
||||
<module>httpclient-cache</module>
|
||||
<module>httpclient-osgi</module>
|
||||
</modules>
|
||||
|
||||
|
|
Loading…
Reference in New Issue