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:
Oleg Kalnichevski 2010-04-30 21:00:08 +00:00
parent 160af55cb1
commit 283bba855f
49 changed files with 11669 additions and 123 deletions

View File

@ -1,75 +1,77 @@
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.
* Dropped dependency on Mime4j for HttpMime.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* Extended SSLSocketFactory with a mechanism to bypass the standard certificate
trust verification (primarily to simplify dealing with self-signed
certificates)
* Extended SSLSocketFactory with a mechanism to bypass the standard certificate
trust verification (primarily to simplify dealing with self-signed
certificates)
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-916] UsernamePasswordCredentials, NTUserPrincipal,
* [HTTPCLIENT-916] UsernamePasswordCredentials, NTUserPrincipal,
BasicClientCookie, BasicClientCookie2 and BasicCookieStore made Serializable.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-914] Upgraded Commons Codec dependency to version 1.4
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-903] Use ConcurrentHashMap instead of [Linked]HashMap for
thread-safety. Improve performance of AuthSchemeRegistry, CookieSpecRegistry
* [HTTPCLIENT-903] Use ConcurrentHashMap instead of [Linked]HashMap for
thread-safety. Improve performance of AuthSchemeRegistry, CookieSpecRegistry
and SchemeRegistry classes.
Contributed by Sebastian Bazley <sebb at apache.org>
* [HTTPCLIENT-902] HttpRequestRetryHandler not called on I/O exceptions
thrown when opening a new connection.
Contributed by Olivier Lamy <olamy at apache.org> and
Contributed by Olivier Lamy <olamy at apache.org> and
Oleg Kalnichevski <olegk at apache.org>
Release 4.1 ALPHA1
-------------------
HttpClient 4.1 ALPHA1 builds on the stable 4.0 release and adds several
HttpClient 4.1 ALPHA1 builds on the stable 4.0 release and adds several
functionality improvements and new features.
* Simplified configuration of connection managers.
* Persistence of authentication data between request executions within
* Persistence of authentication data between request executions within
the same execution context.
* Support for SPNEGO/Kerberos authentication scheme
* Support for transparent content encoding. Please note transparent content
* Support for transparent content encoding. Please note transparent content
encoding is not enabled per default in order to avoid conflicts with
already existing custom content encoding solutions.
* 5 to 10% performance increase due to elimination of unnecessary Log object
* 5 to 10% performance increase due to elimination of unnecessary Log object
lookups by short-lived components.
Please note all methods and classes added in this release and marked as
4.1 are API unstable and can change in the future 4.1 ALPHA releases.
Changelog
-------------------
* [HTTPCLIENT-889] 'expect: continue' handshake disabled per default.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-862] Extended client's redirect handling interface to allow
* [HTTPCLIENT-862] Extended client's redirect handling interface to allow
control of the content of the redirect.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-872] HttpClient can now persist authentication data between request
* [HTTPCLIENT-872] HttpClient can now persist authentication data between request
executions as long as they share the same execution context. It has also become
much easier to make HttpClient authenticate preemptively by pre-populating
much easier to make HttpClient authenticate preemptively by pre-populating
authentication data cache.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-883] SO_TIMEOUT is not reset on persistent (re-used) connections.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-832] Distinguish cookie format errors from violations of
restrictions imposed by a cookie specification. In the latter case
* [HTTPCLIENT-832] Distinguish cookie format errors from violations of
restrictions imposed by a cookie specification. In the latter case
CookieRestrictionViolationException will be thrown.
Contributed by Oleg Kalnichevski <olegk at apache.org>
@ -77,16 +79,16 @@ Changelog
Contributed by Matthew Stevenson <mavricknzwork at yahoo.com>
* Simplified configuration of connection managers. Total connection maximum
and maximum connection per route limits can be set using methods of
and maximum connection per route limits can be set using methods of
the class instead of HTTP parameters.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* Added parameters to define the order of preference for supported auth
* Added parameters to define the order of preference for supported auth
schemes for target host and proxy authentication.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-875] DefaultClientConnectionOperator#openConnection doesn't
update the connection state if the connection socket changed after
* [HTTPCLIENT-875] DefaultClientConnectionOperator#openConnection doesn't
update the connection state if the connection socket changed after
the call to SocketFactory#connectSocket().
Contributed by Oleg Kalnichevski <olegk at apache.org>
@ -96,8 +98,8 @@ Changelog
Release 4.0.1
-------------------
This is a bug fix release that addresses a number of issues discovered since
the previous stable release. None of the fixed bugs is considered critical.
This is a bug fix release that addresses a number of issues discovered since
the previous stable release. None of the fixed bugs is considered critical.
Most notably this release eliminates eliminates dependency on JCIP annotations.
This release is also expected to improve performance by 5 to 10% due to
@ -106,33 +108,33 @@ elimination of unnecessary Log object lookups by short-lived components.
Changelog
-------------------
* [HTTPCLIENT-895] Eliminated Log lookups in short lived objects impairing
* [HTTPCLIENT-895] Eliminated Log lookups in short lived objects impairing
performance.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-885] URLEncodedUtils now correctly parses form-url-encoded
* [HTTPCLIENT-885] URLEncodedUtils now correctly parses form-url-encoded
entities that specify a charset.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-884] UrlEncodedFormEntity now sets charset on the Content-Type
* [HTTPCLIENT-884] UrlEncodedFormEntity now sets charset on the Content-Type
header.
Contributed by Jared Jacobs <jmjacobs at cs.stanford.edu>
Contributed by Jared Jacobs <jmjacobs at cs.stanford.edu>
* [HTTPCLIENT-883] SO_TIMEOUT is not reset on persistent (re-used) connections.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-882] Auth state is now correctly updated if a successful NTLM
authentication results in a redirect. This is a minor bug as HttpClient
* [HTTPCLIENT-882] Auth state is now correctly updated if a successful NTLM
authentication results in a redirect. This is a minor bug as HttpClient
manages to recover from the problem automatically.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-881] Fixed race condition in AbstractClientConnAdapter that makes
it possible for an aborted connection to be returned to the pool.
Contributed by Tim Boemker <tboemker at elynx.com> and
Contributed by Tim Boemker <tboemker at elynx.com> and
Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-866] Removed dependency on jcip-annotations.jar.
Contributed by Oleg Kalnichevski <olegk at apache.org>
Contributed by Oleg Kalnichevski <olegk at apache.org>
and Sebastian Bazley <sebb at apache.org>
@ -148,42 +150,42 @@ a major code overhaul and breaking API compatibility.
Architectural changes
---------------------
* Redesign of the HttpClient internals addressing all known major
* Redesign of the HttpClient internals addressing all known major
architectural shortcomings of the 3.x codeline.
* Cleaner, more flexible and expressive API.
* More modular structure.
* Better performance and smaller memory footprint due to a more efficient HTTP
transport based on HttpCore.
* Better performance and smaller memory footprint due to a more efficient HTTP
transport based on HttpCore.
* Implementation of cross-cutting HTTP protocol aspects through protocol
* Implementation of cross-cutting HTTP protocol aspects through protocol
interceptors.
* Improved connection management, better handling of persistent connections,
support for stateful connections
* Pluggable redirect and authentication handlers.
* Pluggable redirect and authentication handlers.
* Improved support for sending requests via a proxy or a chain of proxies
* Improved support for sending requests via a proxy or a chain of proxies
* More flexible SSL context customization
* More flexible SSL context customization
* Reduced intermediate garbage in the process of generating HTTP requests
and parsing HTTP responses
* Reduced intermediate garbage in the process of generating HTTP requests
and parsing HTTP responses
Important notes
-------------------
* Future releases of HttpMime module may be binary incompatible with this
* Future releases of HttpMime module may be binary incompatible with this
release due to possible API changes in Apache Mime4J. Apache Mime4J is
still being actively developed and its API is considered unstable.
* HttpClient 4.0 is not fully binary compatible with 4.0 BETA1 release.
Some protected variables in connection management class have been
made final in order to help ensure their thread safety:
made final in order to help ensure their thread safety:
org.apache.http.conn.BasicEofSensorWatcher#attemptReuse
org.apache.http.conn.BasicEofSensorWatcher#managedConn
@ -195,20 +197,20 @@ Important notes
org.apache.http.impl.conn.SingleClientConnManager#schemeRegistry
org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager#connOperator
org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager#schemeRegistry
Bug fixes since 4.0 BETA2 release
Bug fixes since 4.0 BETA2 release
-------------------
* [HTTPCLIENT-861] URIUtils#resolve is now compatible with all examples given
* [HTTPCLIENT-861] URIUtils#resolve is now compatible with all examples given
in RFC 3986.
Contributed by Johannes Koch <johannes.koch at fit.fraunhofer.de>
* [HTTPCLIENT-860] HttpClient no longer converts redirects of PUT/POST to GET
* [HTTPCLIENT-860] HttpClient no longer converts redirects of PUT/POST to GET
for status codes 301, 302, 307, as required by the HTTP spec.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-859] CookieIdentityComparator now takes path attribute into
* [HTTPCLIENT-859] CookieIdentityComparator now takes path attribute into
consideration when comparing cookies.
Contributed by Oleg Kalnichevski <olegk at apache.org>
@ -219,20 +221,20 @@ Bug fixes since 4.0 BETA2 release
a different host.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-841] Removed automatic connection release using garbage collection
* [HTTPCLIENT-841] Removed automatic connection release using garbage collection
due to a memory leak.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-853] Fixed bug causing invalid cookie origin port to be selected
when the target is accessed on the default port and the connection is
* [HTTPCLIENT-853] Fixed bug causing invalid cookie origin port to be selected
when the target is accessed on the default port and the connection is
established via a proxy.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-852] Fixed bug causing automatically retried redirects fail with
CircularRedirectException.
* [HTTPCLIENT-852] Fixed bug causing automatically retried redirects fail with
CircularRedirectException.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* Fixed problem with the default HTTP response parser failing to handle garbage
* Fixed problem with the default HTTP response parser failing to handle garbage
preceding a valid HTTP response.
Contributed by Oleg Kalnichevski <olegk at apache.org>
@ -240,12 +242,12 @@ Bug fixes since 4.0 BETA2 release
request failed.
Contributed by Sam Berlin <sberlin at apache.org>
* [HTTPCLIENT-837] Fixed problem with the wire log skipping zero byte values
* [HTTPCLIENT-837] Fixed problem with the wire log skipping zero byte values
if read one byte at a time.
Contributed by Kirill Safonov <ksafonov at swiftteams.com>
* [HTTPCLIENT-823] 'http.conn-manager.max-total' parameter can be adjusted
dynamically. However, the size of existing connection pools per route,
* [HTTPCLIENT-823] 'http.conn-manager.max-total' parameter can be adjusted
dynamically. However, the size of existing connection pools per route,
once allocated, will not be adjusted.
Contributed by Oleg Kalnichevski <olegk at apache.org>
@ -260,20 +262,20 @@ Bug fixes since 4.0 BETA2 release
Release 4.0 beta 2
-------------------
BETA2 is a maintenance release, which addresses a number of issues
discovered since the previous release.
BETA2 is a maintenance release, which addresses a number of issues
discovered since the previous release.
The only significant new feature is an addition of an OSGi compliant
The only significant new feature is an addition of an OSGi compliant
bundle combining HttpClient and HttpMime jars.
All upstream projects are strongly encouraged to upgrade.
* Fixed NPE in DefaultRequestDirector thrown when retrying a failed
request over a proxied connection.
* Fixed NPE in DefaultRequestDirector thrown when retrying a failed
request over a proxied connection.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-803] Fixed bug in SSL host verifier implementations
causing the SSL certificate to be rejected as invalid if the connection
* [HTTPCLIENT-803] Fixed bug in SSL host verifier implementations
causing the SSL certificate to be rejected as invalid if the connection
is established using an IP address.
Contributed by Oleg Kalnichevski <olegk at apache.org>
@ -282,32 +284,32 @@ All upstream projects are strongly encouraged to upgrade.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* DigestScheme can use an arbitrary digest algorithm requested by the
target server (such as SHA) as long as this algorithm is supported by
the Java runtime.
target server (such as SHA) as long as this algorithm is supported by
the Java runtime.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* Fixed parsing and validation of RFC2109 compliant Set-Cookie headers
by the Best-Match cookie spec.
* Fixed parsing and validation of RFC2109 compliant Set-Cookie headers
by the Best-Match cookie spec.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* Fixed bug that can cause a managed connection to be returned from the
pool in an inconsistent state.
pool in an inconsistent state.
Contributed by Oleg Kalnichevski <olegk at apache.org>
4.0 Beta 1
-------------------
BETA1 release brings yet another round of API enhancements and
BETA1 release brings yet another round of API enhancements and
improvements in the area of connection management. Among the most notable
ones is the capability to handle stateful connections such as persistent
ones is the capability to handle stateful connections such as persistent
NTLM connections and private key authenticated SSL connections.
This is the first API stable release of HttpClient 4.0. All further
This is the first API stable release of HttpClient 4.0. All further
releases in the 4.0 code line will maintain API compatibility with this
release.
There has been a number of important bug fixes since ALPHA4. All upstream
There has been a number of important bug fixes since ALPHA4. All upstream
projects are encouraged to upgrade to the latest release.
Please note HttpClient currently provides only limited support for NTLM
@ -318,7 +320,7 @@ authentication. For details please see NTLM_SUPPORT.txt.
Changelog:
-------------------
* [HTTPCLIENT-790] Protocol interceptors are now correctly invoked when
* [HTTPCLIENT-790] Protocol interceptors are now correctly invoked when
executing CONNECT methods.
Contributed by Oleg Kalnichevski <olegk at apache.org>
@ -356,8 +358,8 @@ Changelog:
* Resolved a long standing problem with HttpClient not taking into account
the user context when pooling / re-using connections. HttpClient now
correctly handles stateful / user specific connections such as persistent
NTLM connections and SSL connections with client side authentication.
correctly handles stateful / user specific connections such as persistent
NTLM connections and SSL connections with client side authentication.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-773] Improved handling of the 'expires' attribute by the
@ -376,14 +378,14 @@ Changelog:
Release 4.0 Alpha 4
-------------------
ALPHA4 marks the completion of the overhaul of the connection management
code in HttpClient. All known shortcomings of the old HttpClient 3.x
ALPHA4 marks the completion of the overhaul of the connection management
code in HttpClient. All known shortcomings of the old HttpClient 3.x
connection management API have been addressed.
NTLM authentication remains the only missing major feature in the new
NTLM authentication remains the only missing major feature in the new
codeline that prevents us from moving awards the API freeze.
There has been a number of important bug fixes since ALPHA3. All upstream
There has been a number of important bug fixes since ALPHA3. All upstream
projects are encouraged to upgrade to the latest release.
-------------------
@ -398,19 +400,19 @@ HttpClient 3.x features that have NOT yet been ported:
Changelog:
-------------------
* [HTTPCLIENT-765] String.toLowerCase() / toUpperCase() should specify
* [HTTPCLIENT-765] String.toLowerCase() / toUpperCase() should specify
Locale.ENGLISH
Contributed by Sebastian Bazley <sebb at apache.org>
* [HTTPCLIENT-769] Do not pool connection marked non-reusable.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-763] Fixed problem with AbstractClientConnAdapter#abortConnection()
not releasing the connection if called from the main execution thread while
* [HTTPCLIENT-763] Fixed problem with AbstractClientConnAdapter#abortConnection()
not releasing the connection if called from the main execution thread while
there is no blocking I/O operation.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-652] Added optional state attribute to managed client connections.
* [HTTPCLIENT-652] Added optional state attribute to managed client connections.
This enables connection managers to correctly handle stateful connections.
Contributed by Oleg Kalnichevski <olegk at apache.org>
@ -420,15 +422,15 @@ Changelog:
* [HTTPCLIENT-753] Class Scheme and related classes moved to a separate package
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-757] Improved request wrapping in the DefaultClientRequestDirector.
This also fixed the problem with the default proxy set at the client level
having no effect.
* [HTTPCLIENT-757] Improved request wrapping in the DefaultClientRequestDirector.
This also fixed the problem with the default proxy set at the client level
having no effect.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-734] Request abort will unblock the thread waiting for a connection
Contributed by Sam Berlin <sberlin at gmail.com>
* [HTTPCLIENT-759] Ensure release of connections back to the connection manager
* [HTTPCLIENT-759] Ensure release of connections back to the connection manager
on exceptions.
Contributed by Sam Berlin <sberlin at gmail.com>
@ -448,18 +450,18 @@ Changelog:
Release 4.0 Alpha 3
-------------------
ALPHA3 release brings another round of API refinements and improvements in
functionality. As of this release HttpClient requires Java 5 compatible
ALPHA3 release brings another round of API refinements and improvements in
functionality. As of this release HttpClient requires Java 5 compatible
runtime environment and takes full advantage of generics and new concurrency
primitives.
primitives.
This release also introduces new default cookie policy that selects a cookie
specification depending on the format of cookies sent by the target host.
It is no longer necessary to know beforehand what kind of HTTP cookie support
the target host provides. HttpClient is now able to pick up either a lenient
This release also introduces new default cookie policy that selects a cookie
specification depending on the format of cookies sent by the target host.
It is no longer necessary to know beforehand what kind of HTTP cookie support
the target host provides. HttpClient is now able to pick up either a lenient
or a strict cookie policy depending on the compliance level of the target host.
Another notable improvement is a completely reworked support for multipart
Another notable improvement is a completely reworked support for multipart
entities based on Apache mime4j library.
-------------------
@ -499,10 +501,10 @@ Changelog:
Contributed by Roland Weber <rolandw at apache.org>
* [HTTPCLIENT-730] Fixed rewriting of URIs containing escaped characters
Contributed by Sam Berlin <sberlin at gmail.com> and
Contributed by Sam Berlin <sberlin at gmail.com> and
Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-667] Added 'Meta' cookie policy that selects a cookie
* [HTTPCLIENT-667] Added 'Meta' cookie policy that selects a cookie
specification depending on the format of the cookie(s).
Contributed by Oleg Kalnichevski <olegk at apache.org>
@ -530,7 +532,7 @@ Changelog:
* [HTTPCLIENT-705] Fixed incorrect handling of URIs with null path component.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-688] HttpOptions#getAllowedMethods can now handle multiple
* [HTTPCLIENT-688] HttpOptions#getAllowedMethods can now handle multiple
Allow headers.
Contributed by Andrea Selva <selva.andre at gmail.com>
@ -539,10 +541,10 @@ Changelog:
Release 4.0 Alpha 2
-------------------
ALPHA2 release is another milestone in the redesign of HttpClient. It includes
a number of improvements since ALPHA1, among which are improved connection
ALPHA2 release is another milestone in the redesign of HttpClient. It includes
a number of improvements since ALPHA1, among which are improved connection
pooling, support for proxy chains, redesigned HTTP state and authentication
credentials management API, improved RFC 2965 cookie specification.
credentials management API, improved RFC 2965 cookie specification.
-------------------
@ -550,14 +552,14 @@ HttpClient 3.x features that have NOT yet been ported
-------------------
* NTLM authentication scheme
* Support for multipart MIME coded entities
* Support for multipart MIME coded entities
-------------------
Changelog
-------------------
* [HTTPCLIENT-698] Resolve non-absolute redirect URIs relative to
* [HTTPCLIENT-698] Resolve non-absolute redirect URIs relative to
the request URI
Contributed by Johannes Koch <johannes.koch at fit.fraunhofer.de>
@ -571,7 +573,7 @@ Changelog
* [HTTPCLIENT-689] stackable parameters in AbstractHttpClient
Contributed by Roland Weber <rolandw at apache.org>
* [HTTPCLIENT-477] Use distinct instances of the authentication handler
* [HTTPCLIENT-477] Use distinct instances of the authentication handler
interface for authentication with target and proxy hosts
Contributed by Oleg Kalnichevski <olegk at apache.org>
@ -600,7 +602,7 @@ Changelog
* [HTTPCLIENT-674] use org.apache.http.util.VersionInfo instead of a local one
Contributed by Roland Weber <rolandw at apache.org>
* [HTTPCLIENT-666] Replaced HttpState with CredentialsProvier and CookieStore interfaces
* [HTTPCLIENT-666] Replaced HttpState with CredentialsProvier and CookieStore interfaces
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCORE-100] revised HttpContext hierarchy
@ -632,29 +634,29 @@ Architectural changes
* Redesign of the HttpClient internals addressing all known
major architectural shortcomings of the 3.x codeline
* Cleaner, more flexible and expressive API
* Cleaner, more flexible and expressive API
* Better performance and smaller memory footprint due to a more
efficient HTTP transport based on HttpCore. HttpClient 4.0 is
* Better performance and smaller memory footprint due to a more
efficient HTTP transport based on HttpCore. HttpClient 4.0 is
expected to be 10% to 25% faster than HttpClient 3.x codeline
* More modular structure
* More modular structure
* Pluggable redirect and authentication handlers
* Pluggable redirect and authentication handlers
* Support for protocol incerceptors
* Improved connection management
* Improved connection management
* Improved support for sending requests via a proxy or a chain of
proxies
* Improved support for sending requests via a proxy or a chain of
proxies
* Improved handling redirects of entity enclosing requests
* More flexible SSL context customization
* More flexible SSL context customization
* Reduced intermediate garbage in the process of
generating HTTP requests and parsing HTTP responses
generating HTTP requests and parsing HTTP responses
-------------------
@ -664,7 +666,7 @@ HttpClient 3.x features that have NOT yet been ported
* RFC2965 cookie policy (Cookie2)
* Support for multipart MIME coded entities
* Support for multipart MIME coded entities
-------------------
@ -691,11 +693,11 @@ of the source code repository would have been too burdensome.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-63] Support for pluggable redirect and authentication handlers
Long standing architectural problem. Issue opened on 15/Jul/2002.
Long standing architectural problem. Issue opened on 15/Jul/2002.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-245] Fixed redirect handling. HttpClient can now automatically
handle redirects of entity enclosing requests.
handle redirects of entity enclosing requests.
Long standing architectural problem. Issue opened on 14/Jul/2003.
Contributed by Oleg Kalnichevski <olegk at apache.org>

218
httpclient-cache/pom.xml Normal file
View File

@ -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>

View File

@ -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;
}

View 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;
}

View 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);
}
}

View 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;
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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) {
}
}
}
}

View 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";
}

View 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.
}
}

View 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;
}
}

View 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
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}
}

View 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;
}
}

View 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
}
}

View 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));
}
}

View 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(")");
}
}

View 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;
}
}

View 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);
}
}

View 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());
}
}

View 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();
}
}

View 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);
}
}

View 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));
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

File diff suppressed because it is too large Load Diff

View 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);
}
}

View 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());
}
}

View File

@ -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;
}
}

View 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);
}
}

File diff suppressed because it is too large Load Diff

View 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());
}
}

View 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];
}
}

View 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();
}
}

View 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);
}
}

View File

@ -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>