[SCM] httpcomponents-client: HTTP/1.1 compliant HTTP agent Java implementation branch, upstream, updated. upstream/4.1.1-2-gdbe3299
Damien Raude-Morvan
drazzib at debian.org
Wed Dec 19 23:52:56 UTC 2012
The following commit has been merged in the upstream branch:
commit 0f3aa2b46e2fac9bb5ed5a71bcdd1134769bc38e
Author: Damien Raude-Morvan <drazzib at debian.org>
Date: Sun Aug 19 15:05:13 2012 +0200
Imported Upstream version 4.2.1
diff --git a/NOTICE.txt b/NOTICE.txt
index 3a98f45..ea1911c 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -1,8 +1,6 @@
Apache HttpComponents Client
-Copyright 1999-2011 The Apache Software Foundation
+Copyright 1999-2012 The Apache Software Foundation
This product includes software developed by
The Apache Software Foundation (http://www.apache.org/).
-This project contains annotations derived from JCIP-ANNOTATIONS
-Copyright (c) 2005 Brian Goetz and Tim Peierls. See http://www.jcip.net
\ No newline at end of file
diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 31535ab..9113002 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -1,7 +1,297 @@
+Release 4.2.1
+-------------------
+
+HttpClient 4.2.1 (GA) is a bug fix release that addresses a number of issues reported since
+release 4.2.
+
+Users of HttpClient 4.2 are advised to upgrade.
+
+Changelog
+-------------------
+
+* [HTTPCLIENT-1209] Redirect URIs are now normalized.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1202] ResponseCachingPolicy should honor explicit cache-control
+ directives for other status codes
+ Contributed by Jon Moore <jonm at apache.org>
+
+* [HTTPCLIENT-1199] DecompressingHttpClient strips content from entity enclosing requests
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1198] HttpHost is not set in HttpContext in CachingHttpClient.
+ Contributed by Jon Moore <jonm at apache.org>
+
+* [HTTPCLIENT-1200] DecompressingHttpClient fails to generate correct HttpHost context attribute.
+ Contributed by Guillaume Castagnino <casta+jira at xwing.info>
+
+* [HTTPCLIENT-1192] URIBuilder encodes query parameters twice.
+ Contributed by Oleg Kalnichevski <olegk at apache.org> and Sebastian Bazley <sebb at apache.org>.
+
+* [HTTPCLIENT-1196] Fixed NPE in UrlEncodedFormEntity constructor thrown if charset is null.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1193] Fixed regression in the route tracking logic of the default connection manager
+ causing cross-site redirect failures.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+Release 4.2
+-------------------
+
+This is the first stable (GA) release of HttpClient 4.2. The most notable enhancements included
+in this release are:
+
+* New facade API for HttpClient based on the concept of a fluent interface. The fluent API exposes
+ only the most fundamental functions of HttpClient and is intended for relatively simple use cases
+ that do not require the full flexibility of HttpClient. However, the fluent API almost fully
+ relieves the users from having to deal with connection management and resource deallocation.
+
+* Redesigned and rewritten connection management code.
+
+* Enhanced HTTP authentication API that enables HttpClient to handle more complex authentication
+ scenarios. HttpClient 4.2 is now capable of making use of multiple authentication challenges
+ and retry authentication with a fall-back scheme in case the primary one fails. This can be
+ important for compatibility with Microsoft products that are often configured to use
+ SPNEGO/Kerberos as the preferred authentication scheme.
+
+
+Changelog
+-------------------
+
+* [HTTPCLIENT-1187] If a revalidation response is deemed too old CachingHttpClient fails to
+ consume its content resulting in a connection leak.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1186] State of newly created connections in the connection pool is not always
+ correctly updated potentially allowing those connections to be leased to users with a different
+ security context.
+ Contributed by Ralf Poehlmann <rpn at methodpark.de>
+
+* [HTTPCLIENT-1179] Upgraded Commons Codec dependency to version 1.6
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1177] always remove fragments from request URIs
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+Incompatible changes
+--------------------
+[Compared to release version 4.1.3]
+
+The following fields have been deprecated for some time now and have been deleted:
+
+org.apache.http.client.params.ClientPNames#CONNECTION_MANAGER_FACTORY
+org.apache.http.impl.cookie.BrowserCompatSpec#DATE_PATTERNS
+
+The following methods have been deprecated for some time now and have been deleted:
+
+org.apache.http.client.params.ClientParamBean#setConnectionManagerFactory(org.apache.http.conn.ClientConnectionManagerFactory)
+org.apache.http.client.protocol.ClientContextConfigurer#setAuthSchemePref(java.util.List)
+org.apache.http.entity.mime.content.FileBody#writeTo(java.io.OutputStream, int)
+org.apache.http.entity.mime.content.InputStreamBody#writeTo(java.io.OutputStream, int)
+org.apache.http.entity.mime.content.StringBody#writeTo(java.io.OutputStream, int)
+
+The following classes have been deprecated for some while now and have been deleted:
+
+org.apache.http.impl.conn.tsccm.RefQueueHandler
+org.apache.http.impl.conn.tsccm.AbstractConnPool no longer implements interface org.apache.http.impl.conn.tsccm.RefQueueHandler
+org.apache.http.impl.conn.tsccm.ConnPoolByRoute no longer implements interface org.apache.http.impl.conn.tsccm.RefQueueHandler
+org.apache.http.impl.conn.tsccm.RefQueueWorker
+
+
+
+Release 4.2 BETA1
+-------------------
+
+This is the first BETA release of HttpClient 4.2. This release completes development of several
+notable enhancements in HttpClient:
+
+* New facade API for HttpClient based on the concept of a fluent interface. The fluent API exposes
+ only the most fundamental functions of HttpClient and is intended for relatively simple use cases
+ that do not require the full flexibility of HttpClient. However, the fluent API almost fully
+ relieves the users from having to deal with connection management and resource deallocation.
+
+* Redesigned and rewritten connection management code. As of release 4.2 HttpClient will be using
+ pooling connection manager per default.
+
+* Enhanced HTTP authentication API that enables HttpClient to handle more complex authentication
+ scenarios. HttpClient 4.2 is now capable of making use of multiple authentication challenges
+ and retry authentication with a fall-back scheme in case the primary one fails. This can be
+ important for compatibility with Microsoft products that are often configured to use
+ SPNEGO/Kerberos as the preferred authentication scheme.
+
+
+Changelog
+-------------------
+
+* [HTTPCLIENT-1164] Compressed entities are not being cached properly.
+ Contributed by Jon Moore <jonm at apache dot org>.
+
+* [HTTPCLIENT-1154] MemcachedHttpCacheStorage should allow client to
+ specify custom prefix string for keys.
+ Contributed by Jon Moore <jonm at apache dot org>.
+
+* [HTTPCLIENT-1153] MemcachedHttpCacheStorage uses URL as cache key;
+ shouldn't due to fixed maximum-length memcached keys.
+ Contributed by Jon Moore <jonm at apache dot org>.
+
+* [HTTPCLIENT-1157] MemcachedHttpCacheStroage should throw IOExceptions
+ instead of RuntimeExceptions.
+ Contributed by James Miller <jamesmiller01 at gmail dot com>.
+
+* [HTTPCLIENT-1152] MemcachedHttpCacheStorage should verify class of
+ returned object before casting.
+ Contributed by Rajika Kumarasiri <rajika at wso2 dot com>.
+
+* [HTTPCLIENT-1155] CachingHttpClient fails to ensure that the response content gets fully consumed
+ when using a ResponseHandler, which can potentially lead to connection leaks.
+ Contributed by James Miller <jamesmiller01 at gmail dot com>
+
+* [HTTPCLIENT-1147] When HttpClient-Cache cannot open cache file, should act like miss.
+ Contributed by Joe Campbell <joseph.r.campbell at gmail.com>
+
+* [HTTPCLIENT-1137] Values for the Via header are cached and reused by httpclient-cache.
+ Contributed by Alin Vasile <alinachegalati at yahoo dot com>
+
+* [HTTPCLIENT-1142] Infinite loop on NTLM authentication failure.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1143] CachingHttpClient leaks connections with stale-if-error.
+ Contributed by James Miller <jamesmiller01 at gmail dot com>
+
+Release 4.2 ALPHA1
+-------------------
+
+This is the first ALPHA release of HttpClient 4.2. The 4.2 branch enhances HttpClient in several
+key areas and includes several notable features and improvements:
+
+* New facade API for HttpClient based on the concept of a fluent interface. The fluent API exposes
+ only the most fundamental functions of HttpClient and is intended for relatively simple use cases
+ that do not require the full flexibility of HttpClient. However, the fluent API almost fully
+ relieves the users from having to deal with connection management and resource deallocation.
+
+* Redesigned and rewritten connection management code. As of release 4.2 HttpClient will be using
+ pooling connection manager per default.
+
+* Enhanced HTTP authentication API that enables HttpClient to handle more complex authentication
+ scenarios. HttpClient 4.2 is now capable of making use of multiple authentication challenges
+ and retry authentication with a fall-back scheme in case the primary one fails. This can be
+ important for compatibility with Microsoft products that are often configured to use
+ SPNEGO/Kerberos as the preferred authentication scheme.
+
+Please note that new features included in this release are still considered experimental and
+their API may change in the future ALPHA releases.
+
+Changelog
+-------------------
+
+* [HTTPCLIENT-1128] SystemDefaultHttpClient (HttpClient implementation initialized using system
+ properties).
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1135] RandomAccessFile mode 'w' used by HttpClientCache is not valid.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1131] HttpClient to authenticate preemptively using BASIC scheme if a userinfo
+ attribute is specified in the request URI.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1134] make BasicResponseHandler consume response content in case of an unsuccessful
+ result (status code >= 300).
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1132] ProxyClient implementation.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1127] fixed dead-lock between SingleClientConnManager and AbstractPooledConnAdapter.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1107] Auth framework redesign.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1116] ResponseCachingPolicy uses integers for sizes
+ Contributed by Greg Bowyer <gbowyer at fastmail.co.uk >
+
+* [HTTPCLIENT-1123] Support for pluggable DNS resolvers.
+ Contributed by Alin Vasile <alinachegalati at yahoo dot com>
+
+* [HTTPCLIENT-1120] DefaultHttpRequestRetryHandler#retryRequest should not retry aborted requests.
+ Contributed by Alin Vasile <alinachegalati at yahoo dot com>
+
+* Support for auth-int qop (quality of protection) option in Digest auth scheme.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1076] Fluent facade API (Google summer of code 2011 project).
+ Contributed by Xu Lilu <cookieme at gmail.com>
+
+* UriBuilder implementation.
+ Contributed by Xu Lilu <cookieme at gmail.com>
+
+* Redesign of connection management classes based on new pooling components from HttpCore.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1111] Added #prepareSocket method to SSLSocketFactory.
+ Contributed by Pasi Eronen <pe at iki.fi>
+
+* Added #reset() and #releaseConnection() methods to HttpRequestBase.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1105] AutoRetryHttpClient: built-in way to do auto-retry for certain status codes.
+ Contributed by Dan Checkoway <dcheckoway at gmail.com>
+
+* [HTTPCLIENT-1094] Digest auth scheme refactoring.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* Lax implementation of RedirectStrategy.
+ Contributed by Bartosz Firyn <songo.bercik at interia.pl>
+
+* [HTTPCLIENT-1044] HttpRequestRetryHandler implementation compliant with the definition of
+ idempotent methods given in the RFC 2616.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+
+Release 4.1.2
+-------------------
+
+The HttpClient 4.1.2 is a bug fix release that addresses a number of non-critical issues reported
+since release 4.1.1.
+
+* [HTTPCLIENT-1100] Missing Content-Length header makes cached entry invalid
+ Contributed by Bart Robeyns <bart dot robeyns at gmail dot com>
+
+* [HTTPCLIENT-1098] Avoid expensive reverse DNS lookup on connect timeout exception.
+ Contributed by Thomas Boettcher <tboett at gmail.com>
+
+* [HTTPCLIENT-1097] BrowserCompatHostnameVerifier and StrictHostnameVerifier should handle
+ wildcards in SSL certificates better.
+ Contributed by Sebastian Bazley <sebb at apache.org>
+
+* [HTTPCLIENT-1092] If ClientPNames.VIRTUAL_HOST does not provide the port, derive it from the
+ current request.
+ Contributed by Sebastian Bazley <sebb at apache.org>
+
+* [HTTPCLIENT-1087] NTLM proxy authentication fails on retry if the underlying connection is closed
+ as a result of a target authentication failure.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1079] Fixed Kerberos cross-realm support
+ Contributed by Michael Osipov <1983-01-06 at gmx.net>
+
+* [HTTPCLIENT-1078] Decompressing entities (DeflateDecompressingEntity, GzipDecompressingEntity)
+ do not close content stream in #writeTo() method.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+* [HTTPCLIENT-1075] Decompressing entities (DeflateDecompressingEntity, GzipDecompressingEntity)
+ do not correctly handle content streaming.
+ Contributed by James Abley <james.abley at gmail.com>
+
+* [HTTPCLIENT-1051] Avoid reverse DNS lookups when opening SSL connections by IP address.
+ Contributed by Oleg Kalnichevski <olegk at apache.org>
+
+
Release 4.1.1
-------------------
-The HttpClient 4.1.1 is a bug fix release that addresses a number of issues reported since
+HttpClient v4.1.1 is a bug fix release that addresses a number of issues reported since
release 4.1, including one critical security issue (HTTPCLIENT-1061). All users of HttpClient 4.0.x
and 4.1 are strongly encouraged to upgrade.
diff --git a/fluent-hc/pom.xml b/fluent-hc/pom.xml
new file mode 100644
index 0000000..d1983e9
--- /dev/null
+++ b/fluent-hc/pom.xml
@@ -0,0 +1,150 @@
+<?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.2.1</version>
+ </parent>
+ <artifactId>fluent-hc</artifactId>
+ <name>Fluent HttpClient</name>
+ <inceptionYear>2011</inceptionYear>
+ <description>
+ HttpComponents Client fluent API
+ </description>
+ <url>http://hc.apache.org/httpcomponents-client</url>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>${project.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ <classifier>tests</classifier>
+ </dependency>
+ <!-- direct dependency on logging -->
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <!-- TODO: does not appear to be needed; remove? -->
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <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>true</filtering>
+ <includes>
+ <include>**/*.properties</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>
+ </plugins>
+ </build>
+
+ <reporting>
+ <plugins>
+
+ <plugin>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${hc.javadoc.version}</version>
+ <configuration>
+ <!-- reduce console output. Can override with -Dquiet=false -->
+ <quiet>true</quiet>
+ <source>1.5</source>
+ <links>
+ <link>http://download.oracle.com/javase/1.5.0/docs/api/</link>
+ <link>http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/</link>
+ <link>http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/</link>
+ </links>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${hc.jxr.version}</version>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-surefire-report-plugin</artifactId>
+ <version>${hc.surefire-report.version}</version>
+ </plugin>
+
+ </plugins>
+ </reporting>
+
+</project>
diff --git a/fluent-hc/src/examples/org/apache/http/client/fluent/FluentAsync.java b/fluent-hc/src/examples/org/apache/http/client/fluent/FluentAsync.java
new file mode 100644
index 0000000..41985b2
--- /dev/null
+++ b/fluent-hc/src/examples/org/apache/http/client/fluent/FluentAsync.java
@@ -0,0 +1,88 @@
+/*
+ * ====================================================================
+ *
+ * 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.fluent;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.apache.http.concurrent.FutureCallback;
+
+/**
+ * This example demonstrates how the he HttpClient fluent API can be used to execute multiple
+ * requests asynchronously using background threads.
+ */
+public class FluentAsync {
+
+ public static void main(String[] args)throws Exception {
+ // Use pool of two threads
+ ExecutorService threadpool = Executors.newFixedThreadPool(2);
+ Async async = Async.newInstance().use(threadpool);
+
+ Request[] requests = new Request[] {
+ Request.Get("http://www.google.com/"),
+ Request.Get("http://www.yahoo.com/"),
+ Request.Get("http://www.apache.com/"),
+ Request.Get("http://www.apple.com/")
+ };
+
+
+ Queue<Future<Content>> queue = new LinkedList<Future<Content>>();
+ // Execute requests asynchronously
+ for (final Request request: requests) {
+ Future<Content> future = async.execute(request, new FutureCallback<Content>() {
+
+ public void failed(final Exception ex) {
+ System.out.println(ex.getMessage() + ": " + request);
+ }
+
+ public void completed(final Content content) {
+ System.out.println("Request completed: " + request);
+ }
+
+ public void cancelled() {
+ }
+
+ });
+ queue.add(future);
+ }
+
+ while(!queue.isEmpty()) {
+ Future<Content> future = queue.remove();
+ try {
+ future.get();
+ } catch (ExecutionException ex) {
+ }
+ }
+ System.out.println("Done");
+ threadpool.shutdown();
+ }
+
+}
diff --git a/fluent-hc/src/examples/org/apache/http/client/fluent/FluentExecutor.java b/fluent-hc/src/examples/org/apache/http/client/fluent/FluentExecutor.java
new file mode 100644
index 0000000..30a4b44
--- /dev/null
+++ b/fluent-hc/src/examples/org/apache/http/client/fluent/FluentExecutor.java
@@ -0,0 +1,72 @@
+/*
+ * ====================================================================
+ *
+ * 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.fluent;
+
+import java.io.File;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpVersion;
+import org.apache.http.entity.ContentType;
+
+/**
+ * This example demonstrates how the he HttpClient fluent API can be used to execute multiple
+ * requests within the same security context. The Executor class maintains a common context shared
+ * by all requests executed with it. The Executor is thread-safe and can be used to execute
+ * requests concurrently from multiple threads of execution.
+ */
+public class FluentExecutor {
+
+ public static void main(String[] args)throws Exception {
+ Executor executor = Executor.newInstance()
+ .auth(new HttpHost("somehost"), "username", "password")
+ .auth(new HttpHost("myproxy", 8080), "username", "password")
+ .authPreemptive(new HttpHost("myproxy", 8080));
+
+ // Execute a GET with timeout settings and return response content as String.
+ executor.execute(Request.Get("http://somehost/")
+ .connectTimeout(1000)
+ .socketTimeout(1000)
+ ).returnContent().asString();
+
+ // Execute a POST with the 'expect-continue' handshake, using HTTP/1.1,
+ // containing a request body as String and return response content as byte array.
+ executor.execute(Request.Post("http://somehost/do-stuff")
+ .useExpectContinue()
+ .version(HttpVersion.HTTP_1_1)
+ .bodyString("Important stuff", ContentType.DEFAULT_TEXT)
+ ).returnContent().asBytes();
+
+ // Execute a POST with a custom header through the proxy containing a request body
+ // as an HTML form and save the result to the file
+ executor.execute(Request.Post("http://somehost/some-form")
+ .addHeader("X-Custom-header", "stuff")
+ .viaProxy(new HttpHost("myproxy", 8080))
+ .bodyForm(Form.form().add("username", "vip").add("password", "secret").build())
+ ).saveContent(new File("result.dump"));
+ }
+
+}
diff --git a/fluent-hc/src/examples/org/apache/http/client/fluent/FluentQuickStart.java b/fluent-hc/src/examples/org/apache/http/client/fluent/FluentQuickStart.java
new file mode 100644
index 0000000..74c7394
--- /dev/null
+++ b/fluent-hc/src/examples/org/apache/http/client/fluent/FluentQuickStart.java
@@ -0,0 +1,42 @@
+/*
+ * ====================================================================
+ *
+ * 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.fluent;
+
+public class FluentQuickStart {
+
+ public static void main(String[] args) throws Exception {
+ // The fluent API relieves the user from having to deal with manual
+ // deallocation of system resources at the cost of having to buffer
+ // response content in memory in some cases.
+
+ Request.Get("http://targethost/homepage")
+ .execute().returnContent();
+ Request.Post("http://targethost/login")
+ .bodyForm(Form.form().add("username", "vip").add("password", "secret").build())
+ .execute().returnContent();
+ }
+}
diff --git a/fluent-hc/src/examples/org/apache/http/client/fluent/FluentRequests.java b/fluent-hc/src/examples/org/apache/http/client/fluent/FluentRequests.java
new file mode 100644
index 0000000..70b9ca9
--- /dev/null
+++ b/fluent-hc/src/examples/org/apache/http/client/fluent/FluentRequests.java
@@ -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.fluent;
+
+import java.io.File;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpVersion;
+import org.apache.http.entity.ContentType;
+
+/**
+ * This example demonstrates basics of request execution with the HttpClient fluent API.
+ */
+public class FluentRequests {
+
+ public static void main(String[] args)throws Exception {
+ // Execute a GET with timeout settings and return response content as String.
+ Request.Get("http://somehost/")
+ .connectTimeout(1000)
+ .socketTimeout(1000)
+ .execute().returnContent().asString();
+
+ // Execute a POST with the 'expect-continue' handshake, using HTTP/1.1,
+ // containing a request body as String and return response content as byte array.
+ Request.Post("http://somehost/do-stuff")
+ .useExpectContinue()
+ .version(HttpVersion.HTTP_1_1)
+ .bodyString("Important stuff", ContentType.DEFAULT_TEXT)
+ .execute().returnContent().asBytes();
+
+ // Execute a POST with a custom header through the proxy containing a request body
+ // as an HTML form and save the result to the file
+ Request.Post("http://somehost/some-form")
+ .addHeader("X-Custom-header", "stuff")
+ .viaProxy(new HttpHost("myproxy", 8080))
+ .bodyForm(Form.form().add("username", "vip").add("password", "secret").build())
+ .execute().saveContent(new File("result.dump"));
+ }
+
+}
diff --git a/fluent-hc/src/examples/org/apache/http/client/fluent/FluentResponseHandling.java b/fluent-hc/src/examples/org/apache/http/client/fluent/FluentResponseHandling.java
new file mode 100644
index 0000000..6e6071f
--- /dev/null
+++ b/fluent-hc/src/examples/org/apache/http/client/fluent/FluentResponseHandling.java
@@ -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.fluent;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpResponseException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.entity.ContentType;
+import org.apache.http.protocol.HTTP;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+/**
+ * This example demonstrates how the HttpClient fluent API can be used to handle HTTP responses
+ * without buffering content body in memory.
+ */
+public class FluentResponseHandling {
+
+ public static void main(String[] args)throws Exception {
+ Document result = Request.Get("http://somehost/content")
+ .execute().handleResponse(new ResponseHandler<Document>() {
+
+ public Document handleResponse(final HttpResponse response) throws IOException {
+ StatusLine statusLine = response.getStatusLine();
+ HttpEntity entity = response.getEntity();
+ if (statusLine.getStatusCode() >= 300) {
+ throw new HttpResponseException(
+ statusLine.getStatusCode(),
+ statusLine.getReasonPhrase());
+ }
+ if (entity == null) {
+ throw new ClientProtocolException("Response contains no content");
+ }
+ DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
+ try {
+ DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
+ ContentType contentType = ContentType.getOrDefault(entity);
+ if (!contentType.equals(ContentType.APPLICATION_XML)) {
+ throw new ClientProtocolException("Unexpected content type:" + contentType);
+ }
+ Charset charset = contentType.getCharset();
+ if (charset == null) {
+ charset = HTTP.DEF_CONTENT_CHARSET;
+ }
+ return docBuilder.parse(entity.getContent(), charset.name());
+ } catch (ParserConfigurationException ex) {
+ throw new IllegalStateException(ex);
+ } catch (SAXException ex) {
+ throw new ClientProtocolException("Malformed XML document", ex);
+ }
+ }
+
+ });
+ // Do something useful with the result
+ System.out.println(result);
+ }
+
+}
diff --git a/fluent-hc/src/main/java/org/apache/http/client/fluent/Async.java b/fluent-hc/src/main/java/org/apache/http/client/fluent/Async.java
new file mode 100644
index 0000000..9282ae7
--- /dev/null
+++ b/fluent-hc/src/main/java/org/apache/http/client/fluent/Async.java
@@ -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.fluent;
+
+import java.util.concurrent.Future;
+
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.concurrent.BasicFuture;
+import org.apache.http.concurrent.FutureCallback;
+
+public class Async {
+
+ private Executor executor;
+ private java.util.concurrent.Executor concurrentExec;
+
+ public static Async newInstance() {
+ return new Async();
+ }
+
+ Async() {
+ super();
+ }
+
+ public Async use(final Executor executor) {
+ this.executor = executor;
+ return this;
+ }
+
+ public Async use(final java.util.concurrent.Executor concurrentExec) {
+ this.concurrentExec = concurrentExec;
+ return this;
+ }
+
+ static class ExecRunnable<T> implements Runnable {
+
+ private final BasicFuture<T> future;
+ private final Request request;
+ private final Executor executor;
+ private final ResponseHandler<T> handler;
+
+ ExecRunnable(
+ final BasicFuture<T> future,
+ final Request request,
+ final Executor executor,
+ final ResponseHandler<T> handler) {
+ super();
+ this.future = future;
+ this.request = request;
+ this.executor = executor;
+ this.handler = handler;
+ }
+
+ public void run() {
+ try {
+ Response response = this.executor.execute(this.request);
+ T result = response.handleResponse(this.handler);
+ this.future.completed(result);
+ } catch (Exception ex) {
+ this.future.failed(ex);
+ }
+ }
+
+ }
+
+ public <T> Future<T> execute(
+ final Request request, final ResponseHandler<T> handler, final FutureCallback<T> callback) {
+ BasicFuture<T> future = new BasicFuture<T>(callback);
+ ExecRunnable<T> runnable = new ExecRunnable<T>(
+ future,
+ request,
+ this.executor != null ? this.executor : Executor.newInstance(),
+ handler);
+ if (this.concurrentExec != null) {
+ this.concurrentExec.execute(runnable);
+ } else {
+ Thread t = new Thread(runnable);
+ t.setDaemon(true);
+ t.start();
+ }
+ return future;
+ }
+
+ public <T> Future<T> execute(final Request request, final ResponseHandler<T> handler) {
+ return execute(request, handler, null);
+ }
+
+ public Future<Content> execute(final Request request, final FutureCallback<Content> callback) {
+ return execute(request, new ContentResponseHandler(), callback);
+ }
+
+ public Future<Content> execute(final Request request) {
+ return execute(request, new ContentResponseHandler(), null);
+ }
+
+}
diff --git a/fluent-hc/src/main/java/org/apache/http/client/fluent/Content.java b/fluent-hc/src/main/java/org/apache/http/client/fluent/Content.java
new file mode 100644
index 0000000..7d1ff5f
--- /dev/null
+++ b/fluent-hc/src/main/java/org/apache/http/client/fluent/Content.java
@@ -0,0 +1,79 @@
+/*
+ * ====================================================================
+ *
+ * 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.fluent;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+import org.apache.http.entity.ContentType;
+import org.apache.http.protocol.HTTP;
+
+public class Content {
+
+ public static final Content NO_CONTENT = new Content(new byte[] {}, ContentType.DEFAULT_BINARY);
+
+ private final byte[] raw;
+ private final ContentType type;
+
+ Content(final byte[] raw, final ContentType type) {
+ super();
+ this.raw = raw;
+ this.type = type;
+ }
+
+ public ContentType getType() {
+ return this.type;
+ }
+
+ public byte[] asBytes() {
+ return this.raw.clone();
+ }
+
+ public String asString() {
+ Charset charset = this.type.getCharset();
+ if (charset == null) {
+ charset = HTTP.DEF_CONTENT_CHARSET;
+ }
+ try {
+ return new String(this.raw, charset.name());
+ } catch (UnsupportedEncodingException ex) {
+ return new String(this.raw);
+ }
+ }
+
+ public InputStream asStream() {
+ return new ByteArrayInputStream(this.raw);
+ }
+
+ @Override
+ public String toString() {
+ return asString();
+ }
+
+}
diff --git a/fluent-hc/src/main/java/org/apache/http/client/fluent/ContentResponseHandler.java b/fluent-hc/src/main/java/org/apache/http/client/fluent/ContentResponseHandler.java
new file mode 100644
index 0000000..acc2249
--- /dev/null
+++ b/fluent-hc/src/main/java/org/apache/http/client/fluent/ContentResponseHandler.java
@@ -0,0 +1,59 @@
+/*
+ * ====================================================================
+ *
+ * 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.fluent;
+
+import java.io.IOException;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpResponseException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.EntityUtils;
+
+class ContentResponseHandler implements ResponseHandler<Content> {
+
+ public Content handleResponse(
+ final HttpResponse response) throws ClientProtocolException, IOException {
+ StatusLine statusLine = response.getStatusLine();
+ HttpEntity entity = response.getEntity();
+ if (statusLine.getStatusCode() >= 300) {
+ throw new HttpResponseException(statusLine.getStatusCode(),
+ statusLine.getReasonPhrase());
+ }
+ if (entity != null) {
+ return new Content(
+ EntityUtils.toByteArray(entity),
+ ContentType.getOrDefault(entity));
+ } else {
+ return Content.NO_CONTENT;
+ }
+ }
+
+}
diff --git a/fluent-hc/src/main/java/org/apache/http/client/fluent/Executor.java b/fluent-hc/src/main/java/org/apache/http/client/fluent/Executor.java
new file mode 100644
index 0000000..9c2cef4
--- /dev/null
+++ b/fluent-hc/src/main/java/org/apache/http/client/fluent/Executor.java
@@ -0,0 +1,199 @@
+/*
+ * ====================================================================
+ *
+ * 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.fluent;
+
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.SSLContext;
+
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.ChallengeState;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.NTCredentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.AuthCache;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.scheme.SchemeSocketFactory;
+import org.apache.http.conn.ssl.SSLInitializationException;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.BasicAuthCache;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
+import org.apache.http.protocol.BasicHttpContext;
+
+public class Executor {
+
+ final static PoolingClientConnectionManager CONNMGR;
+ final static DefaultHttpClient CLIENT;
+
+ static {
+ SchemeRegistry schemeRegistry = new SchemeRegistry();
+ SchemeSocketFactory plain = PlainSocketFactory.getSocketFactory();
+ schemeRegistry.register(new Scheme("http", 80, plain));
+ SchemeSocketFactory ssl = null;
+ try {
+ ssl = SSLSocketFactory.getSystemSocketFactory();
+ } catch (SSLInitializationException ex) {
+ SSLContext sslcontext;
+ try {
+ sslcontext = SSLContext.getInstance(SSLSocketFactory.TLS);
+ sslcontext.init(null, null, null);
+ ssl = new SSLSocketFactory(sslcontext);
+ } catch (SecurityException ignore) {
+ } catch (KeyManagementException ignore) {
+ } catch (NoSuchAlgorithmException ignore) {
+ }
+ }
+ if (ssl != null) {
+ schemeRegistry.register(new Scheme("https", 443, ssl));
+ }
+ CONNMGR = new PoolingClientConnectionManager(schemeRegistry);
+ CONNMGR.setDefaultMaxPerRoute(100);
+ CONNMGR.setMaxTotal(200);
+ CLIENT = new DefaultHttpClient(CONNMGR);
+ }
+
+ public static Executor newInstance() {
+ return new Executor(CLIENT);
+ }
+
+ public static Executor newInstance(final HttpClient httpclient) {
+ return new Executor(httpclient != null ? httpclient : CLIENT);
+ }
+
+ private final HttpClient httpclient;
+ private final BasicHttpContext localContext;
+ private final AuthCache authCache;
+
+ private CredentialsProvider credentialsProvider;
+ private CookieStore cookieStore;
+
+ Executor(final HttpClient httpclient) {
+ super();
+ this.httpclient = httpclient;
+ this.localContext = new BasicHttpContext();
+ this.authCache = new BasicAuthCache();
+ }
+
+ public Executor auth(final AuthScope authScope, final Credentials creds) {
+ if (this.credentialsProvider == null) {
+ this.credentialsProvider = new BasicCredentialsProvider();
+ }
+ this.credentialsProvider.setCredentials(authScope, creds);
+ return this;
+ }
+
+ public Executor auth(final HttpHost host, final Credentials creds) {
+ AuthScope authScope = host != null ? new AuthScope(host) : AuthScope.ANY;
+ return auth(authScope, creds);
+ }
+
+ public Executor authPreemptive(final HttpHost host) {
+ this.authCache.put(host, new BasicScheme(ChallengeState.TARGET));
+ return this;
+ }
+
+ public Executor authPreemptiveProxy(final HttpHost host) {
+ this.authCache.put(host, new BasicScheme(ChallengeState.PROXY));
+ return this;
+ }
+
+ public Executor auth(final Credentials cred) {
+ return auth(AuthScope.ANY, cred);
+ }
+
+ public Executor auth(final String username, final String password) {
+ return auth(new UsernamePasswordCredentials(username, password));
+ }
+
+ public Executor auth(final String username, final String password,
+ final String workstation, final String domain) {
+ return auth(new NTCredentials(username, password, workstation, domain));
+ }
+
+ public Executor auth(final HttpHost host,
+ final String username, final String password) {
+ return auth(host, new UsernamePasswordCredentials(username, password));
+ }
+
+ public Executor auth(final HttpHost host,
+ final String username, final String password,
+ final String workstation, final String domain) {
+ return auth(host, new NTCredentials(username, password, workstation, domain));
+ }
+
+ public Executor clearAuth() {
+ if (this.credentialsProvider != null) {
+ this.credentialsProvider.clear();
+ }
+ return this;
+ }
+
+ public Executor cookieStore(final CookieStore cookieStore) {
+ this.cookieStore = cookieStore;
+ return this;
+ }
+
+ public Executor clearCookies() {
+ if (this.cookieStore != null) {
+ this.cookieStore.clear();
+ }
+ return this;
+ }
+
+ public Response execute(
+ final Request request) throws ClientProtocolException, IOException {
+ this.localContext.setAttribute(ClientContext.CREDS_PROVIDER, this.credentialsProvider);
+ this.localContext.setAttribute(ClientContext.AUTH_CACHE, this.authCache);
+ this.localContext.setAttribute(ClientContext.COOKIE_STORE, this.cookieStore);
+ HttpRequestBase httprequest = request.getHttpRequest();
+ httprequest.reset();
+ return new Response(this.httpclient.execute(httprequest, this.localContext));
+ }
+
+ public static void registerScheme(final Scheme scheme) {
+ CONNMGR.getSchemeRegistry().register(scheme);
+ }
+
+ public static void unregisterScheme(final String name) {
+ CONNMGR.getSchemeRegistry().unregister(name);
+ }
+
+}
diff --git a/fluent-hc/src/main/java/org/apache/http/client/fluent/Form.java b/fluent-hc/src/main/java/org/apache/http/client/fluent/Form.java
new file mode 100644
index 0000000..4d87d7f
--- /dev/null
+++ b/fluent-hc/src/main/java/org/apache/http/client/fluent/Form.java
@@ -0,0 +1,57 @@
+/*
+ * ====================================================================
+ *
+ * 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.fluent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicNameValuePair;
+
+public class Form {
+
+ private final List<NameValuePair> params;
+
+ public static Form form() {
+ return new Form();
+ }
+
+ Form() {
+ super();
+ this.params = new ArrayList<NameValuePair>();
+ }
+
+ public Form add(final String name, final String value) {
+ this.params.add(new BasicNameValuePair(name, value));
+ return this;
+ }
+
+ public List<NameValuePair> build() {
+ return new ArrayList<NameValuePair>(this.params);
+ }
+
+}
diff --git a/fluent-hc/src/main/java/org/apache/http/client/fluent/HttpHeader.java b/fluent-hc/src/main/java/org/apache/http/client/fluent/HttpHeader.java
new file mode 100644
index 0000000..85aa744
--- /dev/null
+++ b/fluent-hc/src/main/java/org/apache/http/client/fluent/HttpHeader.java
@@ -0,0 +1,38 @@
+/*
+ * ====================================================================
+ *
+ * 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.fluent;
+
+class HttpHeader {
+
+ public static final String CONTENT_LENGTH = "Content-Length";
+ public static final String DATE = "Date";
+ public static final String CACHE_CONTROL = "Cache-Control";
+ public static final String CONTENT_TYPE = "Content-Type";
+ public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
+ public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
+
+}
diff --git a/fluent-hc/src/main/java/org/apache/http/client/fluent/Request.java b/fluent-hc/src/main/java/org/apache/http/client/fluent/Request.java
new file mode 100644
index 0000000..4e96125
--- /dev/null
+++ b/fluent-hc/src/main/java/org/apache/http/client/fluent/Request.java
@@ -0,0 +1,310 @@
+/*
+ * ====================================================================
+ *
+ * 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.fluent;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpVersion;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.methods.HttpTrace;
+import org.apache.http.conn.params.ConnRoutePNames;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.FileEntity;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.params.CoreProtocolPNames;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HTTP;
+
+public class Request {
+
+ public static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
+ public static final Locale DATE_LOCALE = Locale.US;
+ public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("GMT");
+
+ private final HttpRequestBase request;
+ private final HttpParams localParams;
+
+ private SimpleDateFormat dateFormatter;
+
+ public static Request Get(final URI uri) {
+ return new Request(new HttpGet(uri));
+ }
+
+ public static Request Get(final String uri) {
+ return new Request(new HttpGet(uri));
+ }
+
+ public static Request Head(final URI uri) {
+ return new Request(new HttpHead(uri));
+ }
+
+ public static Request Head(final String uri) {
+ return new Request(new HttpHead(uri));
+ }
+
+ public static Request Post(final URI uri) {
+ return new Request(new HttpPost(uri));
+ }
+
+ public static Request Post(final String uri) {
+ return new Request(new HttpPost(uri));
+ }
+
+ public static Request Put(final URI uri) {
+ return new Request(new HttpPut(uri));
+ }
+
+ public static Request Put(final String uri) {
+ return new Request(new HttpPut(uri));
+ }
+
+ public static Request Trace(final URI uri) {
+ return new Request(new HttpTrace(uri));
+ }
+
+ public static Request Trace(final String uri) {
+ return new Request(new HttpTrace(uri));
+ }
+
+ public static Request Delete(final URI uri) {
+ return new Request(new HttpDelete(uri));
+ }
+
+ public static Request Delete(final String uri) {
+ return new Request(new HttpDelete(uri));
+ }
+
+ public static Request Options(final URI uri) {
+ return new Request(new HttpOptions(uri));
+ }
+
+ public static Request Options(final String uri) {
+ return new Request(new HttpOptions(uri));
+ }
+
+ Request(final HttpRequestBase request) {
+ super();
+ this.request = request;
+ this.localParams = request.getParams();
+ }
+
+ HttpRequestBase getHttpRequest() {
+ return this.request;
+ }
+
+ public Response execute() throws ClientProtocolException, IOException {
+ return new Response(Executor.CLIENT.execute(this.request));
+ }
+
+ public void abort() throws UnsupportedOperationException {
+ this.request.abort();
+ }
+
+ //// HTTP header operations
+
+ public Request addHeader(final Header header) {
+ this.request.addHeader(header);
+ return this;
+ }
+
+ public Request addHeader(final String name, final String value) {
+ this.request.addHeader(name, value);
+ return this;
+ }
+
+ public Request removeHeader(final Header header) {
+ this.request.removeHeader(header);
+ return this;
+ }
+
+ public Request removeHeaders(final String name) {
+ this.request.removeHeaders(name);
+ return this;
+ }
+
+ public Request setHeaders(final Header[] headers) {
+ this.request.setHeaders(headers);
+ return this;
+ }
+
+ public Request setCacheControl(String cacheControl) {
+ this.request.setHeader(HttpHeader.CACHE_CONTROL, cacheControl);
+ return this;
+ }
+
+ private SimpleDateFormat getDateFormat() {
+ if (this.dateFormatter == null) {
+ this.dateFormatter = new SimpleDateFormat(DATE_FORMAT, DATE_LOCALE);
+ this.dateFormatter.setTimeZone(TIME_ZONE);
+ }
+ return this.dateFormatter;
+ }
+
+ public Request setDate(final Date date) {
+ this.request.setHeader(HttpHeader.DATE, getDateFormat().format(date));
+ return this;
+ }
+
+ public Request setIfModifiedSince(final Date date) {
+ this.request.setHeader(HttpHeader.IF_MODIFIED_SINCE, getDateFormat().format(date));
+ return this;
+ }
+
+ public Request setIfUnmodifiedSince(final Date date) {
+ this.request.setHeader(HttpHeader.IF_UNMODIFIED_SINCE, getDateFormat().format(date));
+ return this;
+ }
+
+ //// HTTP config parameter operations
+
+ public Request config(final String param, final Object object) {
+ this.localParams.setParameter(param, object);
+ return this;
+ }
+
+ public Request removeConfig(final String param) {
+ this.localParams.removeParameter(param);
+ return this;
+ }
+
+ //// HTTP protocol parameter operations
+
+ public Request version(final HttpVersion version) {
+ return config(CoreProtocolPNames.PROTOCOL_VERSION, version);
+ }
+
+ public Request elementCharset(final String charset) {
+ return config(CoreProtocolPNames.HTTP_ELEMENT_CHARSET, charset);
+ }
+
+ public Request useExpectContinue() {
+ return config(CoreProtocolPNames.USE_EXPECT_CONTINUE, true);
+ }
+
+ public Request userAgent(final String agent) {
+ return config(CoreProtocolPNames.USER_AGENT, agent);
+ }
+
+ //// HTTP connection parameter operations
+
+ public Request socketTimeout(int timeout) {
+ return config(CoreConnectionPNames.SO_TIMEOUT, timeout);
+ }
+
+ public Request connectTimeout(int timeout) {
+ return config(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
+ }
+
+ public Request staleConnectionCheck(boolean b) {
+ return config(CoreConnectionPNames.STALE_CONNECTION_CHECK, b);
+ }
+
+ //// HTTP connection route operations
+
+ public Request viaProxy(final HttpHost proxy) {
+ return config(ConnRoutePNames.DEFAULT_PROXY, proxy);
+ }
+
+ //// HTTP entity operations
+
+ public Request body(final HttpEntity entity) {
+ if (this.request instanceof HttpEntityEnclosingRequest) {
+ ((HttpEntityEnclosingRequest) this.request).setEntity(entity);
+ } else {
+ throw new IllegalStateException(this.request.getMethod()
+ + " request cannot enclose an entity");
+ }
+ return this;
+ }
+
+ public Request bodyForm(final Iterable <? extends NameValuePair> formParams, final Charset charset) {
+ return body(new UrlEncodedFormEntity(formParams, charset));
+ }
+
+ public Request bodyForm(final Iterable <? extends NameValuePair> formParams) {
+ return bodyForm(formParams, HTTP.DEF_CONTENT_CHARSET);
+ }
+
+ public Request bodyForm(final NameValuePair... formParams) {
+ return bodyForm(Arrays.asList(formParams), HTTP.DEF_CONTENT_CHARSET);
+ }
+
+ public Request bodyString(final String s, final ContentType contentType) {
+ return body(new StringEntity(s, contentType));
+ }
+
+ public Request bodyFile(final File file, final ContentType contentType) {
+ return body(new FileEntity(file, contentType));
+ }
+
+ public Request bodyByteArray(final byte[] b) {
+ return body(new ByteArrayEntity(b));
+ }
+
+ public Request bodyByteArray(final byte[] b, int off, int len) {
+ return body(new ByteArrayEntity(b, off, len));
+ }
+
+ public Request bodyStream(final InputStream instream) {
+ return body(new InputStreamEntity(instream, -1));
+ }
+
+ public Request bodyStream(final InputStream instream, final ContentType contentType) {
+ return body(new InputStreamEntity(instream, -1, contentType));
+ }
+
+ @Override
+ public String toString() {
+ return this.request.getRequestLine().toString();
+ }
+
+}
diff --git a/fluent-hc/src/main/java/org/apache/http/client/fluent/Response.java b/fluent-hc/src/main/java/org/apache/http/client/fluent/Response.java
new file mode 100644
index 0000000..3d7345b
--- /dev/null
+++ b/fluent-hc/src/main/java/org/apache/http/client/fluent/Response.java
@@ -0,0 +1,122 @@
+/*
+ * ====================================================================
+ *
+ * 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.fluent;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpResponseException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.EntityUtils;
+
+public class Response {
+
+ private final HttpResponse response;
+ private boolean consumed;
+
+ Response(final HttpResponse response) {
+ super();
+ this.response = response;
+ }
+
+ private void assertNotConsumed() {
+ if (this.consumed) {
+ throw new IllegalStateException("Response content has been already consumed");
+ }
+ }
+
+ private void dispose() {
+ if (this.consumed) {
+ return;
+ }
+ try {
+ EntityUtils.consume(this.response.getEntity());
+ } catch (Exception ignore) {
+ } finally {
+ this.consumed = true;
+ }
+ }
+
+ public void discardContent() {
+ dispose();
+ }
+
+ public <T> T handleResponse(
+ final ResponseHandler<T> handler) throws ClientProtocolException, IOException {
+ assertNotConsumed();
+ try {
+ return handler.handleResponse(this.response);
+ } finally {
+ dispose();
+ }
+ }
+
+ public Content returnContent() throws ClientProtocolException, IOException {
+ return handleResponse(new ContentResponseHandler());
+ }
+
+ public HttpResponse returnResponse() throws IOException {
+ assertNotConsumed();
+ try {
+ HttpEntity entity = this.response.getEntity();
+ if (entity != null) {
+ this.response.setEntity(new ByteArrayEntity(EntityUtils.toByteArray(entity),
+ ContentType.getOrDefault(entity)));
+ }
+ return this.response;
+ } finally {
+ this.consumed = true;
+ }
+ }
+
+ public void saveContent(final File file) throws IOException {
+ assertNotConsumed();
+ StatusLine statusLine = response.getStatusLine();
+ if (statusLine.getStatusCode() >= 300) {
+ throw new HttpResponseException(statusLine.getStatusCode(),
+ statusLine.getReasonPhrase());
+ }
+ FileOutputStream out = new FileOutputStream(file);
+ try {
+ HttpEntity entity = this.response.getEntity();
+ if (entity != null) {
+ entity.writeTo(out);
+ }
+ } finally {
+ this.consumed = true;
+ out.close();
+ }
+ }
+
+}
diff --git a/fluent-hc/src/site/apt/index.apt b/fluent-hc/src/site/apt/index.apt
new file mode 100644
index 0000000..2e8b7c6
--- /dev/null
+++ b/fluent-hc/src/site/apt/index.apt
@@ -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/>.
+
+ ----------
+ HttpComponents Fluent HC Module
+ ----------
+ ----------
+ ----------
+
+Fluent HC
+
+ This module provides an easy to use facade API for HttpClient based on the concept of
+ a fluent interface. Fluent facade API exposes only the most fundamental functions of
+ HttpClient and is indended for simple use cases that do not require the full flexibility
+ of HttpClient. For instance, fluent facade API relieves the users from having to deal with
+ connection management and resource deallocation. In most cases, though, this comes at
+ a price of having to buffer content of response messages in memory. However, users can
+ still use custom response handlers to process response streams of arbitrary length.
+
+----------------------------------
+String result1 = Request.Get("http://somehost/")
+ .version(HttpVersion.HTTP_1_1)
+ .connectTimeout(1000)
+ .socketTimeout(1000)
+ .viaProxy(new HttpHost("myproxy", 8080))
+ .execute().returnContent().asString();
+
+String result2 = Request.Post("http://somehost/do-stuff")
+ .version(HttpVersion.HTTP_1_1)
+ .useExpectContinue()
+ .viaProxy(new HttpHost("myproxy", 8080))
+ .bodyForm(Form.form().add("username", "vip").add("password", "secret").build())
+ .execute().returnContent().asString();
+----------------------------------
+
+----------------------------------
+Document result3 = Request.Get("http://somehost/content")
+ .execute().handleResponse(new ResponseHandler<Document>() {
+
+ public Document handleResponse(final HttpResponse response) throws IOException {
+ StatusLine statusLine = response.getStatusLine();
+ HttpEntity entity = response.getEntity();
+ if (statusLine.getStatusCode() >= 300) {
+ throw new HttpResponseException(
+ statusLine.getStatusCode(),
+ statusLine.getReasonPhrase());
+ }
+ if (entity == null) {
+ throw new ClientProtocolException("Response contains no content");
+ }
+ DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
+ try {
+ DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
+ ContentType contentType = ContentType.getOrDefault(entity);
+ if (!contentType.equals(ContentType.APPLICATION_XML)) {
+ throw new ClientProtocolException("Unexpected content type:" + contentType);
+ }
+ String charset = contentType.getCharset();
+ if (charset == null) {
+ charset = HTTP.DEFAULT_CONTENT_CHARSET;
+ }
+ return docBuilder.parse(entity.getContent(), charset);
+ } catch (ParserConfigurationException ex) {
+ throw new IllegalStateException(ex);
+ } catch (SAXException ex) {
+ throw new ClientProtocolException("Malformed XML document", ex);
+ }
+ }
+
+});
+----------------------------------
+
+ {{{./apidocs/index.html}Javadocs}}
+
+ {{{./xref/index.html}Project sources}}
+
+ {{{./dependencies.html}Dependencies}}
+
+ {{{./issue-tracking.html}Issue Tracking}}
+
diff --git a/httpclient-cache/src/site/resources/css/site.css b/fluent-hc/src/site/resources/css/site.css
similarity index 100%
copy from httpclient-cache/src/site/resources/css/site.css
copy to fluent-hc/src/site/resources/css/site.css
diff --git a/fluent-hc/src/site/site.xml b/fluent-hc/src/site/site.xml
new file mode 100644
index 0000000..cc8f293
--- /dev/null
+++ b/fluent-hc/src/site/site.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ ====================================================================
+ 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/DECORATION/1.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.0.0 http://maven.apache.org/xsd/decoration-1.0.0.xsd"
+ name="HttpClient">
+
+ <body>
+ <menu name="HttpClient Overview">
+ <item name="Description" href="../index.html"/>
+ <item name="Quick Start" href="../quickstart.html"/>
+ </menu>
+ <menu ref="modules" />
+ <menu ref="reports"/>
+ </body>
+</project>
diff --git a/httpclient-benchmark/pom.xml b/httpclient-benchmark/pom.xml
index 5218876..9ec560a 100644
--- a/httpclient-benchmark/pom.xml
+++ b/httpclient-benchmark/pom.xml
@@ -30,7 +30,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
- <version>4.1.1</version>
+ <version>4.2.1</version>
</parent>
<artifactId>httpclient-benchmark</artifactId>
<name>HttpClient Benchmarks</name>
@@ -60,12 +60,6 @@
<scope>compile</scope>
</dependency>
<dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpcore</artifactId>
- <version>4.1</version>
- <scope>compile</scope>
- </dependency>
- <dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
@@ -74,13 +68,13 @@
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
- <version>7.2.0.v20101020</version>
+ <version>7.3.1.v20110307</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
- <version>7.2.0.v20101020</version>
+ <version>7.3.1.v20110307</version>
<scope>compile</scope>
</dependency>
<dependency>
@@ -98,7 +92,7 @@
<dependency>
<groupId>com.ning</groupId>
<artifactId>async-http-client</artifactId>
- <version>1.4.0</version>
+ <version>1.6.4</version>
<scope>compile</scope>
</dependency>
</dependencies>
diff --git a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/Benchmark.java b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/Benchmark.java
index ecda9aa..80a47bf 100644
--- a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/Benchmark.java
+++ b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/Benchmark.java
@@ -46,167 +46,168 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
public class Benchmark {
- public static void main(String[] args) throws Exception {
-
- String ns = System.getProperty("hc.benchmark.n-requests", "200000");
- String nc = System.getProperty("hc.benchmark.concurrent", "100");
- String cls = System.getProperty("hc.benchmark.content-len", "2048");
-
- int n = Integer.parseInt(ns);
- int c = Integer.parseInt(nc);
- int contentLen = Integer.parseInt(cls);
-
- SocketConnector connector = new SocketConnector();
- connector.setPort(0);
- connector.setRequestBufferSize(12 * 1024);
- connector.setResponseBufferSize(12 * 1024);
- connector.setAcceptors(2);
- connector.setAcceptQueueSize(c);
-
- QueuedThreadPool threadpool = new QueuedThreadPool();
- threadpool.setMinThreads(c);
- threadpool.setMaxThreads(2000);
-
- Server server = new Server();
- server.addConnector(connector);
- server.setThreadPool(threadpool);
- server.setHandler(new RandomDataHandler());
-
- server.start();
- int port = connector.getLocalPort();
-
- // Sleep a little
- Thread.sleep(2000);
-
- TestHttpAgent[] agents = new TestHttpAgent[] {
- new TestHttpClient3(),
- new TestHttpJRE(),
- new TestHttpCore(),
- new TestHttpClient4(),
- new TestJettyHttpClient(),
- new TestNingHttpClient()
- };
-
- byte[] content = new byte[contentLen];
- int r = Math.abs(content.hashCode());
- for (int i = 0; i < content.length; i++) {
- content[i] = (byte) ((r + i) % 96 + 32);
- }
-
- URI target1 = new URI("http", null, "localhost", port, "/rnd", "c=" + contentLen, null);
- URI target2 = new URI("http", null, "localhost", port, "/echo", null, null);
-
- try {
- for (TestHttpAgent agent: agents) {
- agent.init();
- try {
- System.out.println("=================================");
- System.out.println("HTTP agent: " + agent.getClientName());
- System.out.println("---------------------------------");
- System.out.println(n + " GET requests");
- System.out.println("---------------------------------");
-
- long startTime1 = System.currentTimeMillis();
- Stats stats1 = agent.get(target1, n, c);
- long finishTime1 = System.currentTimeMillis();
- Stats.printStats(target1, startTime1, finishTime1, stats1);
- System.out.println("---------------------------------");
- System.out.println(n + " POST requests");
- System.out.println("---------------------------------");
-
- long startTime2 = System.currentTimeMillis();
- Stats stats2 = agent.post(target2, content, n, c);
- long finishTime2 = System.currentTimeMillis();
- Stats.printStats(target2, startTime2, finishTime2, stats2);
- } finally {
- agent.shutdown();
- }
- agent.init();
- System.out.println("---------------------------------");
- }
- } finally {
- server.stop();
- }
- server.join();
+ public static void main(String[] args) throws Exception {
+
+ String ns = System.getProperty("hc.benchmark.n-requests", "200000");
+ String nc = System.getProperty("hc.benchmark.concurrent", "20");
+ String cls = System.getProperty("hc.benchmark.content-len", "2048");
+
+ int n = Integer.parseInt(ns);
+ int c = Integer.parseInt(nc);
+ int contentLen = Integer.parseInt(cls);
+
+ SocketConnector connector = new SocketConnector();
+ connector.setPort(0);
+ connector.setRequestBufferSize(12 * 1024);
+ connector.setResponseBufferSize(12 * 1024);
+ connector.setAcceptors(2);
+ connector.setAcceptQueueSize(c);
+
+ QueuedThreadPool threadpool = new QueuedThreadPool();
+ threadpool.setMinThreads(c);
+ threadpool.setMaxThreads(2000);
+
+ Server server = new Server();
+ server.addConnector(connector);
+ server.setThreadPool(threadpool);
+ server.setHandler(new RandomDataHandler());
+
+ server.start();
+ int port = connector.getLocalPort();
+
+ TestHttpAgent[] agents = new TestHttpAgent[] {
+ new TestHttpClient3(),
+ new TestHttpJRE(),
+ new TestHttpCore(),
+ new TestHttpClient4(),
+ new TestJettyHttpClient(),
+ new TestNingHttpClient()
+ };
+
+ byte[] content = new byte[contentLen];
+ int r = Math.abs(content.hashCode());
+ for (int i = 0; i < content.length; i++) {
+ content[i] = (byte) ((r + i) % 96 + 32);
+ }
+
+ URI warmup = new URI("http", null, "localhost", port, "/rnd", "c=2048", null);
+ URI target1 = new URI("http", null, "localhost", port, "/rnd", "c=" + contentLen, null);
+ URI target2 = new URI("http", null, "localhost", port, "/echo", null, null);
+
+ try {
+ for (TestHttpAgent agent: agents) {
+ try {
+ agent.init();
+ // Warm up
+ agent.get(warmup, 500, 2);
+ // Sleep a little
+ Thread.sleep(5000);
+
+ System.out.println("=================================");
+ System.out.println("HTTP agent: " + agent.getClientName());
+ System.out.println("---------------------------------");
+ System.out.println(n + " GET requests");
+ System.out.println("---------------------------------");
+
+ long startTime1 = System.currentTimeMillis();
+ Stats stats1 = agent.get(target1, n, c);
+ long finishTime1 = System.currentTimeMillis();
+ Stats.printStats(target1, startTime1, finishTime1, stats1);
+ System.out.println("---------------------------------");
+ System.out.println(n + " POST requests");
+ System.out.println("---------------------------------");
+
+ long startTime2 = System.currentTimeMillis();
+ Stats stats2 = agent.post(target2, content, n, c);
+ long finishTime2 = System.currentTimeMillis();
+ Stats.printStats(target2, startTime2, finishTime2, stats2);
+ } finally {
+ agent.shutdown();
+ }
+ System.out.println("---------------------------------");
+ }
+ } finally {
+ server.stop();
+ }
+ server.join();
}
- static class RandomDataHandler extends AbstractHandler {
-
- public RandomDataHandler() {
- super();
- }
-
- public void handle(
- final String target,
- final Request baseRequest,
- final HttpServletRequest request,
- final HttpServletResponse response) throws IOException, ServletException {
- if (target.equals("/rnd")) {
- rnd(request, response);
- } else if (target.equals("/echo")) {
- echo(request, response);
- } else {
- response.setStatus(HttpStatus.NOT_FOUND_404);
- Writer writer = response.getWriter();
- writer.write("Target not found: " + target);
- writer.flush();
- }
- }
-
- private void rnd(
- final HttpServletRequest request,
- final HttpServletResponse response) throws IOException, ServletException {
- int count = 100;
- String s = request.getParameter("c");
- try {
- count = Integer.parseInt(s);
- } catch (NumberFormatException ex) {
- response.setStatus(500);
- Writer writer = response.getWriter();
- writer.write("Invalid query format: " + request.getQueryString());
- writer.flush();
- return;
- }
-
- response.setStatus(200);
- response.setContentLength(count);
-
- OutputStream outstream = response.getOutputStream();
- byte[] tmp = new byte[1024];
- int r = Math.abs(tmp.hashCode());
- int remaining = count;
- while (remaining > 0) {
- int chunk = Math.min(tmp.length, remaining);
- for (int i = 0; i < chunk; i++) {
- tmp[i] = (byte) ((r + i) % 96 + 32);
- }
- outstream.write(tmp, 0, chunk);
- remaining -= chunk;
- }
- outstream.flush();
- }
-
- private void echo(
- final HttpServletRequest request,
- final HttpServletResponse response) throws IOException, ServletException {
-
- ByteArrayOutputStream2 buffer = new ByteArrayOutputStream2();
- InputStream instream = request.getInputStream();
- if (instream != null) {
- IO.copy(instream, buffer);
- buffer.flush();
- }
- byte[] content = buffer.getBuf();
-
- response.setStatus(200);
- response.setContentLength(content.length);
-
- OutputStream outstream = response.getOutputStream();
- outstream.write(content);
- outstream.flush();
- }
-
- }
+ static class RandomDataHandler extends AbstractHandler {
+
+ public RandomDataHandler() {
+ super();
+ }
+
+ public void handle(
+ final String target,
+ final Request baseRequest,
+ final HttpServletRequest request,
+ final HttpServletResponse response) throws IOException, ServletException {
+ if (target.equals("/rnd")) {
+ rnd(request, response);
+ } else if (target.equals("/echo")) {
+ echo(request, response);
+ } else {
+ response.setStatus(HttpStatus.NOT_FOUND_404);
+ Writer writer = response.getWriter();
+ writer.write("Target not found: " + target);
+ writer.flush();
+ }
+ }
+
+ private void rnd(
+ final HttpServletRequest request,
+ final HttpServletResponse response) throws IOException {
+ int count = 100;
+ String s = request.getParameter("c");
+ try {
+ count = Integer.parseInt(s);
+ } catch (NumberFormatException ex) {
+ response.setStatus(500);
+ Writer writer = response.getWriter();
+ writer.write("Invalid query format: " + request.getQueryString());
+ writer.flush();
+ return;
+ }
+
+ response.setStatus(200);
+ response.setContentLength(count);
+
+ OutputStream outstream = response.getOutputStream();
+ byte[] tmp = new byte[1024];
+ int r = Math.abs(tmp.hashCode());
+ int remaining = count;
+ while (remaining > 0) {
+ int chunk = Math.min(tmp.length, remaining);
+ for (int i = 0; i < chunk; i++) {
+ tmp[i] = (byte) ((r + i) % 96 + 32);
+ }
+ outstream.write(tmp, 0, chunk);
+ remaining -= chunk;
+ }
+ outstream.flush();
+ }
+
+ private void echo(
+ final HttpServletRequest request,
+ final HttpServletResponse response) throws IOException {
+
+ ByteArrayOutputStream2 buffer = new ByteArrayOutputStream2();
+ InputStream instream = request.getInputStream();
+ if (instream != null) {
+ IO.copy(instream, buffer);
+ buffer.flush();
+ }
+ byte[] content = buffer.getBuf();
+
+ response.setStatus(200);
+ response.setContentLength(content.length);
+
+ OutputStream outstream = response.getOutputStream();
+ outstream.write(content);
+ outstream.flush();
+ }
-}
+ }
+}
diff --git a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/Stats.java b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/Stats.java
index c478839..5d39311 100644
--- a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/Stats.java
+++ b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/Stats.java
@@ -75,10 +75,6 @@ public class Stats {
return failureCount;
}
- public void setFailureCount(int failureCount) {
- this.failureCount = failureCount;
- }
-
public synchronized long getContentLen() {
return contentLen;
}
@@ -97,7 +93,6 @@ public class Stats {
final URI targetURI, long startTime, long finishTime, final Stats stats) {
float totalTimeSec = (float) (finishTime - startTime) / 1000;
float reqsPerSec = (float) stats.getSuccessCount() / totalTimeSec;
- float timePerReqMs = (float) (finishTime - startTime) / (float) stats.getSuccessCount();
System.out.print("Document URI:\t\t");
System.out.println(targetURI);
@@ -120,9 +115,6 @@ public class Stats {
System.out.print("Requests per second:\t");
System.out.print(reqsPerSec);
System.out.println(" [#/sec] (mean)");
- System.out.print("Time per request:\t");
- System.out.print(timePerReqMs);
- System.out.println(" [ms] (mean)");
}
}
\ No newline at end of file
diff --git a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpClient3.java b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpClient3.java
index c53afd5..d757c3c 100644
--- a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpClient3.java
+++ b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpClient3.java
@@ -125,7 +125,11 @@ public class TestHttpClient3 implements TestHttpAgent {
contentLen += l;
}
}
- this.stats.success(contentLen);
+ if (httpmethod.getStatusCode() == 200) {
+ this.stats.success(contentLen);
+ } else {
+ this.stats.failure(contentLen);
+ }
} catch (IOException ex) {
this.stats.failure(contentLen);
} finally {
diff --git a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpClient4.java b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpClient4.java
index 8548fef..505e245 100644
--- a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpClient4.java
+++ b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpClient4.java
@@ -42,7 +42,7 @@ import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
@@ -52,7 +52,7 @@ import org.apache.http.util.VersionInfo;
public class TestHttpClient4 implements TestHttpAgent {
- private final ThreadSafeClientConnManager mgr;
+ private final PoolingClientConnectionManager mgr;
private final DefaultHttpClient httpclient;
public TestHttpClient4() {
@@ -71,7 +71,7 @@ public class TestHttpClient4 implements TestHttpAgent {
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
- this.mgr = new ThreadSafeClientConnManager(schemeRegistry);
+ this.mgr = new PoolingClientConnectionManager(schemeRegistry);
this.httpclient = new DefaultHttpClient(this.mgr, params);
this.httpclient.setHttpRequestRetryHandler(new HttpRequestRetryHandler() {
@@ -139,19 +139,23 @@ public class TestHttpClient4 implements TestHttpAgent {
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
- try {
- contentLen = 0;
- if (instream != null) {
+ contentLen = 0;
+ if (instream != null) {
+ try {
int l = 0;
while ((l = instream.read(buffer)) != -1) {
contentLen += l;
}
+ } finally {
+ instream.close();
}
- } finally {
- instream.close();
}
}
- this.stats.success(contentLen);
+ if (response.getStatusLine().getStatusCode() == 200) {
+ this.stats.success(contentLen);
+ } else {
+ this.stats.failure(contentLen);
+ }
} catch (IOException ex) {
this.stats.failure(contentLen);
request.abort();
diff --git a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpCore.java b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpCore.java
index 75edadf..2c1a75e 100644
--- a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpCore.java
+++ b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestHttpCore.java
@@ -27,11 +27,13 @@ package org.apache.http.client.benchmark;
import java.io.IOException;
import java.io.InputStream;
-import java.net.Socket;
import java.net.URI;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HeaderIterator;
+import org.apache.http.HttpClientConnection;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
@@ -41,7 +43,8 @@ import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
-import org.apache.http.impl.DefaultHttpClientConnection;
+import org.apache.http.impl.pool.BasicConnPool;
+import org.apache.http.impl.pool.BasicPoolEntry;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.params.HttpConnectionParams;
@@ -66,6 +69,7 @@ public class TestHttpCore implements TestHttpAgent {
private final HttpProcessor httpproc;
private final HttpRequestExecutor httpexecutor;
private final ConnectionReuseStrategy connStrategy;
+ private final BasicConnPool pool;
public TestHttpCore() {
super();
@@ -91,6 +95,8 @@ public class TestHttpCore implements TestHttpAgent {
this.httpexecutor = new HttpRequestExecutor();
this.connStrategy = new DefaultConnectionReuseStrategy();
+
+ this.pool = new BasicConnPool(this.params);
}
public void init() {
@@ -100,6 +106,8 @@ public class TestHttpCore implements TestHttpAgent {
}
Stats execute(final URI target, final byte[] content, int n, int c) throws Exception {
+ this.pool.setMaxTotal(2000);
+ this.pool.setDefaultMaxPerRoute(c);
HttpHost targetHost = new HttpHost(target.getHost(), target.getPort());
StringBuilder buffer = new StringBuilder();
buffer.append(target.getPath());
@@ -143,29 +151,27 @@ public class TestHttpCore implements TestHttpAgent {
public void run() {
byte[] buffer = new byte[4096];
HttpContext context = new BasicHttpContext();
- DefaultHttpClientConnection conn = new DefaultHttpClientConnection();
- try {
- while (!this.stats.isComplete()) {
- HttpRequest request;
- if (this.content == null) {
- BasicHttpRequest httpget = new BasicHttpRequest("GET", this.requestUri);
- request = httpget;
- } else {
- BasicHttpEntityEnclosingRequest httppost = new BasicHttpEntityEnclosingRequest("POST",
- this.requestUri);
- httppost.setEntity(new ByteArrayEntity(this.content));
- request = httppost;
- }
- long contentLen = 0;
- try {
- if (!conn.isOpen()) {
- Socket socket = new Socket(
- this.targetHost.getHostName(),
- this.targetHost.getPort() > 0 ? this.targetHost.getPort() : 80);
- conn.bind(socket, params);
- }
+ while (!this.stats.isComplete()) {
+ HttpRequest request;
+ if (this.content == null) {
+ BasicHttpRequest httpget = new BasicHttpRequest("GET", this.requestUri);
+ request = httpget;
+ } else {
+ BasicHttpEntityEnclosingRequest httppost = new BasicHttpEntityEnclosingRequest("POST",
+ this.requestUri);
+ httppost.setEntity(new ByteArrayEntity(this.content));
+ request = httppost;
+ }
+ long contentLen = 0;
+ boolean reusable = false;
+
+ Future<BasicPoolEntry> future = pool.lease(targetHost, null);
+ try {
+ BasicPoolEntry entry = future.get();
+ try {
+ HttpClientConnection conn = entry.getConnection();
context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, targetHost);
@@ -174,38 +180,44 @@ public class TestHttpCore implements TestHttpAgent {
httpexecutor.postProcess(response, httpproc, context);
HttpEntity entity = response.getEntity();
- if (entity != null) {
+ if (entity != null) { // TODO can this be null?
InputStream instream = entity.getContent();
- try {
- contentLen = 0;
- if (instream != null) {
- int l = 0;
- while ((l = instream.read(buffer)) != -1) {
- contentLen += l;
- }
+ contentLen = 0;
+ if (instream != null) {
+ try {
+ int l = 0;
+ while ((l = instream.read(buffer)) != -1) {
+ contentLen += l;
+ }
+ } finally {
+ instream.close();
}
- } finally {
- instream.close();
}
}
- if (!connStrategy.keepAlive(response, context)) {
- conn.close();
+ if (connStrategy.keepAlive(response, context)) {
+ reusable = true;
}
for (HeaderIterator it = request.headerIterator(); it.hasNext();) {
it.next();
it.remove();
}
- this.stats.success(contentLen);
- } catch (IOException ex) {
- this.stats.failure(contentLen);
- } catch (HttpException ex) {
- this.stats.failure(contentLen);
+ if (response.getStatusLine().getStatusCode() == 200) {
+ this.stats.success(contentLen);
+ } else {
+ this.stats.failure(contentLen);
+ }
+ } finally {
+ pool.release(entry, reusable);
}
+ } catch (InterruptedException ex) {
+ this.stats.failure(contentLen);
+ } catch (ExecutionException ex) {
+ this.stats.failure(contentLen);
+ } catch (IOException ex) {
+ this.stats.failure(contentLen);
+ } catch (HttpException ex) {
+ this.stats.failure(contentLen);
}
- } finally {
- try {
- conn.shutdown();
- } catch (IOException ignore) {}
}
}
diff --git a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestJettyHttpClient.java b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestJettyHttpClient.java
index f9e1a18..3676ca9 100644
--- a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestJettyHttpClient.java
+++ b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestJettyHttpClient.java
@@ -70,6 +70,7 @@ public class TestJettyHttpClient implements TestHttpAgent {
try {
this.client.send(exchange);
} catch (IOException ex) {
+ stats.failure(0L);
}
}
stats.waitFor();
@@ -99,6 +100,7 @@ public class TestJettyHttpClient implements TestHttpAgent {
this.stats = stats;
}
+ @Override
protected void onResponseStatus(
final Buffer version, int status, final Buffer reason) throws IOException {
this.status = status;
@@ -135,7 +137,7 @@ public class TestJettyHttpClient implements TestHttpAgent {
super.onException(x);
}
- };
+ }
public static void main(String[] args) throws Exception {
if (args.length < 2) {
diff --git a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestNingHttpClient.java b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestNingHttpClient.java
index 43b922f..ef7ba7e 100644
--- a/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestNingHttpClient.java
+++ b/httpclient-benchmark/src/main/java/org/apache/http/client/benchmark/TestNingHttpClient.java
@@ -94,7 +94,7 @@ public class TestNingHttpClient implements TestHttpAgent {
}
public String getClientName() {
- return "Ning Async HTTP client 1.4.0";
+ return "Ning async HTTP client 1.6.4";
}
static class SimpleAsyncHandler implements AsyncHandler<Object> {
@@ -135,7 +135,7 @@ public class TestNingHttpClient implements TestHttpAgent {
this.stats.failure(this.contentLen);
}
- };
+ }
public static void main(String[] args) throws Exception {
if (args.length < 2) {
diff --git a/httpclient-cache/pom.xml b/httpclient-cache/pom.xml
index 5fadc95..92361a7 100644
--- a/httpclient-cache/pom.xml
+++ b/httpclient-cache/pom.xml
@@ -30,27 +30,17 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
- <version>4.1.1</version>
+ <version>4.2.1</version>
</parent>
<artifactId>httpclient-cache</artifactId>
<name>HttpClient Cache</name>
+ <inceptionYear>2010</inceptionYear>
<description>
HttpComponents HttpClient - Cache
</description>
<url>http://hc.apache.org/httpcomponents-client</url>
<packaging>jar</packaging>
- <repositories>
- <repository>
- <id>spy</id>
- <name>Spy Repository</name>
- <layout>default</layout>
- <url>http://bleu.west.spy.net/~dustin/m2repo/</url>
- <snapshots>
- <enabled>false</enabled>
- </snapshots>
- </repository>
- </repositories>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
@@ -61,47 +51,41 @@
<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>
- <dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
- <version>${ehcache.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
- <version>${slf4j.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>spy</groupId>
- <artifactId>memcached</artifactId>
- <version>2.5</version>
+ <artifactId>spymemcached</artifactId>
+ <scope>compile</scope>
<optional>true</optional>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymockclassextension</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<properties>
@@ -155,6 +139,13 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>clirr-maven-plugin</artifactId>
+ <configuration>
+ <comparisonVersion>${comparisonVersion}</comparisonVersion>
+ </configuration>
+ </plugin>
</plugins>
</build>
@@ -163,6 +154,7 @@
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
+ <version>${hc.javadoc.version}</version>
<configuration>
<!-- reduce console output. Can override with -Dquiet=false -->
<quiet>true</quiet>
@@ -193,10 +185,12 @@
<plugin>
<artifactId>maven-jxr-plugin</artifactId>
+ <version>${hc.jxr.version}</version>
</plugin>
<plugin>
<artifactId>maven-surefire-report-plugin</artifactId>
+ <version>${hc.surefire-report.version}</version>
</plugin>
</plugins>
diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/CacheResponseStatus.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/CacheResponseStatus.java
index 81899ab..aa94bdb 100644
--- a/httpclient-cache/src/main/java/org/apache/http/client/cache/CacheResponseStatus.java
+++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/CacheResponseStatus.java
@@ -28,7 +28,7 @@ package org.apache.http.client.cache;
/**
* This enumeration represents the various ways a response can be generated
- * by the {@link org.apache.http.impl.client.cache.CachingHttpClient};
+ * by the {@link org.apache.http.impl.client.cache.CachingHttpClient};
* if a request is executed with an {@link org.apache.http.protocol.HttpContext}
* then a parameter with one of these values will be registered in the
* context under the key
diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/HeaderConstants.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/HeaderConstants.java
index e7fb0a2..0a25f24 100644
--- a/httpclient-cache/src/main/java/org/apache/http/client/cache/HeaderConstants.java
+++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/HeaderConstants.java
@@ -56,6 +56,9 @@ public class HeaderConstants {
public static final String AGE = "Age";
public static final String VARY = "Vary";
public static final String ALLOW = "Allow";
+ public static final String VIA = "Via";
+ public static final String PUBLIC = "public";
+ public static final String PRIVATE = "private";
public static final String CACHE_CONTROL = "Cache-Control";
public static final String CACHE_CONTROL_NO_STORE = "no-store";
@@ -65,11 +68,14 @@ public class HeaderConstants {
public static final String CACHE_CONTROL_MIN_FRESH = "min-fresh";
public static final String CACHE_CONTROL_MUST_REVALIDATE = "must-revalidate";
public static final String CACHE_CONTROL_PROXY_REVALIDATE = "proxy-revalidate";
+ public static final String STALE_IF_ERROR = "stale-if-error";
+ public static final String STALE_WHILE_REVALIDATE = "stale-while-revalidate";
public static final String WARNING = "Warning";
public static final String RANGE = "Range";
public static final String CONTENT_RANGE = "Content-Range";
public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
+ public static final String AUTHORIZATION = "Authorization";
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java
index 384c0fc..740e7fa 100644
--- a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java
+++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntry.java
@@ -97,9 +97,6 @@ public class HttpCacheEntry implements Serializable {
if (responseHeaders == null) {
throw new IllegalArgumentException("Response headers may not be null");
}
- if (resource == null) {
- throw new IllegalArgumentException("Resource may not be null");
- }
this.requestDate = requestDate;
this.responseDate = responseDate;
this.statusLine = statusLine;
@@ -110,7 +107,7 @@ public class HttpCacheEntry implements Serializable {
? new HashMap<String,String>(variantMap)
: null;
}
-
+
/**
* Create a new {@link HttpCacheEntry}.
*
@@ -207,7 +204,7 @@ public class HttpCacheEntry implements Serializable {
public Resource getResource() {
return this.resource;
}
-
+
/**
* Indicates whether the origin response indicated the associated
* resource had variants (i.e. that the Vary header was set on the
diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntrySerializer.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntrySerializer.java
index d95c119..c5f5074 100644
--- a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntrySerializer.java
+++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheEntrySerializer.java
@@ -33,7 +33,7 @@ import java.io.OutputStream;
/**
* Used by some {@link HttpCacheStorage} implementations to serialize
* {@link HttpCacheEntry} instances to a byte representation before
- * storage.
+ * storage.
*/
public interface HttpCacheEntrySerializer {
diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheStorage.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheStorage.java
index c4b2ecc..157e5b5 100644
--- a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheStorage.java
+++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheStorage.java
@@ -33,7 +33,7 @@ import java.io.IOException;
* interface. They can then be plugged into the existing
* {@link org.apache.http.impl.client.cache.CachingHttpClient}
* implementation.
- *
+ *
* @since 4.1
*/
public interface HttpCacheStorage {
@@ -68,7 +68,7 @@ public interface HttpCacheStorage {
* Atomically applies the given callback to update an existing cache
* entry under a given key.
* @param key indicates which entry to modify
- * @param callback performs the update; see
+ * @param callback performs the update; see
* {@link HttpCacheUpdateCallback} for details, but roughly the
* callback expects to be handed the current entry and will return
* the new value for the entry.
diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheUpdateCallback.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheUpdateCallback.java
index ea2e80b..8c560b7 100644
--- a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheUpdateCallback.java
+++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheUpdateCallback.java
@@ -49,4 +49,4 @@ public interface HttpCacheUpdateCallback {
*/
HttpCacheEntry update(HttpCacheEntry existing) throws IOException;
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheUpdateException.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheUpdateException.java
index 956cc8b..6f819db 100644
--- a/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheUpdateException.java
+++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/HttpCacheUpdateException.java
@@ -45,4 +45,4 @@ public class HttpCacheUpdateException extends Exception {
initCause(cause);
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/InputLimit.java b/httpclient-cache/src/main/java/org/apache/http/client/cache/InputLimit.java
index fd2cf98..1fef97e 100644
--- a/httpclient-cache/src/main/java/org/apache/http/client/cache/InputLimit.java
+++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/InputLimit.java
@@ -31,7 +31,7 @@ import org.apache.http.annotation.NotThreadSafe;
/**
* Used to limiting the size of an incoming response body of
* unknown size that is optimistically being read in anticipation
- * of caching it.
+ * of caching it.
* @since 4.1
*/
@NotThreadSafe // reached
diff --git a/httpclient-cache/src/main/java/org/apache/http/client/cache/package.html b/httpclient-cache/src/main/java/org/apache/http/client/cache/package.html
index 9e765c5..58a1e3f 100644
--- a/httpclient-cache/src/main/java/org/apache/http/client/cache/package.html
+++ b/httpclient-cache/src/main/java/org/apache/http/client/cache/package.html
@@ -12,7 +12,7 @@ to you under the Apache License, Version 2.0 (the
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
@@ -51,7 +51,7 @@ the {@code CachingHttpClient} to indicate how the request was
processed by the caching module itself.
</p>
<p>
-New storage backends will need to implement the
+New storage backends will need to implement the
{@link org.apache.http.client.cache.HttpCacheStorage}
interface; they can then be passed to one of the {@code CachingHttpClient}
constructors, which will happily make use of the new storage mechanism.
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java
index 51d647e..fcfe93c 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidationRequest.java
@@ -37,7 +37,7 @@ import org.apache.http.protocol.HttpContext;
/**
* Class used to represent an asynchronous revalidation event, such as with
- * "stale-while-revalidate"
+ * "stale-while-revalidate"
*/
class AsynchronousValidationRequest implements Runnable {
private final AsynchronousValidator parent;
@@ -47,12 +47,12 @@ class AsynchronousValidationRequest implements Runnable {
private final HttpContext context;
private final HttpCacheEntry cacheEntry;
private final String identifier;
-
+
private final Log log = LogFactory.getLog(getClass());
-
+
/**
* Used internally by {@link AsynchronousValidator} to schedule a
- * revalidation.
+ * revalidation.
* @param cachingClient
* @param target
* @param request
@@ -74,7 +74,7 @@ class AsynchronousValidationRequest implements Runnable {
this.cacheEntry = cacheEntry;
this.identifier = identifier;
}
-
+
public void run() {
try {
cachingClient.revalidateCacheEntry(target, request, context, cacheEntry);
@@ -90,5 +90,5 @@ class AsynchronousValidationRequest implements Runnable {
String getIdentifier() {
return identifier;
}
-
+
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidator.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidator.java
index 023b397..0b5fd2e 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidator.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/AsynchronousValidator.java
@@ -51,20 +51,20 @@ class AsynchronousValidator {
private final ExecutorService executor;
private final Set<String> queued;
private final CacheKeyGenerator cacheKeyGenerator;
-
+
private final Log log = LogFactory.getLog(getClass());
-
+
/**
* Create AsynchronousValidator which will make revalidation requests
- * using the supplied {@link CachingHttpClient}, and
+ * using the supplied {@link CachingHttpClient}, and
* a {@link ThreadPoolExecutor} generated according to the thread
- * pool settings provided in the given {@link CacheConfig}.
+ * pool settings provided in the given {@link CacheConfig}.
* @param cachingClient used to execute asynchronous requests
* @param config specifies thread pool settings. See
* {@link CacheConfig#getAsynchronousWorkersMax()},
* {@link CacheConfig#getAsynchronousWorkersCore()},
* {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()},
- * and {@link CacheConfig#getRevalidationQueueSize()}.
+ * and {@link CacheConfig#getRevalidationQueueSize()}.
*/
public AsynchronousValidator(CachingHttpClient cachingClient,
CacheConfig config) {
@@ -76,7 +76,7 @@ class AsynchronousValidator {
new ArrayBlockingQueue<Runnable>(config.getRevalidationQueueSize()))
);
}
-
+
/**
* Create AsynchronousValidator which will make revalidation requests
* using the supplied {@link CachingHttpClient} and
@@ -91,10 +91,10 @@ class AsynchronousValidator {
this.queued = new HashSet<String>();
this.cacheKeyGenerator = new CacheKeyGenerator();
}
-
+
/**
* Schedules an asynchronous revalidation
- *
+ *
* @param target
* @param request
* @param context
@@ -104,7 +104,7 @@ class AsynchronousValidator {
HttpRequest request, HttpContext context, HttpCacheEntry entry) {
// getVariantURI will fall back on getURI if no variants exist
String uri = cacheKeyGenerator.getVariantURI(target, request, entry);
-
+
if (!queued.contains(uri)) {
AsynchronousValidationRequest revalidationRequest =
new AsynchronousValidationRequest(this, cachingClient, target,
@@ -121,7 +121,7 @@ class AsynchronousValidator {
/**
* Removes an identifier from the internal list of revalidation jobs in
- * progress. This is meant to be called by
+ * progress. This is meant to be called by
* {@link AsynchronousValidationRequest#run()} once the revalidation is
* complete, using the identifier passed in during constructions.
* @param identifier
@@ -129,11 +129,11 @@ class AsynchronousValidator {
synchronized void markComplete(String identifier) {
queued.remove(identifier);
}
-
+
Set<String> getScheduledIdentifiers() {
return Collections.unmodifiableSet(queued);
}
-
+
ExecutorService getExecutor() {
return executor;
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicHttpCache.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicHttpCache.java
index 1635b76..aa2acf5 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicHttpCache.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicHttpCache.java
@@ -47,12 +47,13 @@ import org.apache.http.client.cache.Resource;
import org.apache.http.client.cache.ResourceFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.protocol.HTTP;
class BasicHttpCache implements HttpCache {
private final CacheKeyGenerator uriExtractor;
private final ResourceFactory resourceFactory;
- private final int maxObjectSizeBytes;
+ private final long maxObjectSizeBytes;
private final CacheEntryUpdater cacheEntryUpdater;
private final CachedHttpResponseGenerator responseGenerator;
private final CacheInvalidator cacheInvalidator;
@@ -64,7 +65,7 @@ class BasicHttpCache implements HttpCache {
this.resourceFactory = resourceFactory;
this.uriExtractor = new CacheKeyGenerator();
this.cacheEntryUpdater = new CacheEntryUpdater(resourceFactory);
- this.maxObjectSizeBytes = config.getMaxObjectSizeBytes();
+ this.maxObjectSizeBytes = config.getMaxObjectSize();
this.responseGenerator = new CachedHttpResponseGenerator();
this.storage = storage;
this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.storage);
@@ -83,7 +84,7 @@ class BasicHttpCache implements HttpCache {
String uri = uriExtractor.getURI(host, request);
storage.removeEntry(uri);
}
-
+
public void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request, HttpResponse response) {
cacheInvalidator.flushInvalidatedCacheEntries(host, request, response);
}
@@ -157,7 +158,7 @@ class BasicHttpCache implements HttpCache {
&& status != HttpStatus.SC_PARTIAL_CONTENT) {
return false;
}
- Header hdr = resp.getFirstHeader("Content-Length");
+ Header hdr = resp.getFirstHeader(HTTP.CONTENT_LEN);
if (hdr == null) return false;
int contentLength;
try {
@@ -170,7 +171,7 @@ class BasicHttpCache implements HttpCache {
HttpResponse generateIncompleteResponseError(HttpResponse response,
Resource resource) {
- int contentLength = Integer.parseInt(response.getFirstHeader("Content-Length").getValue());
+ int contentLength = Integer.parseInt(response.getFirstHeader(HTTP.CONTENT_LEN).getValue());
HttpResponse error =
new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway");
error.setHeader("Content-Type","text/plain;charset=UTF-8");
@@ -193,7 +194,7 @@ class BasicHttpCache implements HttpCache {
if (src == null) {
src = entry;
}
-
+
Resource resource = resourceFactory.copy(requestId, src.getResource());
Map<String,String> variantMap = new HashMap<String,String>(src.getVariantMap());
variantMap.put(variantKey, variantCacheKey);
@@ -231,7 +232,7 @@ class BasicHttpCache implements HttpCache {
storage.putEntry(cacheKey, updatedEntry);
return updatedEntry;
}
-
+
public HttpResponse cacheAndReturnResponse(HttpHost host, HttpRequest request,
HttpResponse originResponse, Date requestSent, Date responseReceived)
throws IOException {
@@ -300,4 +301,4 @@ class BasicHttpCache implements HttpCache {
variants.put(etagHeader.getValue(), new Variant(variantKey, variantCacheKey, entry));
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicIdGenerator.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicIdGenerator.java
index b13e742..184a391 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicIdGenerator.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/BasicIdGenerator.java
@@ -82,4 +82,4 @@ class BasicIdGenerator {
return buffer.toString();
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java
index 821a51a..f5dfcb5 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java
@@ -33,19 +33,19 @@ package org.apache.http.impl.client.cache;
* {@code CacheConfig} instance has sane and conservative defaults, so the
* easiest way to specify options is to get an instance and then set just
* the options you want to modify from their defaults.</p>
- *
+ *
* <p><b>N.B.</b> This class is only for caching-specific configuration; to
- * configure the behavior of the rest of the client, configure the
+ * configure the behavior of the rest of the client, configure the
* {@link org.apache.http.client.HttpClient} used as the "backend"
* for the {@code CachingHttpClient}.</p>
- *
+ *
* <p>Cache configuration can be grouped into the following categories:</p>
- *
+ *
* <p><b>Cache size.</b> If the backend storage supports these limits, you
* can specify the {@link CacheConfig#setMaxCacheEntries maximum number of
* cache entries} as well as the {@link CacheConfig#setMaxObjectSizeBytes
* maximum cacheable response body size}.</p>
- *
+ *
* <p><b>Public/private caching.</b> By default, the caching module considers
* itself to be a shared (public) cache, and will not, for example, cache
* responses to requests with {@code Authorization} headers or responses
@@ -53,7 +53,7 @@ package org.apache.http.impl.client.cache;
* is only going to be used by one logical "user" (behaving similarly to a
* browser cache), then you will want to {@link
* CacheConfig#setSharedCache(boolean) turn off the shared cache setting}.</p>
- *
+ *
* <p><b>Heuristic caching</b>. Per RFC2616, a cache may cache certain cache
* entries even if no explicit cache control headers are set by the origin.
* This behavior is off by default, but you may want to turn this on if you
@@ -67,7 +67,7 @@ package org.apache.http.impl.client.cache;
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.2">
* 13.2.2</a> and <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.4">
* 13.2.4</a> of the HTTP/1.1 RFC for more details on heuristic caching.</p>
- *
+ *
* <p><b>Background validation</b>. The cache module supports the
* {@code stale-while-revalidate} directive of
* <a href="http://tools.ietf.org/html/rfc5861">RFC5861</a>, which allows
@@ -79,7 +79,7 @@ package org.apache.http.impl.client.cache;
* CacheConfig#setAsynchronousWorkerIdleLifetimeSecs(int) maximum time they
* can be idle before being reclaimed}. You can also control the {@link
* CacheConfig#setRevalidationQueueSize(int) size of the queue} used for
- * revalidations when there aren't enough workers to keep up with demand.</b>
+ * revalidations when there aren't enough workers to keep up with demand.</b>
*/
public class CacheConfig {
@@ -115,23 +115,23 @@ public class CacheConfig {
/** Default number of worker threads to allow for background revalidations
* resulting from the stale-while-revalidate directive.
*/
- public static final int DEFAULT_ASYNCHRONOUS_WORKERS_MAX = 1;
+ public static final int DEFAULT_ASYNCHRONOUS_WORKERS_MAX = 1;
/** Default minimum number of worker threads to allow for background
* revalidations resulting from the stale-while-revalidate directive.
*/
public static final int DEFAULT_ASYNCHRONOUS_WORKERS_CORE = 1;
-
+
/** Default maximum idle lifetime for a background revalidation thread
* before it gets reclaimed.
*/
public static final int DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS = 60;
-
- /** Default maximum queue length for background revalidation requests.
+
+ /** Default maximum queue length for background revalidation requests.
*/
public static final int DEFAULT_REVALIDATION_QUEUE_SIZE = 100;
-
- private int maxObjectSizeBytes = DEFAULT_MAX_OBJECT_SIZE_BYTES;
+
+ private long maxObjectSize = DEFAULT_MAX_OBJECT_SIZE_BYTES;
private int maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES;
private int maxUpdateRetries = DEFAULT_MAX_UPDATE_RETRIES;
private boolean heuristicCachingEnabled = false;
@@ -146,17 +146,47 @@ public class CacheConfig {
/**
* Returns the current maximum response body size that will be cached.
* @return size in bytes
+ *
+ * @deprecated (4.2) use {@link #getMaxObjectSize()}
*/
+ @Deprecated
public int getMaxObjectSizeBytes() {
- return maxObjectSizeBytes;
+ return maxObjectSize > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) maxObjectSize;
}
/**
* Specifies the maximum response body size that will be eligible for caching.
* @param maxObjectSizeBytes size in bytes
+ *
+ * @deprecated (4.2) use {@link #setMaxObjectSize(long)}
*/
+ @Deprecated
public void setMaxObjectSizeBytes(int maxObjectSizeBytes) {
- this.maxObjectSizeBytes = maxObjectSizeBytes;
+ if (maxObjectSizeBytes > Integer.MAX_VALUE) {
+ this.maxObjectSize = Integer.MAX_VALUE;
+ } else {
+ this.maxObjectSize = maxObjectSizeBytes;
+ }
+ }
+
+ /**
+ * Returns the current maximum response body size that will be cached.
+ * @return size in bytes
+ *
+ * @since 4.2
+ */
+ public long getMaxObjectSize() {
+ return maxObjectSize;
+ }
+
+ /**
+ * Specifies the maximum response body size that will be eligible for caching.
+ * @param maxObjectSize size in bytes
+ *
+ * @since 4.2
+ */
+ public void setMaxObjectSize(long maxObjectSize) {
+ this.maxObjectSize = maxObjectSize;
}
/**
@@ -236,7 +266,7 @@ public class CacheConfig {
* and {@code Date} headers of a cached response during which the cached
* response will be considered heuristically fresh.
* @param heuristicCoefficient should be between {@code 0.0} and
- * {@code 1.0}.
+ * {@code 1.0}.
*/
public void setHeuristicCoefficient(float heuristicCoefficient) {
this.heuristicCoefficient = heuristicCoefficient;
@@ -255,7 +285,7 @@ public class CacheConfig {
* calculation is not possible. Explicit cache control directives on
* either the request or origin response will override this, as will
* the heuristic {@code Last-Modified} freshness calculation if it is
- * available.
+ * available.
* @param heuristicDefaultLifetimeSecs is the number of seconds to
* consider a cache-eligible response fresh in the absence of other
* information. Set this to {@code 0} to disable this style of
@@ -276,9 +306,9 @@ public class CacheConfig {
/**
* Sets the maximum number of threads to allow for background
- * revalidations due to the {@code stale-while-revalidate} directive.
+ * revalidations due to the {@code stale-while-revalidate} directive.
* @param max number of threads; a value of 0 disables background
- * revalidations.
+ * revalidations.
*/
public void setAsynchronousWorkersMax(int max) {
this.asynchronousWorkersMax = max;
@@ -286,7 +316,7 @@ public class CacheConfig {
/**
* Returns the minimum number of threads to keep alive for background
- * revalidations due to the {@code stale-while-revalidate} directive.
+ * revalidations due to the {@code stale-while-revalidate} directive.
*/
public int getAsynchronousWorkersCore() {
return asynchronousWorkersCore;
@@ -296,7 +326,7 @@ public class CacheConfig {
* Sets the minimum number of threads to keep alive for background
* revalidations due to the {@code stale-while-revalidate} directive.
* @param min should be greater than zero and less than or equal
- * to <code>getAsynchronousWorkersMax()</code>
+ * to <code>getAsynchronousWorkersMax()</code>
*/
public void setAsynchronousWorkersCore(int min) {
this.asynchronousWorkersCore = min;
@@ -337,5 +367,5 @@ public class CacheConfig {
this.revalidationQueueSize = size;
}
-
+
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntity.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntity.java
index 538689a..b63e7d8 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntity.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntity.java
@@ -35,6 +35,7 @@ import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.annotation.Immutable;
import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.client.cache.Resource;
import org.apache.http.protocol.HTTP;
@Immutable
@@ -66,7 +67,8 @@ class CacheEntity implements HttpEntity, Serializable {
}
public long getContentLength() {
- return this.cacheEntry.getResource().length();
+ Resource resource = this.cacheEntry.getResource();
+ return (resource != null) ? resource.length() : 0L;
}
public InputStream getContent() throws IOException {
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheInvalidator.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheInvalidator.java
index c73b5e5..ccb03e7 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheInvalidator.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheInvalidator.java
@@ -43,6 +43,7 @@ import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.impl.cookie.DateParseException;
import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.protocol.HTTP;
/**
* Given a particular HttpRequest, flush any cache entries that this request
@@ -172,7 +173,7 @@ class CacheInvalidator {
}
return relURL;
}
-
+
protected boolean requestShouldNotBeCached(HttpRequest req) {
String method = req.getRequestLine().getMethod();
return notGetOrHeadRequest(method);
@@ -184,7 +185,7 @@ class CacheInvalidator {
}
/** Flushes entries that were invalidated by the given response
- * received for the given host/request pair.
+ * received for the given host/request pair.
*/
public void flushInvalidatedCacheEntries(HttpHost host,
HttpRequest request, HttpResponse response) {
@@ -200,7 +201,7 @@ class CacheInvalidator {
if (!responseDateNewerThanEntryDate(response, entry)) return;
if (!responseAndEntryEtagsDiffer(response, entry)) return;
-
+
flushUriIfSameHost(reqURL, canonURL);
}
@@ -210,21 +211,21 @@ class CacheInvalidator {
String contentLocation = clHeader.getValue();
URL canonURL = getAbsoluteURL(contentLocation);
if (canonURL != null) return canonURL;
- return getRelativeURL(reqURL, contentLocation);
+ return getRelativeURL(reqURL, contentLocation);
}
private boolean responseAndEntryEtagsDiffer(HttpResponse response,
HttpCacheEntry entry) {
- Header entryEtag = entry.getFirstHeader("ETag");
- Header responseEtag = response.getFirstHeader("ETag");
+ Header entryEtag = entry.getFirstHeader(HeaderConstants.ETAG);
+ Header responseEtag = response.getFirstHeader(HeaderConstants.ETAG);
if (entryEtag == null || responseEtag == null) return false;
return (!entryEtag.getValue().equals(responseEtag.getValue()));
}
private boolean responseDateNewerThanEntryDate(HttpResponse response,
HttpCacheEntry entry) {
- Header entryDateHeader = entry.getFirstHeader("Date");
- Header responseDateHeader = response.getFirstHeader("Date");
+ Header entryDateHeader = entry.getFirstHeader(HTTP.DATE_HEADER);
+ Header responseDateHeader = response.getFirstHeader(HTTP.DATE_HEADER);
if (entryDateHeader == null || responseDateHeader == null) {
return false;
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheKeyGenerator.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheKeyGenerator.java
index 647debd..50e8ce2 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheKeyGenerator.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheKeyGenerator.java
@@ -37,6 +37,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpHost;
@@ -44,7 +45,6 @@ import org.apache.http.HttpRequest;
import org.apache.http.annotation.Immutable;
import org.apache.http.client.cache.HeaderConstants;
import org.apache.http.client.cache.HttpCacheEntry;
-import org.apache.http.protocol.HTTP;
/**
* @since 4.1
@@ -144,7 +144,7 @@ class CacheKeyGenerator {
* Compute a "variant key" from the headers of a given request that are
* covered by the Vary header of a given cache entry. Any request whose
* varying headers match those of this request should have the same
- * variant key.
+ * variant key.
* @param req originating request
* @param entry cache entry in question that has variants
* @return a <code>String</code> variant key
@@ -166,10 +166,10 @@ class CacheKeyGenerator {
if (!first) {
buf.append("&");
}
- buf.append(URLEncoder.encode(headerName, HTTP.UTF_8));
+ buf.append(URLEncoder.encode(headerName, Consts.UTF_8.name()));
buf.append("=");
buf.append(URLEncoder.encode(getFullHeaderValue(req.getHeaders(headerName)),
- HTTP.UTF_8));
+ Consts.UTF_8.name()));
first = false;
}
buf.append("}");
@@ -179,4 +179,4 @@ class CacheKeyGenerator {
return buf.toString();
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheMap.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheMap.java
index 1bb1e3a..3bf8247 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheMap.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheMap.java
@@ -47,4 +47,4 @@ final class CacheMap extends LinkedHashMap<String, HttpCacheEntry> {
return size() > this.maxEntries;
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheValidityPolicy.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheValidityPolicy.java
index ea5cf40..c87c7df 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheValidityPolicy.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheValidityPolicy.java
@@ -81,10 +81,10 @@ class CacheValidityPolicy {
* if last-modified and date are defined, freshness lifetime is coefficient*(date-lastModified),
* else freshness lifetime is defaultLifetime
*
- * @param entry
- * @param now
- * @param coefficient
- * @param defaultLifetime
+ * @param entry the cache entry
+ * @param now what time is it currently (When is right NOW)
+ * @param coefficient Part of the heuristic for cache entry freshness
+ * @param defaultLifetime How long can I assume a cache entry is default TTL
* @return {@code true} if the response is fresh
*/
public boolean isResponseHeuristicallyFresh(final HttpCacheEntry entry,
@@ -113,17 +113,17 @@ class CacheValidityPolicy {
}
public boolean mustRevalidate(final HttpCacheEntry entry) {
- return hasCacheControlDirective(entry, "must-revalidate");
+ return hasCacheControlDirective(entry, HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE);
}
public boolean proxyRevalidate(final HttpCacheEntry entry) {
- return hasCacheControlDirective(entry, "proxy-revalidate");
+ return hasCacheControlDirective(entry, HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE);
}
-
+
public boolean mayReturnStaleWhileRevalidating(final HttpCacheEntry entry, Date now) {
- for (Header h : entry.getHeaders("Cache-Control")) {
+ for (Header h : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) {
for(HeaderElement elt : h.getElements()) {
- if ("stale-while-revalidate".equalsIgnoreCase(elt.getName())) {
+ if (HeaderConstants.STALE_WHILE_REVALIDATE.equalsIgnoreCase(elt.getName())) {
try {
int allowedStalenessLifetime = Integer.parseInt(elt.getValue());
if (getStalenessSecs(entry, now) <= allowedStalenessLifetime) {
@@ -135,24 +135,24 @@ class CacheValidityPolicy {
}
}
}
-
+
return false;
}
-
+
public boolean mayReturnStaleIfError(HttpRequest request,
HttpCacheEntry entry, Date now) {
long stalenessSecs = getStalenessSecs(entry, now);
- return mayReturnStaleIfError(request.getHeaders("Cache-Control"),
+ return mayReturnStaleIfError(request.getHeaders(HeaderConstants.CACHE_CONTROL),
stalenessSecs)
- || mayReturnStaleIfError(entry.getHeaders("Cache-Control"),
+ || mayReturnStaleIfError(entry.getHeaders(HeaderConstants.CACHE_CONTROL),
stalenessSecs);
}
-
+
private boolean mayReturnStaleIfError(Header[] headers, long stalenessSecs) {
boolean result = false;
for(Header h : headers) {
for(HeaderElement elt : h.getElements()) {
- if ("stale-if-error".equals(elt.getName())) {
+ if (HeaderConstants.STALE_IF_ERROR.equals(elt.getName())) {
try {
int staleIfErrorSecs = Integer.parseInt(elt.getValue());
if (stalenessSecs <= staleIfErrorSecs) {
@@ -204,14 +204,19 @@ class CacheValidityPolicy {
}
}
+ protected boolean hasContentLengthHeader(HttpCacheEntry entry) {
+ return null != entry.getFirstHeader(HTTP.CONTENT_LEN);
+ }
+
/**
* 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
*
+ * @param entry The cache entry we are currently working with
* @return boolean indicating whether actual length matches Content-Length
*/
protected boolean contentLengthHeaderMatchesActualLength(final HttpCacheEntry entry) {
- return getContentLengthValue(entry) == entry.getResource().length();
+ return !hasContentLengthHeader(entry) || getContentLengthValue(entry) == entry.getResource().length();
}
protected long getApparentAgeSecs(final HttpCacheEntry entry) {
@@ -256,10 +261,6 @@ class CacheValidityPolicy {
return getCorrectedReceivedAgeSecs(entry) + getResponseDelaySecs(entry);
}
- protected Date getCurrentDate() {
- return new Date();
- }
-
protected long getResidentTimeSecs(HttpCacheEntry entry, Date now) {
long diff = now.getTime() - entry.getResponseDate().getTime();
return (diff / 1000L);
@@ -300,7 +301,7 @@ class CacheValidityPolicy {
public boolean hasCacheControlDirective(final HttpCacheEntry entry,
final String directive) {
- for (Header h : entry.getHeaders("Cache-Control")) {
+ for (Header h : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) {
for(HeaderElement elt : h.getElements()) {
if (directive.equalsIgnoreCase(elt.getName())) {
return true;
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheableRequestPolicy.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheableRequestPolicy.java
index d9b1ab0..07148c3 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheableRequestPolicy.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheableRequestPolicy.java
@@ -58,17 +58,17 @@ class CacheableRequestPolicy {
ProtocolVersion pv = request.getRequestLine().getProtocolVersion();
if (HttpVersion.HTTP_1_1.compareToVersion(pv) != 0) {
- log.debug("Request was not serveable from cache");
+ log.trace("non-HTTP/1.1 request was not serveable from cache");
return false;
}
if (!method.equals(HeaderConstants.GET_METHOD)) {
- log.debug("Request was not serveable from cache");
+ log.trace("non-GET request was not serveable from cache");
return false;
}
if (request.getHeaders(HeaderConstants.PRAGMA).length > 0) {
- log.debug("Request was not serveable from cache");
+ log.trace("request with Pragma header was not serveable from cache");
return false;
}
@@ -77,19 +77,19 @@ class CacheableRequestPolicy {
for (HeaderElement cacheControlElement : cacheControl.getElements()) {
if (HeaderConstants.CACHE_CONTROL_NO_STORE.equalsIgnoreCase(cacheControlElement
.getName())) {
- log.debug("Request was not serveable from Cache");
+ log.trace("Request with no-store was not serveable from cache");
return false;
}
if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(cacheControlElement
.getName())) {
- log.debug("Request was not serveable from cache");
+ log.trace("Request with no-cache was not serveable from cache");
return false;
}
}
}
- log.debug("Request was serveable from cache");
+ log.trace("Request was serveable from cache");
return true;
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedHttpResponseGenerator.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedHttpResponseGenerator.java
index c8a3a4a..4a73da4 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedHttpResponseGenerator.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedHttpResponseGenerator.java
@@ -61,10 +61,10 @@ class CachedHttpResponseGenerator {
}
/**
- * If I was able to use a {@link CacheEntry} to response to the {@link org.apache.http.HttpRequest} then
+ * If I was able to use a {@link CacheEntity} to response to the {@link org.apache.http.HttpRequest} then
* generate an {@link HttpResponse} based on the cache entry.
* @param entry
- * {@link CacheEntry} to transform into an {@link HttpResponse}
+ * {@link CacheEntity} to transform into an {@link HttpResponse}
* @return {@link HttpResponse} that was constructed
*/
HttpResponse generateResponse(HttpCacheEntry entry) {
@@ -92,8 +92,8 @@ class CachedHttpResponseGenerator {
}
/**
- * Generate a 304 - Not Modified response from a {@link CacheEntry}. This should be
- * used to respond to conditional requests, when the entry exists or has been revalidated.
+ * Generate a 304 - Not Modified response from a {@link CacheEntity}. This should be
+ * used to respond to conditional requests, when the entry exists or has been re-validated.
*
* @param entry
* @return
@@ -107,15 +107,15 @@ class CachedHttpResponseGenerator {
// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
// - Date, unless its omission is required by section 14.8.1
- Header dateHeader = entry.getFirstHeader("Date");
+ Header dateHeader = entry.getFirstHeader(HTTP.DATE_HEADER);
if (dateHeader == null) {
- dateHeader = new BasicHeader("Date", DateUtils.formatDate(new Date()));
+ dateHeader = new BasicHeader(HTTP.DATE_HEADER, DateUtils.formatDate(new Date()));
}
response.addHeader(dateHeader);
// - ETag and/or Content-Location, if the header would have been sent
// in a 200 response to the same request
- Header etagHeader = entry.getFirstHeader("ETag");
+ Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG);
if (etagHeader != null) {
response.addHeader(etagHeader);
}
@@ -128,17 +128,17 @@ class CachedHttpResponseGenerator {
// - Expires, Cache-Control, and/or Vary, if the field-value might
// differ from that sent in any previous response for the same
// variant
- Header expiresHeader = entry.getFirstHeader("Expires");
+ Header expiresHeader = entry.getFirstHeader(HeaderConstants.EXPIRES);
if (expiresHeader != null) {
response.addHeader(expiresHeader);
}
- Header cacheControlHeader = entry.getFirstHeader("Cache-Control");
+ Header cacheControlHeader = entry.getFirstHeader(HeaderConstants.CACHE_CONTROL);
if (cacheControlHeader != null) {
response.addHeader(cacheControlHeader);
}
- Header varyHeader = entry.getFirstHeader("Vary");
+ Header varyHeader = entry.getFirstHeader(HeaderConstants.VARY);
if (varyHeader != null) {
response.addHeader(varyHeader);
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedResponseSuitabilityChecker.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedResponseSuitabilityChecker.java
index 42f6ea8..439f3df 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedResponseSuitabilityChecker.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachedResponseSuitabilityChecker.java
@@ -91,9 +91,9 @@ class CachedResponseSuitabilityChecker {
private long getMaxStale(HttpRequest request) {
long maxstale = -1;
- for(Header h : request.getHeaders("Cache-Control")) {
+ for(Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
for(HeaderElement elt : h.getElements()) {
- if ("max-stale".equals(elt.getName())) {
+ if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) {
if ((elt.getValue() == null || "".equals(elt.getValue().trim()))
&& maxstale == -1) {
maxstale = Long.MAX_VALUE;
@@ -125,11 +125,14 @@ class CachedResponseSuitabilityChecker {
* {@link HttpRequest}
* @param entry
* {@link HttpCacheEntry}
+ * @param now
+ * Right now in time
* @return boolean yes/no answer
*/
public boolean canCachedResponseBeUsed(HttpHost host, HttpRequest request, HttpCacheEntry entry, Date now) {
+
if (!isFreshEnough(entry, request, now)) {
- log.debug("Cache entry was not fresh enough");
+ log.trace("Cache entry was not fresh enough");
return false;
}
@@ -150,12 +153,12 @@ class CachedResponseSuitabilityChecker {
for (Header ccHdr : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
for (HeaderElement elt : ccHdr.getElements()) {
if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) {
- log.debug("Response contained NO CACHE directive, cache was not suitable");
+ log.trace("Response contained NO CACHE directive, cache was not suitable");
return false;
}
if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elt.getName())) {
- log.debug("Response contained NO STORE directive, cache was not suitable");
+ log.trace("Response contained NO STORE directive, cache was not suitable");
return false;
}
@@ -163,12 +166,12 @@ class CachedResponseSuitabilityChecker {
try {
int maxage = Integer.parseInt(elt.getValue());
if (validityStrategy.getCurrentAgeSecs(entry, now) > maxage) {
- log.debug("Response from cache was NOT suitable due to max age");
+ log.trace("Response from cache was NOT suitable due to max age");
return false;
}
} catch (NumberFormatException ex) {
// err conservatively
- log.debug("Response from cache was malformed: " + ex.getMessage());
+ log.debug("Response from cache was malformed" + ex.getMessage());
return false;
}
}
@@ -177,7 +180,7 @@ class CachedResponseSuitabilityChecker {
try {
int maxstale = Integer.parseInt(elt.getValue());
if (validityStrategy.getFreshnessLifetimeSecs(entry) > maxstale) {
- log.debug("Response from cache was not suitable due to Max stale freshness");
+ log.trace("Response from cache was not suitable due to Max stale freshness");
return false;
}
} catch (NumberFormatException ex) {
@@ -194,7 +197,7 @@ class CachedResponseSuitabilityChecker {
long age = validityStrategy.getCurrentAgeSecs(entry, now);
long freshness = validityStrategy.getFreshnessLifetimeSecs(entry);
if (freshness - age < minfresh) {
- log.debug("Response from cache was not suitable due to min fresh " +
+ log.trace("Response from cache was not suitable due to min fresh " +
"freshness requirement");
return false;
}
@@ -207,51 +210,53 @@ class CachedResponseSuitabilityChecker {
}
}
- log.debug("Response from cache was suitable");
+ log.trace("Response from cache was suitable");
return true;
}
/**
* Is this request the type of conditional request we support?
- * @param request
+ * @param request The current httpRequest being made
* @return {@code true} if the request is supported
*/
public boolean isConditional(HttpRequest request) {
- return hasSupportedEtagVadlidator(request) || hasSupportedLastModifiedValidator(request);
+ return hasSupportedEtagValidator(request) || hasSupportedLastModifiedValidator(request);
}
/**
* Check that conditionals that are part of this request match
- * @param request
- * @param entry
- * @param now
+ * @param request The current httpRequest being made
+ * @param entry the cache entry
+ * @param now right NOW in time
* @return {@code true} if the request matches all conditionals
*/
public boolean allConditionalsMatch(HttpRequest request, HttpCacheEntry entry, Date now) {
- boolean hasEtagValidator = hasSupportedEtagVadlidator(request);
+ boolean hasEtagValidator = hasSupportedEtagValidator(request);
boolean hasLastModifiedValidator = hasSupportedLastModifiedValidator(request);
- boolean etagValidatorMatches = (hasEtagValidator) ? etagValidtorMatches(request, entry) : false;
- boolean lastModifiedValidatorMatches = (hasLastModifiedValidator) ? lastModifiedValidatorMatches(request, entry, now) : false;
+ boolean etagValidatorMatches = (hasEtagValidator) && etagValidatorMatches(request, entry);
+ boolean lastModifiedValidatorMatches = (hasLastModifiedValidator) && lastModifiedValidatorMatches(request, entry, now);
if ((hasEtagValidator && hasLastModifiedValidator)
&& !(etagValidatorMatches && lastModifiedValidatorMatches)) {
return false;
} else if (hasEtagValidator && !etagValidatorMatches) {
return false;
- } if (hasLastModifiedValidator && !lastModifiedValidatorMatches) {
+ }
+
+ if (hasLastModifiedValidator && !lastModifiedValidatorMatches) {
return false;
}
return true;
}
private boolean hasUnsupportedConditionalHeaders(HttpRequest request) {
- return (request.getFirstHeader("If-Range") != null
- || request.getFirstHeader("If-Match") != null
- || hasValidDateField(request, "If-Unmodified-Since"));
+ return (request.getFirstHeader(HeaderConstants.IF_RANGE) != null
+ || request.getFirstHeader(HeaderConstants.IF_MATCH) != null
+ || hasValidDateField(request, HeaderConstants.IF_UNMODIFIED_SINCE));
}
- private boolean hasSupportedEtagVadlidator(HttpRequest request) {
+ private boolean hasSupportedEtagValidator(HttpRequest request) {
return request.containsHeader(HeaderConstants.IF_NONE_MATCH);
}
@@ -261,11 +266,11 @@ class CachedResponseSuitabilityChecker {
/**
* Check entry against If-None-Match
- * @param request
- * @param entry
- * @return
+ * @param request The current httpRequest being made
+ * @param entry the cache entry
+ * @return boolean does the etag validator match
*/
- private boolean etagValidtorMatches(HttpRequest request, HttpCacheEntry entry) {
+ private boolean etagValidatorMatches(HttpRequest request, HttpCacheEntry entry) {
Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG);
String etag = (etagHeader != null) ? etagHeader.getValue() : null;
Header[] ifNoneMatch = request.getHeaders(HeaderConstants.IF_NONE_MATCH);
@@ -286,10 +291,10 @@ class CachedResponseSuitabilityChecker {
/**
* Check entry against If-Modified-Since, if If-Modified-Since is in the future it is invalid as per
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
- * @param request
- * @param entry
- * @param now
- * @return
+ * @param request The current httpRequest being made
+ * @param entry the cache entry
+ * @param now right NOW in time
+ * @return boolean Does the last modified header match
*/
private boolean lastModifiedValidatorMatches(HttpRequest request, HttpCacheEntry entry, Date now) {
Header lastModifiedHeader = entry.getFirstHeader(HeaderConstants.LAST_MODIFIED);
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java
index 9e633f1..deb97a3 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java
@@ -27,8 +27,10 @@
package org.apache.http.impl.client.cache;
import java.io.IOException;
+import java.lang.reflect.UndeclaredThrowableException;
import java.net.URI;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
@@ -37,6 +39,7 @@ 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.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpMessage;
import org.apache.http.HttpRequest;
@@ -62,7 +65,10 @@ import org.apache.http.impl.cookie.DateParseException;
import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
import org.apache.http.util.VersionInfo;
/**
@@ -79,7 +85,7 @@ import org.apache.http.util.VersionInfo;
* related configuration you want to do vis-a-vis timeouts and connection
* pools should be done on this backend client before constructing a {@code
* CachingHttpClient} from it.</p>
- *
+ *
* <p>Generally speaking, the {@code CachingHttpClient} is implemented as a
* <a href="http://en.wikipedia.org/wiki/Decorator_pattern">Decorator</a>
* of the backend client; for any incoming request it attempts to satisfy
@@ -92,7 +98,7 @@ import org.apache.http.util.VersionInfo;
* This notion of "semantic transparency" means you should be able to drop
* a {@link CachingHttpClient} into an existing application without breaking
* anything.</p>
- *
+ *
* <p>Folks that would like to experiment with alternative storage backends
* should look at the {@link HttpCacheStorage} interface and the related
* package documentation there. You may also be interested in the provided
@@ -120,6 +126,8 @@ public class CachingHttpClient implements HttpClient {
private final AtomicLong cacheMisses = new AtomicLong();
private final AtomicLong cacheUpdates = new AtomicLong();
+ private final Map<ProtocolVersion, String> viaHeaders = new HashMap<ProtocolVersion, String>(4);
+
private final HttpClient backend;
private final HttpCache responseCache;
private final CacheValidityPolicy validityPolicy;
@@ -130,14 +138,14 @@ public class CachingHttpClient implements HttpClient {
private final ConditionalRequestBuilder conditionalRequestBuilder;
- private final int maxObjectSizeBytes;
+ private final long maxObjectSizeBytes;
private final boolean sharedCache;
private final ResponseProtocolCompliance responseCompliance;
private final RequestProtocolCompliance requestCompliance;
private final AsynchronousValidator asynchRevalidator;
-
+
private final Log log = LogFactory.getLog(getClass());
CachingHttpClient(
@@ -154,7 +162,7 @@ public class CachingHttpClient implements HttpClient {
if (config == null) {
throw new IllegalArgumentException("CacheConfig may not be null");
}
- this.maxObjectSizeBytes = config.getMaxObjectSizeBytes();
+ this.maxObjectSizeBytes = config.getMaxObjectSize();
this.sharedCache = config.isSharedCache();
this.backend = client;
this.responseCache = cache;
@@ -226,7 +234,7 @@ public class CachingHttpClient implements HttpClient {
* response bodies are managed using the given {@link ResourceFactory}.
* @param client used to make origin requests
* @param resourceFactory how to manage cached response bodies
- * @param storage where to store cache entries
+ * @param storage where to store cache entries
* @param config cache module options
*/
public CachingHttpClient(
@@ -244,7 +252,7 @@ public class CachingHttpClient implements HttpClient {
* that stores cache entries in the provided storage backend and uses
* the given {@link HttpClient} for backend requests.
* @param client used to make origin requests
- * @param storage where to store cache entries
+ * @param storage where to store cache entries
* @param config cache module options
*/
public CachingHttpClient(
@@ -268,7 +276,7 @@ public class CachingHttpClient implements HttpClient {
ResponseProtocolCompliance responseCompliance,
RequestProtocolCompliance requestCompliance) {
CacheConfig config = new CacheConfig();
- this.maxObjectSizeBytes = config.getMaxObjectSizeBytes();
+ this.maxObjectSizeBytes = config.getMaxObjectSize();
this.sharedCache = config.isSharedCache();
this.backend = backend;
this.validityPolicy = validityPolicy;
@@ -282,7 +290,7 @@ public class CachingHttpClient implements HttpClient {
this.requestCompliance = requestCompliance;
this.asynchRevalidator = makeAsynchronousValidator(config);
}
-
+
private AsynchronousValidator makeAsynchronousValidator(
CacheConfig config) {
if (config.getAsynchronousWorkersMax() > 0) {
@@ -292,7 +300,7 @@ public class CachingHttpClient implements HttpClient {
}
/**
- * Reports the number of times that the cache successfully responded
+ * Reports the number of times that the cache successfully responded
* to an {@link HttpRequest} without contacting the origin server.
* @return the number of cache hits
*/
@@ -331,7 +339,7 @@ public class CachingHttpClient implements HttpClient {
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);
+ return handleAndConsume(responseHandler,resp);
}
public HttpResponse execute(HttpUriRequest request) throws IOException {
@@ -353,7 +361,38 @@ public class CachingHttpClient implements HttpClient {
public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler,
HttpContext context) throws IOException {
HttpResponse resp = execute(request, context);
- return responseHandler.handleResponse(resp);
+ return handleAndConsume(responseHandler, resp);
+ }
+
+ private <T> T handleAndConsume(
+ final ResponseHandler<? extends T> responseHandler,
+ HttpResponse response) throws Error, IOException {
+ T result;
+ try {
+ result = responseHandler.handleResponse(response);
+ } catch (Exception t) {
+ HttpEntity entity = response.getEntity();
+ try {
+ EntityUtils.consume(entity);
+ } catch (Exception t2) {
+ // Log this exception. The original exception is more
+ // important and will be thrown to the caller.
+ this.log.warn("Error consuming content after an exception.", t2);
+ }
+ if (t instanceof RuntimeException) {
+ throw (RuntimeException) t;
+ }
+ if (t instanceof IOException) {
+ throw (IOException) t;
+ }
+ throw new UndeclaredThrowableException(t);
+ }
+
+ // Handling the response was successful. Ensure that the content has
+ // been fully consumed.
+ HttpEntity entity = response.getEntity();
+ EntityUtils.consume(entity);
+ return result;
}
public ClientConnectionManager getConnectionManager() {
@@ -395,43 +434,46 @@ public class CachingHttpClient implements HttpClient {
return handleCacheMiss(target, request, context);
}
- return handleCacheHit(target, request, context, entry);
+ return handleCacheHit(target, request, context, entry);
}
private HttpResponse handleCacheHit(HttpHost target, HttpRequest request,
HttpContext context, HttpCacheEntry entry)
throws ClientProtocolException, IOException {
recordCacheHit(target, request);
-
+ HttpResponse out = null;
Date now = getCurrentDate();
if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) {
- return generateCachedResponse(request, context, entry, now);
- }
-
- if (!mayCallBackend(request)) {
- return generateGatewayTimeout(context);
- }
-
- if (validityPolicy.isRevalidatable(entry)) {
+ out = generateCachedResponse(request, context, entry, now);
+ } else if (!mayCallBackend(request)) {
+ out = generateGatewayTimeout(context);
+ } else if (validityPolicy.isRevalidatable(entry)) {
return revalidateCacheEntry(target, request, context, entry, now);
+ } else {
+ return callBackend(target, request, context);
}
- return callBackend(target, request, context);
+ if (context != null) {
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
+ context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
+ context.setAttribute(ExecutionContext.HTTP_RESPONSE, out);
+ context.setAttribute(ExecutionContext.HTTP_REQ_SENT, true);
+ }
+ return out;
}
private HttpResponse revalidateCacheEntry(HttpHost target,
HttpRequest request, HttpContext context, HttpCacheEntry entry,
Date now) throws ClientProtocolException {
- log.debug("Revalidating the cache entry");
+ log.trace("Revalidating the cache entry");
try {
if (asynchRevalidator != null
&& !staleResponseNotAllowed(request, entry, now)
&& validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) {
- final HttpResponse resp = responseGenerator.generateResponse(entry);
- resp.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
-
+ final HttpResponse resp = generateCachedResponse(request, context, entry, now);
+
asynchRevalidator.revalidateCacheEntry(target, request, context, entry);
-
+
return resp;
}
return revalidateCacheEntry(target, request, context, entry);
@@ -469,12 +511,12 @@ public class CachingHttpClient implements HttpClient {
}
return entry;
}
-
+
private HttpResponse getFatallyNoncompliantResponse(HttpRequest request,
HttpContext context) {
HttpResponse fatalErrorResponse = null;
List<RequestProtocolError> fatalError = requestCompliance.requestIsFatallyNonCompliant(request);
-
+
for (RequestProtocolError error : fatalError) {
setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
fatalErrorResponse = requestCompliance.getErrorForRequest(error);
@@ -495,20 +537,20 @@ public class CachingHttpClient implements HttpClient {
private void recordCacheMiss(HttpHost target, HttpRequest request) {
cacheMisses.getAndIncrement();
- if (log.isDebugEnabled()) {
+ if (log.isTraceEnabled()) {
RequestLine rl = request.getRequestLine();
- log.debug("Cache miss [host: " + target + "; uri: " + rl.getUri() + "]");
+ log.trace("Cache miss [host: " + target + "; uri: " + rl.getUri() + "]");
}
}
private void recordCacheHit(HttpHost target, HttpRequest request) {
cacheHits.getAndIncrement();
- if (log.isDebugEnabled()) {
+ if (log.isTraceEnabled()) {
RequestLine rl = request.getRequestLine();
- log.debug("Cache hit [host: " + target + "; uri: " + rl.getUri() + "]");
+ log.trace("Cache hit [host: " + target + "; uri: " + rl.getUri() + "]");
}
}
-
+
private void recordCacheUpdate(HttpContext context) {
cacheUpdates.getAndIncrement();
setResponseStatus(context, CacheResponseStatus.VALIDATED);
@@ -534,7 +576,7 @@ public class CachingHttpClient implements HttpClient {
}
setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
if (validityPolicy.getStalenessSecs(entry, now) > 0L) {
- cachedResponse.addHeader("Warning","110 localhost \"Response is stale\"");
+ cachedResponse.addHeader(HeaderConstants.WARNING,"110 localhost \"Response is stale\"");
}
return cachedResponse;
}
@@ -570,7 +612,7 @@ public class CachingHttpClient implements HttpClient {
}
private boolean mayCallBackend(HttpRequest request) {
- for (Header h: request.getHeaders("Cache-Control")) {
+ for (Header h: request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
for (HeaderElement elt : h.getElements()) {
if ("only-if-cached".equals(elt.getName())) {
return false;
@@ -581,9 +623,9 @@ public class CachingHttpClient implements HttpClient {
}
private boolean explicitFreshnessRequest(HttpRequest request, HttpCacheEntry entry, Date now) {
- for(Header h : request.getHeaders("Cache-Control")) {
+ for(Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
for(HeaderElement elt : h.getElements()) {
- if ("max-stale".equals(elt.getName())) {
+ if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) {
try {
int maxstale = Integer.parseInt(elt.getValue());
long age = validityPolicy.getCurrentAgeSecs(entry, now);
@@ -592,26 +634,35 @@ public class CachingHttpClient implements HttpClient {
} catch (NumberFormatException nfe) {
return true;
}
- } else if ("min-fresh".equals(elt.getName())
- || "max-age".equals(elt.getName())) {
+ } else if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName())
+ || HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) {
return true;
}
}
}
return false;
}
-
+
private String generateViaHeader(HttpMessage msg) {
+
+ final ProtocolVersion pv = msg.getProtocolVersion();
+ String existingEntry = viaHeaders.get(pv);
+ if (existingEntry != null) return existingEntry;
+
final VersionInfo vi = VersionInfo.loadVersionInfo("org.apache.http.client", getClass().getClassLoader());
final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE;
- final ProtocolVersion pv = msg.getProtocolVersion();
+
+ String value;
if ("http".equalsIgnoreCase(pv.getProtocol())) {
- return String.format("%d.%d localhost (Apache-HttpClient/%s (cache))",
- pv.getMajor(), pv.getMinor(), release);
+ value = String.format("%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getMajor(), pv.getMinor(),
+ release);
} else {
- return String.format("%s/%d.%d localhost (Apache-HttpClient/%s (cache))",
- pv.getProtocol(), pv.getMajor(), pv.getMinor(), release);
+ value = String.format("%s/%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getProtocol(), pv.getMajor(),
+ pv.getMinor(), release);
}
+ viaHeaders.put(pv, value);
+
+ return value;
}
private void setResponseStatus(final HttpContext context, final CacheResponseStatus value) {
@@ -665,7 +716,7 @@ public class CachingHttpClient implements HttpClient {
Date requestDate = getCurrentDate();
- log.debug("Calling the backend");
+ log.trace("Calling the backend");
HttpResponse backendResponse = backend.execute(target, request, context);
backendResponse.addHeader("Via", generateViaHeader(backendResponse));
return handleBackendResponse(target, request, requestDate, getCurrentDate(),
@@ -675,8 +726,8 @@ public class CachingHttpClient implements HttpClient {
private boolean revalidationResponseIsTooOld(HttpResponse backendResponse,
HttpCacheEntry cacheEntry) {
- final Header entryDateHeader = cacheEntry.getFirstHeader("Date");
- final Header responseDateHeader = backendResponse.getFirstHeader("Date");
+ final Header entryDateHeader = cacheEntry.getFirstHeader(HTTP.DATE_HEADER);
+ final Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER);
if (entryDateHeader != null && responseDateHeader != null) {
try {
Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
@@ -691,7 +742,7 @@ public class CachingHttpClient implements HttpClient {
}
return false;
}
-
+
HttpResponse negotiateResponseFromVariants(HttpHost target,
HttpRequest request, HttpContext context,
Map<String, Variant> variants) throws IOException {
@@ -721,12 +772,13 @@ public class CachingHttpClient implements HttpClient {
}
HttpCacheEntry matchedEntry = matchingVariant.getEntry();
-
+
if (revalidationResponseIsTooOld(backendResponse, matchedEntry)) {
+ EntityUtils.consume(backendResponse.getEntity());
return retryRequestUnconditionally(target, request, context,
matchedEntry);
}
-
+
recordCacheUpdate(context);
HttpCacheEntry responseEntry = getUpdatedVariantEntry(target,
@@ -793,6 +845,7 @@ public class CachingHttpClient implements HttpClient {
Date responseDate = getCurrentDate();
if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) {
+ EntityUtils.consume(backendResponse.getEntity());
HttpRequest unconditional = conditionalRequestBuilder
.buildUnconditionalRequest(request, cacheEntry);
requestDate = getCurrentDate();
@@ -800,7 +853,7 @@ public class CachingHttpClient implements HttpClient {
responseDate = getCurrentDate();
}
- backendResponse.addHeader("Via", generateViaHeader(backendResponse));
+ backendResponse.addHeader(HeaderConstants.VIA, generateViaHeader(backendResponse));
int statusCode = backendResponse.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
@@ -816,13 +869,15 @@ public class CachingHttpClient implements HttpClient {
}
return responseGenerator.generateResponse(updatedEntry);
}
-
+
if (staleIfErrorAppliesTo(statusCode)
&& !staleResponseNotAllowed(request, cacheEntry, getCurrentDate())
&& validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
final HttpResponse cachedResponse = responseGenerator.generateResponse(cacheEntry);
cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
- return cachedResponse;
+ HttpEntity errorBody = backendResponse.getEntity();
+ if (errorBody != null) EntityUtils.consume(errorBody);
+ return cachedResponse;
}
return handleBackendResponse(target, conditionalRequest, requestDate, responseDate,
@@ -830,9 +885,9 @@ public class CachingHttpClient implements HttpClient {
}
private boolean staleIfErrorAppliesTo(int statusCode) {
- return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
- || statusCode == HttpStatus.SC_BAD_GATEWAY
- || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
+ return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
+ || statusCode == HttpStatus.SC_BAD_GATEWAY
+ || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
|| statusCode == HttpStatus.SC_GATEWAY_TIMEOUT;
}
@@ -843,7 +898,7 @@ public class CachingHttpClient implements HttpClient {
Date responseDate,
HttpResponse backendResponse) throws IOException {
- log.debug("Handling Backend response");
+ log.trace("Handling Backend response");
responseCompliance.ensureProtocolCompliance(request, backendResponse);
boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
@@ -876,15 +931,16 @@ public class CachingHttpClient implements HttpClient {
// nop
}
if (existing == null) return false;
- Header entryDateHeader = existing.getFirstHeader("Date");
+ Header entryDateHeader = existing.getFirstHeader(HTTP.DATE_HEADER);
if (entryDateHeader == null) return false;
- Header responseDateHeader = backendResponse.getFirstHeader("Date");
+ Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER);
if (responseDateHeader == null) return false;
try {
Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
Date responseDate = DateUtils.parseDate(responseDateHeader.getValue());
return responseDate.before(entryDate);
} catch (DateParseException e) {
+ // Empty on Purpose
}
return false;
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ConditionalRequestBuilder.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ConditionalRequestBuilder.java
index d04e973..02e85a7 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ConditionalRequestBuilder.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ConditionalRequestBuilder.java
@@ -46,14 +46,14 @@ import org.apache.http.impl.client.RequestWrapper;
class ConditionalRequestBuilder {
private static final Log log = LogFactory.getLog(ConditionalRequestBuilder.class);
-
+
/**
* When a {@link HttpCacheEntry} is stale but 'might' be used as a response
* to an {@link HttpRequest} we will attempt to revalidate the entry with
* the origin. Build the origin {@link HttpRequest} here and return it.
*
* @param request the original request from the caller
- * @param cacheEntry the entry that needs to be revalidated
+ * @param cacheEntry the entry that needs to be re-validated
* @return the wrapped request
* @throws ProtocolException when I am unable to build a new origin request.
*/
@@ -80,7 +80,7 @@ class ConditionalRequestBuilder {
}
}
if (mustRevalidate) {
- wrapperRequest.addHeader("Cache-Control","max-age=0");
+ wrapperRequest.addHeader(HeaderConstants.CACHE_CONTROL, HeaderConstants.CACHE_CONTROL_MAX_AGE + "=0");
}
return wrapperRequest;
@@ -117,7 +117,7 @@ class ConditionalRequestBuilder {
first = false;
etags.append(etag);
}
-
+
wrapperRequest.setHeader(HeaderConstants.IF_NONE_MATCH, etags.toString());
return wrapperRequest;
}
@@ -133,8 +133,7 @@ class ConditionalRequestBuilder {
* @param entry existing cache entry we are trying to validate
* @return an unconditional validation request
*/
- public HttpRequest buildUnconditionalRequest(HttpRequest request,
- HttpCacheEntry entry) {
+ public HttpRequest buildUnconditionalRequest(HttpRequest request, HttpCacheEntry entry) {
RequestWrapper wrapped;
try {
wrapped = new RequestWrapper(request);
@@ -143,13 +142,13 @@ class ConditionalRequestBuilder {
return request;
}
wrapped.resetHeaders();
- wrapped.addHeader("Cache-Control","no-cache");
- wrapped.addHeader("Pragma","no-cache");
- wrapped.removeHeaders("If-Range");
- wrapped.removeHeaders("If-Match");
- wrapped.removeHeaders("If-None-Match");
- wrapped.removeHeaders("If-Unmodified-Since");
- wrapped.removeHeaders("If-Modified-Since");
+ wrapped.addHeader(HeaderConstants.CACHE_CONTROL,HeaderConstants.CACHE_CONTROL_NO_CACHE);
+ wrapped.addHeader(HeaderConstants.PRAGMA,HeaderConstants.CACHE_CONTROL_NO_CACHE);
+ wrapped.removeHeaders(HeaderConstants.IF_RANGE);
+ wrapped.removeHeaders(HeaderConstants.IF_MATCH);
+ wrapped.removeHeaders(HeaderConstants.IF_NONE_MATCH);
+ wrapped.removeHeaders(HeaderConstants.IF_UNMODIFIED_SINCE);
+ wrapped.removeHeaders(HeaderConstants.IF_MODIFIED_SINCE);
return wrapped;
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/FileResource.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/FileResource.java
index 52c3306..03e2d5f 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/FileResource.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/FileResource.java
@@ -54,24 +54,15 @@ public class FileResource implements Resource {
this.disposed = false;
}
- private void ensureValid() {
- if (this.disposed) {
- throw new IllegalStateException("Resource has been deallocated");
- }
- }
-
synchronized File getFile() {
- ensureValid();
return this.file;
}
public synchronized InputStream getInputStream() throws IOException {
- ensureValid();
return new FileInputStream(this.file);
}
public synchronized long length() {
- ensureValid();
return this.file.length();
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HttpCache.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HttpCache.java
index 71f4942..ad8d2e3 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HttpCache.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HttpCache.java
@@ -56,9 +56,9 @@ interface HttpCache {
*/
void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request)
throws IOException;
-
+
/** Clear any entries that may be invalidated by the given response to
- * a particular request.
+ * a particular request.
* @param host
* @param request
* @param response
@@ -122,7 +122,7 @@ interface HttpCache {
* Update a specific {@link HttpCacheEntry} representing a cached variant
* using a 304 {@link HttpResponse}.
* @param target host for client request
- * @param request actual request from upstream client
+ * @param request actual request from upstream client
* @param stale current variant cache entry
* @param originResponse 304 response received from origin
* @param requestSent when the validating request was sent
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/IOUtils.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/IOUtils.java
index 44b66ef..a9d78a2 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/IOUtils.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/IOUtils.java
@@ -69,7 +69,7 @@ class IOUtils {
static void copyFile(final File in, final File out) throws IOException {
RandomAccessFile f1 = new RandomAccessFile(in, "r");
- RandomAccessFile f2 = new RandomAccessFile(out, "w");
+ RandomAccessFile f2 = new RandomAccessFile(out, "rw");
try {
FileChannel c1 = f1.getChannel();
FileChannel c2 = f2.getChannel();
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/RequestProtocolCompliance.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/RequestProtocolCompliance.java
index 94e2518..bef4ae4 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/RequestProtocolCompliance.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/RequestProtocolCompliance.java
@@ -43,6 +43,7 @@ import org.apache.http.annotation.Immutable;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.cache.HeaderConstants;
import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.RequestWrapper;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpResponse;
@@ -56,7 +57,7 @@ import org.apache.http.protocol.HTTP;
class RequestProtocolCompliance {
private static final List<String> disallowedWithNoCache =
- Arrays.asList("min-fresh", "max-stale", "max-age");
+ Arrays.asList(HeaderConstants.CACHE_CONTROL_MIN_FRESH, HeaderConstants.CACHE_CONTROL_MAX_STALE, HeaderConstants.CACHE_CONTROL_MAX_AGE);
/**
* Test to see if the {@link HttpRequest} is HTTP1.1 compliant or not
@@ -96,7 +97,7 @@ class RequestProtocolCompliance {
*/
public HttpRequest makeRequestCompliant(HttpRequest request)
throws ClientProtocolException {
-
+
if (requestMustNotHaveEntity(request)) {
((HttpEntityEnclosingRequest) request).setEntity(null);
}
@@ -116,23 +117,23 @@ class RequestProtocolCompliance {
return request;
}
-
+
private void stripOtherFreshnessDirectivesWithNoCache(HttpRequest request) {
List<HeaderElement> outElts = new ArrayList<HeaderElement>();
boolean shouldStrip = false;
- for(Header h : request.getHeaders("Cache-Control")) {
+ for(Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
for(HeaderElement elt : h.getElements()) {
if (!disallowedWithNoCache.contains(elt.getName())) {
outElts.add(elt);
}
- if ("no-cache".equals(elt.getName())) {
+ if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) {
shouldStrip = true;
}
}
}
if (!shouldStrip) return;
- request.removeHeaders("Cache-Control");
- request.setHeader("Cache-Control", buildHeaderFromElements(outElts));
+ request.removeHeaders(HeaderConstants.CACHE_CONTROL);
+ request.setHeader(HeaderConstants.CACHE_CONTROL, buildHeaderFromElements(outElts));
}
private String buildHeaderFromElements(List<HeaderElement> outElts) {
@@ -184,7 +185,8 @@ class RequestProtocolCompliance {
private void addContentTypeHeaderIfMissing(HttpEntityEnclosingRequest request) {
if (request.getEntity().getContentType() == null) {
- ((AbstractHttpEntity) request.getEntity()).setContentType(HTTP.OCTET_STREAM_TYPE);
+ ((AbstractHttpEntity) request.getEntity()).setContentType(
+ ContentType.APPLICATION_OCTET_STREAM.getMimeType());
}
}
@@ -376,9 +378,9 @@ class RequestProtocolCompliance {
}
private RequestProtocolError requestContainsNoCacheDirectiveWithFieldName(HttpRequest request) {
- for(Header h : request.getHeaders("Cache-Control")) {
+ for(Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
for(HeaderElement elt : h.getElements()) {
- if ("no-cache".equalsIgnoreCase(elt.getName())
+ if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(elt.getName())
&& elt.getValue() != null) {
return RequestProtocolError.NO_CACHE_DIRECTIVE_WITH_FIELD_NAME;
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java
index 43a3725..8258737 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java
@@ -26,7 +26,10 @@
*/
package org.apache.http.impl.client.cache;
+import java.util.Arrays;
import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -51,10 +54,19 @@ import org.apache.http.protocol.HTTP;
@Immutable
class ResponseCachingPolicy {
- private final int maxObjectSizeBytes;
+ private final long maxObjectSizeBytes;
private final boolean sharedCache;
private final Log log = LogFactory.getLog(getClass());
-
+ private static final Set<Integer> cacheableStatuses =
+ new HashSet<Integer>(Arrays.asList(HttpStatus.SC_OK,
+ HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION,
+ HttpStatus.SC_MULTIPLE_CHOICES,
+ HttpStatus.SC_MOVED_PERMANENTLY,
+ HttpStatus.SC_GONE));
+ private static final Set<Integer> uncacheableStatuses =
+ new HashSet<Integer>(Arrays.asList(HttpStatus.SC_PARTIAL_CONTENT,
+ HttpStatus.SC_SEE_OTHER));
+
/**
* Define a cache policy that limits the size of things that should be stored
* in the cache to a maximum of {@link HttpResponse} bytes in size.
@@ -63,7 +75,7 @@ class ResponseCachingPolicy {
* @param sharedCache whether to behave as a shared cache (true) or a
* non-shared/private cache (false)
*/
- public ResponseCachingPolicy(int maxObjectSizeBytes, boolean sharedCache) {
+ public ResponseCachingPolicy(long maxObjectSizeBytes, boolean sharedCache) {
this.maxObjectSizeBytes = maxObjectSizeBytes;
this.sharedCache = sharedCache;
}
@@ -82,30 +94,19 @@ class ResponseCachingPolicy {
log.debug("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("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("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("Response was not cacheable (Unknown Status code)");
- return cacheable;
+
+ int status = response.getStatusLine().getStatusCode();
+ if (cacheableStatuses.contains(status)) {
+ // these response codes MAY be cached
+ cacheable = true;
+ } else if (uncacheableStatuses.contains(status)) {
+ return false;
+ } else if (unknownStatusCode(status)) {
+ // a response with an unknown status code MUST NOT be
+ // cached
+ return false;
}
-
+
Header contentLength = response.getFirstHeader(HTTP.CONTENT_LEN);
if (contentLength != null) {
int contentLengthValue = Integer.parseInt(contentLength.getValue());
@@ -148,13 +149,22 @@ class ResponseCachingPolicy {
return (cacheable || isExplicitlyCacheable(response));
}
- protected boolean isExplicitlyNonCacheable(HttpResponse response) {
+ private boolean unknownStatusCode(int status) {
+ if (status >= 100 && status <= 101) return false;
+ if (status >= 200 && status <= 206) return false;
+ if (status >= 300 && status <= 307) return false;
+ if (status >= 400 && status <= 417) return false;
+ if (status >= 500 && status <= 505) return false;
+ return true;
+ }
+
+ 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())
- || (sharedCache && "private".equals(elem.getName()))) {
+ || (sharedCache && HeaderConstants.PRIVATE.equals(elem.getName()))) {
return true;
}
}
@@ -179,8 +189,10 @@ class ResponseCachingPolicy {
protected boolean isExplicitlyCacheable(HttpResponse response) {
if (response.getFirstHeader(HeaderConstants.EXPIRES) != null)
return true;
- String[] cacheableParams = { "max-age", "s-maxage",
- "must-revalidate", "proxy-revalidate", "public"
+ String[] cacheableParams = { HeaderConstants.CACHE_CONTROL_MAX_AGE, "s-maxage",
+ HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE,
+ HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE,
+ HeaderConstants.PUBLIC
};
return hasCacheControlParameterFrom(response, cacheableParams);
}
@@ -198,13 +210,13 @@ class ResponseCachingPolicy {
log.debug("Response was not cacheable.");
return false;
}
-
- String[] uncacheableRequestDirectives = { "no-store" };
+
+ String[] uncacheableRequestDirectives = { HeaderConstants.CACHE_CONTROL_NO_STORE };
if (hasCacheControlParameterFrom(request,uncacheableRequestDirectives)) {
return false;
}
- if (request.getRequestLine().getUri().contains("?") &&
+ if (request.getRequestLine().getUri().contains("?") &&
(!isExplicitlyCacheable(response) || from1_0Origin(response))) {
log.debug("Response was not cacheable.");
return false;
@@ -213,12 +225,12 @@ class ResponseCachingPolicy {
if (expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(response)) {
return false;
}
-
+
if (sharedCache) {
- Header[] authNHeaders = request.getHeaders("Authorization");
+ Header[] authNHeaders = request.getHeaders(HeaderConstants.AUTHORIZATION);
if (authNHeaders != null && authNHeaders.length > 0) {
String[] authCacheableParams = {
- "s-maxage", "must-revalidate", "public"
+ "s-maxage", HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE, HeaderConstants.PUBLIC
};
return hasCacheControlParameterFrom(response, authCacheableParams);
}
@@ -230,9 +242,9 @@ class ResponseCachingPolicy {
private boolean expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(
HttpResponse response) {
- if (response.getFirstHeader("Cache-Control") != null) return false;
- Header expiresHdr = response.getFirstHeader("Expires");
- Header dateHdr = response.getFirstHeader("Date");
+ if (response.getFirstHeader(HeaderConstants.CACHE_CONTROL) != null) return false;
+ Header expiresHdr = response.getFirstHeader(HeaderConstants.EXPIRES);
+ Header dateHdr = response.getFirstHeader(HTTP.DATE_HEADER);
if (expiresHdr == null || dateHdr == null) return false;
try {
Date expires = DateUtils.parseDate(expiresHdr.getValue());
@@ -244,12 +256,12 @@ class ResponseCachingPolicy {
}
private boolean from1_0Origin(HttpResponse response) {
- Header via = response.getFirstHeader("Via");
+ Header via = response.getFirstHeader(HeaderConstants.VIA);
if (via != null) {
for(HeaderElement elt : via.getElements()) {
String proto = elt.toString().split("\\s")[0];
if (proto.contains("/")) {
- return proto.equals("HTTP/1.0");
+ return proto.equals("HTTP/1.0");
} else {
return proto.equals("1.0");
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseProtocolCompliance.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseProtocolCompliance.java
index d5de714..fd4bfc7 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseProtocolCompliance.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseProtocolCompliance.java
@@ -26,12 +26,14 @@
*/
package org.apache.http.impl.client.cache;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
+import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
@@ -46,6 +48,7 @@ import org.apache.http.impl.cookie.DateParseException;
import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EntityUtils;
/**
* @since 4.1
@@ -53,6 +56,10 @@ import org.apache.http.protocol.HTTP;
@Immutable
class ResponseProtocolCompliance {
+ private static final String UNEXPECTED_100_CONTINUE = "The incoming request did not contain a "
+ + "100-continue header, but the response was a Status 100, continue.";
+ private static final String UNEXPECTED_PARTIAL_CONTENT = "partial content was returned for a request that did not ask for it";
+
/**
* When we get a response from a down stream server (Origin Server)
* we attempt to see if it is HTTP 1.1 Compliant and if not, attempt to
@@ -60,20 +67,15 @@ class ResponseProtocolCompliance {
*
* @param request The {@link HttpRequest} that generated an origin hit and response
* @param response The {@link HttpResponse} from the origin server
- * @throws ClientProtocolException when we are unable to 'convert' the response to a compliant one
+ * @throws IOException Bad things happened
*/
public void ensureProtocolCompliance(HttpRequest request, HttpResponse response)
- throws ClientProtocolException {
+ throws IOException {
if (backendResponseMustNotHaveBody(request, response)) {
+ consumeBody(response);
response.setEntity(null);
}
- authenticationRequiredDidNotHaveAProxyAuthenticationHeader(request, response);
-
- notAllowedResponseDidNotHaveAnAllowHeader(request, response);
-
- unauthorizedResponseDidNotHaveAWWWAuthenticateHeader(request, response);
-
requestDidNotExpect100ContinueButResponseIsOne(request, response);
transferEncodingIsNotReturnedTo1_0Client(request, response);
@@ -83,7 +85,7 @@ class ResponseProtocolCompliance {
ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(request, response);
ensure206ContainsDateHeader(response);
-
+
ensure304DoesNotContainExtraEntityHeaders(response);
identityIsNotUsedInContentEncoding(response);
@@ -91,30 +93,40 @@ class ResponseProtocolCompliance {
warningsWithNonMatchingWarnDatesAreRemoved(response);
}
+ private void consumeBody(HttpResponse response) throws IOException {
+ HttpEntity body = response.getEntity();
+ if (body != null) EntityUtils.consume(body);
+ }
+
private void warningsWithNonMatchingWarnDatesAreRemoved(
HttpResponse response) {
Date responseDate = null;
try {
- responseDate = DateUtils.parseDate(response.getFirstHeader("Date").getValue());
+ responseDate = DateUtils.parseDate(response.getFirstHeader(HTTP.DATE_HEADER).getValue());
} catch (DateParseException e) {
+ //Empty On Purpose
}
+
if (responseDate == null) return;
- Header[] warningHeaders = response.getHeaders("Warning");
+
+ Header[] warningHeaders = response.getHeaders(HeaderConstants.WARNING);
+
if (warningHeaders == null || warningHeaders.length == 0) return;
+
List<Header> newWarningHeaders = new ArrayList<Header>();
boolean modified = false;
for(Header h : warningHeaders) {
for(WarningValue wv : WarningValue.getWarningValues(h)) {
Date warnDate = wv.getWarnDate();
if (warnDate == null || warnDate.equals(responseDate)) {
- newWarningHeaders.add(new BasicHeader("Warning",wv.toString()));
+ newWarningHeaders.add(new BasicHeader(HeaderConstants.WARNING,wv.toString()));
} else {
modified = true;
}
}
}
if (modified) {
- response.removeHeaders("Warning");
+ response.removeHeaders(HeaderConstants.WARNING);
for(Header h : newWarningHeaders) {
response.addHeader(h);
}
@@ -122,7 +134,7 @@ class ResponseProtocolCompliance {
}
private void identityIsNotUsedInContentEncoding(HttpResponse response) {
- Header[] hdrs = response.getHeaders("Content-Encoding");
+ Header[] hdrs = response.getHeaders(HTTP.CONTENT_ENCODING);
if (hdrs == null || hdrs.length == 0) return;
List<Header> newHeaders = new ArrayList<Header>();
boolean modified = false;
@@ -140,46 +152,16 @@ class ResponseProtocolCompliance {
}
String newHeaderValue = buf.toString();
if (!"".equals(newHeaderValue)) {
- newHeaders.add(new BasicHeader("Content-Encoding", newHeaderValue));
+ newHeaders.add(new BasicHeader(HTTP.CONTENT_ENCODING, newHeaderValue));
}
}
if (!modified) return;
- response.removeHeaders("Content-Encoding");
+ response.removeHeaders(HTTP.CONTENT_ENCODING);
for (Header h : newHeaders) {
response.addHeader(h);
}
}
- private void authenticationRequiredDidNotHaveAProxyAuthenticationHeader(HttpRequest request,
- HttpResponse response) throws ClientProtocolException {
- if (response.getStatusLine().getStatusCode() != HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED)
- return;
-
- if (response.getFirstHeader(HeaderConstants.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(HeaderConstants.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(HeaderConstants.WWW_AUTHENTICATE) == null) {
- throw new ClientProtocolException(
- "401 Response did not contain required WWW-Authenticate challenge header");
- }
- }
-
private void ensure206ContainsDateHeader(HttpResponse response) {
if (response.getFirstHeader(HTTP.DATE_HEADER) == null) {
response.addHeader(HTTP.DATE_HEADER, DateUtils.formatDate(new Date()));
@@ -188,15 +170,13 @@ class ResponseProtocolCompliance {
}
private void ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(HttpRequest request,
- HttpResponse response) throws ClientProtocolException {
- if (request.getFirstHeader(HeaderConstants.RANGE) != null)
+ HttpResponse response) throws IOException {
+ if (request.getFirstHeader(HeaderConstants.RANGE) != null
+ || response.getStatusLine().getStatusCode() != HttpStatus.SC_PARTIAL_CONTENT)
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.");
- }
-
+ consumeBody(response);
+ throw new ClientProtocolException(UNEXPECTED_PARTIAL_CONTENT);
}
private void ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(HttpRequest request,
@@ -215,9 +195,9 @@ class ResponseProtocolCompliance {
}
private void ensure304DoesNotContainExtraEntityHeaders(HttpResponse response) {
- String[] disallowedEntityHeaders = { "Allow", "Content-Encoding",
- "Content-Language", "Content-Length", "Content-MD5",
- "Content-Range", "Content-Type", "Last-Modified"
+ String[] disallowedEntityHeaders = { HeaderConstants.ALLOW, HTTP.CONTENT_ENCODING,
+ "Content-Language", HTTP.CONTENT_LEN, "Content-MD5",
+ "Content-Range", HTTP.CONTENT_TYPE, HeaderConstants.LAST_MODIFIED
};
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED) {
for(String hdr : disallowedEntityHeaders) {
@@ -234,26 +214,18 @@ class ResponseProtocolCompliance {
}
private void requestDidNotExpect100ContinueButResponseIsOne(HttpRequest request,
- HttpResponse response) throws ClientProtocolException {
+ HttpResponse response) throws IOException {
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) {
return;
}
- if (!requestWasWrapped(request)) {
- return;
- }
-
- ProtocolVersion originalProtocol = getOriginalRequestProtocol((RequestWrapper) request);
-
- if (originalProtocol.compareToVersion(HttpVersion.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.");
-
+ HttpRequest originalRequest = requestWasWrapped(request) ?
+ ((RequestWrapper)request).getOriginal() : request;
+ if (originalRequest instanceof HttpEntityEnclosingRequest) {
+ if (((HttpEntityEnclosingRequest)originalRequest).expectContinue()) return;
}
+ consumeBody(response);
+ throw new ClientProtocolException(UNEXPECTED_100_CONTINUE);
}
private void transferEncodingIsNotReturnedTo1_0Client(HttpRequest request, HttpResponse response) {
@@ -275,18 +247,6 @@ class ResponseProtocolCompliance {
response.removeHeaders(HTTP.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();
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/Variant.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/Variant.java
index 0eabe68..77bcc04 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/Variant.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/Variant.java
@@ -44,11 +44,11 @@ class Variant {
public String getVariantKey() {
return variantKey;
}
-
+
public String getCacheKey() {
return cacheKey;
}
-
+
public HttpCacheEntry getEntry() {
return entry;
}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCacheStorage.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCacheStorage.java
index 118b7bc..bf188c9 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCacheStorage.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/EhcacheHttpCacheStorage.java
@@ -47,12 +47,12 @@ import org.apache.http.impl.client.cache.DefaultHttpCacheEntrySerializer;
* In particular, this backend allows for spillover to disk, where the
* cache can be effectively larger than memory, and cached responses are
* paged into and out of memory from disk as needed.</p>
- *
+ *
* <p><b>N.B.</b> Since the Ehcache is configured ahead of time with a
* maximum number of cache entries, this effectively ignores the
* {@link CacheConfig#setMaxCacheEntries(int) maximum cache entries}
* specified by a provided {@link CacheConfig}.</p>
- *
+ *
* <p>Please refer to the <a href="http://ehcache.org/documentation/index.html">
* Ehcache documentation</a> for details on how to configure the Ehcache
* itself.</p>
@@ -154,4 +154,4 @@ public class EhcacheHttpCacheStorage implements HttpCacheStorage {
}while(numRetries <= maxUpdateRetries);
throw new HttpCacheUpdateException("Failed to update");
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/package.html b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/package.html
index 2cdcbca..9a27360 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/package.html
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ehcache/package.html
@@ -12,7 +12,7 @@ to you under the Apache License, Version 2.0 (the
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
@@ -31,7 +31,7 @@ information on the Apache Software Foundation, please see
<p>
This package contains a storage backend based on
-<a href="http://ehcache.org/">Ehcache</a>
+<a href="http://ehcache.org/">Ehcache</a>
that can be plugged into
a {@link org.apache.http.impl.client.cache.CachingHttpClient} and
used for storing cache entries.
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/KeyHashingScheme.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/KeyHashingScheme.java
new file mode 100644
index 0000000..06d5212
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/KeyHashingScheme.java
@@ -0,0 +1,47 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */package org.apache.http.impl.client.cache.memcached;
+
+import org.apache.http.client.cache.HttpCacheStorage;
+
+/**
+ * Since the {@link HttpCacheStorage} interface expects to use variant-annotated
+ * URLs for its storage keys, but Memcached has a maximum key size, we need to
+ * support mapping storage keys to cache keys. Clients can implement this
+ * interface to change the way the mapping is done (for example, to add a prefix
+ * to all cache keys to provide a form of memcached namespacing).
+ */
+public interface KeyHashingScheme {
+
+ /** Maps a storage key to a cache key. The storage key is what
+ * the higher-level HTTP cache uses as a key; the cache key is what
+ * we use as a key for talking to memcached.
+ * @param storageKey what the higher-level HTTP cache wants to use
+ * as its key for looking up cache entries
+ * @return a cache key suitable for use with memcached
+ */
+ public String hash(String storageKey);
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntry.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntry.java
new file mode 100644
index 0000000..bf2217a
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntry.java
@@ -0,0 +1,75 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache.memcached;
+
+import org.apache.http.client.cache.HttpCacheEntry;
+
+/**
+ * Provides for serialization and deserialization of higher-level
+ * {@link HttpCacheEntry} objects into byte arrays suitable for
+ * storage in memcached. Clients wishing to change the serialization
+ * mechanism from the provided defaults should implement this
+ * interface as well as {@link MemcachedCacheEntryFactory}.
+ */
+public interface MemcachedCacheEntry {
+
+ /**
+ * Returns a serialized representation of the current cache entry.
+ */
+ byte[] toByteArray();
+
+ /**
+ * Returns the storage key associated with this entry. May return
+ * <code>null</code> if this is an "unset" instance waiting to be
+ * {@link #set(byte[])} with a serialized representation.
+ */
+ String getStorageKey();
+
+ /**
+ * Returns the {@link HttpCacheEntry} associated with this entry.
+ * May return <code>null</code> if this is an "unset" instance
+ * waiting to be {@link #set(byte[])} with a serialized
+ * representation.
+ */
+ HttpCacheEntry getHttpCacheEntry();
+
+ /**
+ * Given a serialized representation of a {@link MemcachedCacheEntry},
+ * attempt to reconstitute the storage key and {@link HttpCacheEntry}
+ * represented therein. After a successful call to this method, this
+ * object should return updated (as appropriate) values for
+ * {@link #getStorageKey()} and {@link #getHttpCacheEntry()}. This
+ * should be viewed as an atomic operation on the
+ * <code>MemcachedCacheEntry</code>.
+ * @param bytes serialized representation
+ * @throws {@link MemcachedSerializationException} if deserialization
+ * fails. In this case, the prior values for {{@link #getStorageKey()}
+ * and {@link #getHttpCacheEntry()} should remain unchanged.
+ */
+ void set(byte[] bytes);
+
+}
\ No newline at end of file
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryFactory.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryFactory.java
new file mode 100644
index 0000000..9cc4d52
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryFactory.java
@@ -0,0 +1,62 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache.memcached;
+
+import org.apache.http.client.cache.HttpCacheEntry;
+
+/**
+ * Creates {@link MemcachedCacheEntry} instances that can be used for
+ * serializing and deserializing {@link HttpCacheEntry} instances for
+ * storage in memcached.
+ */
+public interface MemcachedCacheEntryFactory {
+
+ /**
+ * Creates a new {@link MemcachedCacheEntry} for storing the
+ * given {@link HttpCacheEntry} under the given storage key. Since
+ * we are hashing storage keys into cache keys to accommodate
+ * limitations in memcached's key space, it is possible to have
+ * cache collisions. Therefore, we store the storage key along
+ * with the <code>HttpCacheEntry</code> so it can be compared
+ * on retrieval and thus detect collisions.
+ * @param storageKey storage key under which the entry will
+ * be logically stored
+ * @param entry the cache entry to store
+ * @return a {@link MemcachedCacheEntry} ready to provide
+ * a serialized representation
+ */
+ MemcachedCacheEntry getMemcachedCacheEntry(String storageKey, HttpCacheEntry entry);
+
+ /**
+ * Creates an "unset" {@link MemcachedCacheEntry} ready to accept
+ * a serialized representation via {@link MemcachedCacheEntry#set(byte[])}
+ * and deserialize it into a storage key and a {@link HttpCacheEntry}.
+ * @return <code>MemcachedCacheEntry</code>
+ */
+ MemcachedCacheEntry getUnsetCacheEntry();
+
+}
\ No newline at end of file
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryFactoryImpl.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryFactoryImpl.java
new file mode 100644
index 0000000..03c939c
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryFactoryImpl.java
@@ -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.impl.client.cache.memcached;
+
+import org.apache.http.client.cache.HttpCacheEntry;
+
+/**
+ * Default implementation of {@link MemcachedCacheEntryFactory}.
+ */
+public class MemcachedCacheEntryFactoryImpl implements MemcachedCacheEntryFactory {
+
+ public MemcachedCacheEntry getMemcachedCacheEntry(String key, HttpCacheEntry entry) {
+ return new MemcachedCacheEntryImpl(key, entry);
+ }
+
+ public MemcachedCacheEntry getUnsetCacheEntry() {
+ return new MemcachedCacheEntryImpl(null, null);
+ }
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryImpl.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryImpl.java
new file mode 100644
index 0000000..4ddb471
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryImpl.java
@@ -0,0 +1,108 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache.memcached;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import org.apache.http.client.cache.HttpCacheEntry;
+
+/**
+ * Default implementation of {@link MemcachedCacheEntry}. This implementation
+ * simply uses Java serialization to serialize the storage key followed by
+ * the {@link HttpCacheEntry} into a byte array.
+ */
+public class MemcachedCacheEntryImpl implements MemcachedCacheEntry {
+
+ private String key;
+ private HttpCacheEntry httpCacheEntry;
+
+ public MemcachedCacheEntryImpl(String key, HttpCacheEntry httpCacheEntry) {
+ this.key = key;
+ this.httpCacheEntry = httpCacheEntry;
+ }
+
+ public MemcachedCacheEntryImpl() {
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.http.impl.client.cache.memcached.MemcachedCacheEntry#toByteArray()
+ */
+ synchronized public byte[] toByteArray() {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream oos;
+ try {
+ oos = new ObjectOutputStream(bos);
+ oos.writeObject(this.key);
+ oos.writeObject(this.httpCacheEntry);
+ oos.close();
+ } catch (IOException ioe) {
+ throw new MemcachedSerializationException(ioe);
+ }
+ return bos.toByteArray();
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.http.impl.client.cache.memcached.MemcachedCacheEntry#getKey()
+ */
+ public synchronized String getStorageKey() {
+ return key;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.http.impl.client.cache.memcached.MemcachedCacheEntry#getHttpCacheEntry()
+ */
+ public synchronized HttpCacheEntry getHttpCacheEntry() {
+ return httpCacheEntry;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.http.impl.client.cache.memcached.MemcachedCacheEntry#set(byte[])
+ */
+ synchronized public void set(byte[] bytes) {
+ ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ ObjectInputStream ois;
+ String s;
+ HttpCacheEntry entry;
+ try {
+ ois = new ObjectInputStream(bis);
+ s = (String)ois.readObject();
+ entry = (HttpCacheEntry)ois.readObject();
+ ois.close();
+ bis.close();
+ } catch (IOException ioe) {
+ throw new MemcachedSerializationException(ioe);
+ } catch (ClassNotFoundException cnfe) {
+ throw new MemcachedSerializationException(cnfe);
+ }
+ this.key = s;
+ this.httpCacheEntry = entry;
+ }
+
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedHttpCacheStorage.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedHttpCacheStorage.java
index 573c796..e8a7cf0 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedHttpCacheStorage.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedHttpCacheStorage.java
@@ -26,24 +26,24 @@
*/
package org.apache.http.impl.client.cache.memcached;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.net.InetSocketAddress;
import net.spy.memcached.CASResponse;
import net.spy.memcached.CASValue;
import net.spy.memcached.MemcachedClient;
import net.spy.memcached.MemcachedClientIF;
+import net.spy.memcached.OperationTimeoutException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheEntrySerializer;
import org.apache.http.client.cache.HttpCacheUpdateException;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.impl.client.cache.CacheConfig;
-import org.apache.http.impl.client.cache.DefaultHttpCacheEntrySerializer;
+import org.apache.http.impl.client.cache.CachingHttpClient;
/**
* <p>This class is a storage backend that uses an external <i>memcached</i>
@@ -54,28 +54,43 @@ import org.apache.http.impl.client.cache.DefaultHttpCacheEntrySerializer;
* <li>in-memory cached objects can survive an application restart since
* they are held in a separate process</li>
* <li>it becomes possible for several cooperating applications to share
- * a large <i>memcached</i> farm together, effectively providing cache
- * peering of a sort</li>
+ * a large <i>memcached</i> farm together</li>
* </ol>
* Note that in a shared memcached pool setting you may wish to make use
- * of the Ketama consistent hashing algorithm to reduce the number of
+ * of the Ketama consistent hashing algorithm to reduce the number of
* cache misses that might result if one of the memcached cluster members
* fails (see the <a href="http://dustin.github.com/java-memcached-client/apidocs/net/spy/memcached/KetamaConnectionFactory.html">
* KetamaConnectionFactory</a>).
* </p>
*
+ * <p>Because memcached places limits on the size of its keys, we need to
+ * introduce a key hashing scheme to map the annotated URLs the higher-level
+ * {@link CachingHttpClient} wants to use as keys onto ones that are suitable
+ * for use with memcached. Please see {@link KeyHashingScheme} if you would
+ * like to use something other than the provided {@link SHA256KeyHashingScheme}.</p>
+ *
+ * <p>Because this hashing scheme can potentially result in key collisions (though
+ * highly unlikely), we need to store the higher-level logical storage key along
+ * with the {@link HttpCacheEntry} so that we can re-check it on retrieval. There
+ * is a default serialization scheme provided for this, although you can provide
+ * your own implementations of {@link MemcachedCacheEntry} and
+ * {@link MemcachedCacheEntryFactory} to customize this serialization.</p>
+ *
* <p>Please refer to the <a href="http://code.google.com/p/memcached/wiki/NewStart">
* memcached documentation</a> and in particular to the documentation for
* the <a href="http://code.google.com/p/spymemcached/">spymemcached
* documentation</a> for details about how to set up and configure memcached
* and the Java client used here, respectively.</p>
- *
+ *
* @since 4.1
*/
public class MemcachedHttpCacheStorage implements HttpCacheStorage {
+ private static final Log log = LogFactory.getLog(MemcachedHttpCacheStorage.class);
+
private final MemcachedClientIF client;
- private final HttpCacheEntrySerializer serializer;
+ private final KeyHashingScheme keyHashingScheme;
+ private final MemcachedCacheEntryFactory memcachedCacheEntryFactory;
private final int maxUpdateRetries;
/**
@@ -84,7 +99,7 @@ public class MemcachedHttpCacheStorage implements HttpCacheStorage {
* just have a single local memcached instance running on the same
* machine as your application, for example.
* @param address where the <i>memcached</i> daemon is running
- * @throws IOException
+ * @throws IOException in case of an error
*/
public MemcachedHttpCacheStorage(InetSocketAddress address) throws IOException {
this(new MemcachedClient(address));
@@ -96,74 +111,163 @@ public class MemcachedHttpCacheStorage implements HttpCacheStorage {
* @param cache client to use for communicating with <i>memcached</i>
*/
public MemcachedHttpCacheStorage(MemcachedClientIF cache) {
- this(cache, new CacheConfig(), new DefaultHttpCacheEntrySerializer());
+ this(cache, new CacheConfig(), new MemcachedCacheEntryFactoryImpl(),
+ new SHA256KeyHashingScheme());
}
/**
* Create a storage backend using the given <i>memcached</i> client and
* applying the given cache configuration and cache entry serialization
- * mechanism.
+ * mechanism. <b>Deprecation note:</b> In the process of fixing a bug
+ * based on the need to hash logical storage keys onto memcached cache
+ * keys, the serialization process was revamped. This constructor still
+ * works, but the serializer argument will be ignored and default
+ * implementations of the new framework will be used. You can still
+ * provide custom serialization by using the
+ * {@link #MemcachedHttpCacheStorage(MemcachedClientIF, CacheConfig,
+ * MemcachedCacheEntryFactory, KeyHashingScheme)} constructor.
* @param client how to talk to <i>memcached</i>
* @param config apply HTTP cache-related options
- * @param serializer how to serialize the cache entries before writing
- * them out to <i>memcached</i>. The provided {@link
- * DefaultHttpCacheEntrySerializer} is a fine serialization mechanism
- * to use here.
+ * @param serializer <b>ignored</b>
+ *
+ * @deprecated (4.2) do not use
*/
+ @Deprecated
public MemcachedHttpCacheStorage(MemcachedClientIF client, CacheConfig config,
HttpCacheEntrySerializer serializer) {
+ this(client, config, new MemcachedCacheEntryFactoryImpl(),
+ new SHA256KeyHashingScheme());
+ }
+
+ /**
+ * Create a storage backend using the given <i>memcached</i> client and
+ * applying the given cache configuration, serialization, and hashing
+ * mechanisms.
+ * @param client how to talk to <i>memcached</i>
+ * @param config apply HTTP cache-related options
+ * @param memcachedCacheEntryFactory Factory pattern used for obtaining
+ * instances of alternative cache entry serialization mechanisms
+ * @param keyHashingScheme how to map higher-level logical "storage keys"
+ * onto "cache keys" suitable for use with memcached
+ */
+ public MemcachedHttpCacheStorage(MemcachedClientIF client, CacheConfig config,
+ MemcachedCacheEntryFactory memcachedCacheEntryFactory,
+ KeyHashingScheme keyHashingScheme) {
this.client = client;
this.maxUpdateRetries = config.getMaxUpdateRetries();
- this.serializer = serializer;
+ this.memcachedCacheEntryFactory = memcachedCacheEntryFactory;
+ this.keyHashingScheme = keyHashingScheme;
}
-
+
public void putEntry(String url, HttpCacheEntry entry) throws IOException {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- serializer.writeTo(entry, bos);
- client.set(url, 0, bos.toByteArray());
+ byte[] bytes = serializeEntry(url, entry);
+ String key = getCacheKey(url);
+ if (key == null) return;
+ try {
+ client.set(key, 0, bytes);
+ } catch (OperationTimeoutException ex) {
+ throw new MemcachedOperationTimeoutException(ex);
+ }
}
- public HttpCacheEntry getEntry(String url) throws IOException {
- byte[] data = (byte[]) client.get(url);
- if (null == data)
+ private String getCacheKey(String url) {
+ try {
+ return keyHashingScheme.hash(url);
+ } catch (MemcachedKeyHashingException mkhe) {
+ return null;
+ }
+ }
+
+ private byte[] serializeEntry(String url, HttpCacheEntry hce) throws IOException {
+ MemcachedCacheEntry mce = memcachedCacheEntryFactory.getMemcachedCacheEntry(url, hce);
+ try {
+ return mce.toByteArray();
+ } catch (MemcachedSerializationException mse) {
+ IOException ioe = new IOException();
+ ioe.initCause(mse);
+ throw ioe;
+ }
+ }
+
+ private byte[] convertToByteArray(Object o) {
+ if (o == null) return null;
+ if (!(o instanceof byte[])) {
+ log.warn("got a non-bytearray back from memcached: " + o);
return null;
- InputStream bis = new ByteArrayInputStream(data);
- return serializer.readFrom(bis);
+ }
+ return (byte[])o;
+ }
+
+ private MemcachedCacheEntry reconstituteEntry(Object o) throws IOException {
+ byte[] bytes = convertToByteArray(o);
+ if (bytes == null) return null;
+ MemcachedCacheEntry mce = memcachedCacheEntryFactory.getUnsetCacheEntry();
+ try {
+ mce.set(bytes);
+ } catch (MemcachedSerializationException mse) {
+ return null;
+ }
+ return mce;
+ }
+
+ public HttpCacheEntry getEntry(String url) throws IOException {
+ String key = getCacheKey(url);
+ if (key == null) return null;
+ try {
+ MemcachedCacheEntry mce = reconstituteEntry(client.get(key));
+ if (mce == null || !url.equals(mce.getStorageKey())) return null;
+ return mce.getHttpCacheEntry();
+ } catch (OperationTimeoutException ex) {
+ throw new MemcachedOperationTimeoutException(ex);
+ }
}
public void removeEntry(String url) throws IOException {
- client.delete(url);
+ String key = getCacheKey(url);
+ if (key == null) return;
+ try {
+ client.delete(key);
+ } catch (OperationTimeoutException ex) {
+ throw new MemcachedOperationTimeoutException(ex);
+ }
}
public void updateEntry(String url, HttpCacheUpdateCallback callback)
throws HttpCacheUpdateException, IOException {
int numRetries = 0;
- do{
-
- CASValue<Object> v = client.gets(url);
- byte[] oldBytes = (v != null) ? (byte[]) v.getValue() : null;
- HttpCacheEntry existingEntry = null;
- if (oldBytes != null) {
- ByteArrayInputStream bis = new ByteArrayInputStream(oldBytes);
- existingEntry = serializer.readFrom(bis);
+ String key = getCacheKey(url);
+ if (key == null) {
+ throw new HttpCacheUpdateException("couldn't generate cache key");
}
- HttpCacheEntry updatedEntry = callback.update(existingEntry);
-
- if (v == null) {
- putEntry(url, updatedEntry);
- return;
-
- } else {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- serializer.writeTo(updatedEntry, bos);
- CASResponse casResult = client.cas(url, v.getCas(), bos.toByteArray());
- if (casResult != CASResponse.OK) {
- numRetries++;
+ do {
+ try {
+ CASValue<Object> v = client.gets(key);
+ MemcachedCacheEntry mce = (v == null) ? null
+ : reconstituteEntry(v.getValue());
+ if (mce != null && (!url.equals(mce.getStorageKey()))) {
+ mce = null;
+ }
+ HttpCacheEntry existingEntry = (mce == null) ? null
+ : mce.getHttpCacheEntry();
+ HttpCacheEntry updatedEntry = callback.update(existingEntry);
+
+ if (existingEntry == null) {
+ putEntry(url, updatedEntry);
+ return;
+
+ } else {
+ byte[] updatedBytes = serializeEntry(url, updatedEntry);
+ CASResponse casResult = client.cas(key, v.getCas(),
+ updatedBytes);
+ if (casResult != CASResponse.OK) {
+ numRetries++;
+ } else return;
+ }
+ } catch (OperationTimeoutException ex) {
+ throw new MemcachedOperationTimeoutException(ex);
}
- else return;
- }
+ } while (numRetries <= maxUpdateRetries);
- } while(numRetries <= maxUpdateRetries);
- throw new HttpCacheUpdateException("Failed to update");
+ throw new HttpCacheUpdateException("Failed to update");
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedKeyHashingException.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedKeyHashingException.java
new file mode 100644
index 0000000..fb84425
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedKeyHashingException.java
@@ -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.impl.client.cache.memcached;
+
+/**
+ * Indicates a problem encountered when trying to map a
+ * logical "storage key" to a "cache key" suitable for use with
+ * memcached.
+ */
+public class MemcachedKeyHashingException extends RuntimeException {
+
+ private static final long serialVersionUID = -7553380015989141114L;
+
+ public MemcachedKeyHashingException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedOperationTimeoutException.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedOperationTimeoutException.java
new file mode 100644
index 0000000..7a5d147
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedOperationTimeoutException.java
@@ -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.impl.client.cache.memcached;
+
+import java.io.IOException;
+
+/**
+ * Raised when memcached times out on us.
+ */
+class MemcachedOperationTimeoutException extends IOException {
+
+ private static final long serialVersionUID = 1608334789051537010L;
+
+ public MemcachedOperationTimeoutException(final Throwable cause) {
+ super(cause.getMessage());
+ initCause(cause);
+ }
+
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedSerializationException.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedSerializationException.java
new file mode 100644
index 0000000..dd13580
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedSerializationException.java
@@ -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.impl.client.cache.memcached;
+
+/**
+ * Raised when there is a problem serializing or deserializing cache
+ * entries into a byte representation suitable for memcached storage.
+ */
+public class MemcachedSerializationException extends RuntimeException {
+
+ private static final long serialVersionUID = 2201652990656412236L;
+
+ public MemcachedSerializationException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/PrefixKeyHashingScheme.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/PrefixKeyHashingScheme.java
new file mode 100644
index 0000000..bc6ade3
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/PrefixKeyHashingScheme.java
@@ -0,0 +1,59 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache.memcached;
+
+
+/**
+ * This is a {@link KeyHashingScheme} decorator that simply adds
+ * a known prefix to the results of another <code>KeyHashingScheme</code>.
+ * Primarily useful for namespacing a shared memcached cluster, for
+ * example.
+ */
+public class PrefixKeyHashingScheme implements KeyHashingScheme {
+
+ private String prefix;
+ private KeyHashingScheme backingScheme;
+
+ /**
+ * Creates a new {@link KeyHashingScheme} that prepends the given
+ * prefix to the results of hashes from the given backing scheme.
+ * Users should be aware that memcached has a fixed maximum key
+ * length, so the combination of this prefix plus the results of
+ * the backing hashing scheme must still fit within these limits.
+ * @param prefix
+ * @param backingScheme
+ */
+ public PrefixKeyHashingScheme(String prefix, KeyHashingScheme backingScheme) {
+ this.prefix = prefix;
+ this.backingScheme = backingScheme;
+ }
+
+ public String hash(String storageKey) {
+ return prefix + backingScheme.hash(storageKey);
+ }
+
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/SHA256KeyHashingScheme.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/SHA256KeyHashingScheme.java
new file mode 100644
index 0000000..0de84ab
--- /dev/null
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/SHA256KeyHashingScheme.java
@@ -0,0 +1,61 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache.memcached;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This is a {@link KeyHashingScheme} based on the
+ * <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-256</a>
+ * algorithm. The hashes produced are hex-encoded SHA-256
+ * digests and hence are always 64-character hexadecimal
+ * strings.
+ */
+public class SHA256KeyHashingScheme implements KeyHashingScheme {
+
+ private static final Log log = LogFactory.getLog(SHA256KeyHashingScheme.class);
+
+ public String hash(String key) {
+ MessageDigest md = getDigest();
+ md.update(key.getBytes());
+ return Hex.encodeHexString(md.digest());
+ }
+
+ private MessageDigest getDigest() {
+ try {
+ return MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException nsae) {
+ log.error("can't find SHA-256 implementation for cache key hashing");
+ throw new MemcachedKeyHashingException(nsae);
+ }
+ }
+}
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/package.html b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/package.html
index a5fd731..72c3285 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/package.html
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/package.html
@@ -12,7 +12,7 @@ to you under the Apache License, Version 2.0 (the
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
@@ -31,7 +31,7 @@ information on the Apache Software Foundation, please see
<p>
This package contains a storage backend based on
-<a href="http://memcached.org/">memcached</a>
+<a href="http://memcached.org/">memcached</a>
that can be plugged into
a {@link org.apache.http.impl.client.cache.CachingHttpClient} and
used for storing cache entries.
diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/package.html b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/package.html
index 5847fa7..4c208e7 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/package.html
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/package.html
@@ -12,7 +12,7 @@ to you under the Apache License, Version 2.0 (the
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
diff --git a/httpclient-cache/src/site/site.xml b/httpclient-cache/src/site/site.xml
index 463453f..cc8f293 100644
--- a/httpclient-cache/src/site/site.xml
+++ b/httpclient-cache/src/site/site.xml
@@ -25,12 +25,15 @@
<http://www.apache.org/>.
-->
-<project name="HttpClient">
+<project xmlns="http://maven.apache.org/DECORATION/1.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.0.0 http://maven.apache.org/xsd/decoration-1.0.0.xsd"
+ name="HttpClient">
<body>
<menu name="HttpClient Overview">
<item name="Description" href="../index.html"/>
- <item name="Examples" href="../examples.html"/>
+ <item name="Quick Start" href="../quickstart.html"/>
</menu>
<menu ref="modules" />
<menu ref="reports"/>
diff --git a/httpclient-cache/src/test/java/org/apache/http/client/cache/TestHttpCacheEntry.java b/httpclient-cache/src/test/java/org/apache/http/client/cache/TestHttpCacheEntry.java
index 928067c..edf359d 100644
--- a/httpclient-cache/src/test/java/org/apache/http/client/cache/TestHttpCacheEntry.java
+++ b/httpclient-cache/src/test/java/org/apache/http/client/cache/TestHttpCacheEntry.java
@@ -178,16 +178,6 @@ public class TestHttpCacheEntry {
}
@Test
- public void mustProvideResource() {
- try {
- new HttpCacheEntry(new Date(), new Date(), statusLine,
- new Header[]{}, null);
- fail("Should have thrown exception");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
public void canRetrieveOriginalStatusLine() {
entry = new HttpCacheEntry(new Date(), new Date(), statusLine,
new Header[]{}, mockResource);
@@ -199,21 +189,21 @@ public class TestHttpCacheEntry {
entry = new HttpCacheEntry(new Date(), new Date(), statusLine,
new Header[]{}, mockResource);
assertSame(statusLine.getProtocolVersion(),
- entry.getProtocolVersion());
+ entry.getProtocolVersion());
}
@Test
public void reasonPhraseComesFromOriginalStatusLine() {
entry = new HttpCacheEntry(new Date(), new Date(), statusLine,
new Header[]{}, mockResource);
- assertSame(statusLine.getReasonPhrase(), entry.getReasonPhrase());
+ assertSame(statusLine.getReasonPhrase(), entry.getReasonPhrase());
}
@Test
public void statusCodeComesFromOriginalStatusLine() {
entry = new HttpCacheEntry(new Date(), new Date(), statusLine,
new Header[]{}, mockResource);
- assertEquals(statusLine.getStatusCode(), entry.getStatusCode());
+ assertEquals(statusLine.getStatusCode(), entry.getStatusCode());
}
@Test
@@ -221,7 +211,7 @@ public class TestHttpCacheEntry {
final Date requestDate = new Date();
entry = new HttpCacheEntry(requestDate, new Date(), statusLine,
new Header[]{}, mockResource);
- assertSame(requestDate, entry.getRequestDate());
+ assertSame(requestDate, entry.getRequestDate());
}
@Test
@@ -229,14 +219,14 @@ public class TestHttpCacheEntry {
final Date responseDate = new Date();
entry = new HttpCacheEntry(new Date(), responseDate, statusLine,
new Header[]{}, mockResource);
- assertSame(responseDate, entry.getResponseDate());
+ assertSame(responseDate, entry.getResponseDate());
}
@Test
public void canGetOriginalResource() {
entry = new HttpCacheEntry(new Date(), new Date(), statusLine,
new Header[]{}, mockResource);
- assertSame(mockResource, entry.getResource());
+ assertSame(mockResource, entry.getResource());
}
@Test
@@ -253,7 +243,7 @@ public class TestHttpCacheEntry {
assertEquals(headers[i], result[i]);
}
}
-
+
@Test
public void canConstructWithoutVariants() {
new HttpCacheEntry(new Date(), new Date(), statusLine,
@@ -266,7 +256,7 @@ public class TestHttpCacheEntry {
new Header[]{}, mockResource,
new HashMap<String,String>());
}
-
+
@Test
public void canRetrieveOriginalVariantMap() {
Map<String,String> variantMap = new HashMap<String,String>();
@@ -280,7 +270,7 @@ public class TestHttpCacheEntry {
assertEquals("B", result.get("A"));
assertEquals("D", result.get("C"));
}
-
+
@Test
public void retrievedVariantMapIsNotModifiable() {
Map<String,String> variantMap = new HashMap<String,String>();
@@ -301,7 +291,7 @@ public class TestHttpCacheEntry {
} catch (UnsupportedOperationException expected) {
}
}
-
+
@Test
public void canConvertToString() {
entry = new HttpCacheEntry(new Date(), new Date(), statusLine,
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java
index cab7158..b0c3ba5 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/AbstractProtocolTest.java
@@ -71,7 +71,7 @@ public abstract class AbstractProtocolTest {
params = new CacheConfig();
params.setMaxCacheEntries(MAX_ENTRIES);
- params.setMaxObjectSizeBytes(MAX_BYTES);
+ params.setMaxObjectSize(MAX_BYTES);
cache = new BasicHttpCache(params);
mockBackend = EasyMock.createMock(HttpClient.class);
mockCache = EasyMock.createMock(HttpCache.class);
@@ -113,7 +113,7 @@ public abstract class AbstractProtocolTest {
mockCache.flushInvalidatedCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class));
EasyMock.expectLastCall().anyTimes();
-
+
mockCache.flushInvalidatedCacheEntriesFor(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class), EasyMock.isA(HttpResponse.class));
EasyMock.expectLastCall().anyTimes();
}
@@ -127,4 +127,4 @@ public abstract class AbstractProtocolTest {
super();
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ConsumableInputStream.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ConsumableInputStream.java
new file mode 100644
index 0000000..a6dd3f4
--- /dev/null
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ConsumableInputStream.java
@@ -0,0 +1,58 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ConsumableInputStream extends InputStream {
+
+ private ByteArrayInputStream buf;
+ private boolean closed = false;
+
+ public ConsumableInputStream(ByteArrayInputStream buf) {
+ this.buf = buf;
+ }
+
+ public int read() throws IOException {
+ return buf.read();
+ }
+
+ @Override
+ public void close() {
+ closed = true;
+ try {
+ buf.close();
+ } catch (IOException e) {
+ }
+ }
+
+ public boolean wasClosed() {
+ return closed;
+ }
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/Counter.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/Counter.java
index ad9b22b..78e403e 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/Counter.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/Counter.java
@@ -38,4 +38,4 @@ public class Counter {
return count;
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java
index e3a5037..4ae512b 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DoNotTestProtocolRequirements.java
@@ -75,7 +75,7 @@ public class DoNotTestProtocolRequirements {
originResponse = make200Response();
CacheConfig params = new CacheConfig();
- params.setMaxObjectSizeBytes(MAX_BYTES);
+ params.setMaxObjectSize(MAX_BYTES);
params.setMaxCacheEntries(MAX_ENTRIES);
HttpCache cache = new BasicHttpCache(params);
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyHttpClient.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyHttpClient.java
new file mode 100644
index 0000000..b829c6b
--- /dev/null
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/DummyHttpClient.java
@@ -0,0 +1,142 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache;
+
+import java.io.IOException;
+
+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.ResponseHandler;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.impl.conn.SingleClientConnManager;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+
+ at SuppressWarnings("deprecation")
+public class DummyHttpClient implements HttpClient {
+
+ private HttpParams params = new BasicHttpParams();
+ private ClientConnectionManager connManager = new SingleClientConnManager();
+ private HttpRequest request;
+ private HttpResponse response = new BasicHttpResponse(new ProtocolVersion("HTTP",1,1), HttpStatus.SC_OK, "OK");
+ private int executions = 0;
+
+ public void setParams(HttpParams params) {
+ this.params = params;
+ }
+
+ public HttpParams getParams() {
+ return params;
+ }
+
+ public ClientConnectionManager getConnectionManager() {
+ return connManager;
+ }
+
+ public void setConnectionManager(ClientConnectionManager ccm) {
+ connManager = ccm;
+ }
+
+ public void setResponse(HttpResponse resp) {
+ response = resp;
+ }
+
+ public HttpRequest getCapturedRequest() {
+ return request;
+ }
+
+ public HttpResponse execute(HttpUriRequest request) throws IOException,
+ ClientProtocolException {
+ this.request = request;
+ executions++;
+ return response;
+ }
+
+ public HttpResponse execute(HttpUriRequest request, HttpContext context)
+ throws IOException, ClientProtocolException {
+ this.request = request;
+ executions++;
+ return response;
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request)
+ throws IOException, ClientProtocolException {
+ this.request = request;
+ executions++;
+ return response;
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request,
+ HttpContext context) throws IOException, ClientProtocolException {
+ this.request = request;
+ executions++;
+ return response;
+ }
+
+ public <T> T execute(HttpUriRequest request,
+ ResponseHandler<? extends T> responseHandler) throws IOException,
+ ClientProtocolException {
+ this.request = request;
+ executions++;
+ return responseHandler.handleResponse(response);
+ }
+
+ public <T> T execute(HttpUriRequest request,
+ ResponseHandler<? extends T> responseHandler, HttpContext context)
+ throws IOException, ClientProtocolException {
+ this.request = request;
+ executions++;
+ return responseHandler.handleResponse(response);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler) throws IOException,
+ ClientProtocolException {
+ this.request = request;
+ executions++;
+ return responseHandler.handleResponse(response);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler, HttpContext context)
+ throws IOException, ClientProtocolException {
+ this.request = request;
+ executions++;
+ return responseHandler.handleResponse(response);
+ }
+
+ public int getExecutions() {
+ return executions;
+ }
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/HttpTestUtils.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/HttpTestUtils.java
index 34d4ce6..8e15465 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/HttpTestUtils.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/HttpTestUtils.java
@@ -76,11 +76,11 @@ public class HttpTestUtils {
"If-None-Match", "If-Range", "If-Unmodified-Since", "Last-Modified", "Location",
"Max-Forwards", "Proxy-Authorization", "Range", "Referer", "Retry-After", "Server",
"User-Agent", "Vary" };
-
+
/*
* "Entity-header fields define metainformation about the entity-body or,
* if no body is present, about the resource identified by the request."
- *
+ *
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.1
*/
public static final String[] ENTITY_HEADERS = { "Allow", "Content-Encoding",
@@ -341,4 +341,4 @@ public class HttpTestUtils {
return new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/OKStatus.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/OKStatus.java
index c62e8da..8390450 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/OKStatus.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/OKStatus.java
@@ -38,4 +38,4 @@ public class OKStatus extends BasicStatusLine {
super(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/RequestEquivalent.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/RequestEquivalent.java
index 4e21102..4bfb65f 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/RequestEquivalent.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/RequestEquivalent.java
@@ -50,4 +50,4 @@ public class RequestEquivalent implements IArgumentMatcher {
buf.append(")");
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidationRequest.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidationRequest.java
index 12ffa6d..4821f2f 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidationRequest.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidationRequest.java
@@ -46,7 +46,7 @@ public class TestAsynchronousValidationRequest {
private HttpRequest request;
private HttpContext mockContext;
private HttpCacheEntry mockCacheEntry;
-
+
@Before
public void setUp() {
mockParent = EasyMock.createMock(AsynchronousValidator.class);
@@ -56,68 +56,68 @@ public class TestAsynchronousValidationRequest {
mockContext = EasyMock.createMock(HttpContext.class);
mockCacheEntry = EasyMock.createMock(HttpCacheEntry.class);
}
-
+
@Test
public void testRunCallsCachingClientAndRemovesIdentifier() throws ProtocolException, IOException {
String identifier = "foo";
-
+
AsynchronousValidationRequest asynchRequest = new AsynchronousValidationRequest(
mockParent, mockClient, target, request, mockContext, mockCacheEntry,
identifier);
-
+
// response not used
EasyMock.expect(mockClient.revalidateCacheEntry(target, request, mockContext, mockCacheEntry)).andReturn(null);
mockParent.markComplete(identifier);
-
+
replayMocks();
asynchRequest.run();
verifyMocks();
}
-
+
@Test
public void testRunGracefullyHandlesProtocolException() throws IOException, ProtocolException {
String identifier = "foo";
-
+
AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
mockParent, mockClient, target, request, mockContext, mockCacheEntry,
identifier);
-
+
// response not used
EasyMock.expect(
mockClient.revalidateCacheEntry(target, request, mockContext,
mockCacheEntry)).andThrow(new ProtocolException());
mockParent.markComplete(identifier);
-
+
replayMocks();
impl.run();
verifyMocks();
}
-
+
@Test
public void testRunGracefullyHandlesIOException() throws IOException, ProtocolException {
String identifier = "foo";
-
+
AsynchronousValidationRequest impl = new AsynchronousValidationRequest(
mockParent, mockClient, target, request, mockContext, mockCacheEntry,
identifier);
-
+
// response not used
EasyMock.expect(
mockClient.revalidateCacheEntry(target, request, mockContext,
mockCacheEntry)).andThrow(new IOException());
mockParent.markComplete(identifier);
-
+
replayMocks();
impl.run();
verifyMocks();
}
-
+
public void replayMocks() {
EasyMock.replay(mockClient);
EasyMock.replay(mockContext);
EasyMock.replay(mockCacheEntry);
}
-
+
public void verifyMocks() {
EasyMock.verify(mockClient);
EasyMock.verify(mockContext);
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidator.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidator.java
index db2364c..37cb75b 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidator.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestAsynchronousValidator.java
@@ -48,17 +48,17 @@ import org.junit.Before;
import org.junit.Test;
public class TestAsynchronousValidator {
-
+
private AsynchronousValidator impl;
-
+
private CachingHttpClient mockClient;
private HttpHost target;
private HttpRequest request;
private HttpContext mockContext;
private HttpCacheEntry mockCacheEntry;
-
+
private ExecutorService mockExecutor;
-
+
@Before
public void setUp() {
mockClient = EasyMock.createMock(CachingHttpClient.class);
@@ -66,138 +66,138 @@ public class TestAsynchronousValidator {
request = new HttpGet("/");
mockContext = EasyMock.createMock(HttpContext.class);
mockCacheEntry = EasyMock.createMock(HttpCacheEntry.class);
-
+
mockExecutor = EasyMock.createMock(ExecutorService.class);
-
+
}
-
+
@Test
public void testRevalidateCacheEntrySchedulesExecutionAndPopulatesIdentifier() {
impl = new AsynchronousValidator(mockClient, mockExecutor);
-
+
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
-
+
replayMocks();
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
verifyMocks();
-
+
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
}
-
+
@Test
public void testMarkCompleteRemovesIdentifier() {
impl = new AsynchronousValidator(mockClient, mockExecutor);
-
+
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
Capture<AsynchronousValidationRequest> cap = new Capture<AsynchronousValidationRequest>();
mockExecutor.execute(EasyMock.capture(cap));
-
+
replayMocks();
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
verifyMocks();
-
+
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
-
+
impl.markComplete(cap.getValue().getIdentifier());
-
+
Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
}
-
+
@Test
public void testRevalidateCacheEntryDoesNotPopulateIdentifierOnRejectedExecutionException() {
impl = new AsynchronousValidator(mockClient, mockExecutor);
-
+
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
EasyMock.expectLastCall().andThrow(new RejectedExecutionException());
-
+
replayMocks();
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
verifyMocks();
-
+
Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
}
-
+
@Test
public void testRevalidateCacheEntryProperlyCollapsesRequest() {
impl = new AsynchronousValidator(mockClient, mockExecutor);
-
+
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
-
+
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
-
+
replayMocks();
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
verifyMocks();
-
+
Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
}
-
+
@Test
public void testVariantsBothRevalidated() {
impl = new AsynchronousValidator(mockClient, mockExecutor);
-
+
HttpRequest req1 = new HttpGet("/");
req1.addHeader(new BasicHeader("Accept-Encoding", "identity"));
-
+
HttpRequest req2 = new HttpGet("/");
req2.addHeader(new BasicHeader("Accept-Encoding", "gzip"));
-
+
Header[] variantHeaders = new Header[] {
new BasicHeader(HeaderConstants.VARY, "Accept-Encoding")
};
-
+
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(true).times(2);
EasyMock.expect(mockCacheEntry.getHeaders(HeaderConstants.VARY)).andReturn(variantHeaders).times(2);
mockExecutor.execute(EasyMock.isA(AsynchronousValidationRequest.class));
EasyMock.expectLastCall().times(2);
-
+
replayMocks();
impl.revalidateCacheEntry(target, req1, mockContext, mockCacheEntry);
impl.revalidateCacheEntry(target, req2, mockContext, mockCacheEntry);
verifyMocks();
-
+
Assert.assertEquals(2, impl.getScheduledIdentifiers().size());
-
+
}
-
+
@Test
public void testRevalidateCacheEntryEndToEnd() throws ProtocolException, IOException {
CacheConfig config = new CacheConfig();
config.setAsynchronousWorkersMax(1);
config.setAsynchronousWorkersCore(1);
impl = new AsynchronousValidator(mockClient, config);
-
+
EasyMock.expect(mockCacheEntry.hasVariants()).andReturn(false);
EasyMock.expect(mockClient.revalidateCacheEntry(target, request, mockContext, mockCacheEntry)).andReturn(null);
-
+
replayMocks();
impl.revalidateCacheEntry(target, request, mockContext, mockCacheEntry);
-
+
try {
// shut down backend executor and make sure all finishes properly, 1 second should be sufficient
ExecutorService implExecutor = impl.getExecutor();
implExecutor.shutdown();
implExecutor.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException ie) {
-
+
} finally {
verifyMocks();
-
+
Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
}
}
-
+
public void replayMocks() {
EasyMock.replay(mockExecutor);
EasyMock.replay(mockClient);
EasyMock.replay(mockContext);
EasyMock.replay(mockCacheEntry);
}
-
+
public void verifyMocks() {
EasyMock.verify(mockExecutor);
EasyMock.verify(mockClient);
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestBasicHttpCache.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestBasicHttpCache.java
index 846907b..6a61d8b 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestBasicHttpCache.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestBasicHttpCache.java
@@ -234,7 +234,7 @@ public class TestBasicHttpCache {
Date responseReceived = new Date(now.getTime() - 1 * 1000L);
HttpResponse originResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
- originResponse.setEntity(HttpTestUtils.makeBody(CacheConfig.DEFAULT_MAX_OBJECT_SIZE_BYTES + 1));
+ originResponse.setEntity(HttpTestUtils.makeBody((int) CacheConfig.DEFAULT_MAX_OBJECT_SIZE_BYTES + 1));
originResponse.setHeader("Cache-Control","public, max-age=3600");
originResponse.setHeader("Date", DateUtils.formatDate(responseGenerated));
originResponse.setHeader("ETag", "\"etag\"");
@@ -256,7 +256,7 @@ public class TestBasicHttpCache {
Date responseReceived = new Date(now.getTime() - 1 * 1000L);
HttpResponse originResponse = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
- originResponse.setEntity(HttpTestUtils.makeBody(CacheConfig.DEFAULT_MAX_OBJECT_SIZE_BYTES - 1));
+ originResponse.setEntity(HttpTestUtils.makeBody((int) CacheConfig.DEFAULT_MAX_OBJECT_SIZE_BYTES - 1));
originResponse.setHeader("Cache-Control","public, max-age=3600");
originResponse.setHeader("Date", DateUtils.formatDate(responseGenerated));
originResponse.setHeader("ETag", "\"etag\"");
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java
index 3743f08..5e5b7fa 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java
@@ -94,7 +94,7 @@ public class TestCacheEntryUpdater {
HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry,
new Date(), new Date(), response);
-
+
Header[] updatedHeaders = updatedEntry.getAllHeaders();
assertEquals(2, updatedHeaders.length);
headersContain(updatedHeaders, "Date", formatDate(responseDate));
@@ -159,7 +159,7 @@ public class TestCacheEntryUpdater {
new BasicHeader("Date", formatDate(oneSecondAgo)),
new BasicHeader("ETag", "\"new-etag\"")
};
- entry = HttpTestUtils.makeCacheEntry(twoSecondsAgo, now, headers);
+ entry = HttpTestUtils.makeCacheEntry(twoSecondsAgo, now, headers);
response.setHeader("Date", formatDate(tenSecondsAgo));
response.setHeader("ETag", "\"old-etag\"");
HttpCacheEntry result = impl.updateCacheEntry("A", entry, new Date(),
@@ -179,7 +179,7 @@ public class TestCacheEntryUpdater {
assertEquals(twoSecondsAgo, updated.getRequestDate());
assertEquals(oneSecondAgo, updated.getResponseDate());
}
-
+
@Test
public void entry1xxWarningsAreRemovedOnUpdate() throws Exception {
Header[] headers = {
@@ -195,7 +195,7 @@ public class TestCacheEntryUpdater {
assertEquals(0, updated.getHeaders("Warning").length);
}
-
+
@Test
public void entryWithMalformedDateIsStillUpdated() throws Exception {
Header[] headers = {
@@ -225,7 +225,7 @@ public class TestCacheEntryUpdater {
assertEquals("\"new\"", updated.getFirstHeader("ETag").getValue());
}
-
+
@Test
public void cannotUpdateFromANon304OriginResponse() throws Exception {
entry = HttpTestUtils.makeCacheEntry();
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheInvalidator.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheInvalidator.java
index 9d2cc21..a86f13f 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheInvalidator.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheInvalidator.java
@@ -71,7 +71,7 @@ public class TestCacheInvalidator {
public void setUp() {
now = new Date();
tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
-
+
host = new HttpHost("foo.example.com");
mockStorage = createMock(HttpCacheStorage.class);
cacheKeyGenerator = new CacheKeyGenerator();
@@ -265,7 +265,7 @@ public class TestCacheInvalidator {
impl.flushInvalidatedCacheEntries(host, request);
verifyMocks();
}
-
+
@Test
public void doesNotFlushForResponsesWithoutContentLocation()
throws Exception {
@@ -273,7 +273,7 @@ public class TestCacheInvalidator {
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
}
-
+
@Test
public void flushesEntryIfFresherAndSpecifiedByContentLocation()
throws Exception {
@@ -281,20 +281,20 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
-
+
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("Date", formatDate(tenSecondsAgo)),
new BasicHeader("ETag", "\"old-etag\"")
});
-
+
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
mockStorage.removeEntry(theURI);
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
}
-
+
@Test
public void doesNotFlushEntryForUnsuccessfulResponse()
throws Exception {
@@ -303,14 +303,14 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
-
+
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("Date", formatDate(tenSecondsAgo)),
new BasicHeader("ETag", "\"old-etag\"")
});
-
+
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
@@ -323,15 +323,15 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String cacheKey = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", "http://foo.example.com/bar");
-
+
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("Date", formatDate(tenSecondsAgo)),
new BasicHeader("ETag", "\"old-etag\"")
});
-
+
expect(mockStorage.getEntry(cacheKey)).andReturn(entry).anyTimes();
mockStorage.removeEntry(cacheKey);
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
@@ -344,15 +344,15 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String cacheKey = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", "/bar");
-
+
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("Date", formatDate(tenSecondsAgo)),
new BasicHeader("ETag", "\"old-etag\"")
});
-
+
expect(mockStorage.getEntry(cacheKey)).andReturn(entry).anyTimes();
mockStorage.removeEntry(cacheKey);
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
@@ -365,21 +365,21 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String cacheKey = "http://baz.example.com:80/bar";
response.setHeader("Content-Location", cacheKey);
-
+
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("Date", formatDate(tenSecondsAgo)),
new BasicHeader("ETag", "\"old-etag\"")
});
-
+
expect(mockStorage.getEntry(cacheKey)).andReturn(entry).anyTimes();
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
}
-
-
+
+
@Test
public void doesNotFlushEntrySpecifiedByContentLocationIfEtagsMatch()
throws Exception {
@@ -387,14 +387,14 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
-
+
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("Date", formatDate(tenSecondsAgo)),
new BasicHeader("ETag", "\"same-etag\"")
});
-
+
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
@@ -407,19 +407,19 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
-
+
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("Date", formatDate(now)),
new BasicHeader("ETag", "\"old-etag\"")
});
-
+
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
}
-
+
@Test
public void doesNotFlushEntryIfNotInCache()
throws Exception {
@@ -427,14 +427,14 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
-
+
expect(mockStorage.getEntry(theURI)).andReturn(null).anyTimes();
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
}
-
+
@Test
public void doesNotFlushEntrySpecifiedByContentLocationIfResponseHasNoEtag()
throws Exception {
@@ -442,19 +442,19 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
-
+
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("Date", formatDate(tenSecondsAgo)),
new BasicHeader("ETag", "\"old-etag\"")
});
-
+
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
}
-
+
@Test
public void doesNotFlushEntrySpecifiedByContentLocationIfEntryHasNoEtag()
throws Exception {
@@ -462,13 +462,13 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
-
+
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("Date", formatDate(tenSecondsAgo)),
});
-
+
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
@@ -481,19 +481,19 @@ public class TestCacheInvalidator {
response.removeHeaders("Date");
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
-
+
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("ETag", "\"old-etag\""),
new BasicHeader("Date", formatDate(tenSecondsAgo)),
});
-
+
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
}
-
+
@Test
public void doesNotFlushEntrySpecifiedByContentLocationIfEntryHasNoDate()
throws Exception {
@@ -501,13 +501,13 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
-
+
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("ETag", "\"old-etag\"")
});
-
+
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
@@ -520,19 +520,19 @@ public class TestCacheInvalidator {
response.setHeader("Date", "blarg");
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
-
- HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
+
+ HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("ETag", "\"old-etag\""),
new BasicHeader("Date", formatDate(tenSecondsAgo))
});
-
+
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
}
-
+
@Test
public void doesNotFlushEntrySpecifiedByContentLocationIfEntryHasMalformedDate()
throws Exception {
@@ -540,20 +540,20 @@ public class TestCacheInvalidator {
response.setHeader("Date", formatDate(now));
String theURI = "http://foo.example.com:80/bar";
response.setHeader("Content-Location", theURI);
-
- HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
+
+ HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
new BasicHeader("ETag", "\"old-etag\""),
new BasicHeader("Date", "foo")
});
-
+
expect(mockStorage.getEntry(theURI)).andReturn(entry).anyTimes();
-
+
replayMocks();
impl.flushInvalidatedCacheEntries(host, request, response);
verifyMocks();
}
-
+
// Expectations
private void cacheEntryHasVariantMap(Map<String,String> variantMap) {
expect(mockEntry.getVariantMap()).andReturn(variantMap);
@@ -572,4 +572,4 @@ public class TestCacheInvalidator {
mockStorage.removeEntry(theUri);
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheValidityPolicy.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheValidityPolicy.java
index 412e328..3085643 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheValidityPolicy.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheValidityPolicy.java
@@ -37,6 +37,7 @@ import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.protocol.HTTP;
import org.junit.Before;
import org.junit.Test;
@@ -137,12 +138,7 @@ public class TestCacheValidityPolicy {
@Test
public void testResidentTimeSecondsIsTimeSinceResponseTime() {
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, sixSecondsAgo);
- impl = new CacheValidityPolicy() {
- @Override
- protected Date getCurrentDate() {
- return now;
- }
- };
+ impl = new CacheValidityPolicy();
assertEquals(6, impl.getResidentTimeSecs(entry, now));
}
@@ -332,6 +328,30 @@ public class TestCacheValidityPolicy {
}
@Test
+ public void testMissingContentLengthDoesntInvalidateEntry() {
+ final int contentLength = 128;
+ Header[] headers = {}; // no Content-Length header
+ HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
+ assertTrue(impl.contentLengthHeaderMatchesActualLength(entry));
+ }
+
+ @Test
+ public void testCorrectContentLengthDoesntInvalidateEntry() {
+ final int contentLength = 128;
+ Header[] headers = { new BasicHeader(HTTP.CONTENT_LEN, Integer.toString(contentLength)) };
+ HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
+ assertTrue(impl.contentLengthHeaderMatchesActualLength(entry));
+ }
+
+ @Test
+ public void testWrongContentLengthInvalidatesEntry() {
+ final int contentLength = 128;
+ Header[] headers = {new BasicHeader(HTTP.CONTENT_LEN, Integer.toString(contentLength+1))};
+ HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers, HttpTestUtils.getRandomBytes(contentLength));
+ assertFalse(impl.contentLengthHeaderMatchesActualLength(entry));
+ }
+
+ @Test
public void testMalformedDateHeaderIsIgnored() {
Header[] headers = new Header[] { new BasicHeader("Date", "asdf") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
@@ -411,7 +431,7 @@ public class TestCacheValidityPolicy {
HttpRequest req = new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1);
assertTrue(impl.mayReturnStaleIfError(req, entry, now));
}
-
+
@Test
public void testMayReturnStaleIfErrorInRequestIsTrueWithinStaleness(){
Header[] headers = new Header[] {
@@ -450,15 +470,15 @@ public class TestCacheValidityPolicy {
@Test
public void testMayReturnStaleWhileRevalidatingIsFalseWhenDirectiveIsAbsent() {
Date now = new Date();
-
+
Header[] headers = new Header[] { new BasicHeader("Cache-control", "public") };
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(headers);
-
+
CacheValidityPolicy impl = new CacheValidityPolicy();
-
+
assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
}
-
+
@Test
public void testMayReturnStaleWhileRevalidatingIsTrueWhenWithinStaleness() {
Date now = new Date();
@@ -468,12 +488,12 @@ public class TestCacheValidityPolicy {
new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=15")
};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
-
+
CacheValidityPolicy impl = new CacheValidityPolicy();
-
+
assertTrue(impl.mayReturnStaleWhileRevalidating(entry, now));
}
-
+
@Test
public void testMayReturnStaleWhileRevalidatingIsFalseWhenPastStaleness() {
Date now = new Date();
@@ -483,12 +503,12 @@ public class TestCacheValidityPolicy {
new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=15")
};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
-
+
CacheValidityPolicy impl = new CacheValidityPolicy();
-
+
assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
}
-
+
@Test
public void testMayReturnStaleWhileRevalidatingIsFalseWhenDirectiveEmpty() {
Date now = new Date();
@@ -498,9 +518,9 @@ public class TestCacheValidityPolicy {
new BasicHeader("Cache-Control", "max-age=5, stale-while-revalidate=")
};
HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(now, now, headers);
-
+
CacheValidityPolicy impl = new CacheValidityPolicy();
-
+
assertFalse(impl.mayReturnStaleWhileRevalidating(entry, now));
}
}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedResponseSuitabilityChecker.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedResponseSuitabilityChecker.java
index 782990b..b2c7bb0 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedResponseSuitabilityChecker.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachedResponseSuitabilityChecker.java
@@ -260,4 +260,4 @@ public class TestCachedResponseSuitabilityChecker {
Assert.assertTrue(impl.canCachedResponseBeUsed(host, request, entry, now));
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java
index 787a9fa..91b42f8 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCachingHttpClient.java
@@ -26,6 +26,7 @@
*/
package org.apache.http.impl.client.cache;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
@@ -34,6 +35,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.http.Header;
+import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
@@ -50,6 +52,7 @@ import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpRequest;
@@ -57,6 +60,7 @@ import org.apache.http.message.BasicHttpResponse;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.easymock.Capture;
import static org.easymock.classextension.EasyMock.*;
@@ -106,6 +110,7 @@ public class TestCachingHttpClient {
private HttpContext context;
private HttpParams params;
private HttpCacheEntry entry;
+ private ConsumableInputStream cis;
@SuppressWarnings("unchecked")
@Before
@@ -203,16 +208,16 @@ public class TestCachingHttpClient {
@Test
public void testCacheableResponsesGoIntoCache() throws Exception {
impl = new CachingHttpClient(mockBackend);
-
+
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control","max-age=3600");
-
+
expect(mockBackend.execute(isA(HttpHost.class), isA(HttpRequest.class),
(HttpContext)isNull())).andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
-
+
replayMocks();
impl.execute(host, req1);
impl.execute(host, req2);
@@ -224,7 +229,7 @@ public class TestCachingHttpClient {
impl = new CachingHttpClient(mockBackend);
Date now = new Date();
Date fiveSecondsAgo = new Date(now.getTime() - 5 * 1000L);
-
+
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", DateUtils.formatDate(now));
@@ -233,7 +238,7 @@ public class TestCachingHttpClient {
expect(mockBackend.execute(isA(HttpHost.class), isA(HttpRequest.class),
(HttpContext)isNull())).andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control","no-cache");
HttpResponse resp2 = HttpTestUtils.make200Response();
@@ -260,7 +265,7 @@ public class TestCachingHttpClient {
impl = new CachingHttpClient(mockBackend);
Date now = new Date();
Date fiveSecondsAgo = new Date(now.getTime() - 5 * 1000L);
-
+
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", DateUtils.formatDate(fiveSecondsAgo));
@@ -269,7 +274,7 @@ public class TestCachingHttpClient {
expect(mockBackend.execute(isA(HttpHost.class), isA(HttpRequest.class),
(HttpContext)isNull())).andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control","max-age=0");
HttpResponse resp2 = HttpTestUtils.make200Response();
@@ -486,7 +491,7 @@ public class TestCachingHttpClient {
impl = new CachingHttpClient(mockBackend,
new BasicHttpCache(new HeapResourceFactory(), mockStorage, config),
config);
-
+
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control","no-cache");
@@ -620,12 +625,61 @@ public class TestCachingHttpClient {
expect(mockHandler.handleResponse(mockBackendResponse)).andReturn(
theObject);
-
+ HttpEntity streamingEntity = makeStreamingEntity();
+ expect(theResponse.getEntity()).andReturn(streamingEntity);
replayMocks();
Object result = impl.execute(host, request, mockHandler, context);
verifyMocks();
Assert.assertEquals(1, c.getCount());
Assert.assertSame(theObject, result);
+ Assert.assertTrue(cis.wasClosed());
+ }
+
+ @Test
+ public void testConsumesEntityOnExecuteWithException() throws Exception {
+
+ final Counter c = new Counter();
+ final HttpHost theHost = host;
+ final HttpRequest theRequest = request;
+ final HttpResponse theResponse = mockBackendResponse;
+ final HttpContext theContext = context;
+ impl = new CachingHttpClient(
+ mockBackend,
+ mockValidityPolicy,
+ mockResponsePolicy,
+ mockCache,
+ mockResponseGenerator,
+ mockRequestPolicy,
+ mockSuitabilityChecker,
+ mockConditionalRequestBuilder,
+ mockResponseProtocolCompliance,
+ mockRequestProtocolCompliance) {
+ @Override
+ public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) {
+ Assert.assertSame(theHost, target);
+ Assert.assertSame(theRequest, request);
+ Assert.assertSame(theContext, context);
+ c.incr();
+ return theResponse;
+ }
+ };
+ IOException ioException = new IOException("test exception");
+
+ expect(mockHandler.handleResponse(mockBackendResponse)).andThrow(ioException);
+ HttpEntity streamingEntity = makeStreamingEntity();
+ expect(theResponse.getEntity()).andReturn(streamingEntity).anyTimes();
+
+ replayMocks();
+ Object result = null;
+ try {
+ result = impl.execute(host, request, mockHandler, context);
+ } catch (Exception e) {
+ assertEquals(ioException, e);
+ }
+ verifyMocks();
+ Assert.assertEquals(1, c.getCount());
+ Assert.assertNull(result);
+ assertTrue(cis.wasClosed());
}
@Test
@@ -769,15 +823,16 @@ public class TestCachingHttpClient {
return theResponse;
}
};
-
expect(mockHandler.handleResponse(mockBackendResponse)).andReturn(
theValue);
-
+ HttpEntity streamingEntity = makeStreamingEntity();
+ expect(theResponse.getEntity()).andReturn(streamingEntity);
replayMocks();
Object result = impl.execute(mockUriRequest, mockHandler, context);
verifyMocks();
Assert.assertEquals(1, c.getCount());
Assert.assertSame(theValue, result);
+ Assert.assertTrue(cis.wasClosed());
}
@Test
@@ -1012,8 +1067,8 @@ public class TestCachingHttpClient {
resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
replayMocks();
@@ -1048,8 +1103,8 @@ public class TestCachingHttpClient {
HttpResponse resp2 = HttpTestUtils.make200Response();
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
expect(
mockBackend.execute(isA(HttpHost.class),
@@ -1088,8 +1143,8 @@ public class TestCachingHttpClient {
.formatDate(tenSecondsAfter));
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1).times(2);
replayMocks();
@@ -1116,8 +1171,8 @@ public class TestCachingHttpClient {
resp1.setHeader("Cache-Control", "public, max-age=3600");
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
replayMocks();
@@ -1147,12 +1202,12 @@ public class TestCachingHttpClient {
HttpResponse resp2 = HttpTestUtils.make200Response();
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp2);
replayMocks();
@@ -1185,8 +1240,8 @@ public class TestCachingHttpClient {
req2.addHeader("If-Modified-Since", DateUtils.formatDate(now));
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
replayMocks();
@@ -1217,12 +1272,12 @@ public class TestCachingHttpClient {
resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
replayMocks();
@@ -1432,13 +1487,13 @@ public class TestCachingHttpClient {
resp2.setHeader("Date", DateUtils.formatDate(now));
resp2.setHeader("Cache-Control", "public, max-age=5");
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp2);
replayMocks();
impl.execute(host, req1);
@@ -1476,13 +1531,13 @@ public class TestCachingHttpClient {
resp2.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
resp2.setHeader("Cache-Control", "public, max-age=5");
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp2);
replayMocks();
impl.execute(host, req1);
@@ -1520,13 +1575,13 @@ public class TestCachingHttpClient {
resp1.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
resp2.setHeader("Cache-Control", "public, max-age=5");
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp2);
replayMocks();
impl.execute(host, req1);
@@ -1565,13 +1620,13 @@ public class TestCachingHttpClient {
resp1.setHeader("Last-Modified", DateUtils.formatDate(now));
resp2.setHeader("Cache-Control", "public, max-age=5");
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp2);
replayMocks();
impl.execute(host, req1);
@@ -1628,18 +1683,18 @@ public class TestCachingHttpClient {
resp3.setHeader("Cache-Control", "public, max-age=3600");
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp2);
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp3);
@@ -1702,18 +1757,18 @@ public class TestCachingHttpClient {
resp4.setHeader("Cache-Control", "public, max-age=3600");
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp1);
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp2);
expect(
- mockBackend.execute(isA(HttpHost.class),
- isA(HttpRequest.class), (HttpContext)
+ mockBackend.execute(isA(HttpHost.class),
+ isA(HttpRequest.class), (HttpContext)
isNull())).andReturn(resp4);
replayMocks();
@@ -1744,7 +1799,7 @@ public class TestCachingHttpClient {
mockCache.flushInvalidatedCacheEntriesFor(host, request);
expectLastCall().andThrow(new IOException()).anyTimes();
mockCache.flushInvalidatedCacheEntriesFor(isA(HttpHost.class), isA(HttpRequest.class), isA(HttpResponse.class));
- expectLastCall().anyTimes();
+ expectLastCall().anyTimes();
expect(mockCache.getCacheEntry(same(host),
isA(HttpRequest.class)))
.andThrow(new IOException()).anyTimes();
@@ -1817,7 +1872,150 @@ public class TestCachingHttpClient {
Assert.assertSame(mockCachedResponse, resp);
}
-
+
+ private DummyHttpClient getContextSettingBackend(final Object value, final HttpResponse response) {
+ return new DummyHttpClient() {
+ @Override
+ public HttpResponse execute(HttpHost target, HttpRequest request,
+ HttpContext context) throws IOException, ClientProtocolException {
+ if (context != null) {
+ context.setAttribute(ExecutionContext.HTTP_CONNECTION, value);
+ context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, value);
+ context.setAttribute(ExecutionContext.HTTP_REQ_SENT, value);
+ context.setAttribute(ExecutionContext.HTTP_REQUEST, value);
+ context.setAttribute(ExecutionContext.HTTP_RESPONSE, value);
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, value);
+ }
+ return response;
+ }
+ };
+ }
+
+ private void assertAllContextVariablesAreEqualTo(HttpContext ctx,
+ final Object value) {
+ assertSame(value, ctx.getAttribute(ExecutionContext.HTTP_CONNECTION));
+ assertSame(value, ctx.getAttribute(ExecutionContext.HTTP_PROXY_HOST));
+ assertSame(value, ctx.getAttribute(ExecutionContext.HTTP_REQ_SENT));
+ assertSame(value, ctx.getAttribute(ExecutionContext.HTTP_REQUEST));
+ assertSame(value, ctx.getAttribute(ExecutionContext.HTTP_RESPONSE));
+ assertSame(value, ctx.getAttribute(ExecutionContext.HTTP_TARGET_HOST));
+ }
+
+ @Test
+ public void testAllowsBackendToSetHttpContextVariablesOnCacheMiss() throws Exception {
+ final Object value = new Object();
+ impl = new CachingHttpClient(getContextSettingBackend(value, HttpTestUtils.make200Response()));
+ HttpContext ctx = new BasicHttpContext();
+ impl.execute(host, request, ctx);
+ assertAllContextVariablesAreEqualTo(ctx, value);
+ }
+
+ @Test
+ public void testDoesNotSetConnectionInContextOnCacheHit() throws Exception {
+ DummyHttpClient backend = new DummyHttpClient();
+ HttpResponse response = HttpTestUtils.make200Response();
+ response.setHeader("Cache-Control", "max-age=3600");
+ backend.setResponse(response);
+ impl = new CachingHttpClient(backend);
+ HttpContext ctx = new BasicHttpContext();
+ impl.execute(host, request);
+ impl.execute(host, request, ctx);
+ assertNull(ctx.getAttribute(ExecutionContext.HTTP_CONNECTION));
+ }
+
+ @Test
+ public void testDoesNotSetProxyHostInContextOnCacheHit() throws Exception {
+ DummyHttpClient backend = new DummyHttpClient();
+ HttpResponse response = HttpTestUtils.make200Response();
+ response.setHeader("Cache-Control", "max-age=3600");
+ backend.setResponse(response);
+ impl = new CachingHttpClient(backend);
+ HttpContext ctx = new BasicHttpContext();
+ impl.execute(host, request);
+ impl.execute(host, request, ctx);
+ assertNull(ctx.getAttribute(ExecutionContext.HTTP_PROXY_HOST));
+ }
+
+ @Test
+ public void testSetsTargetHostInContextOnCacheHit() throws Exception {
+ DummyHttpClient backend = new DummyHttpClient();
+ HttpResponse response = HttpTestUtils.make200Response();
+ response.setHeader("Cache-Control", "max-age=3600");
+ backend.setResponse(response);
+ impl = new CachingHttpClient(backend);
+ HttpContext ctx = new BasicHttpContext();
+ impl.execute(host, request);
+ impl.execute(host, request, ctx);
+ assertSame(host, ctx.getAttribute(ExecutionContext.HTTP_TARGET_HOST));
+ }
+
+ @Test
+ public void testSetsRequestInContextOnCacheHit() throws Exception {
+ DummyHttpClient backend = new DummyHttpClient();
+ HttpResponse response = HttpTestUtils.make200Response();
+ response.setHeader("Cache-Control", "max-age=3600");
+ backend.setResponse(response);
+ impl = new CachingHttpClient(backend);
+ HttpContext ctx = new BasicHttpContext();
+ impl.execute(host, request);
+ impl.execute(host, request, ctx);
+ assertSame(request, ctx.getAttribute(ExecutionContext.HTTP_REQUEST));
+ }
+
+ @Test
+ public void testSetsResponseInContextOnCacheHit() throws Exception {
+ DummyHttpClient backend = new DummyHttpClient();
+ HttpResponse response = HttpTestUtils.make200Response();
+ response.setHeader("Cache-Control", "max-age=3600");
+ backend.setResponse(response);
+ impl = new CachingHttpClient(backend);
+ HttpContext ctx = new BasicHttpContext();
+ impl.execute(host, request);
+ HttpResponse result = impl.execute(host, request, ctx);
+ assertSame(result, ctx.getAttribute(ExecutionContext.HTTP_RESPONSE));
+ }
+
+ @Test
+ public void testSetsRequestSentInContextOnCacheHit() throws Exception {
+ DummyHttpClient backend = new DummyHttpClient();
+ HttpResponse response = HttpTestUtils.make200Response();
+ response.setHeader("Cache-Control", "max-age=3600");
+ backend.setResponse(response);
+ impl = new CachingHttpClient(backend);
+ HttpContext ctx = new BasicHttpContext();
+ impl.execute(host, request);
+ impl.execute(host, request, ctx);
+ assertTrue((Boolean)ctx.getAttribute(ExecutionContext.HTTP_REQ_SENT));
+ }
+
+ @Test
+ public void testAllowsBackendToSetContextVariablesOnRevalidation() throws Exception {
+ final Object value = new Object();
+ HttpResponse response = HttpTestUtils.make200Response();
+ response.setHeader("Cache-Control","public");
+ response.setHeader("Etag", "\"v1\"");
+ DummyHttpClient backend = getContextSettingBackend(value, response);
+ backend.setResponse(response);
+ impl = new CachingHttpClient(backend);
+ HttpContext ctx = new BasicHttpContext();
+ impl.execute(host, request);
+ impl.execute(host, request, ctx);
+ assertAllContextVariablesAreEqualTo(ctx, value);
+ }
+
+ @Test
+ public void testCanCacheAResponseWithoutABody() throws Exception {
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_NO_CONTENT, "No Content");
+ response.setHeader("Date", DateUtils.formatDate(new Date()));
+ response.setHeader("Cache-Control","max-age=300");
+ DummyHttpClient backend = new DummyHttpClient();
+ backend.setResponse(response);
+ impl = new CachingHttpClient(backend);
+ impl.execute(host, request);
+ impl.execute(host, request);
+ assertEquals(1, backend.getExecutions());
+ }
+
private void getCacheEntryReturns(HttpCacheEntry result) throws IOException {
expect(mockCache.getCacheEntry(host, request)).andReturn(result);
}
@@ -1853,7 +2051,7 @@ public class TestCachingHttpClient {
expect(mockValidityPolicy.isRevalidatable(
(HttpCacheEntry)anyObject())).andReturn(b);
}
-
+
private void cacheEntryMustRevalidate(boolean b) {
expect(mockValidityPolicy.mustRevalidate(mockCacheEntry))
.andReturn(b);
@@ -1863,7 +2061,7 @@ public class TestCachingHttpClient {
expect(mockValidityPolicy.proxyRevalidate(mockCacheEntry))
.andReturn(b);
}
-
+
private void mayReturnStaleWhileRevalidating(boolean b) {
expect(mockValidityPolicy.mayReturnStaleWhileRevalidating(
(HttpCacheEntry)anyObject(), (Date)anyObject())).andReturn(b);
@@ -1942,6 +2140,13 @@ public class TestCachingHttpClient {
(HttpRequest)anyObject())).andThrow(exception);
}
+ private HttpEntity makeStreamingEntity() {
+ byte[] body = HttpTestUtils.getRandomBytes(101);
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+ cis = new ConsumableInputStream(buf);
+ return new InputStreamEntity(cis, 101);
+ }
+
private void mockImplMethods(String... methods) {
mockedImpl = true;
impl = createMockBuilder(CachingHttpClient.class).withConstructor(
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java
index 6536d1d..9d0ca15 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java
@@ -54,12 +54,12 @@ public class TestHttpCacheEntrySerializers {
private static final Charset UTF8 = Charset.forName("UTF-8");
private HttpCacheEntrySerializer impl;
-
+
@Before
public void setUp() {
impl = new DefaultHttpCacheEntrySerializer();
}
-
+
@Test
public void canSerializeEntriesWithVariantMaps() throws Exception {
readWriteVerify(makeCacheEntryWithVariantMap());
@@ -96,7 +96,7 @@ public class TestHttpCacheEntrySerializers {
return cacheEntry;
}
-
+
private boolean areEqual(HttpCacheEntry one, HttpCacheEntry two) throws IOException {
// dates are only stored with second precision, so scrub milliseconds
if (!((one.getRequestDate().getTime() / 1000) == (two.getRequestDate()
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheJiraNumber1147.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheJiraNumber1147.java
new file mode 100644
index 0000000..78f80c7
--- /dev/null
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheJiraNumber1147.java
@@ -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.impl.client.cache;
+
+import java.io.File;
+import java.util.Date;
+
+import junit.framework.Assert;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.cache.HttpCacheStorage;
+import org.apache.http.client.cache.ResourceFactory;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.cache.CacheConfig;
+import org.apache.http.impl.client.cache.CachingHttpClient;
+import org.apache.http.impl.client.cache.FileResourceFactory;
+import org.apache.http.impl.client.cache.ManagedHttpCacheStorage;
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestHttpCacheJiraNumber1147 {
+
+ private File cacheDir;
+
+ private void removeCache() {
+ if (this.cacheDir != null) {
+ File[] files = this.cacheDir.listFiles();
+ for (File cacheFile : files) {
+ cacheFile.delete();
+ }
+ this.cacheDir.delete();
+ this.cacheDir = null;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ cacheDir = File.createTempFile("cachedir", "");
+ if (cacheDir.exists()) {
+ cacheDir.delete();
+ }
+ cacheDir.mkdir();
+ }
+
+ @After
+ public void cleanUp() {
+ removeCache();
+ }
+
+ @Test
+ public void testIssue1147() throws Exception {
+ CacheConfig cacheConfig = new CacheConfig();
+ cacheConfig.setSharedCache(true);
+ cacheConfig.setMaxObjectSize(262144); //256kb
+
+ ResourceFactory resourceFactory = new FileResourceFactory(cacheDir);
+ HttpCacheStorage httpCacheStorage = new ManagedHttpCacheStorage(cacheConfig);
+
+ HttpClient client = EasyMock.createMock(HttpClient.class);
+ HttpGet get = new HttpGet("http://somehost/");
+ HttpContext context = new BasicHttpContext();
+ HttpHost target = new HttpHost("somehost");
+
+ Date now = new Date();
+ Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
+
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK");
+ response.setEntity(HttpTestUtils.makeBody(128));
+ response.setHeader("Content-Length", "128");
+ response.setHeader("ETag", "\"etag\"");
+ response.setHeader("Cache-Control", "public, max-age=3600");
+ response.setHeader("Last-Modified", DateUtils.formatDate(tenSecondsAgo));
+
+ EasyMock.expect(client.execute(
+ EasyMock.eq(target),
+ EasyMock.isA(HttpRequest.class),
+ EasyMock.same(context))).andReturn(response);
+ EasyMock.replay(client);
+
+ CachingHttpClient t = new CachingHttpClient(client, resourceFactory, httpCacheStorage, cacheConfig);
+
+ HttpResponse response1 = t.execute(get, context);
+ Assert.assertEquals(200, response1.getStatusLine().getStatusCode());
+ EntityUtils.consume(response1.getEntity());
+
+ EasyMock.verify(client);
+
+ removeCache();
+
+ EasyMock.reset(client);
+ EasyMock.expect(client.execute(
+ EasyMock.eq(target),
+ EasyMock.isA(HttpRequest.class),
+ EasyMock.same(context))).andReturn(response);
+ EasyMock.replay(client);
+
+ HttpResponse response2 = t.execute(get, context);
+ Assert.assertEquals(200, response2.getStatusLine().getStatusCode());
+ EntityUtils.consume(response2.getEntity());
+
+ EasyMock.verify(client);
+ }
+
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolDeviations.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolDeviations.java
index 9a3842c..0b79836 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolDeviations.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolDeviations.java
@@ -94,7 +94,7 @@ public class TestProtocolDeviations {
originResponse = make200Response();
CacheConfig params = new CacheConfig();
- params.setMaxObjectSizeBytes(MAX_BYTES);
+ params.setMaxObjectSize(MAX_BYTES);
params.setMaxCacheEntries(MAX_ENTRIES);
HttpCache cache = new BasicHttpCache(params);
@@ -307,8 +307,8 @@ public class TestProtocolDeviations {
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
*/
- @Test(expected = ClientProtocolException.class)
- public void testCantReturnOrigin401ResponseWithoutWWWAuthenticateHeader() throws Exception {
+ @Test
+ public void testPassesOnOrigin401ResponseWithoutWWWAuthenticateHeader() throws Exception {
originResponse = new BasicHttpResponse(HTTP_1_1, 401, "Unauthorized");
@@ -317,15 +317,9 @@ public class TestProtocolDeviations {
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;
- }
+ HttpResponse result = impl.execute(host, request);
+ verifyMocks();
+ Assert.assertSame(originResponse, result);
}
/*
@@ -334,8 +328,8 @@ public class TestProtocolDeviations {
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
*/
- @Test(expected = ClientProtocolException.class)
- public void testCantReturnAnOrigin405WithoutAllowHeader() throws Exception {
+ @Test
+ public void testPassesOnOrigin405WithoutAllowHeader() throws Exception {
originResponse = new BasicHttpResponse(HTTP_1_1, 405, "Method Not Allowed");
org.easymock.EasyMock.expect(
@@ -343,15 +337,9 @@ public class TestProtocolDeviations {
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;
- }
+ HttpResponse result = impl.execute(host, request);
+ verifyMocks();
+ Assert.assertSame(originResponse, result);
}
/*
@@ -362,7 +350,7 @@ public class TestProtocolDeviations {
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8
*/
@Test
- public void testCantReturnA407WithoutAProxyAuthenticateHeader() throws Exception {
+ public void testPassesOnOrigin407WithoutAProxyAuthenticateHeader() throws Exception {
originResponse = new BasicHttpResponse(HTTP_1_1, 407, "Proxy Authentication Required");
org.easymock.EasyMock.expect(
@@ -370,23 +358,9 @@ public class TestProtocolDeviations {
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;
- }
-
+ HttpResponse result = impl.execute(host, request);
verifyMocks();
- Assert.assertTrue(gotException);
+ Assert.assertSame(originResponse, result);
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java
index 62212f4..1700380 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java
@@ -66,7 +66,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
private Date now;
private Date tenSecondsAgo;
private Date twoMinutesAgo;
-
+
@Override
@Before
public void setUp() {
@@ -76,7 +76,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
twoMinutesAgo = new Date(now.getTime() - 2 * 60 * 1000L);
tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
}
-
+
/* "identity: The default (identity) encoding; the use of no
* transformation whatsoever. This content-coding is used only in the
* Accept-Encoding header, and SHOULD NOT be used in the
@@ -107,7 +107,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
* "304 Not Modified. ... If the conditional GET used a strong cache
* validator (see section 13.3.3), the response SHOULD NOT include
* other entity-headers."
- *
+ *
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
*/
private void cacheGenerated304ForValidatorShouldNotContainEntityHeader(
@@ -119,29 +119,29 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Cache-Control","max-age=3600");
resp1.setHeader(validatorHeader, validator);
resp1.setHeader(headerName, headerValue);
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader(conditionalHeader, validator);
-
+
replayMocks();
impl.execute(host, req1);
HttpResponse result = impl.execute(host, req2);
verifyMocks();
-
+
if (HttpStatus.SC_NOT_MODIFIED == result.getStatusLine().getStatusCode()) {
assertNull(result.getFirstHeader(headerName));
}
}
-
+
private void cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
String headerName, String headerValue) throws Exception,
IOException {
cacheGenerated304ForValidatorShouldNotContainEntityHeader(headerName,
headerValue, "ETag", "\"etag\"", "If-None-Match");
}
-
+
private void cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
String headerName, String headerValue) throws Exception,
IOException {
@@ -149,28 +149,28 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
headerValue, "Last-Modified", formatDate(twoMinutesAgo),
"If-Modified-Since");
}
-
+
@Test
public void cacheGenerated304ForStrongEtagValidatorShouldNotContainAllow()
throws Exception {
cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
"Allow", "GET,HEAD");
}
-
+
@Test
public void cacheGenerated304ForStrongDateValidatorShouldNotContainAllow()
throws Exception {
cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
"Allow", "GET,HEAD");
}
-
+
@Test
public void cacheGenerated304ForStrongEtagValidatorShouldNotContainContentEncoding()
throws Exception {
cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
"Content-Encoding", "gzip");
}
-
+
@Test
public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentEncoding()
throws Exception {
@@ -184,7 +184,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
"Content-Language", "en");
}
-
+
@Test
public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentLanguage()
throws Exception {
@@ -198,21 +198,21 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
"Content-Length", "128");
}
-
+
@Test
public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentLength()
throws Exception {
cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
"Content-Length", "128");
}
-
+
@Test
public void cacheGenerated304ForStrongValidatorShouldNotContainContentMD5()
throws Exception {
cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
"Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
}
-
+
@Test
public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentMD5()
throws Exception {
@@ -229,9 +229,9 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Cache-Control","max-age=3600");
resp1.setHeader(validatorHeader, validator);
resp1.setHeader("Content-Range", "bytes 0-127/256");
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("If-Range", validator);
req2.setHeader("Range","bytes=0-127");
@@ -240,67 +240,67 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, "Not Modified");
resp2.setHeader("Date", formatDate(now));
resp2.setHeader(validatorHeader, validator);
-
+
// cache module does not currently deal with byte ranges, but we want
// this test to work even if it does some day
Capture<HttpRequest> cap = new Capture<HttpRequest>();
expect(mockBackend.execute(same(host), capture(cap), (HttpContext)isNull()))
.andReturn(resp2).times(0,1);
-
+
replayMocks();
impl.execute(host, req1);
HttpResponse result = impl.execute(host, req2);
verifyMocks();
-
+
if (!cap.hasCaptured()
&& HttpStatus.SC_NOT_MODIFIED == result.getStatusLine().getStatusCode()) {
// cache generated a 304
assertNull(result.getFirstHeader("Content-Range"));
}
}
-
+
@Test
public void cacheGenerated304ForStrongEtagValidatorShouldNotContainContentRange()
throws Exception {
cacheGenerated304ForStrongValidatorShouldNotContainContentRange(
"ETag", "\"etag\"", "If-None-Match");
}
-
+
@Test
public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentRange()
throws Exception {
cacheGenerated304ForStrongValidatorShouldNotContainContentRange(
"Last-Modified", formatDate(twoMinutesAgo), "If-Modified-Since");
}
-
+
@Test
public void cacheGenerated304ForStrongEtagValidatorShouldNotContainContentType()
throws Exception {
cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
"Content-Type", "text/html");
}
-
+
@Test
public void cacheGenerated304ForStrongDateValidatorShouldNotContainContentType()
throws Exception {
cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
"Content-Type", "text/html");
}
-
+
@Test
public void cacheGenerated304ForStrongEtagValidatorShouldNotContainLastModified()
throws Exception {
cacheGenerated304ForStrongETagValidatorShouldNotContainEntityHeader(
"Last-Modified", formatDate(tenSecondsAgo));
}
-
+
@Test
public void cacheGenerated304ForStrongDateValidatorShouldNotContainLastModified()
throws Exception {
cacheGenerated304ForStrongDateValidatorShouldNotContainEntityHeader(
"Last-Modified", formatDate(twoMinutesAgo));
}
-
+
private void shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
String entityHeader, String entityHeaderValue) throws Exception,
IOException {
@@ -311,23 +311,23 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp.setHeader("Date", formatDate(now));
resp.setHeader("Etag", "\"etag\"");
resp.setHeader(entityHeader, entityHeaderValue);
-
+
backendExpectsAnyRequest().andReturn(resp);
-
+
replayMocks();
HttpResponse result = impl.execute(host, req);
verifyMocks();
-
+
assertNull(result.getFirstHeader(entityHeader));
}
-
+
@Test
public void shouldStripAllowFromOrigin304ResponseToStrongValidation()
throws Exception {
shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
"Allow", "GET,HEAD");
}
-
+
@Test
public void shouldStripContentEncodingFromOrigin304ResponseToStrongValidation()
throws Exception {
@@ -341,14 +341,14 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
"Content-Language", "en");
}
-
+
@Test
public void shouldStripContentLengthFromOrigin304ResponseToStrongValidation()
throws Exception {
shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
"Content-Length", "128");
}
-
+
@Test
public void shouldStripContentMD5FromOrigin304ResponseToStrongValidation()
throws Exception {
@@ -362,35 +362,35 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
"Content-Type", "text/html;charset=utf-8");
}
-
+
@Test
public void shouldStripContentRangeFromOrigin304ResponseToStringValidation()
throws Exception {
HttpRequest req = HttpTestUtils.makeDefaultRequest();
req.setHeader("If-Range","\"etag\"");
req.setHeader("Range","bytes=0-127");
-
+
HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, "Not Modified");
resp.setHeader("Date", formatDate(now));
resp.setHeader("ETag", "\"etag\"");
resp.setHeader("Content-Range", "bytes 0-127/256");
-
+
backendExpectsAnyRequest().andReturn(resp);
-
+
replayMocks();
HttpResponse result = impl.execute(host, req);
verifyMocks();
-
+
assertNull(result.getFirstHeader("Content-Range"));
}
-
+
@Test
public void shouldStripLastModifiedFromOrigin304ResponseToStrongValidation()
throws Exception {
shouldStripEntityHeaderFromOrigin304ResponseToStrongValidation(
"Last-Modified", formatDate(twoMinutesAgo));
}
-
+
/*
* "For this reason, a cache SHOULD NOT return a stale response if the
* client explicitly requests a first-hand or fresh one, unless it is
@@ -616,7 +616,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
assertEquals(warning, result.getFirstHeader("Warning").getValue());
}
-
+
/*
* "A transparent proxy SHOULD NOT modify an end-to-end header unless
* the definition of that header requires or specifically allows that."
@@ -1175,7 +1175,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
}
assertTrue(foundEtag1 && foundEtag2);
}
-
+
/* "If the entity-tag of the new response matches that of an existing
* entry, the new response SHOULD be used to update the header fields
* of the existing entry, and the result MUST be returned to the
@@ -1237,7 +1237,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
assertEquals(formatDate(now), result1.getFirstHeader("Date").getValue());
assertEquals(formatDate(now), result2.getFirstHeader("Date").getValue());
}
-
+
@Test
public void testResponseToExistingVariantsIsCachedForFutureResponses()
throws Exception {
@@ -1279,7 +1279,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
* for the associated entity, its entity-tag SHOULD NOT be included in
* the If-None-Match header field unless the request is for a range
* that would be fully satisfied by that entry."
- *
+ *
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.6
*/
@Test
@@ -1291,9 +1291,9 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Cache-Control", "max-age=3600");
resp1.setHeader("Vary", "User-Agent");
resp1.setHeader("ETag", "\"etag1\"");
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("User-Agent", "agent2");
req2.setHeader("Range", "bytes=0-49");
@@ -1306,27 +1306,27 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp2.setHeader("ETag", "\"etag2\"");
resp2.setHeader("Cache-Control","max-age=3600");
resp2.setHeader("Date", formatDate(new Date()));
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
HttpRequest req3 = HttpTestUtils.makeDefaultRequest();
req3.setHeader("User-Agent", "agent3");
-
+
HttpResponse resp3 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control", "max-age=3600");
resp1.setHeader("Vary", "User-Agent");
resp1.setHeader("ETag", "\"etag3\"");
-
+
Capture<HttpRequest> cap = new Capture<HttpRequest>();
expect(mockBackend.execute(isA(HttpHost.class), capture(cap),
(HttpContext)isNull())).andReturn(resp3);
-
+
replayMocks();
impl.execute(host, req1);
impl.execute(host, req2);
impl.execute(host, req3);
verifyMocks();
-
+
HttpRequest captured = cap.getValue();
for(Header h : captured.getHeaders("If-None-Match")) {
for(HeaderElement elt : h.getElements()) {
@@ -1334,15 +1334,15 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
}
}
}
-
+
/* "If a cache receives a successful response whose Content-Location
* field matches that of an existing cache entry for the same Request-
* URI, whose entity-tag differs from that of the existing entry, and
* whose Date is more recent than that of the existing entry, the
* existing entry SHOULD NOT be returned in response to future requests
* and SHOULD be deleted from the cache.
- *
- * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.6
+ *
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.6
*/
@Test
public void cachedEntryShouldNotBeUsedIfMoreRecentMentionInContentLocation()
@@ -1352,34 +1352,34 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Cache-Control","max-age=3600");
resp1.setHeader("ETag", "\"old-etag\"");
resp1.setHeader("Date", formatDate(tenSecondsAgo));
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = new HttpGet("http://foo.example.com/bar");
HttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("ETag", "\"new-etag\"");
resp2.setHeader("Date", formatDate(now));
resp2.setHeader("Content-Location", "http://foo.example.com/");
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
HttpRequest req3 = new HttpGet("http://foo.example.com");
HttpResponse resp3 = HttpTestUtils.make200Response();
-
+
backendExpectsAnyRequest().andReturn(resp3);
-
+
replayMocks();
impl.execute(host, req1);
impl.execute(host, req2);
impl.execute(host, req3);
verifyMocks();
}
-
+
/*
* "This specifically means that responses from HTTP/1.0 servers for such
* URIs [those containing a '?' in the rel_path part] SHOULD NOT be taken
* from a cache."
- *
+ *
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9
*/
@Test
@@ -1391,23 +1391,23 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Content-Length","200");
resp1.setHeader("Date", formatDate(now));
resp1.setHeader("Expires", formatDate(tenSecondsFromNow));
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = new HttpGet("http://foo.example.com/bar?baz=quux");
HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
resp2.setEntity(HttpTestUtils.makeBody(200));
resp2.setHeader("Content-Length","200");
resp2.setHeader("Date", formatDate(now));
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host, req1);
impl.execute(host, req2);
verifyMocks();
}
-
+
@Test
public void responseToGetWithQueryFrom1_0OriginVia1_1ProxyIsNotCached()
throws Exception {
@@ -1418,18 +1418,18 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Date", formatDate(now));
resp1.setHeader("Expires", formatDate(tenSecondsFromNow));
resp1.setHeader("Via","1.0 someproxy");
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = new HttpGet("http://foo.example.com/bar?baz=quux");
HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
resp2.setEntity(HttpTestUtils.makeBody(200));
resp2.setHeader("Content-Length","200");
resp2.setHeader("Date", formatDate(now));
resp2.setHeader("Via","1.0 someproxy");
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host, req1);
impl.execute(host, req2);
@@ -1440,7 +1440,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
* "A cache that passes through requests for methods it does not
* understand SHOULD invalidate any entities referred to by the
* Request-URI."
- *
+ *
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10
*/
@Test
@@ -1449,30 +1449,30 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control","max-age=3600");
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = new BasicHttpRequest("FROB", "/", HttpVersion.HTTP_1_1);
HttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("Cache-Control","max-age=3600");
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
HttpRequest req3 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
HttpResponse resp3 = HttpTestUtils.make200Response();
resp3.setHeader("ETag", "\"etag\"");
-
+
backendExpectsAnyRequest().andReturn(resp3);
-
+
replayMocks();
impl.execute(host, req1);
impl.execute(host, req2);
HttpResponse result = impl.execute(host, req3);
verifyMocks();
-
+
assertTrue(HttpTestUtils.semanticallyTransparent(resp3, result));
}
-
+
@Test
public void shouldInvalidateAllVariantsForUnknownMethod()
throws Exception {
@@ -1481,7 +1481,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control","max-age=3600");
resp1.setHeader("Vary", "User-Agent");
-
+
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
@@ -1489,30 +1489,30 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
HttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("Cache-Control","max-age=3600");
resp2.setHeader("Vary", "User-Agent");
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
HttpRequest req3 = new BasicHttpRequest("FROB", "/", HttpVersion.HTTP_1_1);
req3.setHeader("User-Agent", "agent3");
HttpResponse resp3 = HttpTestUtils.make200Response();
resp3.setHeader("Cache-Control","max-age=3600");
-
+
backendExpectsAnyRequest().andReturn(resp3);
-
+
HttpRequest req4 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
req4.setHeader("User-Agent", "agent1");
HttpResponse resp4 = HttpTestUtils.make200Response();
resp4.setHeader("ETag", "\"etag1\"");
-
+
backendExpectsAnyRequest().andReturn(resp4);
HttpRequest req5 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
req5.setHeader("User-Agent", "agent2");
HttpResponse resp5 = HttpTestUtils.make200Response();
resp5.setHeader("ETag", "\"etag2\"");
-
+
backendExpectsAnyRequest().andReturn(resp5);
-
+
replayMocks();
impl.execute(host, req1);
impl.execute(host, req2);
@@ -1520,7 +1520,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
HttpResponse result4 = impl.execute(host, req4);
HttpResponse result5 = impl.execute(host, req5);
verifyMocks();
-
+
assertTrue(HttpTestUtils.semanticallyTransparent(resp4, result4));
assertTrue(HttpTestUtils.semanticallyTransparent(resp5, result5));
}
@@ -1529,7 +1529,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
* "If a new cacheable response is received from a resource while any
* existing responses for the same resource are cached, the cache
* SHOULD use the new response to reply to the current request."
- *
+ *
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.12
*/
@Test
@@ -1540,29 +1540,29 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Date", formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control", "max-age=3600");
resp1.setHeader("ETag", "\"etag1\"");
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "max-age=0");
HttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("Date", formatDate(now));
resp2.setHeader("Cache-Control", "max-age=3600");
resp2.setHeader("ETag", "\"etag2\"");
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
HttpRequest req3 = HttpTestUtils.makeDefaultRequest();
-
+
replayMocks();
impl.execute(host, req1);
impl.execute(host, req2);
HttpResponse result = impl.execute(host, req3);
verifyMocks();
-
+
assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
}
-
+
/*
* "Many HTTP/1.0 cache implementations will treat an Expires value
* that is less than or equal to the response Date value as being
@@ -1571,7 +1571,7 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
* does not include a Cache-Control header field, it SHOULD consider
* the response to be non-cacheable in order to retain compatibility
* with HTTP/1.0 servers."
- *
+ *
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3
*/
@Test
@@ -1582,24 +1582,24 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Date", formatDate(now));
resp1.setHeader("Expires", formatDate(now));
resp1.removeHeaders("Cache-Control");
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "max-stale=1000");
HttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("ETag", "\"etag2\"");
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host, req1);
HttpResponse result = impl.execute(host, req2);
verifyMocks();
-
+
assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
}
-
+
@Test
public void expiresPriorToDateWithNoCacheControlIsNotCacheable()
throws Exception {
@@ -1608,28 +1608,28 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
resp1.setHeader("Date", formatDate(now));
resp1.setHeader("Expires", formatDate(tenSecondsAgo));
resp1.removeHeaders("Cache-Control");
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "max-stale=1000");
HttpResponse resp2 = HttpTestUtils.make200Response();
resp2.setHeader("ETag", "\"etag2\"");
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host, req1);
HttpResponse result = impl.execute(host, req2);
verifyMocks();
-
+
assertTrue(HttpTestUtils.semanticallyTransparent(resp2, result));
}
/*
* "If a request includes the no-cache directive, it SHOULD NOT
* include min-fresh, max-stale, or max-age."
- *
+ *
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4
*/
@Test
@@ -1638,15 +1638,15 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
req1.setHeader("Cache-Control", "min-fresh=10, no-cache");
req1.addHeader("Cache-Control", "max-stale=0, max-age=0");
-
+
Capture<HttpRequest> cap = new Capture<HttpRequest>();
expect(mockBackend.execute(same(host), capture(cap), (HttpContext)isNull()))
.andReturn(HttpTestUtils.make200Response());
-
+
replayMocks();
impl.execute(host, req1);
verifyMocks();
-
+
HttpRequest captured = cap.getValue();
boolean foundNoCache = false;
boolean foundDisallowedDirective = false;
@@ -1665,14 +1665,14 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
assertTrue(foundNoCache);
assertFalse(foundDisallowedDirective);
}
-
+
/*
* "To do this, the client may include the only-if-cached directive in
* a request. If it receives this directive, a cache SHOULD either
* respond using a cached entry that is consistent with the other
* constraints of the request, or respond with a 504 (Gateway Timeout)
* status."
- *
+ *
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4
*/
@Test
@@ -1680,35 +1680,35 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
throws Exception {
HttpRequest req = HttpTestUtils.makeDefaultRequest();
req.setHeader("Cache-Control", "only-if-cached");
-
+
replayMocks();
HttpResponse result = impl.execute(host, req);
verifyMocks();
-
+
assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT,
result.getStatusLine().getStatusCode());
}
-
+
@Test
public void cacheHitOkWithOnlyIfCached()
throws Exception {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control","max-age=3600");
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "only-if-cached");
-
+
replayMocks();
impl.execute(host, req1);
HttpResponse result = impl.execute(host, req2);
verifyMocks();
-
+
assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result));
}
-
+
@Test
public void returns504ForStaleEntryWithOnlyIfCached()
throws Exception {
@@ -1716,21 +1716,21 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control","max-age=5");
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "only-if-cached");
-
+
replayMocks();
impl.execute(host, req1);
HttpResponse result = impl.execute(host, req2);
verifyMocks();
-
+
assertEquals(HttpStatus.SC_GATEWAY_TIMEOUT,
result.getStatusLine().getStatusCode());
}
-
+
@Test
public void returnsStaleCacheEntryWithOnlyIfCachedAndMaxStale()
throws Exception {
@@ -1739,18 +1739,18 @@ public class TestProtocolRecommendations extends AbstractProtocolTest {
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Date", formatDate(tenSecondsAgo));
resp1.setHeader("Cache-Control","max-age=5");
-
+
backendExpectsAnyRequest().andReturn(resp1);
-
+
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control", "max-stale=20, only-if-cached");
-
+
replayMocks();
impl.execute(host, req1);
HttpResponse result = impl.execute(host, req2);
verifyMocks();
-
+
assertTrue(HttpTestUtils.semanticallyTransparent(resp1, result));
}
-
+
}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java
index 83308a4..98c181e 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java
@@ -5836,4 +5836,4 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
Assert.assertTrue(warningHeaders == null || warningHeaders.length == 0);
}
-}
\ No newline at end of file
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRFC5861Compliance.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRFC5861Compliance.java
index 58de0a6..7c1b9b5 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRFC5861Compliance.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRFC5861Compliance.java
@@ -27,37 +27,41 @@
package org.apache.http.impl.client.cache;
import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
import java.util.Date;
import org.apache.http.Header;
+import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
+import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.message.BasicHttpRequest;
import org.junit.Test;
-/**
+/**
* A suite of acceptance tests for compliance with RFC5861, which
* describes the stale-if-error and stale-while-revalidate
* Cache-Control extensions.
*/
public class TestRFC5861Compliance extends AbstractProtocolTest {
- /*
+ /*
* "The stale-if-error Cache-Control extension indicates that when an
* error is encountered, a cached stale response MAY be used to satisfy
- * the request, regardless of other freshness information.When used as a
+ * the request, regardless of other freshness information.When used as a
* request Cache-Control extension, its scope of application is the request
- * it appears in; when used as a response Cache-Control extension, its
- * scope is any request applicable to the cached response in which it
+ * it appears in; when used as a response Cache-Control extension, its
+ * scope is any request applicable to the cached response in which it
* occurs.Its value indicates the upper limit to staleness; when the cached
* response is more stale than the indicated amount, the cached response
* SHOULD NOT be used to satisfy the request, absent other information.
* In this context, an error is any situation that would result in a
* 500, 502, 503, or 504 HTTP response status code being returned."
- *
+ *
* http://tools.ietf.org/html/rfc5861
*/
@Test
@@ -67,22 +71,50 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60");
-
+
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp2 = HttpTestUtils.make500Response();
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
verifyMocks();
-
+
HttpTestUtils.assert110WarningFound(result);
}
-
+
+ @Test
+ public void testConsumesErrorResponseWhenServingStale()
+ throws Exception{
+ Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
+ HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
+ HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
+ "public, max-age=5, stale-if-error=60");
+
+ backendExpectsAnyRequest().andReturn(resp1);
+
+ HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
+ HttpResponse resp2 = HttpTestUtils.make500Response();
+ byte[] body = HttpTestUtils.getRandomBytes(101);
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+ ConsumableInputStream cis = new ConsumableInputStream(buf);
+ HttpEntity entity = new InputStreamEntity(cis, 101);
+ resp2.setEntity(entity);
+
+ backendExpectsAnyRequest().andReturn(resp2);
+
+ replayMocks();
+ impl.execute(host,req1);
+ impl.execute(host,req2);
+ verifyMocks();
+
+ assertTrue(cis.wasClosed());
+ }
+
@Test
public void testStaleIfErrorInResponseYieldsToMustRevalidate()
throws Exception{
@@ -90,22 +122,22 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60, must-revalidate");
-
+
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp2 = HttpTestUtils.make500Response();
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
verifyMocks();
-
+
assertTrue(HttpStatus.SC_OK != result.getStatusLine().getStatusCode());
}
-
+
@Test
public void testStaleIfErrorInResponseYieldsToProxyRevalidateForSharedCache()
throws Exception{
@@ -114,46 +146,46 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60, proxy-revalidate");
-
+
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp2 = HttpTestUtils.make500Response();
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
verifyMocks();
-
+
assertTrue(HttpStatus.SC_OK != result.getStatusLine().getStatusCode());
}
-
+
@Test
public void testStaleIfErrorInResponseNeedNotYieldToProxyRevalidateForPrivateCache()
throws Exception{
CacheConfig config = new CacheConfig();
config.setSharedCache(false);
impl = new CachingHttpClient(mockBackend, config);
-
+
Date tenSecondsAgo = new Date(new Date().getTime() - 10 * 1000L);
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60, proxy-revalidate");
-
+
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp2 = HttpTestUtils.make500Response();
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
verifyMocks();
-
+
HttpTestUtils.assert110WarningFound(result);
}
@@ -164,23 +196,23 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=60");
-
+
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control","min-fresh=2");
HttpResponse resp2 = HttpTestUtils.make500Response();
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
verifyMocks();
-
+
assertTrue(HttpStatus.SC_OK != result.getStatusLine().getStatusCode());
}
-
+
@Test
public void testStaleIfErrorInRequestIsTrueReturnsStaleEntryWithWarning()
throws Exception{
@@ -188,23 +220,23 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5");
-
+
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control","public, stale-if-error=60");
HttpResponse resp2 = HttpTestUtils.make500Response();
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
verifyMocks();
-
+
HttpTestUtils.assert110WarningFound(result);
}
-
+
@Test
public void testStaleIfErrorInResponseIsFalseReturnsError()
throws Exception{
@@ -213,14 +245,14 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5, stale-if-error=2");
-
+
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp2 = HttpTestUtils.make500Response();
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
@@ -229,7 +261,7 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
result.getStatusLine().getStatusCode());
}
-
+
@Test
public void testStaleIfErrorInRequestIsFalseReturnsError()
throws Exception{
@@ -238,15 +270,15 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
HttpRequest req1 = HttpTestUtils.makeDefaultRequest();
HttpResponse resp1 = HttpTestUtils.make200Response(tenSecondsAgo,
"public, max-age=5");
-
+
backendExpectsAnyRequest().andReturn(resp1);
HttpRequest req2 = HttpTestUtils.makeDefaultRequest();
req2.setHeader("Cache-Control","stale-if-error=2");
HttpResponse resp2 = HttpTestUtils.make500Response();
-
+
backendExpectsAnyRequest().andReturn(resp2);
-
+
replayMocks();
impl.execute(host,req1);
HttpResponse result = impl.execute(host,req2);
@@ -255,22 +287,22 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR,
result.getStatusLine().getStatusCode());
}
-
+
/*
* When present in an HTTP response, the stale-while-revalidate Cache-
* Control extension indicates that caches MAY serve the response in
* which it appears after it becomes stale, up to the indicated number
* of seconds.
- *
+ *
* http://tools.ietf.org/html/rfc5861
*/
@Test
public void testStaleWhileRevalidateReturnsStaleEntryWithWarning()
throws Exception {
-
+
params.setAsynchronousWorkersMax(1);
impl = new CachingHttpClient(mockBackend, cache, params);
-
+
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
HttpResponse resp1 = HttpTestUtils.make200Response();
Date now = new Date();
@@ -300,17 +332,57 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
}
assertTrue(warning110Found);
}
-
+
+ @Test
+ public void testCanAlsoServeStale304sWhileRevalidating()
+ throws Exception {
+
+ params.setAsynchronousWorkersMax(1);
+ params.setSharedCache(false);
+ impl = new CachingHttpClient(mockBackend, cache, params);
+
+ HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
+ HttpResponse resp1 = HttpTestUtils.make200Response();
+ Date now = new Date();
+ Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
+ resp1.setHeader("Cache-Control", "private, stale-while-revalidate=15");
+ resp1.setHeader("ETag","\"etag\"");
+ resp1.setHeader("Date", DateUtils.formatDate(tenSecondsAgo));
+
+ backendExpectsAnyRequest().andReturn(resp1).times(1,2);
+
+ HttpRequest req2 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
+ req2.setHeader("If-None-Match","\"etag\"");
+
+ replayMocks();
+ impl.execute(host, req1);
+ HttpResponse result = impl.execute(host, req2);
+ verifyMocks();
+
+ assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getStatusLine().getStatusCode());
+ boolean warning110Found = false;
+ for(Header h : result.getHeaders("Warning")) {
+ for(WarningValue wv : WarningValue.getWarningValues(h)) {
+ if (wv.getWarnCode() == 110) {
+ warning110Found = true;
+ break;
+ }
+ }
+ }
+ assertTrue(warning110Found);
+ }
+
+
@Test
public void testStaleWhileRevalidateYieldsToMustRevalidate()
throws Exception {
-
+
Date now = new Date();
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
-
+
params.setAsynchronousWorkersMax(1);
impl = new CachingHttpClient(mockBackend, cache, params);
-
+
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate");
@@ -324,7 +396,7 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, must-revalidate");
resp2.setHeader("ETag","\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(now));
-
+
backendExpectsAnyRequest().andReturn(resp2);
replayMocks();
@@ -348,14 +420,14 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
@Test
public void testStaleWhileRevalidateYieldsToProxyRevalidateForSharedCache()
throws Exception {
-
+
Date now = new Date();
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
-
+
params.setAsynchronousWorkersMax(1);
params.setSharedCache(true);
impl = new CachingHttpClient(mockBackend, cache, params);
-
+
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate");
@@ -369,7 +441,7 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15, proxy-revalidate");
resp2.setHeader("ETag","\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(now));
-
+
backendExpectsAnyRequest().andReturn(resp2);
replayMocks();
@@ -393,14 +465,14 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
@Test
public void testStaleWhileRevalidateYieldsToExplicitFreshnessRequest()
throws Exception {
-
+
Date now = new Date();
Date tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
-
+
params.setAsynchronousWorkersMax(1);
params.setSharedCache(true);
impl = new CachingHttpClient(mockBackend, cache, params);
-
+
HttpRequest req1 = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
HttpResponse resp1 = HttpTestUtils.make200Response();
resp1.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
@@ -415,7 +487,7 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
resp2.setHeader("Cache-Control", "public, max-age=5, stale-while-revalidate=15");
resp2.setHeader("ETag","\"etag\"");
resp2.setHeader("Date", DateUtils.formatDate(now));
-
+
backendExpectsAnyRequest().andReturn(resp2);
replayMocks();
@@ -435,5 +507,5 @@ public class TestRFC5861Compliance extends AbstractProtocolTest {
}
assertFalse(warning110Found);
}
-
+
}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRequestProtocolCompliance.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRequestProtocolCompliance.java
index 39f52d7..11e64b2 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRequestProtocolCompliance.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestRequestProtocolCompliance.java
@@ -42,22 +42,22 @@ public class TestRequestProtocolCompliance {
private RequestProtocolCompliance impl;
private HttpRequest req;
private HttpRequest result;
-
+
@Before
public void setUp() {
req = HttpTestUtils.makeDefaultRequest();
impl = new RequestProtocolCompliance();
}
-
+
@Test
public void doesNotModifyACompliantRequest() throws Exception {
- result = impl.makeRequestCompliant(req);
+ result = impl.makeRequestCompliant(req);
assertTrue(HttpTestUtils.equivalent(req, result));
}
-
+
@Test
public void removesEntityFromTRACERequest() throws Exception {
- HttpEntityEnclosingRequest req =
+ HttpEntityEnclosingRequest req =
new BasicHttpEntityEnclosingRequest("TRACE", "/", HttpVersion.HTTP_1_1);
req.setEntity(HttpTestUtils.makeBody(50));
result = impl.makeRequestCompliant(req);
@@ -65,7 +65,7 @@ public class TestRequestProtocolCompliance {
assertNull(((HttpEntityEnclosingRequest)result).getEntity());
}
}
-
+
@Test
public void upgrades1_0RequestTo1_1() throws Exception {
req = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_0);
@@ -80,7 +80,7 @@ public class TestRequestProtocolCompliance {
result = impl.makeRequestCompliant(req);
assertEquals(HttpVersion.HTTP_1_1, result.getProtocolVersion());
}
-
+
@Test
public void stripsMinFreshFromRequestIfNoCachePresent()
throws Exception {
@@ -107,7 +107,7 @@ public class TestRequestProtocolCompliance {
assertEquals("no-cache",
result.getFirstHeader("Cache-Control").getValue());
}
-
+
@Test
public void doesNotStripMinFreshFromRequestWithoutNoCache()
throws Exception {
@@ -116,7 +116,7 @@ public class TestRequestProtocolCompliance {
assertEquals("min-fresh=10",
result.getFirstHeader("Cache-Control").getValue());
}
-
+
@Test
public void correctlyStripsMinFreshFromMiddleIfNoCache()
throws Exception {
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java
index 7336c40..e1f28da 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java
@@ -60,7 +60,7 @@ public class TestResponseCachingPolicy {
now = new Date();
sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
-
+
policy = new ResponseCachingPolicy(0, true);
request = new BasicHttpRequest("GET","/",HTTP_1_1);
response = new BasicHttpResponse(
@@ -397,7 +397,7 @@ public class TestResponseCachingPolicy {
response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
Assert.assertFalse(policy.isResponseCacheable(request, response));
}
-
+
@Test
public void getsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithExpires() {
request = new BasicHttpRequest("GET", "/foo?s=bar");
@@ -406,7 +406,7 @@ public class TestResponseCachingPolicy {
response.setHeader("Expires", formatDate(tenSecondsFromNow));
Assert.assertFalse(policy.isResponseCacheable(request, response));
}
-
+
@Test
public void getsWithQueryParametersFrom1_0OriginsViaProxiesAreNotCacheable() {
request = new BasicHttpRequest("GET", "/foo?s=bar");
@@ -424,7 +424,7 @@ public class TestResponseCachingPolicy {
response.setHeader("Via", "1.0 someproxy");
Assert.assertFalse(policy.isResponseCacheable(request, response));
}
-
+
@Test
public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesAreNotCacheableEvenWithExpires() {
request = new BasicHttpRequest("GET", "/foo?s=bar");
@@ -443,7 +443,7 @@ public class TestResponseCachingPolicy {
response.setHeader("Via", "1.1 someproxy");
Assert.assertTrue(policy.isResponseCacheable(request, response));
}
-
+
@Test
public void notCacheableIfExpiresEqualsDateAndNoCacheControl() {
response.setHeader("Date", formatDate(now));
@@ -459,6 +459,14 @@ public class TestResponseCachingPolicy {
response.removeHeaders("Cache-Control");
Assert.assertFalse(policy.isResponseCacheable(request, response));
}
+
+ @Test
+ public void otherStatusCodesAreCacheableWithExplicitCachingHeaders() {
+ response.setStatusCode(HttpStatus.SC_NOT_FOUND);
+ response.setHeader("Date", formatDate(now));
+ response.setHeader("Cache-Control","max-age=300");
+ Assert.assertTrue(policy.isResponseCacheable(request, response));
+ }
private int getRandomStatus() {
int rnd = (new Random()).nextInt(acceptableCodes.length);
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseProtocolCompliance.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseProtocolCompliance.java
new file mode 100644
index 0000000..1f4e4c0
--- /dev/null
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseProtocolCompliance.java
@@ -0,0 +1,152 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.util.Date;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
+import org.apache.http.message.BasicHttpResponse;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestResponseProtocolCompliance {
+
+ private ResponseProtocolCompliance impl;
+
+ @Before
+ public void setUp() {
+ impl = new ResponseProtocolCompliance();
+ }
+
+ private static class Flag {
+ public boolean set;
+ }
+
+ private void setMinimalResponseHeaders(HttpResponse resp) {
+ resp.setHeader("Date", DateUtils.formatDate(new Date()));
+ resp.setHeader("Server", "MyServer/1.0");
+ }
+
+ private ByteArrayInputStream makeTrackableBody(int nbytes, final Flag closed) {
+ byte[] buf = HttpTestUtils.getRandomBytes(nbytes);
+ ByteArrayInputStream bais = new ByteArrayInputStream(buf) {
+ @Override
+ public void close() {
+ closed.set = true;
+ }
+ };
+ return bais;
+ }
+
+ private HttpResponse makePartialResponse(int nbytes) {
+ HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
+ setMinimalResponseHeaders(resp);
+ resp.setHeader("Content-Length","" + nbytes);
+ resp.setHeader("Content-Range","0-127/256");
+ return resp;
+ }
+
+ @Test
+ public void consumesBodyIfOriginSendsOneInResponseToHEAD() throws Exception {
+ HttpRequest req = new HttpHead("http://foo.example.com/");
+ int nbytes = 128;
+ HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
+ setMinimalResponseHeaders(resp);
+ resp.setHeader("Content-Length","" + nbytes);
+
+ final Flag closed = new Flag();
+ ByteArrayInputStream bais = makeTrackableBody(nbytes, closed);
+ resp.setEntity(new InputStreamEntity(bais, -1));
+
+ impl.ensureProtocolCompliance(req, resp);
+ assertNull(resp.getEntity());
+ assertTrue(closed.set || bais.read() == -1);
+ }
+
+ @Test(expected=ClientProtocolException.class)
+ public void throwsExceptionIfOriginReturnsPartialResponseWhenNotRequested() throws Exception {
+ HttpRequest req = new HttpGet("http://foo.example.com/");
+ int nbytes = 128;
+ HttpResponse resp = makePartialResponse(nbytes);
+ resp.setEntity(HttpTestUtils.makeBody(nbytes));
+
+ impl.ensureProtocolCompliance(req, resp);
+ }
+
+ @Test
+ public void consumesPartialContentFromOriginEvenIfNotRequested() throws Exception {
+ HttpRequest req = new HttpGet("http://foo.example.com/");
+ int nbytes = 128;
+ HttpResponse resp = makePartialResponse(nbytes);
+
+ final Flag closed = new Flag();
+ ByteArrayInputStream bais = makeTrackableBody(nbytes, closed);
+ resp.setEntity(new InputStreamEntity(bais, -1));
+
+ try {
+ impl.ensureProtocolCompliance(req, resp);
+ } catch (ClientProtocolException expected) {
+ }
+ assertTrue(closed.set || bais.read() == -1);
+ }
+
+ @Test
+ public void consumesBodyOf100ContinueResponseIfItArrives() throws Exception {
+ HttpEntityEnclosingRequest req = new BasicHttpEntityEnclosingRequest("POST", "/", HttpVersion.HTTP_1_1);
+ int nbytes = 128;
+ req.setHeader("Content-Length","" + nbytes);
+ req.setHeader("Content-Type", "application/octet-stream");
+ HttpEntity postBody = new ByteArrayEntity(HttpTestUtils.getRandomBytes(nbytes));
+ req.setEntity(postBody);
+
+ HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_CONTINUE, "Continue");
+ final Flag closed = new Flag();
+ ByteArrayInputStream bais = makeTrackableBody(nbytes, closed);
+ resp.setEntity(new InputStreamEntity(bais, -1));
+
+ try {
+ impl.ensureProtocolCompliance(req, resp);
+ } catch (ClientProtocolException expected) {
+ }
+ assertTrue(closed.set || bais.read() == -1);
+ }
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestSizeLimitedResponseReader.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestSizeLimitedResponseReader.java
index 53ba64e..90bf669 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestSizeLimitedResponseReader.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestSizeLimitedResponseReader.java
@@ -118,7 +118,7 @@ public class TestSizeLimitedResponseReader {
@Test
public void testTooLargeEntityHasOriginalContentTypes() throws Exception {
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
- StringEntity entity = new StringEntity("large entity content", "text/plain", "utf-8");
+ StringEntity entity = new StringEntity("large entity content");
response.setEntity(entity);
impl = new SizeLimitedResponseReader(new HeapResourceFactory(), MAX_SIZE, request, response);
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ehcache/TestEhcacheProtocolRequirements.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ehcache/TestEhcacheProtocolRequirements.java
index e669f08..21906b3 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ehcache/TestEhcacheProtocolRequirements.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/ehcache/TestEhcacheProtocolRequirements.java
@@ -64,7 +64,7 @@ public class TestEhcacheProtocolRequirements extends TestProtocolRequirements{
public void setUp() {
super.setUp();
params = new CacheConfig();
- params.setMaxObjectSizeBytes(MAX_BYTES);
+ params.setMaxObjectSize(MAX_BYTES);
if (CACHE_MANAGER.cacheExists(TEST_EHCACHE_NAME)){
CACHE_MANAGER.removeCache(TEST_EHCACHE_NAME);
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestMemcachedCacheEntryFactoryImpl.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestMemcachedCacheEntryFactoryImpl.java
new file mode 100644
index 0000000..7653e0d
--- /dev/null
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestMemcachedCacheEntryFactoryImpl.java
@@ -0,0 +1,48 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache.memcached;
+
+import static org.junit.Assert.*;
+
+import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.impl.client.cache.HttpTestUtils;
+import org.junit.Test;
+
+
+public class TestMemcachedCacheEntryFactoryImpl {
+
+ @Test
+ public void createsMemcachedCacheEntryImpls() {
+ String key = "key";
+ HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
+ MemcachedCacheEntryFactoryImpl impl = new MemcachedCacheEntryFactoryImpl();
+ MemcachedCacheEntry result = impl.getMemcachedCacheEntry(key, entry);
+ assertNotNull(result);
+ assertSame(key, result.getStorageKey());
+ assertSame(entry, result.getHttpCacheEntry());
+ }
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestMemcachedCacheEntryImpl.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestMemcachedCacheEntryImpl.java
new file mode 100644
index 0000000..3aea454
--- /dev/null
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestMemcachedCacheEntryImpl.java
@@ -0,0 +1,117 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache.memcached;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.http.client.cache.HttpCacheEntry;
+import org.apache.http.impl.client.cache.DefaultHttpCacheEntrySerializer;
+import org.apache.http.impl.client.cache.HttpTestUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+
+public class TestMemcachedCacheEntryImpl {
+
+ private MemcachedCacheEntryImpl impl;
+ private HttpCacheEntry entry;
+
+ @Before
+ public void setUp() {
+ entry = HttpTestUtils.makeCacheEntry();
+ impl = new MemcachedCacheEntryImpl("foo", entry);
+ }
+
+ @Test
+ public void canBeCreatedEmpty() {
+ impl = new MemcachedCacheEntryImpl();
+ assertNull(impl.getStorageKey());
+ assertNull(impl.getHttpCacheEntry());
+ }
+
+ @Test
+ public void canBeSerialized() {
+ byte[] bytes = impl.toByteArray();
+ assertNotNull(bytes);
+ assertTrue(bytes.length > 0);
+ }
+
+ @Test
+ public void knowsItsCacheKey() {
+ assertEquals("foo", impl.getStorageKey());
+ }
+
+ @Test
+ public void knowsItsCacheEntry() {
+ assertEquals(entry, impl.getHttpCacheEntry());
+ }
+
+ @Test
+ public void canBeReconstitutedFromByteArray() throws Exception {
+ String key = impl.getStorageKey();
+ HttpCacheEntry entry = impl.getHttpCacheEntry();
+ byte[] bytes = impl.toByteArray();
+ impl = new MemcachedCacheEntryImpl();
+ impl.set(bytes);
+
+ assertEquals(key, impl.getStorageKey());
+ assertEquivalent(entry, impl.getHttpCacheEntry());
+ }
+
+ @Test(expected=MemcachedSerializationException.class)
+ public void cannotReconstituteFromGarbage() {
+ impl = new MemcachedCacheEntryImpl();
+ byte[] bytes = HttpTestUtils.getRandomBytes(128);
+ impl.set(bytes);
+ }
+
+ private void assertEquivalent(HttpCacheEntry entry,
+ HttpCacheEntry resultEntry) throws IOException {
+ /* Ugh. Implementing HttpCacheEntry#equals is problematic
+ * due to the Resource response body (may cause unexpected
+ * I/O to users). Checking that two entries
+ * serialize to the same bytes seems simpler, on the whole,
+ * (while still making for a somewhat yucky test). At
+ * least we encapsulate it off here in its own method so
+ * the test that uses it remains clear.
+ */
+ DefaultHttpCacheEntrySerializer ser = new DefaultHttpCacheEntrySerializer();
+ ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
+ ser.writeTo(entry, bos1);
+ byte[] bytes1 = bos1.toByteArray();
+ ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
+ ser.writeTo(resultEntry, bos2);
+ byte[] bytes2 = bos2.toByteArray();
+ assertEquals(bytes1.length, bytes2.length);
+ for(int i = 0; i < bytes1.length; i++) {
+ assertEquals(bytes1[i], bytes2[i]);
+ }
+ }
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestMemcachedHttpCacheStorage.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestMemcachedHttpCacheStorage.java
index e2422eb..e6821ab 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestMemcachedHttpCacheStorage.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestMemcachedHttpCacheStorage.java
@@ -27,17 +27,15 @@
package org.apache.http.impl.client.cache.memcached;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import junit.framework.TestCase;
import net.spy.memcached.CASResponse;
import net.spy.memcached.CASValue;
import net.spy.memcached.MemcachedClientIF;
+import net.spy.memcached.OperationTimeoutException;
import org.apache.http.client.cache.HttpCacheEntry;
-import org.apache.http.client.cache.HttpCacheEntrySerializer;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.client.cache.HttpCacheUpdateException;
import org.apache.http.impl.client.cache.CacheConfig;
@@ -49,85 +47,280 @@ import org.junit.Test;
public class TestMemcachedHttpCacheStorage extends TestCase {
private MemcachedHttpCacheStorage impl;
private MemcachedClientIF mockMemcachedClient;
- private HttpCacheEntrySerializer mockSerializer;
+ private KeyHashingScheme mockKeyHashingScheme;
+ private MemcachedCacheEntryFactory mockMemcachedCacheEntryFactory;
+ private MemcachedCacheEntry mockMemcachedCacheEntry;
+ private MemcachedCacheEntry mockMemcachedCacheEntry2;
+ private MemcachedCacheEntry mockMemcachedCacheEntry3;
+ private MemcachedCacheEntry mockMemcachedCacheEntry4;
@Override
@Before
public void setUp() throws Exception {
mockMemcachedClient = EasyMock.createMock(MemcachedClientIF.class);
- mockSerializer = EasyMock.createMock(HttpCacheEntrySerializer.class);
+ mockKeyHashingScheme = EasyMock.createMock(KeyHashingScheme.class);
+ mockMemcachedCacheEntryFactory = EasyMock.createMock(MemcachedCacheEntryFactory.class);
+ mockMemcachedCacheEntry = EasyMock.createMock(MemcachedCacheEntry.class);
+ mockMemcachedCacheEntry2 = EasyMock.createMock(MemcachedCacheEntry.class);
+ mockMemcachedCacheEntry3 = EasyMock.createMock(MemcachedCacheEntry.class);
+ mockMemcachedCacheEntry4 = EasyMock.createMock(MemcachedCacheEntry.class);
CacheConfig config = new CacheConfig();
config.setMaxUpdateRetries(1);
impl = new MemcachedHttpCacheStorage(mockMemcachedClient, config,
- mockSerializer);
+ mockMemcachedCacheEntryFactory, mockKeyHashingScheme);
}
private void replayMocks() {
EasyMock.replay(mockMemcachedClient);
- EasyMock.replay(mockSerializer);
+ EasyMock.replay(mockKeyHashingScheme);
+ EasyMock.replay(mockMemcachedCacheEntry);
+ EasyMock.replay(mockMemcachedCacheEntry2);
+ EasyMock.replay(mockMemcachedCacheEntry3);
+ EasyMock.replay(mockMemcachedCacheEntry4);
+ EasyMock.replay(mockMemcachedCacheEntryFactory);
}
private void verifyMocks() {
EasyMock.verify(mockMemcachedClient);
- EasyMock.verify(mockSerializer);
+ EasyMock.verify(mockKeyHashingScheme);
+ EasyMock.verify(mockMemcachedCacheEntry);
+ EasyMock.verify(mockMemcachedCacheEntry2);
+ EasyMock.verify(mockMemcachedCacheEntry3);
+ EasyMock.verify(mockMemcachedCacheEntry4);
+ EasyMock.verify(mockMemcachedCacheEntryFactory);
}
@Test
- public void testCachePut() throws IOException {
+ public void testSuccessfulCachePut() throws IOException {
final String url = "foo";
+ final String key = "key";
final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
- mockSerializer.writeTo(EasyMock.isA(HttpCacheEntry.class), EasyMock
- .isA(OutputStream.class));
- EasyMock.expect(
- mockMemcachedClient.set(EasyMock.eq(url), EasyMock.eq(0),
- EasyMock.aryEq(new byte[0]))).andReturn(null);
+ byte[] serialized = HttpTestUtils.getRandomBytes(128);
+
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
+ .andReturn(mockMemcachedCacheEntry);
+ EasyMock.expect(mockMemcachedCacheEntry.toByteArray())
+ .andReturn(serialized);
+ EasyMock.expect(mockKeyHashingScheme.hash(url))
+ .andReturn(key);
+ EasyMock.expect(mockMemcachedClient.set(key, 0, serialized))
+ .andReturn(null);
+
+ replayMocks();
+ impl.putEntry(url, value);
+ verifyMocks();
+ }
+
+ @Test
+ public void testCachePutFailsSilentlyWhenWeCannotHashAKey() throws IOException {
+ final String url = "foo";
+ final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
+ byte[] serialized = HttpTestUtils.getRandomBytes(128);
+
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
+ .andReturn(mockMemcachedCacheEntry).times(0,1);
+ EasyMock.expect(mockMemcachedCacheEntry.toByteArray())
+ .andReturn(serialized).times(0,1);
+ EasyMock.expect(mockKeyHashingScheme.hash(url))
+ .andThrow(new MemcachedKeyHashingException(new Exception()));
+
replayMocks();
impl.putEntry(url, value);
verifyMocks();
}
+
+ public void testThrowsIOExceptionWhenMemcachedPutTimesOut() throws IOException {
+ final String url = "foo";
+ final String key = "key";
+ final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
+ byte[] serialized = HttpTestUtils.getRandomBytes(128);
+
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
+ .andReturn(mockMemcachedCacheEntry);
+ EasyMock.expect(mockMemcachedCacheEntry.toByteArray())
+ .andReturn(serialized);
+ EasyMock.expect(mockKeyHashingScheme.hash(url))
+ .andReturn(key);
+ EasyMock.expect(mockMemcachedClient.set(key, 0, serialized))
+ .andThrow(new OperationTimeoutException("timed out"));
+
+ replayMocks();
+ try {
+ impl.putEntry(url, value);
+ fail("should have thrown exception");
+ } catch (IOException expected) {
+ }
+ verifyMocks();
+ }
@Test
- public void testCacheGet() throws UnsupportedEncodingException,
+ public void testCachePutThrowsIOExceptionIfCannotSerializeEntry() throws IOException {
+ final String url = "foo";
+ final String key = "key";
+ final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
+
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
+ .andReturn(mockMemcachedCacheEntry);
+ EasyMock.expect(mockMemcachedCacheEntry.toByteArray())
+ .andThrow(new MemcachedSerializationException(new Exception()));
+ EasyMock.expect(mockKeyHashingScheme.hash(url))
+ .andReturn(key).times(0,1);
+
+ replayMocks();
+ try {
+ impl.putEntry(url, value);
+ fail("should have thrown exception");
+ } catch (IOException expected) {
+
+ }
+ verifyMocks();
+ }
+
+ @Test
+ public void testSuccessfulCacheGet() throws UnsupportedEncodingException,
IOException {
final String url = "foo";
+ final String key = "key";
+ byte[] serialized = HttpTestUtils.getRandomBytes(128);
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry();
- EasyMock.expect(mockMemcachedClient.get(url)).andReturn(new byte[] {});
- EasyMock.expect(
- mockSerializer.readFrom(EasyMock.isA(InputStream.class)))
- .andReturn(cacheEntry);
+
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
+ EasyMock.expect(mockMemcachedClient.get(key)).andReturn(serialized);
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
+ .andReturn(mockMemcachedCacheEntry);
+ mockMemcachedCacheEntry.set(serialized);
+ EasyMock.expect(mockMemcachedCacheEntry.getStorageKey()).andReturn(url);
+ EasyMock.expect(mockMemcachedCacheEntry.getHttpCacheEntry()).andReturn(cacheEntry);
+
replayMocks();
HttpCacheEntry resultingEntry = impl.getEntry(url);
verifyMocks();
assertSame(cacheEntry, resultingEntry);
}
-
+
@Test
- public void testCacheGetNullEntry() throws IOException {
+ public void testTreatsNoneByteArrayFromMemcachedAsCacheMiss() throws UnsupportedEncodingException,
+ IOException {
final String url = "foo";
-
- EasyMock.expect(mockMemcachedClient.get(url)).andReturn(null);
-
+ final String key = "key";
+
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
+ EasyMock.expect(mockMemcachedClient.get(key)).andReturn(new Object());
+
+ replayMocks();
+ HttpCacheEntry resultingEntry = impl.getEntry(url);
+ verifyMocks();
+ assertNull(resultingEntry);
+ }
+
+ @Test
+ public void testTreatsNullFromMemcachedAsCacheMiss() throws UnsupportedEncodingException,
+ IOException {
+ final String url = "foo";
+ final String key = "key";
+
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
+ EasyMock.expect(mockMemcachedClient.get(key)).andReturn(null);
+
replayMocks();
HttpCacheEntry resultingEntry = impl.getEntry(url);
verifyMocks();
-
assertNull(resultingEntry);
}
+
+ @Test
+ public void testTreatsAsCacheMissIfCannotReconstituteEntry() throws UnsupportedEncodingException,
+ IOException {
+ final String url = "foo";
+ final String key = "key";
+ byte[] serialized = HttpTestUtils.getRandomBytes(128);
+
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
+ EasyMock.expect(mockMemcachedClient.get(key)).andReturn(serialized);
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
+ .andReturn(mockMemcachedCacheEntry);
+ mockMemcachedCacheEntry.set(serialized);
+ EasyMock.expectLastCall().andThrow(new MemcachedSerializationException(new Exception()));
+
+ replayMocks();
+ assertNull(impl.getEntry(url));
+ verifyMocks();
+ }
+
+ @Test
+ public void testTreatsAsCacheMissIfCantHashStorageKey() throws UnsupportedEncodingException,
+ IOException {
+ final String url = "foo";
+
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andThrow(new MemcachedKeyHashingException(new Exception()));
+
+ replayMocks();
+ assertNull(impl.getEntry(url));
+ verifyMocks();
+ }
+
+ @Test
+ public void testThrowsIOExceptionIfMemcachedTimesOutOnGet() throws UnsupportedEncodingException,
+ IOException {
+ final String url = "foo";
+ final String key = "key";
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
+ EasyMock.expect(mockMemcachedClient.get(key))
+ .andThrow(new OperationTimeoutException(""));
+
+ replayMocks();
+ try {
+ impl.getEntry(url);
+ fail("should have thrown exception");
+ } catch (IOException expected) {
+ }
+ verifyMocks();
+ }
@Test
public void testCacheRemove() throws IOException {
final String url = "foo";
- EasyMock.expect(mockMemcachedClient.delete(url)).andReturn(null);
+ final String key = "key";
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
+ EasyMock.expect(mockMemcachedClient.delete(key)).andReturn(null);
replayMocks();
impl.removeEntry(url);
verifyMocks();
}
+
+ @Test
+ public void testCacheRemoveHandlesKeyHashingFailure() throws IOException {
+ final String url = "foo";
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(null);
+ replayMocks();
+ impl.removeEntry(url);
+ verifyMocks();
+ }
+
+ @Test
+ public void testCacheRemoveThrowsIOExceptionOnMemcachedTimeout() throws IOException {
+ final String url = "foo";
+ final String key = "key";
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key);
+ EasyMock.expect(mockMemcachedClient.delete(key))
+ .andThrow(new OperationTimeoutException(""));
+
+ replayMocks();
+ try {
+ impl.removeEntry(url);
+ fail("should have thrown exception");
+ } catch (IOException expected) {
+ }
+ verifyMocks();
+ }
@Test
- public void testCacheUpdateNullEntry() throws IOException,
+ public void testCacheUpdateCanUpdateNullEntry() throws IOException,
HttpCacheUpdateException {
final String url = "foo";
+ final String key = "key";
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
+ final byte[] serialized = HttpTestUtils.getRandomBytes(128);
HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
public HttpCacheEntry update(HttpCacheEntry old) {
@@ -137,15 +330,51 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
};
// get empty old entry
- EasyMock.expect(mockMemcachedClient.gets(url)).andReturn(null);
- // EasyMock.expect(mockCache.get(key)).andReturn(null);
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key).anyTimes();
+ EasyMock.expect(mockMemcachedClient.gets(key)).andReturn(null);
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
+ .andReturn(mockMemcachedCacheEntry);
+ EasyMock.expect(mockMemcachedCacheEntry.toByteArray()).andReturn(serialized);
+ EasyMock.expect(
+ mockMemcachedClient.set(EasyMock.eq(key), EasyMock.eq(0),
+ EasyMock.aryEq(serialized))).andReturn(null);
+
+ replayMocks();
+ impl.updateEntry(url, callback);
+ verifyMocks();
+ }
+
+ @Test
+ public void testCacheUpdateOverwritesNonMatchingHashCollision() throws IOException,
+ HttpCacheUpdateException {
+ final String url = "foo";
+ final String key = "key";
+ final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
+ final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
+ final CASValue<Object> casValue = new CASValue<Object>(-1, oldBytes);
+ final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
- // put new entry
- mockSerializer.writeTo(EasyMock.same(updatedValue), EasyMock
- .isA(OutputStream.class));
+ HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
+ public HttpCacheEntry update(HttpCacheEntry old) {
+ assertNull(old);
+ return updatedValue;
+ }
+ };
+
+ // get empty old entry
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key).anyTimes();
+ EasyMock.expect(mockMemcachedClient.gets(key)).andReturn(casValue);
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
+ .andReturn(mockMemcachedCacheEntry);
+ mockMemcachedCacheEntry.set(oldBytes);
+ EasyMock.expect(mockMemcachedCacheEntry.getStorageKey()).andReturn("not" + url).anyTimes();
+
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
+ .andReturn(mockMemcachedCacheEntry2);
+ EasyMock.expect(mockMemcachedCacheEntry2.toByteArray()).andReturn(newBytes);
EasyMock.expect(
- mockMemcachedClient.set(EasyMock.eq(url), EasyMock.eq(0),
- EasyMock.aryEq(new byte[0]))).andReturn(null);
+ mockMemcachedClient.set(EasyMock.eq(key), EasyMock.eq(0),
+ EasyMock.aryEq(newBytes))).andReturn(null);
replayMocks();
impl.updateEntry(url, callback);
@@ -153,115 +382,200 @@ public class TestMemcachedHttpCacheStorage extends TestCase {
}
@Test
- public void testCacheUpdate() throws IOException, HttpCacheUpdateException {
+ public void testCacheUpdateCanUpdateExistingEntry() throws IOException,
+ HttpCacheUpdateException {
final String url = "foo";
+ final String key = "key";
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
-
- CASValue<Object> v = new CASValue<Object>(1234, new byte[] {});
+ final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
+ CASValue<Object> casValue = new CASValue<Object>(1, oldBytes);
+ final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
+
HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
public HttpCacheEntry update(HttpCacheEntry old) {
- assertEquals(existingValue, old);
+ assertSame(existingValue, old);
return updatedValue;
}
};
- // get existing old entry
- EasyMock.expect(mockMemcachedClient.gets(url)).andReturn(v);
- EasyMock.expect(
- mockSerializer.readFrom(EasyMock.isA(InputStream.class)))
- .andReturn(existingValue);
+ // get empty old entry
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key).anyTimes();
+ EasyMock.expect(mockMemcachedClient.gets(key)).andReturn(casValue);
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
+ .andReturn(mockMemcachedCacheEntry);
+ mockMemcachedCacheEntry.set(oldBytes);
+ EasyMock.expect(mockMemcachedCacheEntry.getStorageKey()).andReturn(url);
+ EasyMock.expect(mockMemcachedCacheEntry.getHttpCacheEntry()).andReturn(existingValue);
+
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
+ .andReturn(mockMemcachedCacheEntry2);
+ EasyMock.expect(mockMemcachedCacheEntry2.toByteArray()).andReturn(newBytes);
- // update
EasyMock.expect(
- mockMemcachedClient.cas(EasyMock.eq(url), EasyMock.eq(v
- .getCas()), EasyMock.aryEq(new byte[0]))).andReturn(
- CASResponse.OK);
- mockSerializer.writeTo(EasyMock.same(updatedValue), EasyMock
- .isA(OutputStream.class));
+ mockMemcachedClient.cas(EasyMock.eq(key), EasyMock.eq(casValue.getCas()),
+ EasyMock.aryEq(newBytes))).andReturn(CASResponse.OK);
replayMocks();
impl.updateEntry(url, callback);
verifyMocks();
}
-
+
@Test
- public void testSingleCacheUpdateRetry() throws IOException,
+ public void testCacheUpdateThrowsExceptionsIfCASFailsEnoughTimes() throws IOException,
HttpCacheUpdateException {
final String url = "foo";
+ final String key = "key";
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
- CASValue<Object> v = new CASValue<Object>(1234, new byte[] {});
+ final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
+ CASValue<Object> casValue = new CASValue<Object>(1, oldBytes);
+ final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
+
+ CacheConfig config = new CacheConfig();
+ config.setMaxUpdateRetries(0);
+ impl = new MemcachedHttpCacheStorage(mockMemcachedClient, config,
+ mockMemcachedCacheEntryFactory, mockKeyHashingScheme);
HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
public HttpCacheEntry update(HttpCacheEntry old) {
- assertEquals(existingValue, old);
+ assertSame(existingValue, old);
return updatedValue;
}
};
- // get existing old entry, will happen twice
- EasyMock.expect(mockMemcachedClient.gets(url)).andReturn(v).times(2);
- EasyMock.expect(
- mockSerializer.readFrom(EasyMock.isA(InputStream.class)))
- .andReturn(existingValue).times(2);
- // update but fail
- mockSerializer.writeTo(EasyMock.same(updatedValue), EasyMock
- .isA(OutputStream.class));
- EasyMock.expectLastCall().times(2);
- EasyMock.expect(
- mockMemcachedClient.cas(EasyMock.eq(url), EasyMock.eq(v
- .getCas()), EasyMock.aryEq(new byte[0]))).andReturn(
- CASResponse.NOT_FOUND);
+ // get empty old entry
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key).anyTimes();
+ EasyMock.expect(mockMemcachedClient.gets(key)).andReturn(casValue);
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
+ .andReturn(mockMemcachedCacheEntry);
+ mockMemcachedCacheEntry.set(oldBytes);
+ EasyMock.expect(mockMemcachedCacheEntry.getStorageKey()).andReturn(url);
+ EasyMock.expect(mockMemcachedCacheEntry.getHttpCacheEntry()).andReturn(existingValue);
+
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
+ .andReturn(mockMemcachedCacheEntry2);
+ EasyMock.expect(mockMemcachedCacheEntry2.toByteArray()).andReturn(newBytes);
- // update again and succeed
EasyMock.expect(
- mockMemcachedClient.cas(EasyMock.eq(url), EasyMock.eq(v
- .getCas()), EasyMock.aryEq(new byte[0]))).andReturn(
- CASResponse.OK);
+ mockMemcachedClient.cas(EasyMock.eq(key), EasyMock.eq(casValue.getCas()),
+ EasyMock.aryEq(newBytes))).andReturn(CASResponse.EXISTS);
replayMocks();
- impl.updateEntry(url, callback);
+ try {
+ impl.updateEntry(url, callback);
+ fail("should have thrown exception");
+ } catch (HttpCacheUpdateException expected) {
+ }
verifyMocks();
}
+
@Test
- public void testCacheUpdateFail() throws IOException {
+ public void testCacheUpdateCanUpdateExistingEntryWithRetry() throws IOException,
+ HttpCacheUpdateException {
final String url = "foo";
+ final String key = "key";
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
+ final HttpCacheEntry existingValue2 = HttpTestUtils.makeCacheEntry();
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
- CASValue<Object> v = new CASValue<Object>(1234, new byte[] {});
+ final HttpCacheEntry updatedValue2 = HttpTestUtils.makeCacheEntry();
+ final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
+ final byte[] oldBytes2 = HttpTestUtils.getRandomBytes(128);
+ CASValue<Object> casValue = new CASValue<Object>(1, oldBytes);
+ CASValue<Object> casValue2 = new CASValue<Object>(2, oldBytes2);
+ final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
+ final byte[] newBytes2 = HttpTestUtils.getRandomBytes(128);
HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
public HttpCacheEntry update(HttpCacheEntry old) {
- assertEquals(existingValue, old);
- return updatedValue;
+ if (old == existingValue) return updatedValue;
+ assertSame(existingValue2, old);
+ return updatedValue2;
}
};
- // get existing old entry
- EasyMock.expect(mockMemcachedClient.gets(url)).andReturn(v).times(2);
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key).anyTimes();
+ EasyMock.expect(mockMemcachedClient.gets(key)).andReturn(casValue);
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
+ .andReturn(mockMemcachedCacheEntry);
+ mockMemcachedCacheEntry.set(oldBytes);
+ EasyMock.expect(mockMemcachedCacheEntry.getStorageKey()).andReturn(url);
+ EasyMock.expect(mockMemcachedCacheEntry.getHttpCacheEntry()).andReturn(existingValue);
+
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
+ .andReturn(mockMemcachedCacheEntry2);
+ EasyMock.expect(mockMemcachedCacheEntry2.toByteArray()).andReturn(newBytes);
+
EasyMock.expect(
- mockSerializer.readFrom(EasyMock.isA(InputStream.class)))
- .andReturn(existingValue).times(2);
+ mockMemcachedClient.cas(EasyMock.eq(key), EasyMock.eq(casValue.getCas()),
+ EasyMock.aryEq(newBytes))).andReturn(CASResponse.EXISTS);
+
+ // take two
+ EasyMock.expect(mockMemcachedClient.gets(key)).andReturn(casValue2);
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
+ .andReturn(mockMemcachedCacheEntry3);
+ mockMemcachedCacheEntry3.set(oldBytes2);
+ EasyMock.expect(mockMemcachedCacheEntry3.getStorageKey()).andReturn(url);
+ EasyMock.expect(mockMemcachedCacheEntry3.getHttpCacheEntry()).andReturn(existingValue2);
+
+ EasyMock.expect(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue2))
+ .andReturn(mockMemcachedCacheEntry4);
+ EasyMock.expect(mockMemcachedCacheEntry4.toByteArray()).andReturn(newBytes2);
- // update but fail
- mockSerializer.writeTo(EasyMock.same(updatedValue), EasyMock
- .isA(OutputStream.class));
- EasyMock.expectLastCall().times(2);
EasyMock.expect(
- mockMemcachedClient.cas(EasyMock.eq(url), EasyMock.eq(v
- .getCas()), EasyMock.aryEq(new byte[0]))).andReturn(
- CASResponse.NOT_FOUND).times(2);
+ mockMemcachedClient.cas(EasyMock.eq(key), EasyMock.eq(casValue2.getCas()),
+ EasyMock.aryEq(newBytes2))).andReturn(CASResponse.OK);
+
+ replayMocks();
+ impl.updateEntry(url, callback);
+ verifyMocks();
+ }
+
+
+ @Test
+ public void testUpdateThrowsIOExceptionIfMemcachedTimesOut() throws IOException,
+ HttpCacheUpdateException {
+ final String url = "foo";
+ final String key = "key";
+ final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
+
+ HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
+ public HttpCacheEntry update(HttpCacheEntry old) {
+ assertNull(old);
+ return updatedValue;
+ }
+ };
+
+ // get empty old entry
+ EasyMock.expect(mockKeyHashingScheme.hash(url)).andReturn(key).anyTimes();
+ EasyMock.expect(mockMemcachedClient.gets(key))
+ .andThrow(new OperationTimeoutException(""));
replayMocks();
try {
impl.updateEntry(url, callback);
- fail("Expected HttpCacheUpdateException");
- } catch (HttpCacheUpdateException e) {
+ fail("should have thrown exception");
+ } catch (IOException expected) {
}
verifyMocks();
}
+
+ @Test(expected=HttpCacheUpdateException.class)
+ public void testThrowsExceptionOnUpdateIfCannotHashStorageKey() throws Exception {
+ final String url = "foo";
+
+ EasyMock.expect(mockKeyHashingScheme.hash(url))
+ .andThrow(new MemcachedKeyHashingException(new Exception()));
+
+ replayMocks();
+ try {
+ impl.updateEntry(url, null);
+ fail("should have thrown exception");
+ } catch (HttpCacheUpdateException expected) {
+ }
+ verifyMocks();
+ }
}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestPrefixKeyHashingScheme.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestPrefixKeyHashingScheme.java
new file mode 100644
index 0000000..295a224
--- /dev/null
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestPrefixKeyHashingScheme.java
@@ -0,0 +1,57 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client.cache.memcached;
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+public class TestPrefixKeyHashingScheme {
+
+ private static final String KEY = "key";
+ private static final String PREFIX = "prefix";
+ private PrefixKeyHashingScheme impl;
+ private KeyHashingScheme scheme;
+
+ @Before
+ public void setUp() {
+ scheme = new KeyHashingScheme() {
+ public String hash(String storageKey) {
+ assertEquals(KEY, storageKey);
+ return "hash";
+ }
+ };
+ impl = new PrefixKeyHashingScheme(PREFIX, scheme);
+ }
+
+ @Test
+ public void addsPrefixToBackingScheme() {
+ assertEquals("prefixhash", impl.hash(KEY));
+ }
+}
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestSHA256HashingScheme.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestSHA256HashingScheme.java
new file mode 100644
index 0000000..d70081f
--- /dev/null
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/memcached/TestSHA256HashingScheme.java
@@ -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.impl.client.cache.memcached;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+
+public class TestSHA256HashingScheme {
+
+ @Test
+ public void canHash() {
+ SHA256KeyHashingScheme impl = new SHA256KeyHashingScheme();
+ String result = impl.hash("hello, hashing world");
+ assertTrue(result != null && result.length() > 0);
+ }
+
+}
diff --git a/httpclient-contrib/pom.xml b/httpclient-contrib/pom.xml
index 55598c3..6deb5fc 100644
--- a/httpclient-contrib/pom.xml
+++ b/httpclient-contrib/pom.xml
@@ -30,7 +30,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
- <version>4.1.1</version>
+ <version>4.2.1</version>
</parent>
<artifactId>httpclient-contrib</artifactId>
<name>HttpClient Contrib</name>
diff --git a/httpclient-osgi/pom.xml b/httpclient-osgi/pom.xml
index 6d6f9b1..21ddcac 100644
--- a/httpclient-osgi/pom.xml
+++ b/httpclient-osgi/pom.xml
@@ -30,7 +30,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
- <version>4.1.1</version>
+ <version>4.2.1</version>
</parent>
<artifactId>httpclient-osgi</artifactId>
<name>HttpClient OSGi bundle</name>
@@ -66,6 +66,12 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>fluent-hc</artifactId>
+ <version>${project.version}</version>
+ <scope>compile</scope>
+ </dependency>
</dependencies>
<properties>
@@ -105,7 +111,7 @@
org.apache.http.impl.conn.*;version=${project.version},
org.apache.http.impl.client.*;version=${project.version}
</_exportcontents>
- <Embed-Dependency>*;scope=compile|runtime;inline=false</Embed-Dependency>
+ <Embed-Dependency>*;scope=compile|runtime;inline=true</Embed-Dependency>
<Import-Package>
javax.crypto,
javax.crypto.spec,
@@ -113,15 +119,18 @@
org.ietf.jgss,
org.apache.commons.logging;version=${commons-logging.version},
org.apache.http;version=${httpcore.version},
+ org.apache.http.concurrent;version=${httpcore.version},
org.apache.http.entity;version=${httpcore.version},
org.apache.http.io;version=${httpcore.version},
org.apache.http.message;version=${httpcore.version},
org.apache.http.params;version=${httpcore.version},
+ org.apache.http.pool;version=${httpcore.version},
org.apache.http.protocol;version=${httpcore.version},
org.apache.http.util;version=${httpcore.version},
org.apache.http.impl;version=${httpcore.version},
org.apache.http.impl.entity;version=${httpcore.version},
org.apache.http.impl.io;version=${httpcore.version},
+ org.apache.http.impl.pool;version=${httpcore.version},
net.sf.ehcache.*;resolution:=optional,
net.spy.memcached.*;resolution:=optional
</Import-Package>
@@ -141,15 +150,16 @@
<finalName>org.apache.httpcomponents.httpclient_${project.version}</finalName>
</build>
- <reports>
+ <reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>clirr-maven-plugin</artifactId>
+ <version>${hc.clirr.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
- </reports>
+ </reporting>
</project>
diff --git a/httpclient/pom.xml b/httpclient/pom.xml
index 4ce1cab..9eef424 100644
--- a/httpclient/pom.xml
+++ b/httpclient/pom.xml
@@ -30,7 +30,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
- <version>4.1.1</version>
+ <version>4.2.1</version>
</parent>
<artifactId>httpclient</artifactId>
<name>HttpClient</name>
@@ -44,31 +44,26 @@
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
- <version>${httpcore.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>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
- <version>${commons-codec.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
- <version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -142,6 +137,7 @@
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
+ <version>${hc.javadoc.version}</version>
<configuration>
<!-- reduce console output. Can override with -Dquiet=false -->
<quiet>true</quiet>
@@ -171,10 +167,12 @@
<plugin>
<artifactId>maven-jxr-plugin</artifactId>
+ <version>${hc.jxr.version}</version>
</plugin>
<plugin>
<artifactId>maven-surefire-report-plugin</artifactId>
+ <version>${hc.surefire-report.version}</version>
</plugin>
</plugins>
diff --git a/httpclient/src/examples/org/apache/http/examples/client/ClientEvictExpiredConnections.java b/httpclient/src/examples/org/apache/http/examples/client/ClientEvictExpiredConnections.java
index 85cb149..c3197e1 100644
--- a/httpclient/src/examples/org/apache/http/examples/client/ClientEvictExpiredConnections.java
+++ b/httpclient/src/examples/org/apache/http/examples/client/ClientEvictExpiredConnections.java
@@ -34,7 +34,7 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.util.EntityUtils;
/**
@@ -44,7 +44,7 @@ import org.apache.http.util.EntityUtils;
public class ClientEvictExpiredConnections {
public static void main(String[] args) throws Exception {
- ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager();
+ PoolingClientConnectionManager cm = new PoolingClientConnectionManager();
cm.setMaxTotal(100);
HttpClient httpclient = new DefaultHttpClient(cm);
diff --git a/httpclient/src/examples/org/apache/http/examples/client/ClientFormLogin.java b/httpclient/src/examples/org/apache/http/examples/client/ClientFormLogin.java
index aae7778..fedcaca 100644
--- a/httpclient/src/examples/org/apache/http/examples/client/ClientFormLogin.java
+++ b/httpclient/src/examples/org/apache/http/examples/client/ClientFormLogin.java
@@ -28,6 +28,8 @@ package org.apache.http.examples.client;
import java.util.ArrayList;
import java.util.List;
+
+import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
@@ -37,7 +39,6 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
-import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
/**
@@ -77,7 +78,7 @@ public class ClientFormLogin {
nvps.add(new BasicNameValuePair("IDToken1", "username"));
nvps.add(new BasicNameValuePair("IDToken2", "password"));
- httpost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
+ httpost.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
response = httpclient.execute(httpost);
entity = response.getEntity();
diff --git a/httpclient/src/examples/org/apache/http/examples/client/ClientGZipContentCompression.java b/httpclient/src/examples/org/apache/http/examples/client/ClientGZipContentCompression.java
index 0f6844e..318d961 100644
--- a/httpclient/src/examples/org/apache/http/examples/client/ClientGZipContentCompression.java
+++ b/httpclient/src/examples/org/apache/http/examples/client/ClientGZipContentCompression.java
@@ -28,8 +28,6 @@
package org.apache.http.examples.client;
import java.io.IOException;
-import java.io.InputStream;
-import java.util.zip.GZIPInputStream;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
@@ -39,8 +37,8 @@ import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
+import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.methods.HttpGet;
-import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
@@ -81,14 +79,16 @@ public class ClientGZipContentCompression {
final HttpResponse response,
final HttpContext context) throws HttpException, IOException {
HttpEntity entity = response.getEntity();
- Header ceheader = entity.getContentEncoding();
- if (ceheader != null) {
- HeaderElement[] codecs = ceheader.getElements();
- for (int i = 0; i < codecs.length; i++) {
- if (codecs[i].getName().equalsIgnoreCase("gzip")) {
- response.setEntity(
- new GzipDecompressingEntity(response.getEntity()));
- return;
+ if (entity != null) {
+ Header ceheader = entity.getContentEncoding();
+ if (ceheader != null) {
+ HeaderElement[] codecs = ceheader.getElements();
+ for (int i = 0; i < codecs.length; i++) {
+ if (codecs[i].getName().equalsIgnoreCase("gzip")) {
+ response.setEntity(
+ new GzipDecompressingEntity(response.getEntity()));
+ return;
+ }
}
}
}
@@ -125,29 +125,5 @@ public class ClientGZipContentCompression {
}
}
- static class GzipDecompressingEntity extends HttpEntityWrapper {
-
- public GzipDecompressingEntity(final HttpEntity entity) {
- super(entity);
- }
-
- @Override
- public InputStream getContent()
- throws IOException, IllegalStateException {
-
- // the wrapped entity's getContent() decides about repeatability
- InputStream wrappedin = wrappedEntity.getContent();
-
- return new GZIPInputStream(wrappedin);
- }
-
- @Override
- public long getContentLength() {
- // length of ungzipped content is not known
- return -1;
- }
-
- }
-
}
diff --git a/httpclient/src/examples/org/apache/http/examples/client/ClientInteractiveAuthentication.java b/httpclient/src/examples/org/apache/http/examples/client/ClientInteractiveAuthentication.java
index 5294e26..5e6d4c1 100644
--- a/httpclient/src/examples/org/apache/http/examples/client/ClientInteractiveAuthentication.java
+++ b/httpclient/src/examples/org/apache/http/examples/client/ClientInteractiveAuthentication.java
@@ -29,8 +29,10 @@ import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
+import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
@@ -39,6 +41,7 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
@@ -71,22 +74,23 @@ public class ClientInteractiveAuthentication {
int sc = response.getStatusLine().getStatusCode();
AuthState authState = null;
+ HttpHost authhost = null;
if (sc == HttpStatus.SC_UNAUTHORIZED) {
// Target host authentication required
authState = (AuthState) localContext.getAttribute(ClientContext.TARGET_AUTH_STATE);
+ authhost = (HttpHost) localContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
}
if (sc == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
// Proxy authentication required
authState = (AuthState) localContext.getAttribute(ClientContext.PROXY_AUTH_STATE);
+ authhost = (HttpHost) localContext.getAttribute(ExecutionContext.HTTP_PROXY_HOST);
}
if (authState != null) {
System.out.println("----------------------------------------");
- AuthScope authScope = authState.getAuthScope();
- System.out.println("Please provide credentials");
- System.out.println(" Host: " + authScope.getHost() + ":" + authScope.getPort());
- System.out.println(" Realm: " + authScope.getRealm());
-
+ AuthScheme authscheme = authState.getAuthScheme();
+ System.out.println("Please provide credentials for " +
+ authscheme.getRealm() + "@" + authhost.toHostString());
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
@@ -97,7 +101,7 @@ public class ClientInteractiveAuthentication {
if (user != null && user.length() > 0) {
Credentials creds = new UsernamePasswordCredentials(user, password);
- httpclient.getCredentialsProvider().setCredentials(authScope, creds);
+ httpclient.getCredentialsProvider().setCredentials(new AuthScope(authhost), creds);
trying = true;
} else {
trying = false;
diff --git a/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java b/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java
index 4ce0820..08737c5 100644
--- a/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java
+++ b/httpclient/src/examples/org/apache/http/examples/client/ClientKerberosAuthentication.java
@@ -34,12 +34,12 @@ import org.apache.http.auth.Credentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.AuthPolicy;
-import org.apache.http.impl.auth.NegotiateSchemeFactory;
+import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
/**
- * Kerberos auth example.
+ * SPNEGO (Kerberos) auth example.
*
* <p><b>Information</b></p>
* <p>For the best compatibility use Java >= 1.6 as it supports SPNEGO authentication more
@@ -127,11 +127,7 @@ public class ClientKerberosAuthentication {
DefaultHttpClient httpclient = new DefaultHttpClient();
try {
- NegotiateSchemeFactory nsf = new NegotiateSchemeFactory();
-// nsf.setStripPort(false);
-// nsf.setSpengoGenerator(new BouncySpnegoTokenGenerator());
-
- httpclient.getAuthSchemes().register(AuthPolicy.SPNEGO, nsf);
+ httpclient.getAuthSchemes().register(AuthPolicy.SPNEGO, new SPNegoSchemeFactory());
Credentials use_jaas_creds = new Credentials() {
diff --git a/httpclient/src/examples/org/apache/http/examples/client/ClientMultiThreadedExecution.java b/httpclient/src/examples/org/apache/http/examples/client/ClientMultiThreadedExecution.java
index db6cd70..bc8751f 100644
--- a/httpclient/src/examples/org/apache/http/examples/client/ClientMultiThreadedExecution.java
+++ b/httpclient/src/examples/org/apache/http/examples/client/ClientMultiThreadedExecution.java
@@ -31,7 +31,7 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
@@ -46,7 +46,7 @@ public class ClientMultiThreadedExecution {
// Create an HttpClient with the ThreadSafeClientConnManager.
// This connection manager must be used if more than one thread will
// be using the HttpClient.
- ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager();
+ PoolingClientConnectionManager cm = new PoolingClientConnectionManager();
cm.setMaxTotal(100);
HttpClient httpclient = new DefaultHttpClient(cm);
diff --git a/httpclient/src/examples/org/apache/http/examples/client/ProxyTunnelDemo.java b/httpclient/src/examples/org/apache/http/examples/client/ProxyTunnelDemo.java
new file mode 100644
index 0000000..3fd6464
--- /dev/null
+++ b/httpclient/src/examples/org/apache/http/examples/client/ProxyTunnelDemo.java
@@ -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.examples.client;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.Socket;
+
+import org.apache.http.HttpHost;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.impl.client.ProxyClient;
+import org.apache.http.protocol.HTTP;
+
+/**
+ * Example code for using {@link ProxyClient} in order to establish a tunnel through an HTTP proxy.
+ */
+public class ProxyTunnelDemo {
+
+ public final static void main(String[] args) throws Exception {
+
+ ProxyClient proxyClient = new ProxyClient();
+ HttpHost target = new HttpHost("www.yahoo.com", 80);
+ HttpHost proxy = new HttpHost("localhost", 8888);
+ UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("user", "pwd");
+ Socket socket = proxyClient.tunnel(proxy, target, credentials);
+ try {
+ Writer out = new OutputStreamWriter(socket.getOutputStream(), HTTP.DEF_CONTENT_CHARSET);
+ out.write("GET / HTTP/1.1\r\n");
+ out.write("Host: " + target.toHostString() + "\r\n");
+ out.write("Agent: whatever\r\n");
+ out.write("Connection: close\r\n");
+ out.write("\r\n");
+ out.flush();
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(socket.getInputStream(), HTTP.DEF_CONTENT_CHARSET));
+ String line = null;
+ while ((line = in.readLine()) != null) {
+ System.out.println(line);
+ }
+ } finally {
+ socket.close();
+ }
+ }
+
+}
+
diff --git a/httpclient/src/examples/org/apache/http/examples/client/QuickStart.java b/httpclient/src/examples/org/apache/http/examples/client/QuickStart.java
new file mode 100644
index 0000000..d4e7ac7
--- /dev/null
+++ b/httpclient/src/examples/org/apache/http/examples/client/QuickStart.java
@@ -0,0 +1,84 @@
+/*
+ * ====================================================================
+ *
+ * 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.examples.client;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+public class QuickStart {
+
+ public static void main(String[] args) throws Exception {
+ DefaultHttpClient httpclient = new DefaultHttpClient();
+ HttpGet httpGet = new HttpGet("http://targethost/homepage");
+
+ HttpResponse response1 = httpclient.execute(httpGet);
+
+ // The underlying HTTP connection is still held by the response object
+ // to allow the response content to be streamed directly from the network socket.
+ // In order to ensure correct deallocation of system resources
+ // the user MUST either fully consume the response content or abort request
+ // execution by calling HttpGet#releaseConnection().
+
+ try {
+ System.out.println(response1.getStatusLine());
+ HttpEntity entity1 = response1.getEntity();
+ // do something useful with the response body
+ // and ensure it is fully consumed
+ EntityUtils.consume(entity1);
+ } finally {
+ httpGet.releaseConnection();
+ }
+
+ HttpPost httpPost = new HttpPost("http://targethost/login");
+ List <NameValuePair> nvps = new ArrayList <NameValuePair>();
+ nvps.add(new BasicNameValuePair("username", "vip"));
+ nvps.add(new BasicNameValuePair("password", "secret"));
+ httpPost.setEntity(new UrlEncodedFormEntity(nvps));
+ HttpResponse response2 = httpclient.execute(httpPost);
+
+ try {
+ System.out.println(response2.getStatusLine());
+ HttpEntity entity2 = response2.getEntity();
+ // do something useful with the response body
+ // and ensure it is fully consumed
+ EntityUtils.consume(entity2);
+ } finally {
+ httpPost.releaseConnection();
+ }
+ }
+
+}
diff --git a/httpclient/src/examples/org/apache/http/examples/conn/ManagerConnectDirect.java b/httpclient/src/examples/org/apache/http/examples/conn/ManagerConnectDirect.java
deleted file mode 100644
index c38a5ee..0000000
--- a/httpclient/src/examples/org/apache/http/examples/conn/ManagerConnectDirect.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * ====================================================================
- * 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.examples.conn;
-
-import org.apache.http.Header;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpVersion;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.routing.HttpRoute;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.conn.scheme.SchemeSocketFactory;
-import org.apache.http.conn.ClientConnectionRequest;
-import org.apache.http.conn.ManagedClientConnection;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.apache.http.message.BasicHttpRequest;
-import org.apache.http.params.HttpParams;
-import org.apache.http.params.HttpProtocolParams;
-import org.apache.http.params.SyncBasicHttpParams;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.protocol.BasicHttpContext;
-
-/**
- * How to open a direct connection using
- * {@link ClientConnectionManager ClientConnectionManager}.
- * This exemplifies the <i>opening</i> of the connection only.
- * The subsequent message exchange in this example should not
- * be used as a template.
- *
- * @since 4.0
- */
-public class ManagerConnectDirect {
-
- /**
- * Main entry point to this example.
- *
- * @param args ignored
- */
- public final static void main(String[] args) throws Exception {
-
- HttpHost target = new HttpHost("www.apache.org", 80, "http");
-
- // Register the "http" protocol scheme, it is required
- // by the default operator to look up socket factories.
- SchemeRegistry supportedSchemes = new SchemeRegistry();
- SchemeSocketFactory sf = PlainSocketFactory.getSocketFactory();
- supportedSchemes.register(new Scheme("http", 80, sf));
-
- // Prepare parameters.
- // Since this example doesn't use the full core framework,
- // only few parameters are actually required.
- HttpParams params = new SyncBasicHttpParams();
- HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
- HttpProtocolParams.setUseExpectContinue(params, false);
-
- ClientConnectionManager clcm = new ThreadSafeClientConnManager(supportedSchemes);
-
- HttpRequest req = new BasicHttpRequest("OPTIONS", "*", HttpVersion.HTTP_1_1);
- req.addHeader("Host", target.getHostName());
-
- HttpContext ctx = new BasicHttpContext();
-
- System.out.println("preparing route to " + target);
- HttpRoute route = new HttpRoute
- (target, null, supportedSchemes.getScheme(target).isLayered());
-
- System.out.println("requesting connection for " + route);
- ClientConnectionRequest connRequest = clcm.requestConnection(route, null);
- ManagedClientConnection conn = connRequest.getConnection(0, null);
- try {
- System.out.println("opening connection");
- conn.open(route, ctx, params);
-
- System.out.println("sending request");
- conn.sendRequestHeader(req);
- // there is no request entity
- conn.flush();
-
- System.out.println("receiving response header");
- HttpResponse rsp = conn.receiveResponseHeader();
-
- System.out.println("----------------------------------------");
- System.out.println(rsp.getStatusLine());
- Header[] headers = rsp.getAllHeaders();
- for (int i=0; i<headers.length; i++) {
- System.out.println(headers[i]);
- }
- System.out.println("----------------------------------------");
-
- System.out.println("closing connection");
- conn.close();
-
- } finally {
-
- if (conn.isOpen()) {
- System.out.println("shutting down connection");
- try {
- conn.shutdown();
- } catch (Exception ex) {
- System.out.println("problem during shutdown");
- ex.printStackTrace();
- }
- }
-
- System.out.println("releasing connection");
- clcm.releaseConnection(conn, -1, null);
- }
-
- }
-
-}
-
diff --git a/httpclient/src/examples/org/apache/http/examples/conn/ManagerConnectProxy.java b/httpclient/src/examples/org/apache/http/examples/conn/ManagerConnectProxy.java
deleted file mode 100644
index df176d6..0000000
--- a/httpclient/src/examples/org/apache/http/examples/conn/ManagerConnectProxy.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * ====================================================================
- * 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.examples.conn;
-
-import org.apache.http.Header;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpVersion;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.routing.HttpRoute;
-import org.apache.http.conn.ClientConnectionRequest;
-import org.apache.http.conn.ManagedClientConnection;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.conn.ssl.SSLSocketFactory;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.apache.http.message.BasicHttpRequest;
-import org.apache.http.params.HttpParams;
-import org.apache.http.params.HttpProtocolParams;
-import org.apache.http.params.SyncBasicHttpParams;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.protocol.BasicHttpContext;
-
-/**
- * How to open a secure connection through a proxy using
- * {@link ClientConnectionManager ClientConnectionManager}.
- * This exemplifies the <i>opening</i> of the connection only.
- * The message exchange, both subsequently and for tunnelling,
- * should not be used as a template.
- *
- * @since 4.0
- */
-public class ManagerConnectProxy {
-
- /**
- * Main entry point to this example.
- *
- * @param args ignored
- */
- public final static void main(String[] args) throws Exception {
-
- // make sure to use a proxy that supports CONNECT
- HttpHost target = new HttpHost("issues.apache.org", 443, "https");
- HttpHost proxy = new HttpHost("127.0.0.1", 8666, "http");
-
- // Register the "http" and "https" protocol schemes, they are
- // required by the default operator to look up socket factories.
- SchemeRegistry supportedSchemes = new SchemeRegistry();
- supportedSchemes.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
- supportedSchemes.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
-
- // Prepare parameters.
- // Since this example doesn't use the full core framework,
- // only few parameters are actually required.
- HttpParams params = new SyncBasicHttpParams();
- HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
- HttpProtocolParams.setUseExpectContinue(params, false);
-
- ClientConnectionManager clcm = new ThreadSafeClientConnManager(supportedSchemes);
-
- HttpRequest req = new BasicHttpRequest("OPTIONS", "*", HttpVersion.HTTP_1_1);
- req.addHeader("Host", target.getHostName());
-
- HttpContext ctx = new BasicHttpContext();
-
- System.out.println("preparing route to " + target + " via " + proxy);
- HttpRoute route = new HttpRoute
- (target, null, proxy,
- supportedSchemes.getScheme(target).isLayered());
-
- System.out.println("requesting connection for " + route);
- ClientConnectionRequest connRequest = clcm.requestConnection(route, null);
- ManagedClientConnection conn = connRequest.getConnection(0, null);
- try {
- System.out.println("opening connection");
- conn.open(route, ctx, params);
-
- String authority = target.getHostName() + ":" + target.getPort();
- HttpRequest connect = new BasicHttpRequest("CONNECT", authority, HttpVersion.HTTP_1_1);
- connect.addHeader("Host", authority);
-
- System.out.println("opening tunnel to " + target);
- conn.sendRequestHeader(connect);
- // there is no request entity
- conn.flush();
-
- System.out.println("receiving confirmation for tunnel");
- HttpResponse connected = conn.receiveResponseHeader();
- System.out.println("----------------------------------------");
- System.out.println(connected.getStatusLine());
- Header[] headers = connected.getAllHeaders();
- for (int i = 0; i < headers.length; i++) {
- System.out.println(headers[i]);
- }
- System.out.println("----------------------------------------");
- int status = connected.getStatusLine().getStatusCode();
- if ((status < 200) || (status > 299)) {
- System.out.println("unexpected status code " + status);
- System.exit(1);
- }
- System.out.println("receiving response body (ignored)");
- conn.receiveResponseEntity(connected);
-
- conn.tunnelTarget(false, params);
-
- System.out.println("layering secure connection");
- conn.layerProtocol(ctx, params);
-
- // finally we have the secure connection and can send the request
-
- System.out.println("sending request");
- conn.sendRequestHeader(req);
- // there is no request entity
- conn.flush();
-
- System.out.println("receiving response header");
- HttpResponse rsp = conn.receiveResponseHeader();
-
- System.out.println("----------------------------------------");
- System.out.println(rsp.getStatusLine());
- headers = rsp.getAllHeaders();
- for (int i = 0; i < headers.length; i++) {
- System.out.println(headers[i]);
- }
- System.out.println("----------------------------------------");
-
- System.out.println("closing connection");
- conn.close();
-
- } finally {
-
- if (conn.isOpen()) {
- System.out.println("shutting down connection");
- try {
- conn.shutdown();
- } catch (Exception ex) {
- System.out.println("problem during shutdown");
- ex.printStackTrace();
- }
- }
-
- System.out.println("releasing connection");
- clcm.releaseConnection(conn, -1, null);
- }
-
- }
-
-}
-
diff --git a/httpclient/src/main/java/org/apache/http/annotation/GuardedBy.java b/httpclient/src/main/java/org/apache/http/annotation/GuardedBy.java
deleted file mode 100644
index 5907d26..0000000
--- a/httpclient/src/main/java/org/apache/http/annotation/GuardedBy.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * ====================================================================
- *
- * 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.annotation;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * The field or method to which this annotation is applied can only be accessed
- * when holding a particular lock, which may be a built-in (synchronization) lock,
- * or may be an explicit java.util.concurrent.Lock.
- *
- * The argument determines which lock guards the annotated field or method:
- * <ul>
- * <li>
- * <code>this</code> : The intrinsic lock of the object in whose class the field is defined.
- * </li>
- * <li>
- * <code>class-name.this</code> : For inner classes, it may be necessary to disambiguate 'this';
- * the <em>class-name.this</em> designation allows you to specify which 'this' reference is intended
- * </li>
- * <li>
- * <code>itself</code> : For reference fields only; the object to which the field refers.
- * </li>
- * <li>
- * <code>field-name</code> : The lock object is referenced by the (instance or static) field
- * specified by <em>field-name</em>.
- * </li>
- * <li>
- * <code>class-name.field-name</code> : The lock object is reference by the static field specified
- * by <em>class-name.field-name</em>.
- * </li>
- * <li>
- * <code>method-name()</code> : The lock object is returned by calling the named nil-ary method.
- * </li>
- * <li>
- * <code>class-name.class</code> : The Class object for the specified class should be used as the lock object.
- * </li>
- * <p>
- * Based on code developed by Brian Goetz and Tim Peierls and concepts
- * published in 'Java Concurrency in Practice' by Brian Goetz, Tim Peierls,
- * Joshua Bloch, Joseph Bowbeer, David Holmes and Doug Lea.
- */
- at Documented
- at Target({ElementType.FIELD, ElementType.METHOD})
- at Retention(RetentionPolicy.CLASS) // The original version used RUNTIME
-public @interface GuardedBy {
- String value();
-}
diff --git a/httpclient/src/main/java/org/apache/http/annotation/Immutable.java b/httpclient/src/main/java/org/apache/http/annotation/Immutable.java
deleted file mode 100644
index da201b3..0000000
--- a/httpclient/src/main/java/org/apache/http/annotation/Immutable.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * ====================================================================
- *
- * 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.annotation;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * The class to which this annotation is applied is immutable. This means that
- * its state cannot be seen to change by callers, which implies that
- * <ul>
- * <li> all public fields are final, </li>
- * <li> all public final reference fields refer to other immutable objects, and </li>
- * <li> constructors and methods do not publish references to any internal state
- * which is potentially mutable by the implementation. </li>
- * </ul>
- * Immutable objects may still have internal mutable state for purposes of performance
- * optimization; some state variables may be lazily computed, so long as they are computed
- * from immutable state and that callers cannot tell the difference.
- * <p>
- * Immutable objects are inherently thread-safe; they may be passed between threads or
- * published without synchronization.
- * <p>
- * Based on code developed by Brian Goetz and Tim Peierls and concepts
- * published in 'Java Concurrency in Practice' by Brian Goetz, Tim Peierls,
- * Joshua Bloch, Joseph Bowbeer, David Holmes and Doug Lea.
- */
- at Documented
- at Target(ElementType.TYPE)
- at Retention(RetentionPolicy.CLASS) // The original version used RUNTIME
-public @interface Immutable {
-}
diff --git a/httpclient/src/main/java/org/apache/http/annotation/NotThreadSafe.java b/httpclient/src/main/java/org/apache/http/annotation/NotThreadSafe.java
deleted file mode 100644
index ddc6787..0000000
--- a/httpclient/src/main/java/org/apache/http/annotation/NotThreadSafe.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * ====================================================================
- *
- * 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.annotation;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * The class to which this annotation is applied is not thread-safe.
- * This annotation primarily exists for clarifying the non-thread-safety of a class
- * that might otherwise be assumed to be thread-safe, despite the fact that it is a bad
- * idea to assume a class is thread-safe without good reason.
- * @see ThreadSafe
- * <p>
- * Based on code developed by Brian Goetz and Tim Peierls and concepts
- * published in 'Java Concurrency in Practice' by Brian Goetz, Tim Peierls,
- * Joshua Bloch, Joseph Bowbeer, David Holmes and Doug Lea.
- */
- at Documented
- at Target(ElementType.TYPE)
- at Retention(RetentionPolicy.CLASS) // The original version used RUNTIME
-public @interface NotThreadSafe {
-}
diff --git a/httpclient/src/main/java/org/apache/http/annotation/ThreadSafe.java b/httpclient/src/main/java/org/apache/http/annotation/ThreadSafe.java
deleted file mode 100644
index 53c91b9..0000000
--- a/httpclient/src/main/java/org/apache/http/annotation/ThreadSafe.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * ====================================================================
- *
- * 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.annotation;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * The class to which this annotation is applied is thread-safe. This means that
- * no sequences of accesses (reads and writes to public fields, calls to public methods)
- * may put the object into an invalid state, regardless of the interleaving of those actions
- * by the runtime, and without requiring any additional synchronization or coordination on the
- * part of the caller.
- * @see NotThreadSafe
- * <p>
- * Based on code developed by Brian Goetz and Tim Peierls and concepts
- * published in 'Java Concurrency in Practice' by Brian Goetz, Tim Peierls,
- * Joshua Bloch, Joseph Bowbeer, David Holmes and Doug Lea.
- */
- at Documented
- at Target(ElementType.TYPE)
- at Retention(RetentionPolicy.CLASS) // The original version used RUNTIME
-public @interface ThreadSafe {
-}
diff --git a/httpclient/src/main/java/org/apache/http/auth/AuthOption.java b/httpclient/src/main/java/org/apache/http/auth/AuthOption.java
new file mode 100644
index 0000000..97c0c58
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/auth/AuthOption.java
@@ -0,0 +1,66 @@
+/*
+ * ====================================================================
+ *
+ * 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.auth;
+
+import org.apache.http.annotation.Immutable;
+
+/**
+ * @since 4.2
+ */
+ at Immutable
+public final class AuthOption {
+
+ private final AuthScheme authScheme;
+ private final Credentials creds;
+
+ public AuthOption(final AuthScheme authScheme, final Credentials creds) {
+ super();
+ if (authScheme == null) {
+ throw new IllegalArgumentException("Auth scheme may not be null");
+ }
+ if (creds == null) {
+ throw new IllegalArgumentException("User credentials may not be null");
+ }
+ this.authScheme = authScheme;
+ this.creds = creds;
+ }
+
+ public AuthScheme getAuthScheme() {
+ return this.authScheme;
+ }
+
+ public Credentials getCredentials() {
+ return this.creds;
+ }
+
+ @Override
+ public String toString() {
+ return this.authScheme.toString();
+ }
+
+}
+
diff --git a/httpclient/src/main/java/org/apache/http/auth/AuthProtocolState.java b/httpclient/src/main/java/org/apache/http/auth/AuthProtocolState.java
new file mode 100644
index 0000000..786f107
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/auth/AuthProtocolState.java
@@ -0,0 +1,33 @@
+/*
+ * ====================================================================
+ *
+ * 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.auth;
+
+public enum AuthProtocolState {
+
+ UNCHALLENGED, CHALLENGED, HANDSHAKE, FAILURE, SUCCESS
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/auth/AuthScheme.java b/httpclient/src/main/java/org/apache/http/auth/AuthScheme.java
index cccb7c4..d782014 100644
--- a/httpclient/src/main/java/org/apache/http/auth/AuthScheme.java
+++ b/httpclient/src/main/java/org/apache/http/auth/AuthScheme.java
@@ -121,7 +121,7 @@ public interface AuthScheme {
*
* @return the authorization string
*
- * @deprecated Use {@link ContextAwareAuthScheme#authenticate(Credentials, HttpRequest, org.apache.http.protocol.HttpContext)}
+ * @deprecated (4.1) Use {@link ContextAwareAuthScheme#authenticate(Credentials, HttpRequest, org.apache.http.protocol.HttpContext)}
*/
@Deprecated
Header authenticate(Credentials credentials, HttpRequest request)
diff --git a/httpclient/src/main/java/org/apache/http/auth/AuthScope.java b/httpclient/src/main/java/org/apache/http/auth/AuthScope.java
index f14627c..e48f7d8 100644
--- a/httpclient/src/main/java/org/apache/http/auth/AuthScope.java
+++ b/httpclient/src/main/java/org/apache/http/auth/AuthScope.java
@@ -28,6 +28,7 @@ package org.apache.http.auth;
import java.util.Locale;
+import org.apache.http.HttpHost;
import org.apache.http.annotation.Immutable;
import org.apache.http.util.LangUtils;
@@ -109,6 +110,20 @@ public class AuthScope {
this.scheme = (scheme == null) ? ANY_SCHEME: scheme.toUpperCase(Locale.ENGLISH);
}
+ /**
+ * @since 4.2
+ */
+ public AuthScope(final HttpHost host, final String realm, final String schemeName) {
+ this(host.getHostName(), host.getPort(), realm, schemeName);
+ }
+
+ /**
+ * @since 4.2
+ */
+ public AuthScope(final HttpHost host) {
+ this(host, ANY_REALM, ANY_SCHEME);
+ }
+
/** Creates a new credentials scope for the given
* <tt>host</tt>, <tt>port</tt>, <tt>realm</tt>, and any
* authentication scheme.
diff --git a/httpclient/src/main/java/org/apache/http/auth/AuthState.java b/httpclient/src/main/java/org/apache/http/auth/AuthState.java
index d3d2b2d..925d7f8 100644
--- a/httpclient/src/main/java/org/apache/http/auth/AuthState.java
+++ b/httpclient/src/main/java/org/apache/http/auth/AuthState.java
@@ -26,19 +26,21 @@
package org.apache.http.auth;
-import org.apache.http.annotation.NotThreadSafe;
+import java.util.Queue;
+import org.apache.http.annotation.NotThreadSafe;
/**
- * This class provides detailed information about the state of the
- * authentication process.
- *
+ * This class provides detailed information about the state of the authentication process.
*
* @since 4.0
*/
@NotThreadSafe
public class AuthState {
+ /** Actual state of authentication protocol */
+ private AuthProtocolState state;
+
/** Actual authentication scheme */
private AuthScheme authScheme;
@@ -48,97 +50,190 @@ public class AuthState {
/** Credentials selected for authentication */
private Credentials credentials;
- /**
- * Default constructor.
- *
- */
+ /** Available auth options */
+ private Queue<AuthOption> authOptions;
+
public AuthState() {
super();
+ this.state = AuthProtocolState.UNCHALLENGED;
}
/**
- * Invalidates the authentication state by resetting its parameters.
+ * Resets the auth state.
+ *
+ * @since 4.2
*/
- public void invalidate() {
+ public void reset() {
+ this.state = AuthProtocolState.UNCHALLENGED;
+ this.authOptions = null;
this.authScheme = null;
this.authScope = null;
this.credentials = null;
}
- public boolean isValid() {
- return this.authScheme != null;
+ /**
+ * @since 4.2
+ */
+ public AuthProtocolState getState() {
+ return this.state;
}
/**
- * Assigns the given {@link AuthScheme authentication scheme}.
+ * @since 4.2
+ */
+ public void setState(final AuthProtocolState state) {
+ this.state = state != null ? state : AuthProtocolState.UNCHALLENGED;
+ }
+
+ /**
+ * Returns actual {@link AuthScheme}. May be null.
+ */
+ public AuthScheme getAuthScheme() {
+ return this.authScheme;
+ }
+
+ /**
+ * Returns actual {@link Credentials}. May be null.
+ */
+ public Credentials getCredentials() {
+ return this.credentials;
+ }
+
+ /**
+ * Updates the auth state with {@link AuthScheme} and {@link Credentials}.
*
- * @param authScheme the {@link AuthScheme authentication scheme}
+ * @param authScheme auth scheme. May not be null.
+ * @param credentials user crednetials. May not be null.
+ *
+ * @since 4.2
*/
- public void setAuthScheme(final AuthScheme authScheme) {
+ public void update(final AuthScheme authScheme, final Credentials credentials) {
if (authScheme == null) {
- invalidate();
- return;
+ throw new IllegalArgumentException("Auth scheme may not be null or empty");
+ }
+ if (credentials == null) {
+ throw new IllegalArgumentException("Credentials may not be null or empty");
}
this.authScheme = authScheme;
+ this.credentials = credentials;
+ this.authOptions = null;
}
/**
- * Returns the {@link AuthScheme authentication scheme}.
+ * Returns available {@link AuthOption}s. May be null.
*
- * @return {@link AuthScheme authentication scheme}
+ * @since 4.2
*/
- public AuthScheme getAuthScheme() {
- return this.authScheme;
+ public Queue<AuthOption> getAuthOptions() {
+ return this.authOptions;
+ }
+
+ /**
+ * Returns <code>true</code> if {@link AuthOption}s are available, <code>false</code>
+ * otherwise.
+ *
+ * @since 4.2
+ */
+ public boolean hasAuthOptions() {
+ return this.authOptions != null && !this.authOptions.isEmpty();
}
+ /**
+ * Updates the auth state with a queue of {@link AuthOption}s.
+ *
+ * @param authOptions a queue of auth options. May not be null or empty.
+ *
+ * @since 4.2
+ */
+ public void update(final Queue<AuthOption> authOptions) {
+ if (authOptions == null || authOptions.isEmpty()) {
+ throw new IllegalArgumentException("Queue of auth options may not be null or empty");
+ }
+ this.authOptions = authOptions;
+ this.authScheme = null;
+ this.credentials = null;
+ }
/**
- * Returns user {@link Credentials} selected for authentication if available
+ * Invalidates the authentication state by resetting its parameters.
*
- * @return user credentials if available, <code>null</code otherwise
+ * @deprecated (4.2) use {@link #reset()}
*/
- public Credentials getCredentials() {
- return this.credentials;
+ @Deprecated
+ public void invalidate() {
+ reset();
}
+ /**
+ * @deprecated (4.2) do not use
+ */
+ @Deprecated
+ public boolean isValid() {
+ return this.authScheme != null;
+ }
+
+ /**
+ * Assigns the given {@link AuthScheme authentication scheme}.
+ *
+ * @param authScheme the {@link AuthScheme authentication scheme}
+ *
+ * @deprecated (4.2) use {@link #update(AuthScheme, Credentials)}
+ */
+ @Deprecated
+ public void setAuthScheme(final AuthScheme authScheme) {
+ if (authScheme == null) {
+ reset();
+ return;
+ }
+ this.authScheme = authScheme;
+ }
/**
* Sets user {@link Credentials} to be used for authentication
*
* @param credentials User credentials
+ *
+ * @deprecated (4.2) use {@link #update(AuthScheme, Credentials)}
*/
+ @Deprecated
public void setCredentials(final Credentials credentials) {
this.credentials = credentials;
}
-
/**
* Returns actual {@link AuthScope} if available
*
* @return actual authentication scope if available, <code>null</code otherwise
+ *
+ * @deprecated (4.2) do not use.
*/
- public AuthScope getAuthScope() {
+ @Deprecated
+ public AuthScope getAuthScope() {
return this.authScope;
- }
-
- /**
- * Sets actual {@link AuthScope}.
- *
- * @param authScope Authentication scope
- */
- public void setAuthScope(final AuthScope authScope) {
- this.authScope = authScope;
- }
+ }
+ /**
+ * Sets actual {@link AuthScope}.
+ *
+ * @param authScope Authentication scope
+ *
+ * @deprecated (4.2) do not use.
+ */
+ @Deprecated
+ public void setAuthScope(final AuthScope authScope) {
+ this.authScope = authScope;
+ }
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
- buffer.append("auth scope [");
- buffer.append(this.authScope);
- buffer.append("]; credentials set [");
- buffer.append(this.credentials != null ? "true" : "false");
- buffer.append("]");
+ buffer.append("state:").append(this.state).append(";");
+ if (this.authScheme != null) {
+ buffer.append("auth scheme:").append(this.authScheme.getSchemeName()).append(";");
+ }
+ if (this.credentials != null) {
+ buffer.append("credentials present");
+ }
return buffer.toString();
}
diff --git a/httpclient/src/main/java/org/apache/http/auth/ChallengeState.java b/httpclient/src/main/java/org/apache/http/auth/ChallengeState.java
new file mode 100644
index 0000000..4d25d61
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/auth/ChallengeState.java
@@ -0,0 +1,37 @@
+/*
+ * ====================================================================
+ *
+ * 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.auth;
+
+/**
+ * Challenge mode (TARGET or PROXY)
+ *
+ * @since 4.2
+ */
+public enum ChallengeState {
+
+ TARGET, PROXY
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/auth/params/AuthParams.java b/httpclient/src/main/java/org/apache/http/auth/params/AuthParams.java
index 55cf9a2..8e8250a 100644
--- a/httpclient/src/main/java/org/apache/http/auth/params/AuthParams.java
+++ b/httpclient/src/main/java/org/apache/http/auth/params/AuthParams.java
@@ -61,7 +61,7 @@ public final class AuthParams {
String charset = (String) params.getParameter
(AuthPNames.CREDENTIAL_CHARSET);
if (charset == null) {
- charset = HTTP.DEFAULT_PROTOCOL_CHARSET;
+ charset = HTTP.DEF_PROTOCOL_CHARSET.name();
}
return charset;
}
diff --git a/httpclient/src/main/java/org/apache/http/client/AuthenticationHandler.java b/httpclient/src/main/java/org/apache/http/client/AuthenticationHandler.java
index 2d0da42..60065bd 100644
--- a/httpclient/src/main/java/org/apache/http/client/AuthenticationHandler.java
+++ b/httpclient/src/main/java/org/apache/http/client/AuthenticationHandler.java
@@ -47,7 +47,10 @@ import org.apache.http.protocol.HttpContext;
* from multiple threads.
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link AuthenticationStrategy}
*/
+ at Deprecated
public interface AuthenticationHandler {
/**
diff --git a/httpclient/src/main/java/org/apache/http/client/AuthenticationStrategy.java b/httpclient/src/main/java/org/apache/http/client/AuthenticationStrategy.java
new file mode 100644
index 0000000..4f4ef96
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/client/AuthenticationStrategy.java
@@ -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;
+
+import java.util.Map;
+import java.util.Queue;
+
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthOption;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.MalformedChallengeException;
+import org.apache.http.protocol.HttpContext;
+
+/**
+/**
+ * A handler for determining if an HTTP response represents an authentication challenge that was
+ * sent back to the client as a result of authentication failure.
+ * <p>
+ * Implementations of this interface must be thread-safe. Access to shared data must be
+ * synchronized as methods of this interface may be executed from multiple threads.
+ *
+ * @since 4.2
+ */
+public interface AuthenticationStrategy {
+
+ /**
+ * Determines if the given HTTP response response represents
+ * an authentication challenge that was sent back as a result
+ * of authentication failure.
+ *
+ * @param authhost authentication host.
+ * @param response HTTP response.
+ * @param context HTTP context.
+ * @return <code>true</code> if user authentication is required,
+ * <code>false</code> otherwise.
+ */
+ boolean isAuthenticationRequested(
+ HttpHost authhost,
+ HttpResponse response,
+ HttpContext context);
+
+ /**
+ * Extracts from the given HTTP response a collection of authentication
+ * challenges, each of which represents an authentication scheme supported
+ * by the authentication host.
+ *
+ * @param authhost authentication host.
+ * @param response HTTP response.
+ * @param context HTTP context.
+ * @return a collection of challenges keyed by names of corresponding
+ * authentication schemes.
+ * @throws MalformedChallengeException if one of the authentication
+ * challenges is not valid or malformed.
+ */
+ Map<String, Header> getChallenges(
+ HttpHost authhost,
+ HttpResponse response,
+ HttpContext context) throws MalformedChallengeException;
+
+ /**
+ * Selects one authentication challenge out of all available and
+ * creates and generates {@link AuthOption} instance capable of
+ * processing that challenge.
+ *
+ * @param challenges collection of challenges.
+ * @param authhost authentication host.
+ * @param response HTTP response.
+ * @param context HTTP context.
+ * @return authentication auth schemes that can be used for authentication. Can be empty.
+ * @throws MalformedChallengeException if one of the authentication
+ * challenges is not valid or malformed.
+ */
+ Queue<AuthOption> select(
+ Map<String, Header> challenges,
+ HttpHost authhost,
+ HttpResponse response,
+ HttpContext context) throws MalformedChallengeException;
+
+ /**
+ * Callback invoked in case of successful authentication.
+ *
+ * @param authhost authentication host.
+ * @param authScheme authentication scheme used.
+ * @param context HTTP context.
+ */
+ void authSucceeded(
+ HttpHost authhost,
+ AuthScheme authScheme,
+ HttpContext context);
+
+ /**
+ * Callback invoked in case of unsuccessful authentication.
+ *
+ * @param authhost authentication host.
+ * @param authScheme authentication scheme used.
+ * @param context HTTP context.
+ */
+ void authFailed(
+ HttpHost authhost,
+ AuthScheme authScheme,
+ HttpContext context);
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/BackoffManager.java b/httpclient/src/main/java/org/apache/http/client/BackoffManager.java
new file mode 100644
index 0000000..f13330d
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/client/BackoffManager.java
@@ -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;
+
+import org.apache.http.conn.routing.HttpRoute;
+
+/**
+ * Represents a controller that dynamically adjusts the size
+ * of an available connection pool based on feedback from
+ * using the connections.
+ *
+ * @since 4.2
+ *
+ */
+public interface BackoffManager {
+
+ /**
+ * Called when we have decided that the result of
+ * using a connection should be interpreted as a
+ * backoff signal.
+ */
+ public void backOff(HttpRoute route);
+
+ /**
+ * Called when we have determined that the result of
+ * using a connection has succeeded and that we may
+ * probe for more connections.
+ */
+ public void probe(HttpRoute route);
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/ConnectionBackoffStrategy.java b/httpclient/src/main/java/org/apache/http/client/ConnectionBackoffStrategy.java
new file mode 100644
index 0000000..3154817
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/client/ConnectionBackoffStrategy.java
@@ -0,0 +1,63 @@
+/*
+ * ====================================================================
+ *
+ * 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;
+
+import org.apache.http.HttpResponse;
+
+/**
+ * When managing a dynamic number of connections for a given route, this
+ * strategy assesses whether a given request execution outcome should
+ * result in a backoff signal or not, based on either examining the
+ * <code>Throwable</code> that resulted or by examining the resulting
+ * response (e.g. for its status code).
+ *
+ * @since 4.2
+ *
+ */
+public interface ConnectionBackoffStrategy {
+
+ /**
+ * Determines whether seeing the given <code>Throwable</code> as
+ * a result of request execution should result in a backoff
+ * signal.
+ * @param t the <code>Throwable</code> that happened
+ * @return <code>true</code> if a backoff signal should be
+ * given
+ */
+ boolean shouldBackoff(Throwable t);
+
+ /**
+ * Determines whether receiving the given {@link HttpResponse} as
+ * a result of request execution should result in a backoff
+ * signal. Implementations MUST restrict themselves to examining
+ * the response header and MUST NOT consume any of the response
+ * body, if any.
+ * @param resp the <code>HttpResponse</code> that was received
+ * @return <code>true</code> if a backoff signal should be
+ * given
+ */
+ boolean shouldBackoff(HttpResponse resp);
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/RedirectHandler.java b/httpclient/src/main/java/org/apache/http/client/RedirectHandler.java
index af47638..7bd4bd4 100644
--- a/httpclient/src/main/java/org/apache/http/client/RedirectHandler.java
+++ b/httpclient/src/main/java/org/apache/http/client/RedirectHandler.java
@@ -44,7 +44,7 @@ import org.apache.http.protocol.HttpContext;
*
* @since 4.0
*
- * @deprecated use {@link RedirectStrategy}
+ * @deprecated (4.1) use {@link RedirectStrategy}
*/
@Deprecated
public interface RedirectHandler {
diff --git a/httpclient/src/main/java/org/apache/http/client/RedirectStrategy.java b/httpclient/src/main/java/org/apache/http/client/RedirectStrategy.java
index 572f505..9a82469 100644
--- a/httpclient/src/main/java/org/apache/http/client/RedirectStrategy.java
+++ b/httpclient/src/main/java/org/apache/http/client/RedirectStrategy.java
@@ -78,4 +78,4 @@ public interface RedirectStrategy {
HttpResponse response,
HttpContext context) throws ProtocolException;
-}
\ No newline at end of file
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/ServiceUnavailableRetryStrategy.java b/httpclient/src/main/java/org/apache/http/client/ServiceUnavailableRetryStrategy.java
new file mode 100644
index 0000000..69c449f
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/client/ServiceUnavailableRetryStrategy.java
@@ -0,0 +1,60 @@
+/*
+ * ====================================================================
+ * 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;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * Strategy interface that allows API users to plug in their own logic to
+ * control whether or not a retry should automatically be done, how many times
+ * it should be retried and so on.
+ *
+ * @since 4.2
+ */
+public interface ServiceUnavailableRetryStrategy {
+
+ /**
+ * Determines if a method should be retried given the response from the target server.
+ *
+ * @param response the response from the target server
+ * @param executionCount the number of times this method has been
+ * unsuccessfully executed
+ * @param context the context for the request execution
+
+ * @return <code>true</code> if the method should be retried, <code>false</code>
+ * otherwise
+ */
+ boolean retryRequest(HttpResponse response, int executionCount, HttpContext context);
+
+ /**
+ * @return The interval between the subsequent auto-retries.
+ */
+ long getRetryInterval();
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/entity/DecompressingEntity.java b/httpclient/src/main/java/org/apache/http/client/entity/DecompressingEntity.java
index ebb7495..06272a6 100644
--- a/httpclient/src/main/java/org/apache/http/client/entity/DecompressingEntity.java
+++ b/httpclient/src/main/java/org/apache/http/client/entity/DecompressingEntity.java
@@ -45,6 +45,12 @@ abstract class DecompressingEntity extends HttpEntityWrapper {
private static final int BUFFER_SIZE = 1024 * 2;
/**
+ * DecompressingEntities are not repeatable, so they will return the same
+ * InputStream instance when {@link #getContent()} is called.
+ */
+ private InputStream content;
+
+ /**
* Creates a new {@link DecompressingEntity}.
*
* @param wrapped
@@ -54,6 +60,23 @@ abstract class DecompressingEntity extends HttpEntityWrapper {
super(wrapped);
}
+ abstract InputStream getDecompressingInputStream(final InputStream wrapped) throws IOException;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public InputStream getContent() throws IOException {
+ if (wrappedEntity.isStreaming()) {
+ if (content == null) {
+ content = getDecompressingInputStream(wrappedEntity.getContent());
+ }
+ return content;
+ } else {
+ return getDecompressingInputStream(wrappedEntity.getContent());
+ }
+ }
+
/**
* {@inheritDoc}
*/
@@ -62,15 +85,17 @@ abstract class DecompressingEntity extends HttpEntityWrapper {
if (outstream == null) {
throw new IllegalArgumentException("Output stream may not be null");
}
-
InputStream instream = getContent();
+ try {
+ byte[] buffer = new byte[BUFFER_SIZE];
- byte[] buffer = new byte[BUFFER_SIZE];
-
- int l;
+ int l;
- while ((l = instream.read(buffer)) != -1) {
- outstream.write(buffer, 0, l);
+ while ((l = instream.read(buffer)) != -1) {
+ outstream.write(buffer, 0, l);
+ }
+ } finally {
+ instream.close();
}
}
diff --git a/httpclient/src/main/java/org/apache/http/client/entity/DeflateDecompressingEntity.java b/httpclient/src/main/java/org/apache/http/client/entity/DeflateDecompressingEntity.java
index 9cef5eb..bb9cbf8 100644
--- a/httpclient/src/main/java/org/apache/http/client/entity/DeflateDecompressingEntity.java
+++ b/httpclient/src/main/java/org/apache/http/client/entity/DeflateDecompressingEntity.java
@@ -63,12 +63,14 @@ public class DeflateDecompressingEntity extends DecompressingEntity {
}
/**
- * {@inheritDoc}
+ * Returns the non-null InputStream that should be returned to by all requests to
+ * {@link #getContent()}.
+ *
+ * @return a non-null InputStream
+ * @throws IOException if there was a problem
*/
@Override
- public InputStream getContent() throws IOException {
- InputStream wrapped = this.wrappedEntity.getContent();
-
+ InputStream getDecompressingInputStream(final InputStream wrapped) throws IOException {
/*
* A zlib stream will have a header.
*
diff --git a/httpclient/src/main/java/org/apache/http/client/entity/GzipDecompressingEntity.java b/httpclient/src/main/java/org/apache/http/client/entity/GzipDecompressingEntity.java
index a1dc7b9..9e22eb4 100644
--- a/httpclient/src/main/java/org/apache/http/client/entity/GzipDecompressingEntity.java
+++ b/httpclient/src/main/java/org/apache/http/client/entity/GzipDecompressingEntity.java
@@ -51,16 +51,9 @@ public class GzipDecompressingEntity extends DecompressingEntity {
super(entity);
}
- /**
- * {@inheritDoc}
- */
@Override
- public InputStream getContent() throws IOException, IllegalStateException {
-
- // the wrapped entity's getContent() decides about repeatability
- InputStream wrappedin = wrappedEntity.getContent();
-
- return new GZIPInputStream(wrappedin);
+ InputStream getDecompressingInputStream(final InputStream wrapped) throws IOException {
+ return new GZIPInputStream(wrapped);
}
/**
diff --git a/httpclient/src/main/java/org/apache/http/client/entity/UrlEncodedFormEntity.java b/httpclient/src/main/java/org/apache/http/client/entity/UrlEncodedFormEntity.java
index 7b6f086..33e389f 100644
--- a/httpclient/src/main/java/org/apache/http/client/entity/UrlEncodedFormEntity.java
+++ b/httpclient/src/main/java/org/apache/http/client/entity/UrlEncodedFormEntity.java
@@ -27,12 +27,14 @@
package org.apache.http.client.entity;
import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
import java.util.List;
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.protocol.HTTP;
@@ -50,15 +52,32 @@ public class UrlEncodedFormEntity extends StringEntity {
* of parameters in the specified encoding.
*
* @param parameters list of name/value pairs
- * @param encoding encoding the name/value pairs be encoded with
+ * @param charset encoding the name/value pairs be encoded with
* @throws UnsupportedEncodingException if the encoding isn't supported
*/
public UrlEncodedFormEntity (
final List <? extends NameValuePair> parameters,
- final String encoding) throws UnsupportedEncodingException {
- super(URLEncodedUtils.format(parameters, encoding), encoding);
- setContentType(URLEncodedUtils.CONTENT_TYPE + HTTP.CHARSET_PARAM +
- (encoding != null ? encoding : HTTP.DEFAULT_CONTENT_CHARSET));
+ final String charset) throws UnsupportedEncodingException {
+ super(URLEncodedUtils.format(parameters,
+ charset != null ? charset : HTTP.DEF_CONTENT_CHARSET.name()),
+ ContentType.create(URLEncodedUtils.CONTENT_TYPE, charset));
+ }
+
+ /**
+ * Constructs a new {@link UrlEncodedFormEntity} with the list
+ * of parameters in the specified encoding.
+ *
+ * @param parameters iterable collection of name/value pairs
+ * @param charset encoding the name/value pairs be encoded with
+ *
+ * @since 4.2
+ */
+ public UrlEncodedFormEntity (
+ final Iterable <? extends NameValuePair> parameters,
+ final Charset charset) {
+ super(URLEncodedUtils.format(parameters,
+ charset != null ? charset : HTTP.DEF_CONTENT_CHARSET),
+ ContentType.create(URLEncodedUtils.CONTENT_TYPE, charset));
}
/**
@@ -70,7 +89,20 @@ public class UrlEncodedFormEntity extends StringEntity {
*/
public UrlEncodedFormEntity (
final List <? extends NameValuePair> parameters) throws UnsupportedEncodingException {
- this(parameters, HTTP.DEFAULT_CONTENT_CHARSET);
+ this(parameters, (Charset) null);
+ }
+
+ /**
+ * Constructs a new {@link UrlEncodedFormEntity} with the list
+ * of parameters with the default encoding of {@link HTTP#DEFAULT_CONTENT_CHARSET}
+ *
+ * @param parameters iterable collection of name/value pairs
+ *
+ * @since 4.2
+ */
+ public UrlEncodedFormEntity (
+ final Iterable <? extends NameValuePair> parameters) {
+ this(parameters, null);
}
}
diff --git a/httpclient/src/main/java/org/apache/http/client/methods/AbortableHttpRequest.java b/httpclient/src/main/java/org/apache/http/client/methods/AbortableHttpRequest.java
index b397fac..29a8710 100644
--- a/httpclient/src/main/java/org/apache/http/client/methods/AbortableHttpRequest.java
+++ b/httpclient/src/main/java/org/apache/http/client/methods/AbortableHttpRequest.java
@@ -34,7 +34,6 @@ import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ClientConnectionRequest;
import org.apache.http.conn.ConnectionReleaseTrigger;
import org.apache.http.conn.ManagedClientConnection;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
/**
* Interface representing an HTTP request that can be aborted by shutting
@@ -52,7 +51,6 @@ public interface AbortableHttpRequest {
* If the request is already aborted, throws an {@link IOException}.
*
* @see ClientConnectionManager
- * @see ThreadSafeClientConnManager
*/
void setConnectionRequest(ClientConnectionRequest connRequest) throws IOException;
diff --git a/httpclient/src/main/java/org/apache/http/client/methods/HttpPatch.java b/httpclient/src/main/java/org/apache/http/client/methods/HttpPatch.java
new file mode 100644
index 0000000..3a6b126
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/client/methods/HttpPatch.java
@@ -0,0 +1,75 @@
+/*
+ * ====================================================================
+ * 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.methods;
+
+import java.net.URI;
+
+import org.apache.http.annotation.NotThreadSafe;
+
+/**
+ * HTTP PATCH method.
+ * <p>
+ * The HTTP PATCH method is defined in <a
+ * href="http://tools.ietf.org/html/rfc5789">RF5789</a>: <blockquote> The PATCH
+ * method requests that a set of changes described in the request entity be
+ * applied to the resource identified by the Request- URI. Differs from the PUT
+ * method in the way the server processes the enclosed entity to modify the
+ * resource identified by the Request-URI. In a PUT request, the enclosed entity
+ * origin server, and the client is requesting that the stored version be
+ * replaced. With PATCH, however, the enclosed entity contains a set of
+ * instructions describing how a resource currently residing on the origin
+ * server should be modified to produce a new version. </blockquote>
+ * </p>
+ *
+ * @since 4.2
+ */
+ at NotThreadSafe
+public class HttpPatch extends HttpEntityEnclosingRequestBase {
+
+ public final static String METHOD_NAME = "PATCH";
+
+ public HttpPatch() {
+ super();
+ }
+
+ public HttpPatch(final URI uri) {
+ super();
+ setURI(uri);
+ }
+
+ public HttpPatch(final String uri) {
+ super();
+ setURI(URI.create(uri));
+ }
+
+ @Override
+ public String getMethod() {
+ return METHOD_NAME;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/methods/HttpRequestBase.java b/httpclient/src/main/java/org/apache/http/client/methods/HttpRequestBase.java
index 92b03e5..5c23fe3 100644
--- a/httpclient/src/main/java/org/apache/http/client/methods/HttpRequestBase.java
+++ b/httpclient/src/main/java/org/apache/http/client/methods/HttpRequestBase.java
@@ -46,8 +46,8 @@ import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
/**
- * Basic implementation of an HTTP request that can be modified.
- *
+ * Basic implementation of an HTTP request that can be modified. Methods of the
+ * {@link AbortableHttpRequest} interface implemented by this class are thread safe.
*
* @since 4.0
*/
@@ -56,8 +56,7 @@ public abstract class HttpRequestBase extends AbstractHttpMessage
implements HttpUriRequest, AbortableHttpRequest, Cloneable {
private Lock abortLock;
-
- private boolean aborted;
+ private volatile boolean aborted;
private URI uri;
private ClientConnectionRequest connRequest;
@@ -104,13 +103,11 @@ public abstract class HttpRequestBase extends AbstractHttpMessage
public void setConnectionRequest(final ClientConnectionRequest connRequest)
throws IOException {
+ if (this.aborted) {
+ throw new IOException("Request already aborted");
+ }
this.abortLock.lock();
try {
- if (this.aborted) {
- throw new IOException("Request already aborted");
- }
-
- this.releaseTrigger = null;
this.connRequest = connRequest;
} finally {
this.abortLock.unlock();
@@ -119,56 +116,73 @@ public abstract class HttpRequestBase extends AbstractHttpMessage
public void setReleaseTrigger(final ConnectionReleaseTrigger releaseTrigger)
throws IOException {
+ if (this.aborted) {
+ throw new IOException("Request already aborted");
+ }
this.abortLock.lock();
try {
- if (this.aborted) {
- throw new IOException("Request already aborted");
- }
-
- this.connRequest = null;
this.releaseTrigger = releaseTrigger;
} finally {
this.abortLock.unlock();
}
}
- public void abort() {
- ClientConnectionRequest localRequest;
- ConnectionReleaseTrigger localTrigger;
+ private void cleanup() {
+ if (this.connRequest != null) {
+ this.connRequest.abortRequest();
+ this.connRequest = null;
+ }
+ if (this.releaseTrigger != null) {
+ try {
+ this.releaseTrigger.abortConnection();
+ } catch (IOException ex) {
+ }
+ this.releaseTrigger = null;
+ }
+ }
+ public void abort() {
+ if (this.aborted) {
+ return;
+ }
this.abortLock.lock();
try {
- if (this.aborted) {
- return;
- }
this.aborted = true;
-
- localRequest = connRequest;
- localTrigger = releaseTrigger;
+ cleanup();
} finally {
this.abortLock.unlock();
}
-
- // Trigger the callbacks outside of the lock, to prevent
- // deadlocks in the scenario where the callbacks have
- // their own locks that may be used while calling
- // setReleaseTrigger or setConnectionRequest.
- if (localRequest != null) {
- localRequest.abortRequest();
- }
- if (localTrigger != null) {
- try {
- localTrigger.abortConnection();
- } catch (IOException ex) {
- // ignore
- }
- }
}
public boolean isAborted() {
return this.aborted;
}
+ /**
+ * Resets internal state of the request making it reusable.
+ *
+ * @since 4.2
+ */
+ public void reset() {
+ this.abortLock.lock();
+ try {
+ cleanup();
+ this.aborted = false;
+ } finally {
+ this.abortLock.unlock();
+ }
+ }
+
+ /**
+ * A convenience method to simplify migration from HttpClient 3.1 API. This method is
+ * equivalent to {@link #reset()}.
+ *
+ * @since 4.2
+ */
+ public void releaseConnection() {
+ reset();
+ }
+
@Override
public Object clone() throws CloneNotSupportedException {
HttpRequestBase clone = (HttpRequestBase) super.clone();
@@ -181,4 +195,9 @@ public abstract class HttpRequestBase extends AbstractHttpMessage
return clone;
}
+ @Override
+ public String toString() {
+ return getMethod() + " " + getURI() + " " + getProtocolVersion();
+ }
+
}
diff --git a/httpclient/src/main/java/org/apache/http/client/params/AuthPolicy.java b/httpclient/src/main/java/org/apache/http/client/params/AuthPolicy.java
index 3f77310..4391dcf 100644
--- a/httpclient/src/main/java/org/apache/http/client/params/AuthPolicy.java
+++ b/httpclient/src/main/java/org/apache/http/client/params/AuthPolicy.java
@@ -60,10 +60,17 @@ public final class AuthPolicy {
public static final String BASIC = "Basic";
/**
- * SPNEGO/Kerberos Authentication scheme.
+ * SPNEGO Authentication scheme.
*
* @since 4.1
*/
public static final String SPNEGO = "negotiate";
+ /**
+ * Kerberos Authentication scheme.
+ *
+ * @since 4.2
+ */
+ public static final String KERBEROS = "Kerberos";
+
}
diff --git a/httpclient/src/main/java/org/apache/http/client/params/ClientPNames.java b/httpclient/src/main/java/org/apache/http/client/params/ClientPNames.java
index 6a84119..cb895a8 100644
--- a/httpclient/src/main/java/org/apache/http/client/params/ClientPNames.java
+++ b/httpclient/src/main/java/org/apache/http/client/params/ClientPNames.java
@@ -34,18 +34,10 @@ package org.apache.http.client.params;
public interface ClientPNames {
/**
- * Defines the class name of the default {@link org.apache.http.conn.ClientConnectionManager}
- * <p>
- * This parameter expects a value of type {@link String}.
- * </p>
- */
- public static final String CONNECTION_MANAGER_FACTORY_CLASS_NAME = "http.connection-manager.factory-class-name";
-
- /**
- * @deprecated use #CONNECTION_MANAGER_FACTORY_CLASS_NAME
+ * @deprecated (4.2) do not use
*/
@Deprecated
- public static final String CONNECTION_MANAGER_FACTORY = "http.connection-manager.factory-object";
+ public static final String CONNECTION_MANAGER_FACTORY_CLASS_NAME = "http.connection-manager.factory-class-name";
/**
* Defines whether redirects should be handled automatically
@@ -100,11 +92,12 @@ public interface ClientPNames {
public static final String COOKIE_POLICY = "http.protocol.cookie-policy";
/**
- * Defines the virtual host name to be used in the <code>Host</code>
- * request header instead of the physical host name.
+ * Defines the virtual host to be used in the <code>Host</code>
+ * request header instead of the physical host.
* <p>
* This parameter expects a value of type {@link org.apache.http.HttpHost}.
* </p>
+ * If a port is not provided, it will be derived from the request URL.
*/
public static final String VIRTUAL_HOST = "http.virtual-host";
@@ -126,5 +119,16 @@ public interface ClientPNames {
*/
public static final String DEFAULT_HOST = "http.default-host";
+ /**
+ * Defines the timeout in milliseconds used when retrieving an instance of
+ * {@link org.apache.http.conn.ManagedClientConnection} from the
+ * {@link org.apache.http.conn.ClientConnectionManager}.
+ * <p>
+ * This parameter expects a value of type {@link Long}.
+ * <p>
+ * @since 4.2
+ */
+ public static final String CONN_MANAGER_TIMEOUT = "http.conn-manager.timeout";
+
}
diff --git a/httpclient/src/main/java/org/apache/http/client/params/ClientParamBean.java b/httpclient/src/main/java/org/apache/http/client/params/ClientParamBean.java
index 794818b..a9b1f73 100644
--- a/httpclient/src/main/java/org/apache/http/client/params/ClientParamBean.java
+++ b/httpclient/src/main/java/org/apache/http/client/params/ClientParamBean.java
@@ -33,7 +33,6 @@ import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.Header;
import org.apache.http.HttpHost;
-import org.apache.http.conn.ClientConnectionManagerFactory;
import org.apache.http.params.HttpAbstractParamBean;
import org.apache.http.params.HttpParams;
@@ -51,15 +50,14 @@ public class ClientParamBean extends HttpAbstractParamBean {
super(params);
}
+ /**
+ * @deprecated (4.2) do not use.
+ */
+ @Deprecated
public void setConnectionManagerFactoryClassName (final String factory) {
params.setParameter(ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME, factory);
}
- @Deprecated
- public void setConnectionManagerFactory(ClientConnectionManagerFactory factory) {
- params.setParameter(ClientPNames.CONNECTION_MANAGER_FACTORY, factory);
- }
-
public void setHandleRedirects (final boolean handle) {
params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, handle);
}
@@ -96,4 +94,11 @@ public class ClientParamBean extends HttpAbstractParamBean {
params.setParameter(ClientPNames.DEFAULT_HOST, host);
}
+ /**
+ * @since 4.2
+ */
+ public void setConnectionManagerTimeout(final long timeout) {
+ params.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT, timeout);
+ }
+
}
diff --git a/httpclient/src/main/java/org/apache/http/client/params/CookiePolicy.java b/httpclient/src/main/java/org/apache/http/client/params/CookiePolicy.java
index 3a1e5bb..a80b817 100644
--- a/httpclient/src/main/java/org/apache/http/client/params/CookiePolicy.java
+++ b/httpclient/src/main/java/org/apache/http/client/params/CookiePolicy.java
@@ -64,12 +64,12 @@ public final class CookiePolicy {
public static final String BEST_MATCH = "best-match";
/**
- * The policy that ignores cookies.
- *
+ * The policy that ignores cookies.
+ *
* @since 4.1-beta1
*/
public static final String IGNORE_COOKIES = "ignoreCookies";
-
+
private CookiePolicy() {
super();
}
diff --git a/httpclient/src/main/java/org/apache/http/client/params/HttpClientParams.java b/httpclient/src/main/java/org/apache/http/client/params/HttpClientParams.java
index 93fa855..84c5d48 100644
--- a/httpclient/src/main/java/org/apache/http/client/params/HttpClientParams.java
+++ b/httpclient/src/main/java/org/apache/http/client/params/HttpClientParams.java
@@ -28,6 +28,7 @@ package org.apache.http.client.params;
import org.apache.http.annotation.Immutable;
+import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
/**
@@ -93,4 +94,36 @@ public class HttpClientParams {
params.setParameter(ClientPNames.COOKIE_POLICY, cookiePolicy);
}
+ /**
+ * Set the parameter {@code ClientPNames.CONN_MANAGER_TIMEOUT}.
+ *
+ * @since 4.2
+ */
+ public static void setConnectionManagerTimeout(final HttpParams params, long timeout) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ params.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT, timeout);
+ }
+
+ /**
+ * Get the connectiion manager timeout value.
+ * This is defined by the parameter {@code ClientPNames.CONN_MANAGER_TIMEOUT}.
+ * Failing that it uses the parameter {@code CoreConnectionPNames.CONNECTION_TIMEOUT}
+ * which defaults to 0 if not defined.
+ *
+ * @since 4.2
+ * @return the timeout value
+ */
+ public static long getConnectionManagerTimeout(final HttpParams params) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ Long timeout = (Long) params.getParameter(ClientPNames.CONN_MANAGER_TIMEOUT);
+ if (timeout != null) {
+ return timeout.longValue();
+ }
+ return HttpConnectionParams.getConnectionTimeout(params);
+ }
+
}
diff --git a/httpclient/src/main/java/org/apache/http/client/protocol/ClientContext.java b/httpclient/src/main/java/org/apache/http/client/protocol/ClientContext.java
index 9e7cbd3..72ce5cc 100644
--- a/httpclient/src/main/java/org/apache/http/client/protocol/ClientContext.java
+++ b/httpclient/src/main/java/org/apache/http/client/protocol/ClientContext.java
@@ -95,7 +95,7 @@ public interface ClientContext {
public static final String PROXY_AUTH_STATE = "http.auth.proxy-scope";
/**
- * @deprecated do not use
+ * @deprecated (4.1) do not use
*/
@Deprecated
public static final String AUTH_SCHEME_PREF = "http.auth.scheme-pref";
diff --git a/httpclient/src/main/java/org/apache/http/client/protocol/ClientContextConfigurer.java b/httpclient/src/main/java/org/apache/http/client/protocol/ClientContextConfigurer.java
index 59a9f6f..8fd1d10 100644
--- a/httpclient/src/main/java/org/apache/http/client/protocol/ClientContextConfigurer.java
+++ b/httpclient/src/main/java/org/apache/http/client/protocol/ClientContextConfigurer.java
@@ -27,15 +27,12 @@
package org.apache.http.client.protocol;
-import java.util.List;
-
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.auth.AuthSchemeRegistry;
import org.apache.http.client.CookieStore;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.cookie.CookieSpecRegistry;
-import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
/**
@@ -70,14 +67,4 @@ public class ClientContextConfigurer implements ClientContext {
this.context.setAttribute(CREDS_PROVIDER, provider);
}
- /**
- * @deprecated (4.1-alpha1) Use {@link HttpParams#setParameter(String, Object)} to set the parameters
- * {@link org.apache.http.auth.params.AuthPNames#TARGET_AUTH_PREF AuthPNames#TARGET_AUTH_PREF} and
- * {@link org.apache.http.auth.params.AuthPNames#PROXY_AUTH_PREF AuthPNames#PROXY_AUTH_PREF} instead
- */
- @Deprecated
- public void setAuthSchemePref(final List<String> list) {
- this.context.setAttribute(AUTH_SCHEME_PREF, list);
- }
-
}
diff --git a/httpclient/src/main/java/org/apache/http/client/protocol/RequestAcceptEncoding.java b/httpclient/src/main/java/org/apache/http/client/protocol/RequestAcceptEncoding.java
index 1df09a9..3b72666 100644
--- a/httpclient/src/main/java/org/apache/http/client/protocol/RequestAcceptEncoding.java
+++ b/httpclient/src/main/java/org/apache/http/client/protocol/RequestAcceptEncoding.java
@@ -52,7 +52,9 @@ public class RequestAcceptEncoding implements HttpRequestInterceptor {
final HttpContext context) throws HttpException, IOException {
/* Signal support for Accept-Encoding transfer encodings. */
- request.addHeader("Accept-Encoding", "gzip,deflate");
+ if (!request.containsHeader("Accept-Encoding")) {
+ request.addHeader("Accept-Encoding", "gzip,deflate");
+ }
}
}
diff --git a/httpclient/src/main/java/org/apache/http/client/protocol/RequestAuthCache.java b/httpclient/src/main/java/org/apache/http/client/protocol/RequestAuthCache.java
index 4693418..efa23e1 100644
--- a/httpclient/src/main/java/org/apache/http/client/protocol/RequestAuthCache.java
+++ b/httpclient/src/main/java/org/apache/http/client/protocol/RequestAuthCache.java
@@ -36,12 +36,15 @@ import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.annotation.Immutable;
+import org.apache.http.auth.AuthProtocolState;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
@@ -84,8 +87,16 @@ public class RequestAuthCache implements HttpRequestInterceptor {
}
HttpHost target = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
+ if (target.getPort() < 0) {
+ SchemeRegistry schemeRegistry = (SchemeRegistry) context.getAttribute(
+ ClientContext.SCHEME_REGISTRY);
+ Scheme scheme = schemeRegistry.getScheme(target);
+ target = new HttpHost(target.getHostName(),
+ scheme.resolvePort(target.getPort()), target.getSchemeName());
+ }
+
AuthState targetState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
- if (target != null && targetState != null && targetState.getAuthScheme() == null) {
+ if (target != null && targetState != null && targetState.getState() == AuthProtocolState.UNCHALLENGED) {
AuthScheme authScheme = authCache.get(target);
if (authScheme != null) {
doPreemptiveAuth(target, authScheme, targetState, credsProvider);
@@ -94,7 +105,7 @@ public class RequestAuthCache implements HttpRequestInterceptor {
HttpHost proxy = (HttpHost) context.getAttribute(ExecutionContext.HTTP_PROXY_HOST);
AuthState proxyState = (AuthState) context.getAttribute(ClientContext.PROXY_AUTH_STATE);
- if (proxy != null && proxyState != null && proxyState.getAuthScheme() == null) {
+ if (proxy != null && proxyState != null && proxyState.getState() == AuthProtocolState.UNCHALLENGED) {
AuthScheme authScheme = authCache.get(proxy);
if (authScheme != null) {
doPreemptiveAuth(proxy, authScheme, proxyState, credsProvider);
@@ -112,13 +123,12 @@ public class RequestAuthCache implements HttpRequestInterceptor {
this.log.debug("Re-using cached '" + schemeName + "' auth scheme for " + host);
}
- AuthScope authScope = new AuthScope(host.getHostName(), host.getPort(),
- AuthScope.ANY_REALM, schemeName);
+ AuthScope authScope = new AuthScope(host, AuthScope.ANY_REALM, schemeName);
Credentials creds = credsProvider.getCredentials(authScope);
if (creds != null) {
- authState.setAuthScheme(authScheme);
- authState.setCredentials(creds);
+ authState.setState(AuthProtocolState.SUCCESS);
+ authState.update(authScheme, creds);
} else {
this.log.debug("No credentials for preemptive authentication");
}
diff --git a/httpclient/src/main/java/org/apache/http/client/protocol/RequestAuthenticationBase.java b/httpclient/src/main/java/org/apache/http/client/protocol/RequestAuthenticationBase.java
new file mode 100644
index 0000000..43a34b5
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/client/protocol/RequestAuthenticationBase.java
@@ -0,0 +1,131 @@
+/*
+ * ====================================================================
+ * 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.protocol;
+
+import java.io.IOException;
+import java.util.Queue;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Header;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.auth.AuthOption;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthState;
+import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.ContextAwareAuthScheme;
+import org.apache.http.auth.Credentials;
+import org.apache.http.protocol.HttpContext;
+
+abstract class RequestAuthenticationBase implements HttpRequestInterceptor {
+
+ final Log log = LogFactory.getLog(getClass());
+
+ public RequestAuthenticationBase() {
+ super();
+ }
+
+ void process(
+ final AuthState authState,
+ final HttpRequest request,
+ final HttpContext context) throws HttpException, IOException {
+ AuthScheme authScheme = authState.getAuthScheme();
+ Credentials creds = authState.getCredentials();
+ switch (authState.getState()) {
+ case FAILURE:
+ return;
+ case SUCCESS:
+ ensureAuthScheme(authScheme);
+ if (authScheme.isConnectionBased()) {
+ return;
+ }
+ break;
+ case CHALLENGED:
+ Queue<AuthOption> authOptions = authState.getAuthOptions();
+ if (authOptions != null) {
+ while (!authOptions.isEmpty()) {
+ AuthOption authOption = authOptions.remove();
+ authScheme = authOption.getAuthScheme();
+ creds = authOption.getCredentials();
+ authState.update(authScheme, creds);
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Generating response to an authentication challenge using "
+ + authScheme.getSchemeName() + " scheme");
+ }
+ try {
+ Header header = authenticate(authScheme, creds, request, context);
+ request.addHeader(header);
+ break;
+ } catch (AuthenticationException ex) {
+ if (this.log.isWarnEnabled()) {
+ this.log.warn(authScheme + " authentication error: " + ex.getMessage());
+ }
+ }
+ }
+ return;
+ } else {
+ ensureAuthScheme(authScheme);
+ }
+ }
+ if (authScheme != null) {
+ try {
+ Header header = authenticate(authScheme, creds, request, context);
+ request.addHeader(header);
+ } catch (AuthenticationException ex) {
+ if (this.log.isErrorEnabled()) {
+ this.log.error(authScheme + " authentication error: " + ex.getMessage());
+ }
+ }
+ }
+ }
+
+ private void ensureAuthScheme(final AuthScheme authScheme) {
+ if (authScheme == null) {
+ throw new IllegalStateException("Auth scheme is not set");
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private Header authenticate(
+ final AuthScheme authScheme,
+ final Credentials creds,
+ final HttpRequest request,
+ final HttpContext context) throws AuthenticationException {
+ if (authScheme == null) {
+ throw new IllegalStateException("Auth state object is null");
+ }
+ if (authScheme instanceof ContextAwareAuthScheme) {
+ return ((ContextAwareAuthScheme) authScheme).authenticate(creds, request, context);
+ } else {
+ return authScheme.authenticate(creds, request);
+ }
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/protocol/RequestProxyAuthentication.java b/httpclient/src/main/java/org/apache/http/client/protocol/RequestProxyAuthentication.java
index 05f7056..9da3ede 100644
--- a/httpclient/src/main/java/org/apache/http/client/protocol/RequestProxyAuthentication.java
+++ b/httpclient/src/main/java/org/apache/http/client/protocol/RequestProxyAuthentication.java
@@ -29,20 +29,11 @@ package org.apache.http.client.protocol;
import java.io.IOException;
-import org.apache.http.annotation.Immutable;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
-import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.annotation.Immutable;
import org.apache.http.auth.AUTH;
-import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthState;
-import org.apache.http.auth.AuthenticationException;
-import org.apache.http.auth.ContextAwareAuthScheme;
-import org.apache.http.auth.Credentials;
import org.apache.http.conn.HttpRoutedConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.protocol.ExecutionContext;
@@ -55,15 +46,12 @@ import org.apache.http.protocol.HttpContext;
* @since 4.0
*/
@Immutable
-public class RequestProxyAuthentication implements HttpRequestInterceptor {
-
- private final Log log = LogFactory.getLog(getClass());
+public class RequestProxyAuthentication extends RequestAuthenticationBase {
public RequestProxyAuthentication() {
super();
}
- @SuppressWarnings("deprecation")
public void process(final HttpRequest request, final HttpContext context)
throws HttpException, IOException {
if (request == null) {
@@ -95,33 +83,10 @@ public class RequestProxyAuthentication implements HttpRequestInterceptor {
this.log.debug("Proxy auth state not set in the context");
return;
}
-
- AuthScheme authScheme = authState.getAuthScheme();
- if (authScheme == null) {
- return;
- }
-
- Credentials creds = authState.getCredentials();
- if (creds == null) {
- this.log.debug("User credentials not available");
- return;
- }
- if (authState.getAuthScope() != null || !authScheme.isConnectionBased()) {
- try {
- Header header;
- if (authScheme instanceof ContextAwareAuthScheme) {
- header = ((ContextAwareAuthScheme) authScheme).authenticate(
- creds, request, context);
- } else {
- header = authScheme.authenticate(creds, request);
- }
- request.addHeader(header);
- } catch (AuthenticationException ex) {
- if (this.log.isErrorEnabled()) {
- this.log.error("Proxy authentication error: " + ex.getMessage());
- }
- }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Proxy auth state: " + authState.getState());
}
+ process(authState, request, context);
}
}
diff --git a/httpclient/src/main/java/org/apache/http/client/protocol/RequestTargetAuthentication.java b/httpclient/src/main/java/org/apache/http/client/protocol/RequestTargetAuthentication.java
index 10074ed..475b14b 100644
--- a/httpclient/src/main/java/org/apache/http/client/protocol/RequestTargetAuthentication.java
+++ b/httpclient/src/main/java/org/apache/http/client/protocol/RequestTargetAuthentication.java
@@ -29,20 +29,11 @@ package org.apache.http.client.protocol;
import java.io.IOException;
-import org.apache.http.annotation.Immutable;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
-import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.annotation.Immutable;
import org.apache.http.auth.AUTH;
-import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthState;
-import org.apache.http.auth.AuthenticationException;
-import org.apache.http.auth.ContextAwareAuthScheme;
-import org.apache.http.auth.Credentials;
import org.apache.http.protocol.HttpContext;
/**
@@ -52,15 +43,12 @@ import org.apache.http.protocol.HttpContext;
* @since 4.0
*/
@Immutable
-public class RequestTargetAuthentication implements HttpRequestInterceptor {
-
- private final Log log = LogFactory.getLog(getClass());
+public class RequestTargetAuthentication extends RequestAuthenticationBase {
public RequestTargetAuthentication() {
super();
}
- @SuppressWarnings("deprecation")
public void process(final HttpRequest request, final HttpContext context)
throws HttpException, IOException {
if (request == null) {
@@ -86,34 +74,10 @@ public class RequestTargetAuthentication implements HttpRequestInterceptor {
this.log.debug("Target auth state not set in the context");
return;
}
-
- AuthScheme authScheme = authState.getAuthScheme();
- if (authScheme == null) {
- return;
- }
-
- Credentials creds = authState.getCredentials();
- if (creds == null) {
- this.log.debug("User credentials not available");
- return;
- }
-
- if (authState.getAuthScope() != null || !authScheme.isConnectionBased()) {
- try {
- Header header;
- if (authScheme instanceof ContextAwareAuthScheme) {
- header = ((ContextAwareAuthScheme) authScheme).authenticate(
- creds, request, context);
- } else {
- header = authScheme.authenticate(creds, request);
- }
- request.addHeader(header);
- } catch (AuthenticationException ex) {
- if (this.log.isErrorEnabled()) {
- this.log.error("Authentication error: " + ex.getMessage());
- }
- }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Target auth state: " + authState.getState());
}
+ process(authState, request, context);
}
}
diff --git a/httpclient/src/main/java/org/apache/http/client/protocol/ResponseAuthCache.java b/httpclient/src/main/java/org/apache/http/client/protocol/ResponseAuthCache.java
index 7c5cbb2..6696fab 100644
--- a/httpclient/src/main/java/org/apache/http/client/protocol/ResponseAuthCache.java
+++ b/httpclient/src/main/java/org/apache/http/client/protocol/ResponseAuthCache.java
@@ -39,7 +39,10 @@ import org.apache.http.annotation.Immutable;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthState;
import org.apache.http.client.AuthCache;
+import org.apache.http.client.AuthenticationStrategy;
import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
@@ -51,8 +54,11 @@ import org.apache.http.protocol.HttpContext;
* additional authentication round-trips.
*
* @since 4.1
+ *
+ * @deprecated (4.2) use {@link AuthenticationStrategy}
*/
@Immutable
+ at Deprecated
public class ResponseAuthCache implements HttpResponseInterceptor {
private final Log log = LogFactory.getLog(getClass());
@@ -74,24 +80,49 @@ public class ResponseAuthCache implements HttpResponseInterceptor {
HttpHost target = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
AuthState targetState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
if (target != null && targetState != null) {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Target auth state: " + targetState.getState());
+ }
if (isCachable(targetState)) {
+ if (target.getPort() < 0) {
+ SchemeRegistry schemeRegistry = (SchemeRegistry) context.getAttribute(
+ ClientContext.SCHEME_REGISTRY);
+ Scheme scheme = schemeRegistry.getScheme(target);
+ target = new HttpHost(target.getHostName(),
+ scheme.resolvePort(target.getPort()), target.getSchemeName());
+ }
if (authCache == null) {
authCache = new BasicAuthCache();
context.setAttribute(ClientContext.AUTH_CACHE, authCache);
}
- cache(authCache, target, targetState);
+ switch (targetState.getState()) {
+ case CHALLENGED:
+ cache(authCache, target, targetState.getAuthScheme());
+ break;
+ case FAILURE:
+ uncache(authCache, target, targetState.getAuthScheme());
+ }
}
}
HttpHost proxy = (HttpHost) context.getAttribute(ExecutionContext.HTTP_PROXY_HOST);
AuthState proxyState = (AuthState) context.getAttribute(ClientContext.PROXY_AUTH_STATE);
if (proxy != null && proxyState != null) {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Proxy auth state: " + proxyState.getState());
+ }
if (isCachable(proxyState)) {
if (authCache == null) {
authCache = new BasicAuthCache();
context.setAttribute(ClientContext.AUTH_CACHE, authCache);
}
- cache(authCache, proxy, proxyState);
+ switch (proxyState.getState()) {
+ case CHALLENGED:
+ cache(authCache, proxy, proxyState.getAuthScheme());
+ break;
+ case FAILURE:
+ uncache(authCache, proxy, proxyState.getAuthScheme());
+ }
}
}
}
@@ -106,19 +137,19 @@ public class ResponseAuthCache implements HttpResponseInterceptor {
schemeName.equalsIgnoreCase(AuthPolicy.DIGEST);
}
- private void cache(final AuthCache authCache, final HttpHost host, final AuthState authState) {
- AuthScheme authScheme = authState.getAuthScheme();
- if (authState.getAuthScope() != null) {
- if (authState.getCredentials() != null) {
- if (this.log.isDebugEnabled()) {
- this.log.debug("Caching '" + authScheme.getSchemeName() +
- "' auth scheme for " + host);
- }
- authCache.put(host, authScheme);
- } else {
- authCache.remove(host);
- }
+ private void cache(final AuthCache authCache, final HttpHost host, final AuthScheme authScheme) {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Caching '" + authScheme.getSchemeName() +
+ "' auth scheme for " + host);
}
+ authCache.put(host, authScheme);
}
+ private void uncache(final AuthCache authCache, final HttpHost host, final AuthScheme authScheme) {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Removing from cache '" + authScheme.getSchemeName() +
+ "' auth scheme for " + host);
+ }
+ authCache.remove(host);
+ }
}
diff --git a/httpclient/src/main/java/org/apache/http/client/protocol/ResponseContentEncoding.java b/httpclient/src/main/java/org/apache/http/client/protocol/ResponseContentEncoding.java
index f218aa0..b86a088 100644
--- a/httpclient/src/main/java/org/apache/http/client/protocol/ResponseContentEncoding.java
+++ b/httpclient/src/main/java/org/apache/http/client/protocol/ResponseContentEncoding.java
@@ -52,6 +52,8 @@ import org.apache.http.protocol.HttpContext;
@Immutable
public class ResponseContentEncoding implements HttpResponseInterceptor {
+ public static final String UNCOMPRESSED = "http.client.response.uncompressed";
+
/**
* Handles the following {@code Content-Encoding}s by
* using the appropriate decompressor to wrap the response Entity:
@@ -80,9 +82,11 @@ public class ResponseContentEncoding implements HttpResponseInterceptor {
String codecname = codec.getName().toLowerCase(Locale.US);
if ("gzip".equals(codecname) || "x-gzip".equals(codecname)) {
response.setEntity(new GzipDecompressingEntity(response.getEntity()));
+ if (context != null) context.setAttribute(UNCOMPRESSED, true);
return;
} else if ("deflate".equals(codecname)) {
response.setEntity(new DeflateDecompressingEntity(response.getEntity()));
+ if (context != null) context.setAttribute(UNCOMPRESSED, true);
return;
} else if ("identity".equals(codecname)) {
diff --git a/httpclient/src/main/java/org/apache/http/client/utils/HttpClientUtils.java b/httpclient/src/main/java/org/apache/http/client/utils/HttpClientUtils.java
new file mode 100644
index 0000000..ea9342d
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/client/utils/HttpClientUtils.java
@@ -0,0 +1,107 @@
+/*
+ * ====================================================================
+ * 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.utils;
+
+import java.io.IOException;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.util.EntityUtils;
+
+/**
+ * Static helpers for dealing with {@link HttpResponse}s and {@link HttpClient}s.
+ *
+ * @since 4.2
+ */
+public class HttpClientUtils {
+
+ private HttpClientUtils() {
+ }
+
+ /**
+ * Unconditionally close a response.
+ * <p>
+ * Example Code:
+ *
+ * <pre>
+ * HttpResponse httpResponse = null;
+ * try {
+ * httpResponse = httpClient.execute(httpGet);
+ * } catch (Exception e) {
+ * // error handling
+ * } finally {
+ * HttpClientUtils.closeQuietly(httpResponse);
+ * }
+ * </pre>
+ *
+ * @param response
+ * the HttpResponse to release resources, may be null or already
+ * closed.
+ *
+ * @since 4.2
+ */
+ public static void closeQuietly(final HttpResponse response) {
+ if (response != null) {
+ HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ try {
+ EntityUtils.consume(entity);
+ } catch (final IOException ex) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Unconditionally close a httpClient. Shuts down the underlying connection
+ * manager and releases the resources.
+ * <p>
+ * Example Code:
+ *
+ * <pre>
+ * HttpClient httpClient = null;
+ * try {
+ * httpClient = new DefaultHttpClient(...);
+ * } catch (Exception e) {
+ * // error handling
+ * } finally {
+ * HttpClientUtils.closeQuietly(httpClient);
+ * }
+ * </pre>
+ *
+ * @param httpClient
+ * the HttpClient to close, may be null or already closed.
+ * @since 4.2
+ */
+ public static void closeQuietly(final HttpClient httpClient) {
+ if (httpClient != null) {
+ httpClient.getConnectionManager().shutdown();
+ }
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/utils/Idn.java b/httpclient/src/main/java/org/apache/http/client/utils/Idn.java
index 8ce84eb..36fb291 100644
--- a/httpclient/src/main/java/org/apache/http/client/utils/Idn.java
+++ b/httpclient/src/main/java/org/apache/http/client/utils/Idn.java
@@ -43,4 +43,4 @@ public interface Idn {
* @return the Unicode domain name
*/
String toUnicode(String punycode);
-}
\ No newline at end of file
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/utils/JdkIdn.java b/httpclient/src/main/java/org/apache/http/client/utils/JdkIdn.java
index dacd75a..1ecd171 100644
--- a/httpclient/src/main/java/org/apache/http/client/utils/JdkIdn.java
+++ b/httpclient/src/main/java/org/apache/http/client/utils/JdkIdn.java
@@ -72,4 +72,4 @@ public class JdkIdn implements Idn {
}
}
-}
\ No newline at end of file
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/utils/Rfc3492Idn.java b/httpclient/src/main/java/org/apache/http/client/utils/Rfc3492Idn.java
index 0bcb8a7..e7f83cb 100644
--- a/httpclient/src/main/java/org/apache/http/client/utils/Rfc3492Idn.java
+++ b/httpclient/src/main/java/org/apache/http/client/utils/Rfc3492Idn.java
@@ -123,4 +123,4 @@ public class Rfc3492Idn implements Idn {
return output.toString();
}
-}
\ No newline at end of file
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/utils/URIBuilder.java b/httpclient/src/main/java/org/apache/http/client/utils/URIBuilder.java
new file mode 100644
index 0000000..421e78e
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/client/utils/URIBuilder.java
@@ -0,0 +1,362 @@
+/*
+ * ====================================================================
+ *
+ * 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.utils;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.http.Consts;
+import org.apache.http.NameValuePair;
+import org.apache.http.annotation.NotThreadSafe;
+import org.apache.http.conn.util.InetAddressUtils;
+import org.apache.http.message.BasicNameValuePair;
+
+/**
+ * {@link URI} builder for HTTP requests.
+ *
+ * @since 4.2
+ */
+ at NotThreadSafe
+public class URIBuilder {
+
+ private String scheme;
+ private String encodedSchemeSpecificPart;
+ private String encodedAuthority;
+ private String userInfo;
+ private String encodedUserInfo;
+ private String host;
+ private int port;
+ private String path;
+ private String encodedPath;
+ private String encodedQuery;
+ private List<NameValuePair> queryParams;
+ private String fragment;
+ private String encodedFragment;
+
+ /**
+ * Constructs an empty instance.
+ */
+ public URIBuilder() {
+ super();
+ this.port = -1;
+ }
+
+ /**
+ * Construct an instance from the string which must be a valid URI.
+ *
+ * @param string a valid URI in string form
+ * @throws URISyntaxException if the input is not a valid URI
+ */
+ public URIBuilder(final String string) throws URISyntaxException {
+ super();
+ digestURI(new URI(string));
+ }
+
+ /**
+ * Construct an instance from the provided URI.
+ * @param uri
+ */
+ public URIBuilder(final URI uri) {
+ super();
+ digestURI(uri);
+ }
+
+ private List <NameValuePair> parseQuery(final String query, final Charset charset) {
+ if (query != null && query.length() > 0) {
+ return URLEncodedUtils.parse(query, charset);
+ }
+ return null;
+ }
+
+ /**
+ * Builds a {@link URI} instance.
+ */
+ public URI build() throws URISyntaxException {
+ return new URI(buildString());
+ }
+
+ private String buildString() {
+ StringBuilder sb = new StringBuilder();
+ if (this.scheme != null) {
+ sb.append(this.scheme).append(':');
+ }
+ if (this.encodedSchemeSpecificPart != null) {
+ sb.append(this.encodedSchemeSpecificPart);
+ } else {
+ if (this.encodedAuthority != null) {
+ sb.append("//").append(this.encodedAuthority);
+ } else if (this.host != null) {
+ sb.append("//");
+ if (this.encodedUserInfo != null) {
+ sb.append(this.encodedUserInfo).append("@");
+ } else if (this.userInfo != null) {
+ sb.append(encodeUserInfo(this.userInfo)).append("@");
+ }
+ if (InetAddressUtils.isIPv6Address(this.host)) {
+ sb.append("[").append(this.host).append("]");
+ } else {
+ sb.append(this.host);
+ }
+ if (this.port >= 0) {
+ sb.append(":").append(this.port);
+ }
+ }
+ if (this.encodedPath != null) {
+ sb.append(normalizePath(this.encodedPath));
+ } else if (this.path != null) {
+ sb.append(encodePath(normalizePath(this.path)));
+ }
+ if (this.encodedQuery != null) {
+ sb.append("?").append(this.encodedQuery);
+ } else if (this.queryParams != null) {
+ sb.append("?").append(encodeQuery(this.queryParams));
+ }
+ }
+ if (this.encodedFragment != null) {
+ sb.append("#").append(this.encodedFragment);
+ } else if (this.fragment != null) {
+ sb.append("#").append(encodeFragment(this.fragment));
+ }
+ return sb.toString();
+ }
+
+ private void digestURI(final URI uri) {
+ this.scheme = uri.getScheme();
+ this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart();
+ this.encodedAuthority = uri.getRawAuthority();
+ this.host = uri.getHost();
+ this.port = uri.getPort();
+ this.encodedUserInfo = uri.getRawUserInfo();
+ this.userInfo = uri.getUserInfo();
+ this.encodedPath = uri.getRawPath();
+ this.path = uri.getPath();
+ this.encodedQuery = uri.getRawQuery();
+ this.queryParams = parseQuery(uri.getRawQuery(), Consts.UTF_8);
+ this.encodedFragment = uri.getRawFragment();
+ this.fragment = uri.getFragment();
+ }
+
+ private String encodeUserInfo(final String userInfo) {
+ return URLEncodedUtils.encUserInfo(userInfo, Consts.UTF_8);
+ }
+
+ private String encodePath(final String path) {
+ return URLEncodedUtils.encPath(path, Consts.UTF_8);
+ }
+
+ private String encodeQuery(final List<NameValuePair> params) {
+ return URLEncodedUtils.format(params, Consts.UTF_8);
+ }
+
+ private String encodeFragment(final String fragment) {
+ return URLEncodedUtils.encFragment(fragment, Consts.UTF_8);
+ }
+
+ /**
+ * Sets URI scheme.
+ */
+ public URIBuilder setScheme(final String scheme) {
+ this.scheme = scheme;
+ return this;
+ }
+
+ /**
+ * Sets URI user info. The value is expected to be unescaped and may contain non ASCII
+ * characters.
+ */
+ public URIBuilder setUserInfo(final String userInfo) {
+ this.userInfo = userInfo;
+ this.encodedSchemeSpecificPart = null;
+ this.encodedAuthority = null;
+ this.encodedUserInfo = null;
+ return this;
+ }
+
+ /**
+ * Sets URI user info as a combination of username and password. These values are expected to
+ * be unescaped and may contain non ASCII characters.
+ */
+ public URIBuilder setUserInfo(final String username, final String password) {
+ return setUserInfo(username + ':' + password);
+ }
+
+ /**
+ * Sets URI host.
+ */
+ public URIBuilder setHost(final String host) {
+ this.host = host;
+ this.encodedSchemeSpecificPart = null;
+ this.encodedAuthority = null;
+ return this;
+ }
+
+ /**
+ * Sets URI port.
+ */
+ public URIBuilder setPort(final int port) {
+ this.port = port < 0 ? -1 : port;
+ this.encodedSchemeSpecificPart = null;
+ this.encodedAuthority = null;
+ return this;
+ }
+
+ /**
+ * Sets URI path. The value is expected to be unescaped and may contain non ASCII characters.
+ */
+ public URIBuilder setPath(final String path) {
+ this.path = path;
+ this.encodedSchemeSpecificPart = null;
+ this.encodedPath = null;
+ return this;
+ }
+
+ /**
+ * Removes URI query.
+ */
+ public URIBuilder removeQuery() {
+ this.queryParams = null;
+ this.encodedQuery = null;
+ this.encodedSchemeSpecificPart = null;
+ return this;
+ }
+
+ /**
+ * Sets URI query.
+ * <p>
+ * The value is expected to be encoded form data.
+ */
+ public URIBuilder setQuery(final String query) {
+ this.queryParams = parseQuery(query, Consts.UTF_8);
+ this.encodedQuery = null;
+ this.encodedSchemeSpecificPart = null;
+ return this;
+ }
+
+ /**
+ * Adds parameter to URI query. The parameter name and value are expected to be unescaped
+ * and may contain non ASCII characters.
+ */
+ public URIBuilder addParameter(final String param, final String value) {
+ if (this.queryParams == null) {
+ this.queryParams = new ArrayList<NameValuePair>();
+ }
+ this.queryParams.add(new BasicNameValuePair(param, value));
+ this.encodedQuery = null;
+ this.encodedSchemeSpecificPart = null;
+ return this;
+ }
+
+ /**
+ * Sets parameter of URI query overriding existing value if set. The parameter name and value
+ * are expected to be unescaped and may contain non ASCII characters.
+ */
+ public URIBuilder setParameter(final String param, final String value) {
+ if (this.queryParams == null) {
+ this.queryParams = new ArrayList<NameValuePair>();
+ }
+ if (!this.queryParams.isEmpty()) {
+ for (Iterator<NameValuePair> it = this.queryParams.iterator(); it.hasNext(); ) {
+ NameValuePair nvp = it.next();
+ if (nvp.getName().equals(param)) {
+ it.remove();
+ }
+ }
+ }
+ this.queryParams.add(new BasicNameValuePair(param, value));
+ this.encodedQuery = null;
+ this.encodedSchemeSpecificPart = null;
+ return this;
+ }
+
+ /**
+ * Sets URI fragment. The value is expected to be unescaped and may contain non ASCII
+ * characters.
+ */
+ public URIBuilder setFragment(final String fragment) {
+ this.fragment = fragment;
+ this.encodedFragment = null;
+ return this;
+ }
+
+ public String getScheme() {
+ return this.scheme;
+ }
+
+ public String getUserInfo() {
+ return this.userInfo;
+ }
+
+ public String getHost() {
+ return this.host;
+ }
+
+ public int getPort() {
+ return this.port;
+ }
+
+ public String getPath() {
+ return this.path;
+ }
+
+ public List<NameValuePair> getQueryParams() {
+ if (this.queryParams != null) {
+ return new ArrayList<NameValuePair>(this.queryParams);
+ } else {
+ return new ArrayList<NameValuePair>();
+ }
+ }
+
+ public String getFragment() {
+ return this.fragment;
+ }
+
+ @Override
+ public String toString() {
+ return buildString();
+ }
+
+ private static String normalizePath(String path) {
+ if (path == null) {
+ return null;
+ }
+ int n = 0;
+ for (; n < path.length(); n++) {
+ if (path.charAt(n) != '/') {
+ break;
+ }
+ }
+ if (n > 1) {
+ path = path.substring(n - 1);
+ }
+ return path;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/client/utils/URIUtils.java b/httpclient/src/main/java/org/apache/http/client/utils/URIUtils.java
index 50dc9f3..c120fa7 100644
--- a/httpclient/src/main/java/org/apache/http/client/utils/URIUtils.java
+++ b/httpclient/src/main/java/org/apache/http/client/utils/URIUtils.java
@@ -68,7 +68,10 @@ public class URIUtils {
* components violates RFC 2396, or if the authority
* component of the string is present but cannot be parsed
* as a server-based authority
+ *
+ * @deprecated (4.2) use {@link URIBuilder}.
*/
+ @Deprecated
public static URI createURI(
final String scheme,
final String host,
@@ -76,7 +79,6 @@ public class URIUtils {
final String path,
final String query,
final String fragment) throws URISyntaxException {
-
StringBuilder buffer = new StringBuilder();
if (host != null) {
if (scheme != null) {
@@ -127,41 +129,22 @@ public class URIUtils {
final HttpHost target,
boolean dropFragment) throws URISyntaxException {
if (uri == null) {
- throw new IllegalArgumentException("URI may nor be null");
+ throw new IllegalArgumentException("URI may not be null");
}
+ URIBuilder uribuilder = new URIBuilder(uri);
if (target != null) {
- return URIUtils.createURI(
- target.getSchemeName(),
- target.getHostName(),
- target.getPort(),
- normalizePath(uri.getRawPath()),
- uri.getRawQuery(),
- dropFragment ? null : uri.getRawFragment());
+ uribuilder.setScheme(target.getSchemeName());
+ uribuilder.setHost(target.getHostName());
+ uribuilder.setPort(target.getPort());
} else {
- return URIUtils.createURI(
- null,
- null,
- -1,
- normalizePath(uri.getRawPath()),
- uri.getRawQuery(),
- dropFragment ? null : uri.getRawFragment());
- }
- }
-
- private static String normalizePath(String path) {
- if (path == null) {
- return null;
- }
- int n = 0;
- for (; n < path.length(); n++) {
- if (path.charAt(n) != '/') {
- break;
- }
+ uribuilder.setScheme(null);
+ uribuilder.setHost(null);
+ uribuilder.setPort(-1);
}
- if (n > 1) {
- path = path.substring(n - 1);
+ if (dropFragment) {
+ uribuilder.setFragment(null);
}
- return path;
+ return uribuilder.build();
}
/**
@@ -176,6 +159,27 @@ public class URIUtils {
}
/**
+ * A convenience method that creates a new {@link URI} whose scheme, host, port, path,
+ * query are taken from the existing URI, dropping any fragment or user-information.
+ * The existing URI is returned unmodified if it has no fragment or user-information.
+ *
+ * @param uri
+ * original URI.
+ * @throws URISyntaxException
+ * If the resulting URI is invalid.
+ */
+ public static URI rewriteURI(final URI uri) throws URISyntaxException {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI may not be null");
+ }
+ if (uri.getFragment() != null || uri.getUserInfo() != null) {
+ return new URIBuilder(uri).setFragment(null).setUserInfo(null).build();
+ } else {
+ return uri;
+ }
+ }
+
+ /**
* Resolves a URI reference against a base URI. Work-around for bug in
* java.net.URI (<http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535>)
*
@@ -274,11 +278,11 @@ public class URIUtils {
/**
* Extracts target host from the given {@link URI}.
- *
- * @param uri
- * @return the target host if the URI is absolute or <code>null</null> if the URI is
+ *
+ * @param uri
+ * @return the target host if the URI is absolute or <code>null</null> if the URI is
* relative or does not contain a valid host name.
- *
+ *
* @since 4.1
*/
public static HttpHost extractHost(final URI uri) {
@@ -303,15 +307,27 @@ public class URIUtils {
}
}
// Extract the port suffix, if present
- if (host != null) {
+ if (host != null) {
int colon = host.indexOf(':');
if (colon >= 0) {
- if (colon+1 < host.length()) {
- port = Integer.parseInt(host.substring(colon+1));
+ int pos = colon + 1;
+ int len = 0;
+ for (int i = pos; i < host.length(); i++) {
+ if (Character.isDigit(host.charAt(i))) {
+ len++;
+ } else {
+ break;
+ }
+ }
+ if (len > 0) {
+ try {
+ port = Integer.parseInt(host.substring(pos, pos + len));
+ } catch (NumberFormatException ex) {
+ }
}
- host = host.substring(0,colon);
- }
- }
+ host = host.substring(0, colon);
+ }
+ }
}
}
String scheme = uri.getScheme();
@@ -321,7 +337,7 @@ public class URIUtils {
}
return target;
}
-
+
/**
* This class should not be instantiated.
*/
diff --git a/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java b/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java
index 505ea91..800d0cc 100644
--- a/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java
+++ b/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java
@@ -28,23 +28,29 @@
package org.apache.http.client.utils;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.net.URI;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
import java.util.ArrayList;
+import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
import org.apache.http.annotation.Immutable;
+import org.apache.http.entity.ContentType;
+import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicHeaderValueParser;
import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.message.ParserCursor;
import org.apache.http.protocol.HTTP;
+import org.apache.http.util.CharArrayBuffer;
import org.apache.http.util.EntityUtils;
/**
@@ -73,13 +79,15 @@ public class URLEncodedUtils {
* encoding to use while parsing the query
*/
public static List <NameValuePair> parse (final URI uri, final String encoding) {
- List <NameValuePair> result = Collections.emptyList();
final String query = uri.getRawQuery();
if (query != null && query.length() > 0) {
- result = new ArrayList <NameValuePair>();
- parse(result, new Scanner(query), encoding);
+ List<NameValuePair> result = new ArrayList<NameValuePair>();
+ Scanner scanner = new Scanner(query);
+ parse(result, scanner, encoding);
+ return result;
+ } else {
+ return Collections.emptyList();
}
- return result;
}
/**
@@ -96,32 +104,18 @@ public class URLEncodedUtils {
*/
public static List <NameValuePair> parse (
final HttpEntity entity) throws IOException {
- List <NameValuePair> result = Collections.emptyList();
-
- String contentType = null;
- String charset = null;
-
- Header h = entity.getContentType();
- if (h != null) {
- HeaderElement[] elems = h.getElements();
- if (elems.length > 0) {
- HeaderElement elem = elems[0];
- contentType = elem.getName();
- NameValuePair param = elem.getParameterByName("charset");
- if (param != null) {
- charset = param.getValue();
- }
- }
- }
-
- if (contentType != null && contentType.equalsIgnoreCase(CONTENT_TYPE)) {
- final String content = EntityUtils.toString(entity, HTTP.ASCII);
+ ContentType contentType = ContentType.get(entity);
+ if (contentType != null && contentType.getMimeType().equalsIgnoreCase(CONTENT_TYPE)) {
+ String content = EntityUtils.toString(entity, Consts.ASCII);
if (content != null && content.length() > 0) {
- result = new ArrayList <NameValuePair>();
- parse(result, new Scanner(content), charset);
+ Charset charset = contentType.getCharset();
+ if (charset == null) {
+ charset = HTTP.DEF_CONTENT_CHARSET;
+ }
+ return parse(content, charset);
}
}
- return result;
+ return Collections.emptyList();
}
/**
@@ -154,27 +148,62 @@ public class URLEncodedUtils {
* List to add parameters to.
* @param scanner
* Input that contains the parameters to parse.
- * @param encoding
+ * @param charset
* Encoding to use when decoding the parameters.
*/
public static void parse (
final List <NameValuePair> parameters,
final Scanner scanner,
- final String encoding) {
+ final String charset) {
scanner.useDelimiter(PARAMETER_SEPARATOR);
while (scanner.hasNext()) {
- final String[] nameValue = scanner.next().split(NAME_VALUE_SEPARATOR);
- if (nameValue.length == 0 || nameValue.length > 2)
- throw new IllegalArgumentException("bad parameter");
-
- final String name = decode(nameValue[0], encoding);
+ String name = null;
String value = null;
- if (nameValue.length == 2)
- value = decode(nameValue[1], encoding);
+ String token = scanner.next();
+ int i = token.indexOf(NAME_VALUE_SEPARATOR);
+ if (i != -1) {
+ name = decodeFormFields(token.substring(0, i).trim(), charset);
+ value = decodeFormFields(token.substring(i + 1).trim(), charset);
+ } else {
+ name = decodeFormFields(token.trim(), charset);
+ }
parameters.add(new BasicNameValuePair(name, value));
}
}
+ private static final char[] DELIM = new char[] { '&' };
+
+ /**
+ * Returns a list of {@link NameValuePair NameValuePairs} as parsed from the given string
+ * using the given character encoding.
+ *
+ * @param s
+ * text to parse.
+ * @param charset
+ * Encoding to use when decoding the parameters.
+ *
+ * @since 4.2
+ */
+ public static List<NameValuePair> parse (final String s, final Charset charset) {
+ if (s == null) {
+ return Collections.emptyList();
+ }
+ BasicHeaderValueParser parser = BasicHeaderValueParser.DEFAULT;
+ CharArrayBuffer buffer = new CharArrayBuffer(s.length());
+ buffer.append(s);
+ ParserCursor cursor = new ParserCursor(0, buffer.length());
+ List<NameValuePair> list = new ArrayList<NameValuePair>();
+ while (!cursor.atEnd()) {
+ NameValuePair nvp = parser.parseNameValuePair(buffer, cursor, DELIM);
+ if (nvp.getName().length() > 0) {
+ list.add(new BasicNameValuePair(
+ decodeFormFields(nvp.getName(), charset),
+ decodeFormFields(nvp.getValue(), charset)));
+ }
+ }
+ return list;
+ }
+
/**
* Returns a String that is suitable for use as an <code>application/x-www-form-urlencoded</code>
* list of parameters in an HTTP PUT or HTTP POST.
@@ -187,34 +216,329 @@ public class URLEncodedUtils {
final String encoding) {
final StringBuilder result = new StringBuilder();
for (final NameValuePair parameter : parameters) {
- final String encodedName = encode(parameter.getName(), encoding);
- final String value = parameter.getValue();
- final String encodedValue = value != null ? encode(value, encoding) : "";
- if (result.length() > 0)
+ final String encodedName = encodeFormFields(parameter.getName(), encoding);
+ final String encodedValue = encodeFormFields(parameter.getValue(), encoding);
+ if (result.length() > 0) {
result.append(PARAMETER_SEPARATOR);
+ }
result.append(encodedName);
- result.append(NAME_VALUE_SEPARATOR);
- result.append(encodedValue);
+ if (encodedValue != null) {
+ result.append(NAME_VALUE_SEPARATOR);
+ result.append(encodedValue);
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Returns a String that is suitable for use as an <code>application/x-www-form-urlencoded</code>
+ * list of parameters in an HTTP PUT or HTTP POST.
+ *
+ * @param parameters The parameters to include.
+ * @param charset The encoding to use.
+ *
+ * @since 4.2
+ */
+ public static String format (
+ final Iterable<? extends NameValuePair> parameters,
+ final Charset charset) {
+ final StringBuilder result = new StringBuilder();
+ for (final NameValuePair parameter : parameters) {
+ final String encodedName = encodeFormFields(parameter.getName(), charset);
+ final String encodedValue = encodeFormFields(parameter.getValue(), charset);
+ if (result.length() > 0) {
+ result.append(PARAMETER_SEPARATOR);
+ }
+ result.append(encodedName);
+ if (encodedValue != null) {
+ result.append(NAME_VALUE_SEPARATOR);
+ result.append(encodedValue);
+ }
}
return result.toString();
}
- private static String decode (final String content, final String encoding) {
- try {
- return URLDecoder.decode(content,
- encoding != null ? encoding : HTTP.DEFAULT_CONTENT_CHARSET);
- } catch (UnsupportedEncodingException problem) {
- throw new IllegalArgumentException(problem);
+ /**
+ * Unreserved characters, i.e. alphanumeric, plus: {@code _ - ! . ~ ' ( ) *}
+ * <p>
+ * This list is the same as the {@code unreserved} list in
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>
+ */
+ private static final BitSet UNRESERVED = new BitSet(256);
+ /**
+ * Punctuation characters: , ; : $ & + =
+ * <p>
+ * These are the additional characters allowed by userinfo.
+ */
+ private static final BitSet PUNCT = new BitSet(256);
+ /** Characters which are safe to use in userinfo, i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation */
+ private static final BitSet USERINFO = new BitSet(256);
+ /** Characters which are safe to use in a path, i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation plus / @ */
+ private static final BitSet PATHSAFE = new BitSet(256);
+ /** Characters which are safe to use in a fragment, i.e. {@link #RESERVED} plus {@link #UNRESERVED} */
+ private static final BitSet FRAGMENT = new BitSet(256);
+
+ /**
+ * Reserved characters, i.e. {@code ;/?:@&=+$,[]}
+ * <p>
+ * This list is the same as the {@code reserved} list in
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>
+ * as augmented by
+ * <a href="http://www.ietf.org/rfc/rfc2732.txt">RFC 2732</a>
+ */
+ private static final BitSet RESERVED = new BitSet(256);
+
+
+ /**
+ * Safe characters for x-www-form-urlencoded data, as per java.net.URLEncoder and browser behaviour,
+ * i.e. alphanumeric plus {@code "-", "_", ".", "*"}
+ */
+ private static final BitSet URLENCODER = new BitSet(256);
+
+ static {
+ // unreserved chars
+ // alpha characters
+ for (int i = 'a'; i <= 'z'; i++) {
+ UNRESERVED.set(i);
+ }
+ for (int i = 'A'; i <= 'Z'; i++) {
+ UNRESERVED.set(i);
+ }
+ // numeric characters
+ for (int i = '0'; i <= '9'; i++) {
+ UNRESERVED.set(i);
+ }
+ UNRESERVED.set('_'); // these are the charactes of the "mark" list
+ UNRESERVED.set('-');
+ UNRESERVED.set('.');
+ UNRESERVED.set('*');
+ URLENCODER.or(UNRESERVED); // skip remaining unreserved characters
+ UNRESERVED.set('!');
+ UNRESERVED.set('~');
+ UNRESERVED.set('\'');
+ UNRESERVED.set('(');
+ UNRESERVED.set(')');
+ // punct chars
+ PUNCT.set(',');
+ PUNCT.set(';');
+ PUNCT.set(':');
+ PUNCT.set('$');
+ PUNCT.set('&');
+ PUNCT.set('+');
+ PUNCT.set('=');
+ // Safe for userinfo
+ USERINFO.or(UNRESERVED);
+ USERINFO.or(PUNCT);
+
+ // URL path safe
+ PATHSAFE.or(UNRESERVED);
+ PATHSAFE.set('/'); // segment separator
+ PATHSAFE.set(';'); // param separator
+ PATHSAFE.set(':'); // rest as per list in 2396, i.e. : @ & = + $ ,
+ PATHSAFE.set('@');
+ PATHSAFE.set('&');
+ PATHSAFE.set('=');
+ PATHSAFE.set('+');
+ PATHSAFE.set('$');
+ PATHSAFE.set(',');
+
+ RESERVED.set(';');
+ RESERVED.set('/');
+ RESERVED.set('?');
+ RESERVED.set(':');
+ RESERVED.set('@');
+ RESERVED.set('&');
+ RESERVED.set('=');
+ RESERVED.set('+');
+ RESERVED.set('$');
+ RESERVED.set(',');
+ RESERVED.set('['); // added by RFC 2732
+ RESERVED.set(']'); // added by RFC 2732
+
+ FRAGMENT.or(RESERVED);
+ FRAGMENT.or(UNRESERVED);
+ }
+
+ private static final int RADIX = 16;
+
+ /**
+ * Emcode/escape a portion of a URL, to use with the query part ensure {@code plusAsBlank} is true.
+ *
+ * @param content the portion to decode
+ * @param charset the charset to use
+ * @param blankAsPlus if {@code true}, then convert space to '+' (e.g. for www-url-form-encoded content), otherwise leave as is.
+ * @return
+ */
+ private static String urlencode(
+ final String content,
+ final Charset charset,
+ final BitSet safechars,
+ final boolean blankAsPlus) {
+ if (content == null) {
+ return null;
}
+ StringBuilder buf = new StringBuilder();
+ ByteBuffer bb = charset.encode(content);
+ while (bb.hasRemaining()) {
+ int b = bb.get() & 0xff;
+ if (safechars.get(b)) {
+ buf.append((char) b);
+ } else if (blankAsPlus && b == ' ') {
+ buf.append('+');
+ } else {
+ buf.append("%");
+ char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX));
+ char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, RADIX));
+ buf.append(hex1);
+ buf.append(hex2);
+ }
+ }
+ return buf.toString();
}
- private static String encode (final String content, final String encoding) {
- try {
- return URLEncoder.encode(content,
- encoding != null ? encoding : HTTP.DEFAULT_CONTENT_CHARSET);
- } catch (UnsupportedEncodingException problem) {
- throw new IllegalArgumentException(problem);
+ /**
+ * Decode/unescape a portion of a URL, to use with the query part ensure {@code plusAsBlank} is true.
+ *
+ * @param content the portion to decode
+ * @param charset the charset to use
+ * @param plusAsBlank if {@code true}, then convert '+' to space (e.g. for www-url-form-encoded content), otherwise leave as is.
+ * @return
+ */
+ private static String urldecode(
+ final String content,
+ final Charset charset,
+ final boolean plusAsBlank) {
+ if (content == null) {
+ return null;
+ }
+ ByteBuffer bb = ByteBuffer.allocate(content.length());
+ CharBuffer cb = CharBuffer.wrap(content);
+ while (cb.hasRemaining()) {
+ char c = cb.get();
+ if (c == '%' && cb.remaining() >= 2) {
+ char uc = cb.get();
+ char lc = cb.get();
+ int u = Character.digit(uc, 16);
+ int l = Character.digit(lc, 16);
+ if (u != -1 && l != -1) {
+ bb.put((byte) ((u << 4) + l));
+ } else {
+ bb.put((byte) '%');
+ bb.put((byte) uc);
+ bb.put((byte) lc);
+ }
+ } else if (plusAsBlank && c == '+') {
+ bb.put((byte) ' ');
+ } else {
+ bb.put((byte) c);
+ }
+ }
+ bb.flip();
+ return charset.decode(bb).toString();
+ }
+
+ /**
+ * Decode/unescape www-url-form-encoded content.
+ *
+ * @param content the content to decode, will decode '+' as space
+ * @param charset the charset to use
+ * @return
+ */
+ private static String decodeFormFields (final String content, final String charset) {
+ if (content == null) {
+ return null;
}
+ return urldecode(content, charset != null ? Charset.forName(charset) : Consts.UTF_8, true);
+ }
+
+ /**
+ * Decode/unescape www-url-form-encoded content.
+ *
+ * @param content the content to decode, will decode '+' as space
+ * @param charset the charset to use
+ * @return
+ */
+ private static String decodeFormFields (final String content, final Charset charset) {
+ if (content == null) {
+ return null;
+ }
+ return urldecode(content, charset != null ? charset : Consts.UTF_8, true);
+ }
+
+ /**
+ * Encode/escape www-url-form-encoded content.
+ * <p>
+ * Uses the {@link #URLENCODER} set of characters, rather than
+ * the {@link #UNRSERVED} set; this is for compatibilty with previous
+ * releases, URLEncoder.encode() and most browsers.
+ *
+ * @param content the content to encode, will convert space to '+'
+ * @param charset the charset to use
+ * @return
+ */
+ private static String encodeFormFields (final String content, final String charset) {
+ if (content == null) {
+ return null;
+ }
+ return urlencode(content, charset != null ? Charset.forName(charset) :
+ Consts.UTF_8, URLENCODER, true);
+ }
+
+ /**
+ * Encode/escape www-url-form-encoded content.
+ * <p>
+ * Uses the {@link #URLENCODER} set of characters, rather than
+ * the {@link #UNRSERVED} set; this is for compatibilty with previous
+ * releases, URLEncoder.encode() and most browsers.
+ *
+ * @param content the content to encode, will convert space to '+'
+ * @param charset the charset to use
+ * @return
+ */
+ private static String encodeFormFields (final String content, final Charset charset) {
+ if (content == null) {
+ return null;
+ }
+ return urlencode(content, charset != null ? charset : Consts.UTF_8, URLENCODER, true);
+ }
+
+ /**
+ * Encode a String using the {@link #USERINFO} set of characters.
+ * <p>
+ * Used by URIBuilder to encode the userinfo segment.
+ *
+ * @param content the string to encode, does not convert space to '+'
+ * @param charset the charset to use
+ * @return the encoded string
+ */
+ static String encUserInfo(final String content, final Charset charset) {
+ return urlencode(content, charset, USERINFO, false);
+ }
+
+ /**
+ * Encode a String using the {@link #FRAGMENT} set of characters.
+ * <p>
+ * Used by URIBuilder to encode the userinfo segment.
+ *
+ * @param content the string to encode, does not convert space to '+'
+ * @param charset the charset to use
+ * @return the encoded string
+ */
+ static String encFragment(final String content, final Charset charset) {
+ return urlencode(content, charset, FRAGMENT, false);
+ }
+
+ /**
+ * Encode a String using the {@link #PATHSAFE} set of characters.
+ * <p>
+ * Used by URIBuilder to encode path segments.
+ *
+ * @param content the string to encode, does not convert space to '+'
+ * @param charset the charset to use
+ * @return the encoded string
+ */
+ static String encPath(final String content, final Charset charset) {
+ return urlencode(content, charset, PATHSAFE, false);
}
}
diff --git a/httpclient/src/main/java/org/apache/http/conn/BasicManagedEntity.java b/httpclient/src/main/java/org/apache/http/conn/BasicManagedEntity.java
index 81df17e..3f6975c 100644
--- a/httpclient/src/main/java/org/apache/http/conn/BasicManagedEntity.java
+++ b/httpclient/src/main/java/org/apache/http/conn/BasicManagedEntity.java
@@ -30,6 +30,7 @@ package org.apache.http.conn;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.SocketException;
import org.apache.http.annotation.NotThreadSafe;
@@ -103,7 +104,9 @@ public class BasicManagedEntity extends HttpEntityWrapper
}
}
- @Deprecated
+ /**
+ * @deprecated (4.1) Use {@link EntityUtils#consume(HttpEntity)}
+ */
@Override
public void consumeContent() throws IOException {
ensureConsumed();
@@ -147,10 +150,17 @@ public class BasicManagedEntity extends HttpEntityWrapper
public boolean streamClosed(InputStream wrapped) throws IOException {
try {
if (attemptReuse && (managedConn != null)) {
+ boolean valid = managedConn.isOpen();
// this assumes that closing the stream will
// consume the remainder of the response body:
- wrapped.close();
- managedConn.markReusable();
+ try {
+ wrapped.close();
+ managedConn.markReusable();
+ } catch (SocketException ex) {
+ if (valid) {
+ throw ex;
+ }
+ }
}
} finally {
releaseManagedConnection();
diff --git a/httpclient/src/main/java/org/apache/http/conn/DnsResolver.java b/httpclient/src/main/java/org/apache/http/conn/DnsResolver.java
new file mode 100644
index 0000000..c4501f3
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/conn/DnsResolver.java
@@ -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.conn;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Users may implement this interface to override the normal DNS lookup offered
+ * by the OS.
+ *
+ * @since 4.2
+ */
+public interface DnsResolver {
+
+ /**
+ * Returns the IP address for the specified host name, or null if the given
+ * host is not recognized or the associated IP address cannot be used to
+ * build an InetAddress instance.
+ *
+ * @see InetAddress
+ *
+ * @param host
+ * The host name to be resolved by this resolver.
+ * @return The IP address associated to the given host name, or null if the
+ * host name is not known by the implementation class.
+ */
+ InetAddress[] resolve(String host) throws UnknownHostException;
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/conn/HttpInetSocketAddress.java b/httpclient/src/main/java/org/apache/http/conn/HttpInetSocketAddress.java
new file mode 100644
index 0000000..c59d212
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/conn/HttpInetSocketAddress.java
@@ -0,0 +1,63 @@
+/*
+ * ====================================================================
+ *
+ * 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.conn;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+import org.apache.http.HttpHost;
+
+/**
+ * Extended {@link InetSocketAddress} implementation that also provides access to the original
+ * {@link HttpHost} used to resolve the address.
+ *
+ * @since 4.2
+ */
+public class HttpInetSocketAddress extends InetSocketAddress {
+
+ private static final long serialVersionUID = -6650701828361907957L;
+
+ private final HttpHost httphost;
+
+ public HttpInetSocketAddress(final HttpHost httphost, final InetAddress addr, int port) {
+ super(addr, port);
+ if (httphost == null) {
+ throw new IllegalArgumentException("HTTP host may not be null");
+ }
+ this.httphost = httphost;
+ }
+
+ public HttpHost getHttpHost() {
+ return this.httphost;
+ }
+
+ @Override
+ public String toString() {
+ return this.httphost.getHostName() + ":" + getPort();
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/conn/MultihomePlainSocketFactory.java b/httpclient/src/main/java/org/apache/http/conn/MultihomePlainSocketFactory.java
index 551477f..22fa0a7 100644
--- a/httpclient/src/main/java/org/apache/http/conn/MultihomePlainSocketFactory.java
+++ b/httpclient/src/main/java/org/apache/http/conn/MultihomePlainSocketFactory.java
@@ -53,7 +53,7 @@ import org.apache.http.params.HttpParams;
*
* @since 4.0
*
- * @deprecated Do not use. For multihome support socket factories must implement
+ * @deprecated (4.1) Do not use. For multihome support socket factories must implement
* {@link SchemeSocketFactory} interface.
*/
@Deprecated
diff --git a/httpclient/src/main/java/org/apache/http/conn/params/ConnConnectionPNames.java b/httpclient/src/main/java/org/apache/http/conn/params/ConnConnectionPNames.java
index 2ac6f66..a6d66b0 100644
--- a/httpclient/src/main/java/org/apache/http/conn/params/ConnConnectionPNames.java
+++ b/httpclient/src/main/java/org/apache/http/conn/params/ConnConnectionPNames.java
@@ -26,11 +26,14 @@
package org.apache.http.conn.params;
+import org.apache.http.impl.conn.DefaultHttpResponseParser;
+
/**
* Parameter names for HTTP client connections.
*
* @since 4.0
*/
+ at Deprecated
public interface ConnConnectionPNames {
/**
@@ -49,6 +52,8 @@ public interface ConnConnectionPNames {
* 0 disallows all garbage/empty lines before the status line.
* Use {@link java.lang.Integer#MAX_VALUE} for unlimited number.
* </p>
+ *
+ * @deprecated Use custom {@link DefaultHttpResponseParser} implementation
*/
public static final String MAX_STATUS_LINE_GARBAGE = "http.connection.max-status-line-garbage";
diff --git a/httpclient/src/main/java/org/apache/http/conn/params/ConnConnectionParamBean.java b/httpclient/src/main/java/org/apache/http/conn/params/ConnConnectionParamBean.java
index b44fb3c..eac4c52 100644
--- a/httpclient/src/main/java/org/apache/http/conn/params/ConnConnectionParamBean.java
+++ b/httpclient/src/main/java/org/apache/http/conn/params/ConnConnectionParamBean.java
@@ -27,8 +27,7 @@
package org.apache.http.conn.params;
-import org.apache.http.annotation.NotThreadSafe;
-
+import org.apache.http.impl.conn.DefaultHttpResponseParser;
import org.apache.http.params.HttpAbstractParamBean;
import org.apache.http.params.HttpParams;
@@ -38,8 +37,10 @@ import org.apache.http.params.HttpParams;
* using Java Beans conventions.
*
* @since 4.0
+ *
+ * @deprecated (4.2) do not use
*/
- at NotThreadSafe
+ at Deprecated
public class ConnConnectionParamBean extends HttpAbstractParamBean {
public ConnConnectionParamBean (final HttpParams params) {
@@ -47,7 +48,7 @@ public class ConnConnectionParamBean extends HttpAbstractParamBean {
}
/**
- * @see ConnConnectionPNames#MAX_STATUS_LINE_GARBAGE
+ * @deprecated (4.2) Use custom {@link DefaultHttpResponseParser} implementation
*/
public void setMaxStatusLineGarbage (final int maxStatusLineGarbage) {
params.setIntParameter(ConnConnectionPNames.MAX_STATUS_LINE_GARBAGE, maxStatusLineGarbage);
diff --git a/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerPNames.java b/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerPNames.java
index f0eaeb3..375838b 100644
--- a/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerPNames.java
+++ b/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerPNames.java
@@ -26,15 +26,14 @@
package org.apache.http.conn.params;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.apache.http.params.CoreConnectionPNames;
-
/**
* Parameter names for connection managers in HttpConn.
*
* @since 4.0
- */
- at Deprecated
+ *
+ * @deprecated (4.1.2) use configuration methods of the specific connection manager implementation.
+*/
+ at Deprecated
public interface ConnManagerPNames {
/**
@@ -43,10 +42,7 @@ public interface ConnManagerPNames {
* {@link org.apache.http.conn.ClientConnectionManager}.
* <p>
* This parameter expects a value of type {@link Long}.
- * <p>
- * @deprecated use {@link CoreConnectionPNames#CONNECTION_TIMEOUT}
*/
- @Deprecated
public static final String TIMEOUT = "http.conn-manager.timeout";
/**
@@ -56,10 +52,7 @@ public interface ConnManagerPNames {
* <p>
* This parameter expects a value of type {@link ConnPerRoute}.
* <p>
- * @deprecated use {@link ThreadSafeClientConnManager#setMaxForRoute(org.apache.http.conn.routing.HttpRoute, int)},
- * {@link ThreadSafeClientConnManager#getMaxForRoute(org.apache.http.conn.routing.HttpRoute)}
*/
- @Deprecated
public static final String MAX_CONNECTIONS_PER_ROUTE = "http.conn-manager.max-per-route";
/**
@@ -68,11 +61,7 @@ public interface ConnManagerPNames {
* and applies to individual manager instances.
* <p>
* This parameter expects a value of type {@link Integer}.
- * <p>
- * @deprecated use {@link ThreadSafeClientConnManager#setMaxTotal(int)},
- * {@link ThreadSafeClientConnManager#getMaxTotal()}
*/
- @Deprecated
public static final String MAX_TOTAL_CONNECTIONS = "http.conn-manager.max-total";
}
diff --git a/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerParamBean.java b/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerParamBean.java
index ecd8547..953af49 100644
--- a/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerParamBean.java
+++ b/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerParamBean.java
@@ -38,9 +38,11 @@ import org.apache.http.params.HttpParams;
* using Java Beans conventions.
*
* @since 4.0
+ *
+ * @deprecated (4.1.2) use configuration methods of the specific connection manager implementation.
*/
@NotThreadSafe
- at Deprecated
+ at Deprecated
public class ConnManagerParamBean extends HttpAbstractParamBean {
public ConnManagerParamBean (final HttpParams params) {
@@ -51,14 +53,10 @@ public class ConnManagerParamBean extends HttpAbstractParamBean {
params.setLongParameter(ConnManagerPNames.TIMEOUT, timeout);
}
- /** @see ConnManagerPNames#MAX_TOTAL_CONNECTIONS */
- @Deprecated
public void setMaxTotalConnections (final int maxConnections) {
params.setIntParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, maxConnections);
}
- /** @see ConnManagerPNames#MAX_CONNECTIONS_PER_ROUTE */
- @Deprecated
public void setConnectionsPerRoute(final ConnPerRouteBean connPerRoute) {
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, connPerRoute);
}
diff --git a/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerParams.java b/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerParams.java
index 58eac19..bcb9a2d 100644
--- a/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerParams.java
+++ b/httpclient/src/main/java/org/apache/http/conn/params/ConnManagerParams.java
@@ -29,7 +29,6 @@ package org.apache.http.conn.params;
import org.apache.http.annotation.Immutable;
import org.apache.http.conn.routing.HttpRoute;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
@@ -40,8 +39,10 @@ import org.apache.http.params.HttpParams;
* @since 4.0
*
* @see ConnManagerPNames
+ *
+ * @deprecated (4.1.2) use configuration methods of the specific connection manager implementation.
*/
- at Deprecated
+ at Deprecated
@Immutable
public final class ConnManagerParams implements ConnManagerPNames {
@@ -55,9 +56,8 @@ public final class ConnManagerParams implements ConnManagerPNames {
*
* @return timeout in milliseconds.
*
- * @deprecated use {@link HttpConnectionParams#getConnectionTimeout(HttpParams)}
+ * @deprecated (4.1) use {@link HttpConnectionParams#getConnectionTimeout(HttpParams)}
*/
- @Deprecated
public static long getTimeout(final HttpParams params) {
if (params == null) {
throw new IllegalArgumentException("HTTP parameters may not be null");
@@ -72,9 +72,8 @@ public final class ConnManagerParams implements ConnManagerPNames {
*
* @param timeout the timeout in milliseconds
*
- * @deprecated use {@link HttpConnectionParams#setConnectionTimeout(HttpParams, int)}
+ * @deprecated (4.1) use {@link HttpConnectionParams#setConnectionTimeout(HttpParams, int)}
*/
- @Deprecated
public static void setTimeout(final HttpParams params, long timeout) {
if (params == null) {
throw new IllegalArgumentException("HTTP parameters may not be null");
@@ -97,10 +96,7 @@ public final class ConnManagerParams implements ConnManagerPNames {
* @param params HTTP parameters
* @param connPerRoute lookup interface for maximum number of connections allowed
* per route
- *
- * @deprecated use {@link ThreadSafeClientConnManager#setMaxForRoute(org.apache.http.conn.routing.HttpRoute, int)}
*/
- @Deprecated
public static void setMaxConnectionsPerRoute(final HttpParams params,
final ConnPerRoute connPerRoute) {
if (params == null) {
@@ -116,10 +112,7 @@ public final class ConnManagerParams implements ConnManagerPNames {
* @param params HTTP parameters
*
* @return lookup interface for maximum number of connections allowed per route.
- *
- * @deprecated use {@link ThreadSafeClientConnManager#getMaxForRoute(org.apache.http.conn.routing.HttpRoute)}
*/
- @Deprecated
public static ConnPerRoute getMaxConnectionsPerRoute(final HttpParams params) {
if (params == null) {
throw new IllegalArgumentException
@@ -137,10 +130,7 @@ public final class ConnManagerParams implements ConnManagerPNames {
*
* @param params HTTP parameters
* @param maxTotalConnections The maximum number of connections allowed.
- *
- * @deprecated use {@link ThreadSafeClientConnManager#setMaxTotal(int)}
*/
- @Deprecated
public static void setMaxTotalConnections(
final HttpParams params,
int maxTotalConnections) {
@@ -157,10 +147,7 @@ public final class ConnManagerParams implements ConnManagerPNames {
* @param params HTTP parameters
*
* @return The maximum number of connections allowed.
- *
- * @deprecated use {@link ThreadSafeClientConnManager#getMaxTotal()}
*/
- @Deprecated
public static int getMaxTotalConnections(
final HttpParams params) {
if (params == null) {
diff --git a/httpclient/src/main/java/org/apache/http/conn/params/ConnPerRouteBean.java b/httpclient/src/main/java/org/apache/http/conn/params/ConnPerRouteBean.java
index 917c41c..be49a53 100644
--- a/httpclient/src/main/java/org/apache/http/conn/params/ConnPerRouteBean.java
+++ b/httpclient/src/main/java/org/apache/http/conn/params/ConnPerRouteBean.java
@@ -32,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.http.annotation.ThreadSafe;
import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.pool.ConnPoolControl;
/**
* This class maintains a map of HTTP routes to maximum number of connections allowed
@@ -40,7 +41,10 @@ import org.apache.http.conn.routing.HttpRoute;
* a fine-grained control of connections on a per route basis.
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link ConnPoolControl}
*/
+ at Deprecated
@ThreadSafe
public final class ConnPerRouteBean implements ConnPerRoute {
@@ -61,10 +65,6 @@ public final class ConnPerRouteBean implements ConnPerRoute {
this(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);
}
- /**
- * @deprecated Use {@link #getDefaultMaxPerRoute()} instead
- */
- @Deprecated
public int getDefaultMax() {
return this.defaultMax;
}
diff --git a/httpclient/src/main/java/org/apache/http/conn/routing/HttpRoute.java b/httpclient/src/main/java/org/apache/http/conn/routing/HttpRoute.java
index 7db7c16..511f424 100644
--- a/httpclient/src/main/java/org/apache/http/conn/routing/HttpRoute.java
+++ b/httpclient/src/main/java/org/apache/http/conn/routing/HttpRoute.java
@@ -334,7 +334,7 @@ public final class HttpRoute implements RouteInfo, Cloneable {
if (this == obj) return true;
if (obj instanceof HttpRoute) {
HttpRoute that = (HttpRoute) obj;
- return
+ return
// Do the cheapest tests first
(this.secure == that.secure) &&
(this.tunnelled == that.tunnelled) &&
@@ -376,8 +376,6 @@ public final class HttpRoute implements RouteInfo, Cloneable {
@Override
public final String toString() {
StringBuilder cab = new StringBuilder(50 + getHopCount()*30);
-
- cab.append("HttpRoute[");
if (this.localAddress != null) {
cab.append(this.localAddress);
cab.append("->");
@@ -395,8 +393,6 @@ public final class HttpRoute implements RouteInfo, Cloneable {
cab.append("->");
}
cab.append(this.targetHost);
- cab.append(']');
-
return cab.toString();
}
diff --git a/httpclient/src/main/java/org/apache/http/conn/routing/RouteTracker.java b/httpclient/src/main/java/org/apache/http/conn/routing/RouteTracker.java
index dcf227d..ee9f324 100644
--- a/httpclient/src/main/java/org/apache/http/conn/routing/RouteTracker.java
+++ b/httpclient/src/main/java/org/apache/http/conn/routing/RouteTracker.java
@@ -87,6 +87,16 @@ public final class RouteTracker implements RouteInfo, Cloneable {
this.layered = LayerType.PLAIN;
}
+ /**
+ * @since 4.2
+ */
+ public void reset() {
+ this.connected = false;
+ this.proxyChain = null;
+ this.tunnelled = TunnelType.PLAIN;
+ this.layered = LayerType.PLAIN;
+ this.secure = false;
+ }
/**
* Creates a new tracker for the given route.
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/HostNameResolver.java b/httpclient/src/main/java/org/apache/http/conn/scheme/HostNameResolver.java
index d325f42..b2f2f88 100644
--- a/httpclient/src/main/java/org/apache/http/conn/scheme/HostNameResolver.java
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/HostNameResolver.java
@@ -35,9 +35,9 @@ import java.net.InetAddress;
*
* @since 4.0
*
- * @deprecated Do not use
+ * @deprecated (4.1) Do not use
*/
- at Deprecated
+ at Deprecated
public interface HostNameResolver {
/**
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSchemeSocketFactory.java b/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSchemeSocketFactory.java
index 9bc57c9..4a01bd1 100644
--- a/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSchemeSocketFactory.java
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSchemeSocketFactory.java
@@ -32,11 +32,13 @@ import java.net.Socket;
import java.net.UnknownHostException;
/**
- * A {@link SocketFactory SocketFactory} for layered sockets (SSL/TLS).
- * See there for things to consider when implementing a socket factory.
+ * Extended {@link SchemeSocketFactory} interface for layered sockets such as SSL/TLS.
*
* @since 4.1
+ *
+ * @deprecated (4.2) use {@link SchemeLayeredSocketFactory}
*/
+ at Deprecated
public interface LayeredSchemeSocketFactory extends SchemeSocketFactory {
/**
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSchemeSocketFactoryAdaptor.java b/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSchemeSocketFactoryAdaptor.java
deleted file mode 100644
index 29a649a..0000000
--- a/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSchemeSocketFactoryAdaptor.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * ====================================================================
- * 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.conn.scheme;
-
-import java.io.IOException;
-import java.net.Socket;
-import java.net.UnknownHostException;
-
- at Deprecated
-class LayeredSchemeSocketFactoryAdaptor extends SchemeSocketFactoryAdaptor
- implements LayeredSchemeSocketFactory {
-
- private final LayeredSocketFactory factory;
-
- LayeredSchemeSocketFactoryAdaptor(final LayeredSocketFactory factory) {
- super(factory);
- this.factory = factory;
- }
-
- public Socket createLayeredSocket(
- final Socket socket,
- final String target, int port,
- boolean autoClose) throws IOException, UnknownHostException {
- return this.factory.createSocket(socket, target, port, autoClose);
- }
-
-}
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSocketFactory.java b/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSocketFactory.java
index 3dd7581..8cc1bc9 100644
--- a/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSocketFactory.java
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSocketFactory.java
@@ -37,9 +37,9 @@ import java.net.UnknownHostException;
*
* @since 4.0
*
- * @deprecated use {@link SchemeSocketFactory}
+ * @deprecated (4.1) use {@link SchemeSocketFactory}
*/
- at Deprecated
+ at Deprecated
public interface LayeredSocketFactory extends SocketFactory {
/**
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSocketFactoryAdaptor.java b/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSocketFactoryAdaptor.java
index 7843435..f761778 100644
--- a/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSocketFactoryAdaptor.java
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/LayeredSocketFactoryAdaptor.java
@@ -31,6 +31,9 @@ import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
+/**
+ * @deprecated (4.1) do not use
+ */
@Deprecated
class LayeredSocketFactoryAdaptor extends SocketFactoryAdaptor implements LayeredSocketFactory {
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/PlainSocketFactory.java b/httpclient/src/main/java/org/apache/http/conn/scheme/PlainSocketFactory.java
index 544ce08..72f7279 100644
--- a/httpclient/src/main/java/org/apache/http/conn/scheme/PlainSocketFactory.java
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/PlainSocketFactory.java
@@ -37,6 +37,7 @@ import java.net.UnknownHostException;
import org.apache.http.annotation.Immutable;
import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.DnsResolver;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
@@ -67,6 +68,9 @@ public class PlainSocketFactory implements SocketFactory, SchemeSocketFactory {
return new PlainSocketFactory();
}
+ /**
+ * @deprecated (4.1) use {@link DnsResolver}
+ */
@Deprecated
public PlainSocketFactory(final HostNameResolver nameResolver) {
super();
@@ -122,8 +126,7 @@ public class PlainSocketFactory implements SocketFactory, SchemeSocketFactory {
sock.setSoTimeout(soTimeout);
sock.connect(remoteAddress, connTimeout);
} catch (SocketTimeoutException ex) {
- throw new ConnectTimeoutException("Connect to " + remoteAddress.getHostName() + "/"
- + remoteAddress.getAddress() + " timed out");
+ throw new ConnectTimeoutException("Connect to " + remoteAddress + " timed out");
}
return sock;
}
@@ -154,7 +157,7 @@ public class PlainSocketFactory implements SocketFactory, SchemeSocketFactory {
}
/**
- * @deprecated Use {@link #connectSocket(Socket, InetSocketAddress, InetSocketAddress, HttpParams)}
+ * @deprecated (4.1) Use {@link #connectSocket(Socket, InetSocketAddress, InetSocketAddress, HttpParams)}
*/
@Deprecated
public Socket connectSocket(
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/Scheme.java b/httpclient/src/main/java/org/apache/http/conn/scheme/Scheme.java
index ec20040..c022639 100644
--- a/httpclient/src/main/java/org/apache/http/conn/scheme/Scheme.java
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/Scheme.java
@@ -81,6 +81,7 @@ public final class Scheme {
*
* @since 4.1
*/
+ @SuppressWarnings("deprecation")
public Scheme(final String name, final int port, final SchemeSocketFactory factory) {
if (name == null) {
throw new IllegalArgumentException("Scheme name may not be null");
@@ -92,9 +93,17 @@ public final class Scheme {
throw new IllegalArgumentException("Socket factory may not be null");
}
this.name = name.toLowerCase(Locale.ENGLISH);
- this.socketFactory = factory;
this.defaultPort = port;
- this.layered = factory instanceof LayeredSchemeSocketFactory;
+ if (factory instanceof SchemeLayeredSocketFactory) {
+ this.layered = true;
+ this.socketFactory = factory;
+ } else if (factory instanceof LayeredSchemeSocketFactory) {
+ this.layered = true;
+ this.socketFactory = new SchemeLayeredSocketFactoryAdaptor2((LayeredSchemeSocketFactory) factory);
+ } else {
+ this.layered = false;
+ this.socketFactory = factory;
+ }
}
/**
@@ -108,9 +117,9 @@ public final class Scheme {
* with this scheme
* @param port the default port for this scheme
*
- * @deprecated Use {@link #Scheme(String, int, SchemeSocketFactory)}
+ * @deprecated (4.1) Use {@link #Scheme(String, int, SchemeSocketFactory)}
*/
- @Deprecated
+ @Deprecated
public Scheme(final String name,
final SocketFactory factory,
final int port) {
@@ -130,7 +139,7 @@ public final class Scheme {
this.name = name.toLowerCase(Locale.ENGLISH);
if (factory instanceof LayeredSocketFactory) {
- this.socketFactory = new LayeredSchemeSocketFactoryAdaptor(
+ this.socketFactory = new SchemeLayeredSocketFactoryAdaptor(
(LayeredSocketFactory) factory);
this.layered = true;
} else {
@@ -157,7 +166,7 @@ public final class Scheme {
*
* @return the socket factory for this scheme
*
- * @deprecated Use {@link #getSchemeSocketFactory()}
+ * @deprecated (4.1) Use {@link #getSchemeSocketFactory()}
*/
@Deprecated
public final SocketFactory getSocketFactory() {
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeLayeredSocketFactory.java b/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeLayeredSocketFactory.java
new file mode 100644
index 0000000..7ede8cd
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeLayeredSocketFactory.java
@@ -0,0 +1,65 @@
+/*
+ * ====================================================================
+ * 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.conn.scheme;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import org.apache.http.params.HttpParams;
+
+/**
+ * Extended {@link SchemeSocketFactory} interface for layered sockets such as SSL/TLS.
+ *
+ * @since 4.2
+ */
+public interface SchemeLayeredSocketFactory extends SchemeSocketFactory {
+
+ /**
+ * Returns a socket connected to the given host that is layered over an
+ * existing socket. Used primarily for creating secure sockets through
+ * proxies.
+ *
+ * @param socket the existing socket
+ * @param target the name of the target host.
+ * @param port the port to connect to on the target host
+ * @param params HTTP parameters
+ *
+ * @return Socket a new socket
+ *
+ * @throws IOException if an I/O error occurs while creating the socket
+ * @throws UnknownHostException if the IP address of the host cannot be
+ * determined
+ */
+ Socket createLayeredSocket(
+ Socket socket,
+ String target,
+ int port,
+ HttpParams params) throws IOException, UnknownHostException;
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeLayeredSocketFactoryAdaptor.java b/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeLayeredSocketFactoryAdaptor.java
new file mode 100644
index 0000000..38d45e2
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeLayeredSocketFactoryAdaptor.java
@@ -0,0 +1,57 @@
+/*
+ * ====================================================================
+ * 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.conn.scheme;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import org.apache.http.params.HttpParams;
+
+/**
+ * @deprecated (4.2) do not use
+ */
+ at Deprecated
+class SchemeLayeredSocketFactoryAdaptor extends SchemeSocketFactoryAdaptor
+ implements SchemeLayeredSocketFactory {
+
+ private final LayeredSocketFactory factory;
+
+ SchemeLayeredSocketFactoryAdaptor(final LayeredSocketFactory factory) {
+ super(factory);
+ this.factory = factory;
+ }
+
+ public Socket createLayeredSocket(
+ final Socket socket,
+ final String target, int port,
+ final HttpParams params) throws IOException, UnknownHostException {
+ return this.factory.createSocket(socket, target, port, true);
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeLayeredSocketFactoryAdaptor2.java b/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeLayeredSocketFactoryAdaptor2.java
new file mode 100644
index 0000000..12c1577
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeLayeredSocketFactoryAdaptor2.java
@@ -0,0 +1,74 @@
+/*
+ * ====================================================================
+ * 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.conn.scheme;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.params.HttpParams;
+
+/**
+ * @deprecated (4.2) do not use
+ */
+ at Deprecated
+class SchemeLayeredSocketFactoryAdaptor2 implements SchemeLayeredSocketFactory {
+
+ private final LayeredSchemeSocketFactory factory;
+
+ SchemeLayeredSocketFactoryAdaptor2(final LayeredSchemeSocketFactory factory) {
+ super();
+ this.factory = factory;
+ }
+
+ public Socket createSocket(final HttpParams params) throws IOException {
+ return this.factory.createSocket(params);
+ }
+
+ public Socket connectSocket(
+ final Socket sock,
+ final InetSocketAddress remoteAddress,
+ final InetSocketAddress localAddress,
+ final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
+ return this.factory.connectSocket(sock, remoteAddress, localAddress, params);
+ }
+
+ public boolean isSecure(Socket sock) throws IllegalArgumentException {
+ return this.factory.isSecure(sock);
+ }
+
+ public Socket createLayeredSocket(
+ final Socket socket,
+ final String target, int port,
+ final HttpParams params) throws IOException, UnknownHostException {
+ return this.factory.createLayeredSocket(socket, target, port, true);
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeSocketFactory.java b/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeSocketFactory.java
index f45bc09..43fca33 100644
--- a/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeSocketFactory.java
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeSocketFactory.java
@@ -32,7 +32,9 @@ import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
+import org.apache.http.HttpHost;
import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.HttpInetSocketAddress;
import org.apache.http.params.HttpParams;
/**
@@ -63,12 +65,17 @@ public interface SchemeSocketFactory {
/**
* Connects a socket to the target host with the given remote address.
+ * <p/>
+ * Please note that {@link HttpInetSocketAddress} class should be used in order to pass
+ * the target remote address along with the original {@link HttpHost} value used to resolve
+ * the address. The use of {@link HttpInetSocketAddress} can also ensure that no reverse
+ * DNS lookup will be performed if the target remote address was specified as an IP address.
*
* @param sock the socket to connect, as obtained from
* {@link #createSocket(HttpParams) createSocket}.
* <code>null</code> indicates that a new socket
* should be created and connected.
- * @param remoteAddress the remote address to connect to
+ * @param remoteAddress the remote address to connect to.
* @param localAddress the local address to bind the socket to, or
* <code>null</code> for any
* @param params additional {@link HttpParams parameters} for connecting
@@ -82,6 +89,8 @@ public interface SchemeSocketFactory {
* can not be determined
* @throws ConnectTimeoutException if the socket cannot be connected
* within the time limit defined in the <code>params</code>
+ *
+ * @see HttpInetSocketAddress
*/
Socket connectSocket(
Socket sock,
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeSocketFactoryAdaptor.java b/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeSocketFactoryAdaptor.java
index 077e664..8aeac0f 100644
--- a/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeSocketFactoryAdaptor.java
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/SchemeSocketFactoryAdaptor.java
@@ -36,6 +36,9 @@ import java.net.UnknownHostException;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.params.HttpParams;
+/**
+ * @deprecated (4.1) do not use
+ */
@Deprecated
class SchemeSocketFactoryAdaptor implements SchemeSocketFactory {
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/SocketFactory.java b/httpclient/src/main/java/org/apache/http/conn/scheme/SocketFactory.java
index f086a44..99cbf50 100644
--- a/httpclient/src/main/java/org/apache/http/conn/scheme/SocketFactory.java
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/SocketFactory.java
@@ -41,7 +41,7 @@ import org.apache.http.params.HttpParams;
*
* @since 4.0
*
- * @deprecated use {@link SchemeSocketFactory}
+ * @deprecated (4.1) use {@link SchemeSocketFactory}
*/
@Deprecated
public interface SocketFactory {
diff --git a/httpclient/src/main/java/org/apache/http/conn/scheme/SocketFactoryAdaptor.java b/httpclient/src/main/java/org/apache/http/conn/scheme/SocketFactoryAdaptor.java
index f8f0017..1716189 100644
--- a/httpclient/src/main/java/org/apache/http/conn/scheme/SocketFactoryAdaptor.java
+++ b/httpclient/src/main/java/org/apache/http/conn/scheme/SocketFactoryAdaptor.java
@@ -37,6 +37,9 @@ import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
+/**
+ * @deprecated (4.1) do not use
+ */
@Deprecated
class SocketFactoryAdaptor implements SocketFactory {
diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java b/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java
index 547204a..79a6a6c 100644
--- a/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java
+++ b/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java
@@ -197,13 +197,21 @@ public abstract class AbstractVerifier implements X509HostnameVerifier {
// The CN better have at least two dots if it wants wildcard
// action. It also can't be [*.co.uk] or [*.co.jp] or
// [*.org.uk], etc...
- boolean doWildcard = cn.startsWith("*.") &&
- cn.lastIndexOf('.') >= 0 &&
+ String parts[] = cn.split("\\.");
+ boolean doWildcard = parts.length >= 3 &&
+ parts[0].endsWith("*") &&
acceptableCountryWildcard(cn) &&
!isIPAddress(host);
if(doWildcard) {
- match = hostName.endsWith(cn.substring(1));
+ if (parts[0].length() > 1) { // e.g. server*
+ String prefix = parts[0].substring(0, parts.length-2); // e.g. server
+ String suffix = cn.substring(parts[0].length()); // skip wildcard part from cn
+ String hostSuffix = hostName.substring(prefix.length()); // skip wildcard part from host
+ match = hostName.startsWith(prefix) && hostSuffix.endsWith(suffix);
+ } else {
+ match = hostName.endsWith(cn.substring(1));
+ }
if(match && strictWithSubDomains) {
// If we're in strict mode, then [*.foo.com] is not
// allowed to match [a.b.foo.com]
@@ -222,18 +230,11 @@ public abstract class AbstractVerifier implements X509HostnameVerifier {
}
public static boolean acceptableCountryWildcard(String cn) {
- int cnLen = cn.length();
- if(cnLen >= 7 && cnLen <= 9) {
- // Look for the '.' in the 3rd-last position:
- if(cn.charAt(cnLen - 3) == '.') {
- // Trim off the [*.] and the [.XX].
- String s = cn.substring(2, cnLen - 3);
- // And test against the sorted array of bad 2lds:
- int x = Arrays.binarySearch(BAD_COUNTRY_2LDS, s);
- return x < 0;
- }
+ String parts[] = cn.split("\\.");
+ if (parts.length != 3 || parts[2].length() != 2) {
+ return true; // it's not an attempt to wildcard a 2TLD within a country code
}
- return true;
+ return Arrays.binarySearch(BAD_COUNTRY_2LDS, parts[1]) < 0;
}
public static String[] getCNs(X509Certificate cert) {
diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/SSLInitializationException.java b/httpclient/src/main/java/org/apache/http/conn/ssl/SSLInitializationException.java
new file mode 100644
index 0000000..defd7e1
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/conn/ssl/SSLInitializationException.java
@@ -0,0 +1,37 @@
+/*
+ * ====================================================================
+ * 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.conn.ssl;
+
+public class SSLInitializationException extends IllegalStateException {
+
+ private static final long serialVersionUID = -8243587425648536702L;
+
+ public SSLInitializationException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java b/httpclient/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java
index 38b6141..632a04f 100644
--- a/httpclient/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java
+++ b/httpclient/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java
@@ -27,12 +27,15 @@
package org.apache.http.conn.ssl;
+import org.apache.http.HttpHost;
import org.apache.http.annotation.ThreadSafe;
import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.HttpInetSocketAddress;
import org.apache.http.conn.scheme.HostNameResolver;
import org.apache.http.conn.scheme.LayeredSchemeSocketFactory;
import org.apache.http.conn.scheme.LayeredSocketFactory;
+import org.apache.http.conn.scheme.SchemeLayeredSocketFactory;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
@@ -44,6 +47,8 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -54,8 +59,10 @@ import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
/**
* Layered socket factory for TLS/SSL connections.
@@ -140,7 +147,8 @@ import java.security.UnrecoverableKeyException;
*/
@SuppressWarnings("deprecation")
@ThreadSafe
-public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSocketFactory {
+public class SSLSocketFactory implements SchemeLayeredSocketFactory,
+ LayeredSchemeSocketFactory, LayeredSocketFactory {
public static final String TLS = "TLS";
public static final String SSL = "SSL";
@@ -155,14 +163,45 @@ public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSock
public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER
= new StrictHostnameVerifier();
+ private final static char[] EMPTY_PASSWORD = "".toCharArray();
+
/**
- * Gets the default factory, which uses the default JVM settings for secure
- * connections.
+ * Gets the default factory, which uses the default JSSE settings for initializing
+ * the SSL context.
*
- * @return the default factory
+ * @return the default SSL socket factory
*/
- public static SSLSocketFactory getSocketFactory() {
- return new SSLSocketFactory();
+ public static SSLSocketFactory getSocketFactory() throws SSLInitializationException {
+ return new SSLSocketFactory(createDefaultSSLContext());
+ }
+
+ /**
+ * Gets the default factory, which uses system properties for initializing the SSL context
+ * as described in
+ * <a href="http://download.oracle.com/javase/1,5.0/docs/guide/security/jsse/JSSERefGuide.html">
+ * "JavaTM Secure Socket Extension (JSSE) Reference Guide for the JavaTM 2 Platform
+ * Standard Edition 5</a>
+ * <p>
+ * The following system properties are taken into account by this method:
+ * <ul>
+ * <li>ssl.TrustManagerFactory.algorithm</li>
+ * <li>javax.net.ssl.trustStoreType</li>
+ * <li>javax.net.ssl.trustStore</li>
+ * <li>javax.net.ssl.trustStoreProvider</li>
+ * <li>javax.net.ssl.trustStorePassword</li>
+ * <li>java.home</li>
+ * <li>ssl.KeyManagerFactory.algorithm</li>
+ * <li>javax.net.ssl.keyStoreType</li>
+ * <li>javax.net.ssl.keyStore</li>
+ * <li>javax.net.ssl.keyStoreProvider</li>
+ * <li>javax.net.ssl.keyStorePassword</li>
+ * </ul>
+ * <p>
+ *
+ * @return the system SSL socket factory
+ */
+ public static SSLSocketFactory getSystemSocketFactory() throws SSLInitializationException {
+ return new SSLSocketFactory(createSystemSSLContext());
}
private final javax.net.ssl.SSLSocketFactory socketfactory;
@@ -203,12 +242,130 @@ public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSock
sslcontext.init(keymanagers, trustmanagers, random);
return sslcontext;
}
+
+ private static SSLContext createSystemSSLContext(
+ String algorithm,
+ final SecureRandom random) throws IOException, NoSuchAlgorithmException, NoSuchProviderException,
+ KeyStoreException, CertificateException, UnrecoverableKeyException, KeyManagementException {
+ if (algorithm == null) {
+ algorithm = TLS;
+ }
+ TrustManagerFactory tmfactory = null;
+
+ String trustAlgorithm = System.getProperty("ssl.TrustManagerFactory.algorithm");
+ if (trustAlgorithm == null) {
+ trustAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+ }
+ String trustStoreType = System.getProperty("javax.net.ssl.trustStoreType");
+ if (trustStoreType == null) {
+ trustStoreType = KeyStore.getDefaultType();
+ }
+ if ("none".equalsIgnoreCase(trustStoreType)) {
+ tmfactory = TrustManagerFactory.getInstance(trustAlgorithm);
+ } else {
+ File trustStoreFile = null;
+ String s = System.getProperty("javax.net.ssl.trustStore");
+ if (s != null) {
+ trustStoreFile = new File(s);
+ tmfactory = TrustManagerFactory.getInstance(trustAlgorithm);
+ String trustStoreProvider = System.getProperty("javax.net.ssl.trustStoreProvider");
+ KeyStore trustStore;
+ if (trustStoreProvider != null) {
+ trustStore = KeyStore.getInstance(trustStoreType, trustStoreProvider);
+ } else {
+ trustStore = KeyStore.getInstance(trustStoreType);
+ }
+ String trustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword");
+ FileInputStream instream = new FileInputStream(trustStoreFile);
+ try {
+ trustStore.load(instream, trustStorePassword != null ?
+ trustStorePassword.toCharArray() : EMPTY_PASSWORD);
+ } finally {
+ instream.close();
+ }
+ tmfactory.init(trustStore);
+ } else {
+ File javaHome = new File(System.getProperty("java.home"));
+ File file = new File(javaHome, "lib/security/jssecacerts");
+ if (!file.exists()) {
+ file = new File(javaHome, "lib/security/cacerts");
+ trustStoreFile = file;
+ } else {
+ trustStoreFile = file;
+ }
+ tmfactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ String trustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword");
+ FileInputStream instream = new FileInputStream(trustStoreFile);
+ try {
+ trustStore.load(instream, trustStorePassword != null ? trustStorePassword.toCharArray() : null);
+ } finally {
+ instream.close();
+ }
+ tmfactory.init(trustStore);
+ }
+ }
+
+ KeyManagerFactory kmfactory = null;
+ String keyAlgorithm = System.getProperty("ssl.KeyManagerFactory.algorithm");
+ if (keyAlgorithm == null) {
+ keyAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
+ }
+ String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType");
+ if (keyStoreType == null) {
+ keyStoreType = KeyStore.getDefaultType();
+ }
+ if ("none".equalsIgnoreCase(keyStoreType)) {
+ kmfactory = KeyManagerFactory.getInstance(keyAlgorithm);
+ } else {
+ File keyStoreFile = null;
+ String s = System.getProperty("javax.net.ssl.keyStore");
+ if (s != null) {
+ keyStoreFile = new File(s);
+ }
+ if (keyStoreFile != null) {
+ kmfactory = KeyManagerFactory.getInstance(keyAlgorithm);
+ String keyStoreProvider = System.getProperty("javax.net.ssl.keyStoreProvider");
+ KeyStore keyStore;
+ if (keyStoreProvider != null) {
+ keyStore = KeyStore.getInstance(keyStoreType, keyStoreProvider);
+ } else {
+ keyStore = KeyStore.getInstance(keyStoreType);
+ }
+ String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword");
+ FileInputStream instream = new FileInputStream(keyStoreFile);
+ try {
+ keyStore.load(instream, keyStorePassword != null ?
+ keyStorePassword.toCharArray() : EMPTY_PASSWORD);
+ } finally {
+ instream.close();
+ }
+ kmfactory.init(keyStore, keyStorePassword != null ?
+ keyStorePassword.toCharArray() : EMPTY_PASSWORD);
+ }
+ }
+
+ SSLContext sslcontext = SSLContext.getInstance(algorithm);
+ sslcontext.init(
+ kmfactory != null ? kmfactory.getKeyManagers() : null,
+ tmfactory != null ? tmfactory.getTrustManagers() : null,
+ random);
+ return sslcontext;
+ }
- private static SSLContext createDefaultSSLContext() {
+ private static SSLContext createDefaultSSLContext() throws SSLInitializationException {
try {
return createSSLContext(TLS, null, null, null, null, null);
} catch (Exception ex) {
- throw new IllegalStateException("Failure initializing default SSL context", ex);
+ throw new SSLInitializationException("Failure initializing default SSL context", ex);
+ }
+ }
+
+ private static SSLContext createSystemSSLContext() throws SSLInitializationException {
+ try {
+ return createSystemSSLContext(TLS, null);
+ } catch (Exception ex) {
+ throw new SSLInitializationException("Failure initializing default system SSL context", ex);
}
}
@@ -324,13 +481,26 @@ public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSock
public SSLSocketFactory(
final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) {
super();
+ if (sslContext == null) {
+ throw new IllegalArgumentException("SSL context may not be null");
+ }
this.socketfactory = sslContext.getSocketFactory();
this.hostnameVerifier = hostnameVerifier;
this.nameResolver = null;
}
- private SSLSocketFactory() {
- this(createDefaultSSLContext());
+ /**
+ * @since 4.2
+ */
+ public SSLSocketFactory(
+ final javax.net.ssl.SSLSocketFactory socketfactory,
+ final X509HostnameVerifier hostnameVerifier) {
+ if (socketfactory == null) {
+ throw new IllegalArgumentException("SSL socket factory may not be null");
+ }
+ this.socketfactory = socketfactory;
+ this.hostnameVerifier = hostnameVerifier;
+ this.nameResolver = null;
}
/**
@@ -339,12 +509,16 @@ public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSock
* @since 4.1
*/
public Socket createSocket(final HttpParams params) throws IOException {
- return this.socketfactory.createSocket();
+ SSLSocket sock = (SSLSocket) this.socketfactory.createSocket();
+ prepareSocket(sock);
+ return sock;
}
@Deprecated
public Socket createSocket() throws IOException {
- return this.socketfactory.createSocket();
+ SSLSocket sock = (SSLSocket) this.socketfactory.createSocket();
+ prepareSocket(sock);
+ return sock;
}
/**
@@ -361,7 +535,7 @@ public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSock
if (params == null) {
throw new IllegalArgumentException("HTTP parameters may not be null");
}
- Socket sock = socket != null ? socket : new Socket();
+ Socket sock = socket != null ? socket : this.socketfactory.createSocket();
if (localAddress != null) {
sock.setReuseAddress(HttpConnectionParams.getSoReuseaddr(params));
sock.bind(localAddress);
@@ -374,20 +548,28 @@ public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSock
sock.setSoTimeout(soTimeout);
sock.connect(remoteAddress, connTimeout);
} catch (SocketTimeoutException ex) {
- throw new ConnectTimeoutException("Connect to " + remoteAddress.getHostName() + "/"
- + remoteAddress.getAddress() + " timed out");
+ throw new ConnectTimeoutException("Connect to " + remoteAddress + " timed out");
}
+
+ String hostname;
+ if (remoteAddress instanceof HttpInetSocketAddress) {
+ hostname = ((HttpInetSocketAddress) remoteAddress).getHttpHost().getHostName();
+ } else {
+ hostname = remoteAddress.getHostName();
+ }
+
SSLSocket sslsock;
// Setup SSL layering if necessary
if (sock instanceof SSLSocket) {
sslsock = (SSLSocket) sock;
} else {
- sslsock = (SSLSocket) this.socketfactory.createSocket(
- sock, remoteAddress.getHostName(), remoteAddress.getPort(), true);
+ int port = remoteAddress.getPort();
+ sslsock = (SSLSocket) this.socketfactory.createSocket(sock, hostname, port, true);
+ prepareSocket(sslsock);
}
if (this.hostnameVerifier != null) {
try {
- this.hostnameVerifier.verify(remoteAddress.getHostName(), sslsock);
+ this.hostnameVerifier.verify(hostname, sslsock);
// verifyHostName() didn't blowup - good!
} catch (IOException iox) {
// close the socket before re-throwing the exception
@@ -429,7 +611,28 @@ public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSock
}
/**
- * @since 4.1
+ * @since 4.2
+ */
+ public Socket createLayeredSocket(
+ final Socket socket,
+ final String host,
+ final int port,
+ final HttpParams params) throws IOException, UnknownHostException {
+ SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(
+ socket,
+ host,
+ port,
+ true);
+ prepareSocket(sslSocket);
+ if (this.hostnameVerifier != null) {
+ this.hostnameVerifier.verify(host, sslSocket);
+ }
+ // verifyHostName() didn't blowup - good!
+ return sslSocket;
+ }
+
+ /**
+ * @deprecated use {@link #createLayeredSocket(Socket, String, int, HttpParams)}
*/
public Socket createLayeredSocket(
final Socket socket,
@@ -442,6 +645,7 @@ public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSock
port,
autoClose
);
+ prepareSocket(sslSocket);
if (this.hostnameVerifier != null) {
this.hostnameVerifier.verify(host, sslSocket);
}
@@ -484,7 +688,7 @@ public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSock
} else {
remoteAddress = InetAddress.getByName(host);
}
- InetSocketAddress remote = new InetSocketAddress(remoteAddress, port);
+ InetSocketAddress remote = new HttpInetSocketAddress(new HttpHost(host, port), remoteAddress, port);
return connectSocket(socket, remote, local, params);
}
@@ -499,4 +703,15 @@ public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSock
return createLayeredSocket(socket, host, port, autoClose);
}
+ /**
+ * Performs any custom initialization for a newly created SSLSocket
+ * (before the SSL handshake happens).
+ *
+ * The default implementation is a no-op, but could be overriden to, e.g.,
+ * call {@link SSLSocket#setEnabledCipherSuites(java.lang.String[])}.
+ *
+ * @since 4.2
+ */
+ protected void prepareSocket(final SSLSocket socket) throws IOException {
+ }
}
diff --git a/httpclient/src/main/java/org/apache/http/cookie/ClientCookie.java b/httpclient/src/main/java/org/apache/http/cookie/ClientCookie.java
index a2db6f8..164d302 100644
--- a/httpclient/src/main/java/org/apache/http/cookie/ClientCookie.java
+++ b/httpclient/src/main/java/org/apache/http/cookie/ClientCookie.java
@@ -59,4 +59,4 @@ public interface ClientCookie extends Cookie {
boolean containsAttribute(String name);
-}
\ No newline at end of file
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/AuthSchemeBase.java b/httpclient/src/main/java/org/apache/http/impl/auth/AuthSchemeBase.java
index c790f3e..bc72333 100644
--- a/httpclient/src/main/java/org/apache/http/impl/auth/AuthSchemeBase.java
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/AuthSchemeBase.java
@@ -26,6 +26,8 @@
package org.apache.http.impl.auth;
+import java.util.Locale;
+
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.FormattedHeader;
@@ -33,6 +35,7 @@ import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AUTH;
import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.ChallengeState;
import org.apache.http.auth.ContextAwareAuthScheme;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.MalformedChallengeException;
@@ -50,16 +53,24 @@ import org.apache.http.util.CharArrayBuffer;
*
* @since 4.0
*/
- at NotThreadSafe // proxy
+ at NotThreadSafe
public abstract class AuthSchemeBase implements ContextAwareAuthScheme {
+ private ChallengeState challengeState;
+
/**
- * Flag whether authenticating against a proxy.
+ * Creates an instance of <tt>AuthSchemeBase</tt> with the given challenge
+ * state.
+ *
+ * @since 4.2
*/
- private boolean proxy;
+ public AuthSchemeBase(final ChallengeState challengeState) {
+ super();
+ this.challengeState = challengeState;
+ }
public AuthSchemeBase() {
- super();
+ this(null);
}
/**
@@ -78,9 +89,9 @@ public abstract class AuthSchemeBase implements ContextAwareAuthScheme {
}
String authheader = header.getName();
if (authheader.equalsIgnoreCase(AUTH.WWW_AUTH)) {
- this.proxy = false;
+ this.challengeState = ChallengeState.TARGET;
} else if (authheader.equalsIgnoreCase(AUTH.PROXY_AUTH)) {
- this.proxy = true;
+ this.challengeState = ChallengeState.PROXY;
} else {
throw new MalformedChallengeException("Unexpected header name: " + authheader);
}
@@ -130,17 +141,28 @@ public abstract class AuthSchemeBase implements ContextAwareAuthScheme {
/**
* Returns <code>true</code> if authenticating against a proxy, <code>false</code>
* otherwise.
- *
- * @return <code>true</code> if authenticating against a proxy, <code>false</code>
- * otherwise
*/
public boolean isProxy() {
- return this.proxy;
+ return this.challengeState != null && this.challengeState == ChallengeState.PROXY;
+ }
+
+ /**
+ * Returns {@link ChallengeState} value or <code>null</code> if unchallenged.
+ *
+ * @since 4.2
+ */
+ public ChallengeState getChallengeState() {
+ return this.challengeState;
}
@Override
public String toString() {
- return getSchemeName();
+ String name = getSchemeName();
+ if (name != null) {
+ return name.toUpperCase(Locale.US);
+ } else {
+ return super.toString();
+ }
}
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java b/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java
index 91e02cf..0534903 100644
--- a/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java
@@ -32,12 +32,16 @@ import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.ChallengeState;
+import org.apache.http.auth.ContextAwareAuthScheme;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.AUTH;
import org.apache.http.auth.InvalidCredentialsException;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.auth.params.AuthParams;
import org.apache.http.message.BufferedHeader;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
import org.apache.http.util.CharArrayBuffer;
import org.apache.http.util.EncodingUtils;
@@ -59,13 +63,20 @@ public class BasicScheme extends RFC2617Scheme {
private boolean complete;
/**
- * Default constructor for the basic authentication scheme.
+ * Creates an instance of <tt>BasicScheme</tt> with the given challenge
+ * state.
+ *
+ * @since 4.2
*/
- public BasicScheme() {
- super();
+ public BasicScheme(final ChallengeState challengeState) {
+ super(challengeState);
this.complete = false;
}
+ public BasicScheme() {
+ this(null);
+ }
+
/**
* Returns textual designation of the basic authentication scheme.
*
@@ -110,6 +121,15 @@ public class BasicScheme extends RFC2617Scheme {
}
/**
+ * @deprecated (4.2) Use {@link ContextAwareAuthScheme#authenticate(Credentials, HttpRequest, org.apache.http.protocol.HttpContext)}
+ */
+ @Deprecated
+ public Header authenticate(
+ final Credentials credentials, final HttpRequest request) throws AuthenticationException {
+ return authenticate(credentials, request, new BasicHttpContext());
+ }
+
+ /**
* Produces basic authorization header for the given set of {@link Credentials}.
*
* @param credentials The set of credentials to be used for authentication
@@ -121,9 +141,11 @@ public class BasicScheme extends RFC2617Scheme {
*
* @return a basic authorization string
*/
+ @Override
public Header authenticate(
final Credentials credentials,
- final HttpRequest request) throws AuthenticationException {
+ final HttpRequest request,
+ final HttpContext context) throws AuthenticationException {
if (credentials == null) {
throw new IllegalArgumentException("Credentials may not be null");
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java b/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java
index 6b47350..f183cb5 100644
--- a/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java
@@ -26,18 +26,26 @@
package org.apache.http.impl.auth;
+import java.io.IOException;
import java.security.MessageDigest;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Formatter;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.StringTokenizer;
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.ChallengeState;
+import org.apache.http.auth.ContextAwareAuthScheme;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.AUTH;
import org.apache.http.auth.MalformedChallengeException;
@@ -45,6 +53,8 @@ import org.apache.http.auth.params.AuthParams;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.message.BasicHeaderValueFormatter;
import org.apache.http.message.BufferedHeader;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
import org.apache.http.util.CharArrayBuffer;
import org.apache.http.util.EncodingUtils;
@@ -90,24 +100,32 @@ public class DigestScheme extends RFC2617Scheme {
/** Whether the digest authentication process is complete */
private boolean complete;
+ private static final int QOP_UNKNOWN = -1;
private static final int QOP_MISSING = 0;
private static final int QOP_AUTH_INT = 1;
private static final int QOP_AUTH = 2;
- private int qopVariant = QOP_MISSING;
private String lastNonce;
private long nounceCount;
private String cnonce;
- private String nc;
+ private String a1;
+ private String a2;
/**
- * Default constructor for the digest authetication scheme.
+ * Creates an instance of <tt>DigestScheme</tt> with the given challenge
+ * state.
+ *
+ * @since 4.2
*/
- public DigestScheme() {
- super();
+ public DigestScheme(final ChallengeState challengeState) {
+ super(challengeState);
this.complete = false;
}
+ public DigestScheme() {
+ this(null);
+ }
+
/**
* Processes the Digest challenge.
*
@@ -120,35 +138,6 @@ public class DigestScheme extends RFC2617Scheme {
public void processChallenge(
final Header header) throws MalformedChallengeException {
super.processChallenge(header);
-
- if (getParameter("realm") == null) {
- throw new MalformedChallengeException("missing realm in challange");
- }
- if (getParameter("nonce") == null) {
- throw new MalformedChallengeException("missing nonce in challange");
- }
-
- boolean unsupportedQop = false;
- // qop parsing
- String qop = getParameter("qop");
- if (qop != null) {
- StringTokenizer tok = new StringTokenizer(qop,",");
- while (tok.hasMoreTokens()) {
- String variant = tok.nextToken().trim();
- if (variant.equals("auth")) {
- qopVariant = QOP_AUTH;
- break; //that's our favourite, because auth-int is unsupported
- } else if (variant.equals("auth-int")) {
- qopVariant = QOP_AUTH_INT;
- } else {
- unsupportedQop = true;
- }
- }
- }
-
- if (unsupportedQop && (qopVariant == QOP_MISSING)) {
- throw new MalformedChallengeException("None of the qop methods is supported");
- }
this.complete = true;
}
@@ -189,21 +178,13 @@ public class DigestScheme extends RFC2617Scheme {
getParameters().put(name, value);
}
- private String getCnonce() {
- if (this.cnonce == null) {
- this.cnonce = createCnonce();
- }
- return this.cnonce;
- }
-
- private String getNc() {
- if (this.nc == null) {
- StringBuilder sb = new StringBuilder();
- Formatter formatter = new Formatter(sb, Locale.US);
- formatter.format("%08x", this.nounceCount);
- this.nc = sb.toString();
- }
- return this.nc;
+ /**
+ * @deprecated (4.2) Use {@link ContextAwareAuthScheme#authenticate(Credentials, HttpRequest, org.apache.http.protocol.HttpContext)}
+ */
+ @Deprecated
+ public Header authenticate(
+ final Credentials credentials, final HttpRequest request) throws AuthenticationException {
+ return authenticate(credentials, request, new BasicHttpContext());
}
/**
@@ -220,9 +201,11 @@ public class DigestScheme extends RFC2617Scheme {
*
* @return a digest authorization string
*/
+ @Override
public Header authenticate(
final Credentials credentials,
- final HttpRequest request) throws AuthenticationException {
+ final HttpRequest request,
+ final HttpContext context) throws AuthenticationException {
if (credentials == null) {
throw new IllegalArgumentException("Credentials may not be null");
@@ -230,7 +213,12 @@ public class DigestScheme extends RFC2617Scheme {
if (request == null) {
throw new IllegalArgumentException("HTTP request may not be null");
}
-
+ if (getParameter("realm") == null) {
+ throw new AuthenticationException("missing realm in challenge");
+ }
+ if (getParameter("nonce") == null) {
+ throw new AuthenticationException("missing nonce in challenge");
+ }
// Add method name and request-URI to the parameter map
getParameters().put("methodname", request.getRequestLine().getMethod());
getParameters().put("uri", request.getRequestLine().getUri());
@@ -239,8 +227,7 @@ public class DigestScheme extends RFC2617Scheme {
charset = AuthParams.getCredentialCharset(request.getParams());
getParameters().put("charset", charset);
}
- String digest = createDigest(credentials);
- return createDigestHeader(credentials, digest);
+ return createDigestHeader(credentials, request);
}
private static MessageDigest createMessageDigest(
@@ -255,154 +242,160 @@ public class DigestScheme extends RFC2617Scheme {
}
/**
- * Creates an MD5 response digest.
+ * Creates digest-response header as defined in RFC2617.
*
- * @return The created digest as string. This will be the response tag's
- * value in the Authentication HTTP header.
- * @throws AuthenticationException when MD5 is an unsupported algorithm
+ * @param credentials User credentials
+ *
+ * @return The digest-response as String.
*/
- private String createDigest(final Credentials credentials) throws AuthenticationException {
- // Collecting required tokens
+ private Header createDigestHeader(
+ final Credentials credentials,
+ final HttpRequest request) throws AuthenticationException {
String uri = getParameter("uri");
String realm = getParameter("realm");
String nonce = getParameter("nonce");
+ String opaque = getParameter("opaque");
String method = getParameter("methodname");
String algorithm = getParameter("algorithm");
- if (uri == null) {
- throw new IllegalStateException("URI may not be null");
- }
- if (realm == null) {
- throw new IllegalStateException("Realm may not be null");
- }
- if (nonce == null) {
- throw new IllegalStateException("Nonce may not be null");
+
+ Set<String> qopset = new HashSet<String>(8);
+ int qop = QOP_UNKNOWN;
+ String qoplist = getParameter("qop");
+ if (qoplist != null) {
+ StringTokenizer tok = new StringTokenizer(qoplist, ",");
+ while (tok.hasMoreTokens()) {
+ String variant = tok.nextToken().trim();
+ qopset.add(variant.toLowerCase(Locale.US));
+ }
+ if (request instanceof HttpEntityEnclosingRequest && qopset.contains("auth-int")) {
+ qop = QOP_AUTH_INT;
+ } else if (qopset.contains("auth")) {
+ qop = QOP_AUTH;
+ }
+ } else {
+ qop = QOP_MISSING;
}
- // Reset
- this.cnonce = null;
- this.nc = null;
+ if (qop == QOP_UNKNOWN) {
+ throw new AuthenticationException("None of the qop methods is supported: " + qoplist);
+ }
// If an algorithm is not specified, default to MD5.
if (algorithm == null) {
algorithm = "MD5";
}
- // If an charset is not specified, default to ISO-8859-1.
String charset = getParameter("charset");
if (charset == null) {
charset = "ISO-8859-1";
}
- if (qopVariant == QOP_AUTH_INT) {
- throw new AuthenticationException(
- "Unsupported qop in HTTP Digest authentication");
- }
-
String digAlg = algorithm;
if (digAlg.equalsIgnoreCase("MD5-sess")) {
digAlg = "MD5";
}
- if (nonce.equals(this.lastNonce)) {
- this.nounceCount++;
- } else {
- this.nounceCount = 1;
- this.lastNonce = nonce;
+ MessageDigest digester;
+ try {
+ digester = createMessageDigest(digAlg);
+ } catch (UnsupportedDigestAlgorithmException ex) {
+ throw new AuthenticationException("Unsuppported digest algorithm: " + digAlg);
}
- MessageDigest digester = createMessageDigest(digAlg);
-
String uname = credentials.getUserPrincipal().getName();
String pwd = credentials.getPassword();
+ if (nonce.equals(this.lastNonce)) {
+ nounceCount++;
+ } else {
+ nounceCount = 1;
+ cnonce = null;
+ lastNonce = nonce;
+ }
+ StringBuilder sb = new StringBuilder(256);
+ Formatter formatter = new Formatter(sb, Locale.US);
+ formatter.format("%08x", nounceCount);
+ String nc = sb.toString();
+
+ if (cnonce == null) {
+ cnonce = createCnonce();
+ }
+
+ a1 = null;
+ a2 = null;
// 3.2.2.2: Calculating digest
- StringBuilder tmp = new StringBuilder(uname.length() + realm.length() + pwd.length() + 2);
- tmp.append(uname);
- tmp.append(':');
- tmp.append(realm);
- tmp.append(':');
- tmp.append(pwd);
- // unq(username-value) ":" unq(realm-value) ":" passwd
- String a1 = tmp.toString();
-
- //a1 is suitable for MD5 algorithm
if (algorithm.equalsIgnoreCase("MD5-sess")) {
// H( unq(username-value) ":" unq(realm-value) ":" passwd )
// ":" unq(nonce-value)
// ":" unq(cnonce-value)
- algorithm = "MD5";
- String cnonce = getCnonce();
-
- String tmp2 = encode(digester.digest(EncodingUtils.getBytes(a1, charset)));
- StringBuilder tmp3 = new StringBuilder(
- tmp2.length() + nonce.length() + cnonce.length() + 2);
- tmp3.append(tmp2);
- tmp3.append(':');
- tmp3.append(nonce);
- tmp3.append(':');
- tmp3.append(cnonce);
- a1 = tmp3.toString();
+ // calculated one per session
+ sb.setLength(0);
+ sb.append(uname).append(':').append(realm).append(':').append(pwd);
+ String checksum = encode(digester.digest(EncodingUtils.getBytes(sb.toString(), charset)));
+ sb.setLength(0);
+ sb.append(checksum).append(':').append(nonce).append(':').append(cnonce);
+ a1 = sb.toString();
+ } else {
+ // unq(username-value) ":" unq(realm-value) ":" passwd
+ sb.setLength(0);
+ sb.append(uname).append(':').append(realm).append(':').append(pwd);
+ a1 = sb.toString();
}
+
String hasha1 = encode(digester.digest(EncodingUtils.getBytes(a1, charset)));
- String a2 = null;
- if (qopVariant == QOP_AUTH_INT) {
- // Unhandled qop auth-int
- //we do not have access to the entity-body or its hash
- //TODO: add Method ":" digest-uri-value ":" H(entity-body)
+ if (qop == QOP_AUTH) {
+ // Method ":" digest-uri-value
+ a2 = method + ':' + uri;
+ } else if (qop == QOP_AUTH_INT) {
+ // Method ":" digest-uri-value ":" H(entity-body)
+ HttpEntity entity = null;
+ if (request instanceof HttpEntityEnclosingRequest) {
+ entity = ((HttpEntityEnclosingRequest) request).getEntity();
+ }
+ if (entity != null && !entity.isRepeatable()) {
+ // If the entity is not repeatable, try falling back onto QOP_AUTH
+ if (qopset.contains("auth")) {
+ qop = QOP_AUTH;
+ a2 = method + ':' + uri;
+ } else {
+ throw new AuthenticationException("Qop auth-int cannot be used with " +
+ "a non-repeatable entity");
+ }
+ } else {
+ HttpEntityDigester entityDigester = new HttpEntityDigester(digester);
+ try {
+ if (entity != null) {
+ entity.writeTo(entityDigester);
+ }
+ entityDigester.close();
+ } catch (IOException ex) {
+ throw new AuthenticationException("I/O error reading entity content", ex);
+ }
+ a2 = method + ':' + uri + ':' + encode(entityDigester.getDigest());
+ }
} else {
a2 = method + ':' + uri;
}
- String hasha2 = encode(digester.digest(EncodingUtils.getAsciiBytes(a2)));
+
+ String hasha2 = encode(digester.digest(EncodingUtils.getBytes(a2, charset)));
// 3.2.2.1
- String serverDigestValue;
- if (qopVariant == QOP_MISSING) {
- StringBuilder tmp2 = new StringBuilder(
- hasha1.length() + nonce.length() + hasha1.length());
- tmp2.append(hasha1);
- tmp2.append(':');
- tmp2.append(nonce);
- tmp2.append(':');
- tmp2.append(hasha2);
- serverDigestValue = tmp2.toString();
+
+ String digestValue;
+ if (qop == QOP_MISSING) {
+ sb.setLength(0);
+ sb.append(hasha1).append(':').append(nonce).append(':').append(hasha2);
+ digestValue = sb.toString();
} else {
- String qopOption = getQopVariantString();
- String cnonce = getCnonce();
- String nc = getNc();
- StringBuilder tmp2 = new StringBuilder(hasha1.length() + nonce.length()
- + nc.length() + cnonce.length() + qopOption.length() + hasha2.length() + 5);
- tmp2.append(hasha1);
- tmp2.append(':');
- tmp2.append(nonce);
- tmp2.append(':');
- tmp2.append(nc);
- tmp2.append(':');
- tmp2.append(cnonce);
- tmp2.append(':');
- tmp2.append(qopOption);
- tmp2.append(':');
- tmp2.append(hasha2);
- serverDigestValue = tmp2.toString();
+ sb.setLength(0);
+ sb.append(hasha1).append(':').append(nonce).append(':').append(nc).append(':')
+ .append(cnonce).append(':').append(qop == QOP_AUTH_INT ? "auth-int" : "auth")
+ .append(':').append(hasha2);
+ digestValue = sb.toString();
}
- String serverDigest =
- encode(digester.digest(EncodingUtils.getAsciiBytes(serverDigestValue)));
-
- return serverDigest;
- }
-
- /**
- * Creates digest-response header as defined in RFC2617.
- *
- * @param credentials User credentials
- * @param digest The response tag's value as String.
- *
- * @return The digest-response as String.
- */
- private Header createDigestHeader(
- final Credentials credentials,
- final String digest) {
+ String digest = encode(digester.digest(EncodingUtils.getAsciiBytes(digestValue)));
CharArrayBuffer buffer = new CharArrayBuffer(128);
if (isProxy()) {
@@ -412,26 +405,17 @@ public class DigestScheme extends RFC2617Scheme {
}
buffer.append(": Digest ");
- String uri = getParameter("uri");
- String realm = getParameter("realm");
- String nonce = getParameter("nonce");
- String opaque = getParameter("opaque");
- String response = digest;
- String algorithm = getParameter("algorithm");
-
- String uname = credentials.getUserPrincipal().getName();
-
List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>(20);
params.add(new BasicNameValuePair("username", uname));
params.add(new BasicNameValuePair("realm", realm));
params.add(new BasicNameValuePair("nonce", nonce));
params.add(new BasicNameValuePair("uri", uri));
- params.add(new BasicNameValuePair("response", response));
+ params.add(new BasicNameValuePair("response", digest));
- if (qopVariant != QOP_MISSING) {
- params.add(new BasicNameValuePair("qop", getQopVariantString()));
- params.add(new BasicNameValuePair("nc", getNc()));
- params.add(new BasicNameValuePair("cnonce", getCnonce()));
+ if (qop != QOP_MISSING) {
+ params.add(new BasicNameValuePair("qop", qop == QOP_AUTH_INT ? "auth-int" : "auth"));
+ params.add(new BasicNameValuePair("nc", nc));
+ params.add(new BasicNameValuePair("cnonce", cnonce));
}
if (algorithm != null) {
params.add(new BasicNameValuePair("algorithm", algorithm));
@@ -445,22 +429,22 @@ public class DigestScheme extends RFC2617Scheme {
if (i > 0) {
buffer.append(", ");
}
- boolean noQuotes = "nc".equals(param.getName()) ||
- "qop".equals(param.getName());
- BasicHeaderValueFormatter.DEFAULT
- .formatNameValuePair(buffer, param, !noQuotes);
+ boolean noQuotes = "nc".equals(param.getName()) || "qop".equals(param.getName());
+ BasicHeaderValueFormatter.DEFAULT.formatNameValuePair(buffer, param, !noQuotes);
}
return new BufferedHeader(buffer);
}
- private String getQopVariantString() {
- String qopOption;
- if (qopVariant == QOP_AUTH_INT) {
- qopOption = "auth-int";
- } else {
- qopOption = "auth";
- }
- return qopOption;
+ String getCnonce() {
+ return cnonce;
+ }
+
+ String getA1() {
+ return a1;
+ }
+
+ String getA2() {
+ return a2;
}
/**
@@ -470,7 +454,7 @@ public class DigestScheme extends RFC2617Scheme {
* @param binaryData array containing the digest
* @return encoded MD5, or <CODE>null</CODE> if encoding failed
*/
- private static String encode(byte[] binaryData) {
+ static String encode(byte[] binaryData) {
int n = binaryData.length;
char[] buffer = new char[n * 2];
for (int i = 0; i < n; i++) {
@@ -488,16 +472,12 @@ public class DigestScheme extends RFC2617Scheme {
* Creates a random cnonce value based on the current time.
*
* @return The cnonce value as String.
- * @throws UnsupportedDigestAlgorithmException if MD5 algorithm is not supported.
*/
public static String createCnonce() {
- String cnonce;
-
- MessageDigest md5Helper = createMessageDigest("MD5");
-
- cnonce = Long.toString(System.currentTimeMillis());
- cnonce = encode(md5Helper.digest(EncodingUtils.getAsciiBytes(cnonce)));
-
- return cnonce;
+ SecureRandom rnd = new SecureRandom();
+ byte[] tmp = new byte[8];
+ rnd.nextBytes(tmp);
+ return encode(tmp);
}
+
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/GGSSchemeBase.java b/httpclient/src/main/java/org/apache/http/impl/auth/GGSSchemeBase.java
new file mode 100644
index 0000000..075c0a1
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/GGSSchemeBase.java
@@ -0,0 +1,199 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.http.impl.auth;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.ContextAwareAuthScheme;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.InvalidCredentialsException;
+import org.apache.http.auth.MalformedChallengeException;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.CharArrayBuffer;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+/**
+ * @since 4.2
+ */
+public abstract class GGSSchemeBase extends AuthSchemeBase {
+
+ enum State {
+ UNINITIATED,
+ CHALLENGE_RECEIVED,
+ TOKEN_GENERATED,
+ FAILED,
+ }
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ private final boolean stripPort;
+ private final Base64 base64codec;
+
+ /** Authentication process state */
+ private State state;
+
+ /** base64 decoded challenge **/
+ private byte[] token;
+
+ GGSSchemeBase(boolean stripPort) {
+ super();
+ this.base64codec = new Base64();
+ this.state = State.UNINITIATED;
+ this.stripPort = stripPort;
+ }
+
+ GGSSchemeBase() {
+ this(false);
+ }
+
+ protected GSSManager getManager() {
+ return GSSManager.getInstance();
+ }
+
+ protected byte[] generateGSSToken(
+ final byte[] input, final Oid oid, final String authServer) throws GSSException {
+ byte[] token = input;
+ if (token == null) {
+ token = new byte[0];
+ }
+ GSSManager manager = getManager();
+ GSSName serverName = manager.createName("HTTP@" + authServer, GSSName.NT_HOSTBASED_SERVICE);
+ GSSContext gssContext = manager.createContext(
+ serverName.canonicalize(oid), oid, null, GSSContext.DEFAULT_LIFETIME);
+ gssContext.requestMutualAuth(true);
+ gssContext.requestCredDeleg(true);
+ return gssContext.initSecContext(token, 0, token.length);
+ }
+
+ protected abstract byte[] generateToken(
+ byte[] input, final String authServer) throws GSSException;
+
+ public boolean isComplete() {
+ return this.state == State.TOKEN_GENERATED || this.state == State.FAILED;
+ }
+
+ /**
+ * @deprecated (4.2) Use {@link ContextAwareAuthScheme#authenticate(Credentials, HttpRequest, org.apache.http.protocol.HttpContext)}
+ */
+ @Deprecated
+ public Header authenticate(
+ final Credentials credentials,
+ final HttpRequest request) throws AuthenticationException {
+ return authenticate(credentials, request, null);
+ }
+
+ @Override
+ public Header authenticate(
+ final Credentials credentials,
+ final HttpRequest request,
+ final HttpContext context) throws AuthenticationException {
+ if (request == null) {
+ throw new IllegalArgumentException("HTTP request may not be null");
+ }
+ switch (state) {
+ case UNINITIATED:
+ throw new AuthenticationException(getSchemeName() + " authentication has not been initiated");
+ case FAILED:
+ throw new AuthenticationException(getSchemeName() + " authentication has failed");
+ case CHALLENGE_RECEIVED:
+ try {
+ String key = null;
+ if (isProxy()) {
+ key = ExecutionContext.HTTP_PROXY_HOST;
+ } else {
+ key = ExecutionContext.HTTP_TARGET_HOST;
+ }
+ HttpHost host = (HttpHost) context.getAttribute(key);
+ if (host == null) {
+ throw new AuthenticationException("Authentication host is not set " +
+ "in the execution context");
+ }
+ String authServer;
+ if (!this.stripPort && host.getPort() > 0) {
+ authServer = host.toHostString();
+ } else {
+ authServer = host.getHostName();
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("init " + authServer);
+ }
+ token = generateToken(token, authServer);
+ state = State.TOKEN_GENERATED;
+ } catch (GSSException gsse) {
+ state = State.FAILED;
+ if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL
+ || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED)
+ throw new InvalidCredentialsException(gsse.getMessage(), gsse);
+ if (gsse.getMajor() == GSSException.NO_CRED )
+ throw new InvalidCredentialsException(gsse.getMessage(), gsse);
+ if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN
+ || gsse.getMajor() == GSSException.DUPLICATE_TOKEN
+ || gsse.getMajor() == GSSException.OLD_TOKEN)
+ throw new AuthenticationException(gsse.getMessage(), gsse);
+ // other error
+ throw new AuthenticationException(gsse.getMessage());
+ }
+ case TOKEN_GENERATED:
+ String tokenstr = new String(base64codec.encode(token));
+ if (log.isDebugEnabled()) {
+ log.debug("Sending response '" + tokenstr + "' back to the auth server");
+ }
+ return new BasicHeader("Authorization", "Negotiate " + tokenstr);
+ default:
+ throw new IllegalStateException("Illegal state: " + state);
+ }
+ }
+
+ @Override
+ protected void parseChallenge(
+ final CharArrayBuffer buffer,
+ int beginIndex, int endIndex) throws MalformedChallengeException {
+ String challenge = buffer.substringTrimmed(beginIndex, endIndex);
+ if (log.isDebugEnabled()) {
+ log.debug("Received challenge '" + challenge + "' from the auth server");
+ }
+ if (state == State.UNINITIATED) {
+ token = base64codec.decode(challenge.getBytes());
+ state = State.CHALLENGE_RECEIVED;
+ } else {
+ log.debug("Authentication already attempted");
+ state = State.FAILED;
+ }
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java b/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java
new file mode 100644
index 0000000..75fe561
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java
@@ -0,0 +1,75 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.auth;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+
+class HttpEntityDigester extends OutputStream {
+
+ private final MessageDigest digester;
+ private boolean closed;
+ private byte[] digest;
+
+ HttpEntityDigester(final MessageDigest digester) {
+ super();
+ this.digester = digester;
+ this.digester.reset();
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ if (this.closed) {
+ throw new IOException("Stream has been already closed");
+ }
+ this.digester.update((byte) b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (this.closed) {
+ throw new IOException("Stream has been already closed");
+ }
+ this.digester.update(b, off, len);
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (this.closed) {
+ return;
+ }
+ this.closed = true;
+ this.digest = this.digester.digest();
+ super.close();
+ }
+
+ public byte[] getDigest() {
+ return this.digest;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/KerberosScheme.java b/httpclient/src/main/java/org/apache/http/impl/auth/KerberosScheme.java
new file mode 100644
index 0000000..d9971c1
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/KerberosScheme.java
@@ -0,0 +1,114 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.http.impl.auth;
+
+import org.apache.http.Header;
+import org.apache.http.HttpRequest;
+import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.Credentials;
+import org.apache.http.protocol.HttpContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.Oid;
+
+/**
+ * KERBEROS authentication scheme.
+ *
+ * @since 4.2
+ */
+public class KerberosScheme extends GGSSchemeBase {
+
+ private static final String KERBEROS_OID = "1.2.840.113554.1.2.2";
+
+ public KerberosScheme(boolean stripPort) {
+ super(stripPort);
+ }
+
+ public KerberosScheme() {
+ super(false);
+ }
+
+ public String getSchemeName() {
+ return "Kerberos";
+ }
+
+ /**
+ * Produces KERBEROS authorization Header based on token created by
+ * processChallenge.
+ *
+ * @param credentials not used by the KERBEROS scheme.
+ * @param request The request being authenticated
+ *
+ * @throws AuthenticationException if authentication string cannot
+ * be generated due to an authentication failure
+ *
+ * @return KERBEROS authentication Header
+ */
+ @Override
+ public Header authenticate(
+ final Credentials credentials,
+ final HttpRequest request,
+ final HttpContext context) throws AuthenticationException {
+ return super.authenticate(credentials, request, context);
+ }
+
+ @Override
+ protected byte[] generateToken(final byte[] input, final String authServer) throws GSSException {
+ return generateGSSToken(input, new Oid(KERBEROS_OID), authServer);
+ }
+
+ /**
+ * There are no valid parameters for KERBEROS authentication so this
+ * method always returns <code>null</code>.
+ *
+ * @return <code>null</code>
+ */
+ public String getParameter(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("Parameter name may not be null");
+ }
+ return null;
+ }
+
+ /**
+ * The concept of an authentication realm is not supported by the Negotiate
+ * authentication scheme. Always returns <code>null</code>.
+ *
+ * @return <code>null</code>
+ */
+ public String getRealm() {
+ return null;
+ }
+
+ /**
+ * Returns <tt>true</tt>. KERBEROS authentication scheme is connection based.
+ *
+ * @return <tt>true</tt>.
+ */
+ public boolean isConnectionBased() {
+ return true;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/KerberosSchemeFactory.java b/httpclient/src/main/java/org/apache/http/impl/auth/KerberosSchemeFactory.java
new file mode 100644
index 0000000..6bb8a64
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/KerberosSchemeFactory.java
@@ -0,0 +1,61 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.auth;
+
+import org.apache.http.annotation.Immutable;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthSchemeFactory;
+import org.apache.http.params.HttpParams;
+
+/**
+ * Kerberos authentication scheme factory.
+ *
+ * @since 4.2
+ */
+ at Immutable
+public class KerberosSchemeFactory implements AuthSchemeFactory {
+
+ private final boolean stripPort;
+
+ public KerberosSchemeFactory(boolean stripPort) {
+ super();
+ this.stripPort = stripPort;
+ }
+
+ public KerberosSchemeFactory() {
+ this(false);
+ }
+
+ public AuthScheme newInstance(final HttpParams params) {
+ return new KerberosScheme(this.stripPort);
+ }
+
+ public boolean isStripPort() {
+ return stripPort;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java b/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java
index 3d38527..463e961 100644
--- a/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateScheme.java
@@ -27,24 +27,14 @@ package org.apache.http.impl.auth;
import java.io.IOException;
-import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
-import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.Credentials;
-import org.apache.http.auth.InvalidCredentialsException;
-import org.apache.http.auth.MalformedChallengeException;
-import org.apache.http.message.BasicHeader;
-import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
-import org.apache.http.util.CharArrayBuffer;
-import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSException;
-import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
/**
@@ -52,44 +42,26 @@ import org.ietf.jgss.Oid;
* scheme.
*
* @since 4.1
+ *
+ * @deprecated (4.2) use {@link SPNegoScheme} or {@link KerberosScheme}.
*/
-public class NegotiateScheme extends AuthSchemeBase {
+ at Deprecated
+public class NegotiateScheme extends GGSSchemeBase {
- enum State {
- UNINITIATED,
- CHALLENGE_RECEIVED,
- TOKEN_GENERATED,
- FAILED,
- }
+ private final Log log = LogFactory.getLog(getClass());
private static final String SPNEGO_OID = "1.3.6.1.5.5.2";
private static final String KERBEROS_OID = "1.2.840.113554.1.2.2";
- private final Log log = LogFactory.getLog(getClass());
-
private final SpnegoTokenGenerator spengoGenerator;
- private final boolean stripPort;
-
- private GSSContext gssContext = null;
-
- /** Authentication process state */
- private State state;
-
- /** base64 decoded challenge **/
- private byte[] token;
-
- private Oid negotiationOid = null;
-
/**
* Default constructor for the Negotiate authentication scheme.
*
*/
public NegotiateScheme(final SpnegoTokenGenerator spengoGenerator, boolean stripPort) {
- super();
- this.state = State.UNINITIATED;
+ super(stripPort);
this.spengoGenerator = spengoGenerator;
- this.stripPort = stripPort;
}
public NegotiateScheme(final SpnegoTokenGenerator spengoGenerator) {
@@ -101,17 +73,6 @@ public class NegotiateScheme extends AuthSchemeBase {
}
/**
- * Tests if the Negotiate authentication process has been completed.
- *
- * @return <tt>true</tt> if authorization has been processed,
- * <tt>false</tt> otherwise.
- *
- */
- public boolean isComplete() {
- return this.state == State.TOKEN_GENERATED || this.state == State.FAILED;
- }
-
- /**
* Returns textual designation of the Negotiate authentication scheme.
*
* @return <code>Negotiate</code>
@@ -120,17 +81,12 @@ public class NegotiateScheme extends AuthSchemeBase {
return "Negotiate";
}
- @Deprecated
public Header authenticate(
final Credentials credentials,
final HttpRequest request) throws AuthenticationException {
return authenticate(credentials, request, null);
}
- protected GSSManager getManager() {
- return GSSManager.getInstance();
- }
-
/**
* Produces Negotiate authorization Header based on token created by
* processChallenge.
@@ -149,127 +105,64 @@ public class NegotiateScheme extends AuthSchemeBase {
final Credentials credentials,
final HttpRequest request,
final HttpContext context) throws AuthenticationException {
- if (request == null) {
- throw new IllegalArgumentException("HTTP request may not be null");
- }
- if (state != State.CHALLENGE_RECEIVED) {
- throw new IllegalStateException(
- "Negotiation authentication process has not been initiated");
- }
+ return super.authenticate(credentials, request, context);
+ }
+
+ @Override
+ protected byte[] generateToken(final byte[] input, final String authServer) throws GSSException {
+ /* Using the SPNEGO OID is the correct method.
+ * Kerberos v5 works for IIS but not JBoss. Unwrapping
+ * the initial token when using SPNEGO OID looks like what is
+ * described here...
+ *
+ * http://msdn.microsoft.com/en-us/library/ms995330.aspx
+ *
+ * Another helpful URL...
+ *
+ * http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/tsec_SPNEGO_token.html
+ *
+ * Unfortunately SPNEGO is JRE >=1.6.
+ */
+
+ /** Try SPNEGO by default, fall back to Kerberos later if error */
+ Oid negotiationOid = new Oid(SPNEGO_OID);
+
+ byte[] token = input;
+ boolean tryKerberos = false;
try {
- String key = null;
- if (isProxy()) {
- key = ExecutionContext.HTTP_PROXY_HOST;
+ token = generateGSSToken(token, negotiationOid, authServer);
+ } catch (GSSException ex){
+ // BAD MECH means we are likely to be using 1.5, fall back to Kerberos MECH.
+ // Rethrow any other exception.
+ if (ex.getMajor() == GSSException.BAD_MECH ){
+ log.debug("GSSException BAD_MECH, retry with Kerberos MECH");
+ tryKerberos = true;
} else {
- key = ExecutionContext.HTTP_TARGET_HOST;
+ throw ex;
}
- HttpHost host = (HttpHost) context.getAttribute(key);
- if (host == null) {
- throw new AuthenticationException("Authentication host is not set " +
- "in the execution context");
- }
- String authServer;
- if (!this.stripPort && host.getPort() > 0) {
- authServer = host.toHostString();
- } else {
- authServer = host.getHostName();
- }
-
- if (log.isDebugEnabled()) {
- log.debug("init " + authServer);
- }
- /* Using the SPNEGO OID is the correct method.
- * Kerberos v5 works for IIS but not JBoss. Unwrapping
- * the initial token when using SPNEGO OID looks like what is
- * described here...
- *
- * http://msdn.microsoft.com/en-us/library/ms995330.aspx
- *
- * Another helpful URL...
- *
- * http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/tsec_SPNEGO_token.html
- *
- * Unfortunately SPNEGO is JRE >=1.6.
- */
- /** Try SPNEGO by default, fall back to Kerberos later if error */
- negotiationOid = new Oid(SPNEGO_OID);
-
- boolean tryKerberos = false;
- try {
- GSSManager manager = getManager();
- GSSName serverName = manager.createName("HTTP/" + authServer, null);
- gssContext = manager.createContext(
- serverName.canonicalize(negotiationOid), negotiationOid, null,
- GSSContext.DEFAULT_LIFETIME);
- gssContext.requestMutualAuth(true);
- gssContext.requestCredDeleg(true);
- } catch (GSSException ex){
- // BAD MECH means we are likely to be using 1.5, fall back to Kerberos MECH.
- // Rethrow any other exception.
- if (ex.getMajor() == GSSException.BAD_MECH ){
- log.debug("GSSException BAD_MECH, retry with Kerberos MECH");
- tryKerberos = true;
- } else {
- throw ex;
- }
-
- }
- if (tryKerberos){
- /* Kerberos v5 GSS-API mechanism defined in RFC 1964.*/
- log.debug("Using Kerberos MECH " + KERBEROS_OID);
- negotiationOid = new Oid(KERBEROS_OID);
- GSSManager manager = getManager();
- GSSName serverName = manager.createName("HTTP/" + authServer, null);
- gssContext = manager.createContext(
- serverName.canonicalize(negotiationOid), negotiationOid, null,
- GSSContext.DEFAULT_LIFETIME);
- gssContext.requestMutualAuth(true);
- gssContext.requestCredDeleg(true);
- }
- if (token == null) {
- token = new byte[0];
- }
- token = gssContext.initSecContext(token, 0, token.length);
- if (token == null) {
- state = State.FAILED;
- throw new AuthenticationException("GSS security context initialization failed");
- }
+ }
+ if (tryKerberos){
+ /* Kerberos v5 GSS-API mechanism defined in RFC 1964.*/
+ log.debug("Using Kerberos MECH " + KERBEROS_OID);
+ negotiationOid = new Oid(KERBEROS_OID);
+ token = generateGSSToken(token, negotiationOid, authServer);
/*
* IIS accepts Kerberos and SPNEGO tokens. Some other servers Jboss, Glassfish?
* seem to only accept SPNEGO. Below wraps Kerberos into SPNEGO token.
*/
- if (spengoGenerator != null && negotiationOid.toString().equals(KERBEROS_OID)) {
- token = spengoGenerator.generateSpnegoDERObject(token);
- }
-
- state = State.TOKEN_GENERATED;
- String tokenstr = new String(Base64.encodeBase64(token, false));
- if (log.isDebugEnabled()) {
- log.debug("Sending response '" + tokenstr + "' back to the auth server");
+ if (token != null && spengoGenerator != null) {
+ try {
+ token = spengoGenerator.generateSpnegoDERObject(token);
+ } catch (IOException ex) {
+ log.error(ex.getMessage(), ex);
+ }
}
- return new BasicHeader("Authorization", "Negotiate " + tokenstr);
- } catch (GSSException gsse) {
- state = State.FAILED;
- if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL
- || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED)
- throw new InvalidCredentialsException(gsse.getMessage(), gsse);
- if (gsse.getMajor() == GSSException.NO_CRED )
- throw new InvalidCredentialsException(gsse.getMessage(), gsse);
- if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN
- || gsse.getMajor() == GSSException.DUPLICATE_TOKEN
- || gsse.getMajor() == GSSException.OLD_TOKEN)
- throw new AuthenticationException(gsse.getMessage(), gsse);
- // other error
- throw new AuthenticationException(gsse.getMessage());
- } catch (IOException ex){
- state = State.FAILED;
- throw new AuthenticationException(ex.getMessage());
}
+ return token;
}
-
/**
* Returns the authentication parameter with the given name, if available.
*
@@ -307,21 +200,4 @@ public class NegotiateScheme extends AuthSchemeBase {
return true;
}
- @Override
- protected void parseChallenge(
- final CharArrayBuffer buffer,
- int beginIndex, int endIndex) throws MalformedChallengeException {
- String challenge = buffer.substringTrimmed(beginIndex, endIndex);
- if (log.isDebugEnabled()) {
- log.debug("Received challenge '" + challenge + "' from the auth server");
- }
- if (state == State.UNINITIATED) {
- token = new Base64().decode(challenge.getBytes());
- state = State.CHALLENGE_RECEIVED;
- } else {
- log.debug("Authentication already attempted");
- state = State.FAILED;
- }
- }
-
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java b/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java
index 4ad002d..520e14d 100644
--- a/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/NegotiateSchemeFactory.java
@@ -26,7 +26,6 @@
package org.apache.http.impl.auth;
-import org.apache.http.annotation.Immutable;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthSchemeFactory;
import org.apache.http.params.HttpParams;
@@ -36,8 +35,10 @@ import org.apache.http.params.HttpParams;
* scheme factory.
*
* @since 4.1
+ *
+ * @deprecated (4.2) use {@link SPNegoSchemeFactory} or {@link KerberosSchemeFactory}.
*/
- at Immutable
+ at Deprecated
public class NegotiateSchemeFactory implements AuthSchemeFactory {
private final SpnegoTokenGenerator spengoGenerator;
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java b/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java
index 021c39d..b8fd45f 100644
--- a/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java
@@ -33,6 +33,7 @@ import java.util.Map;
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.HeaderElement;
+import org.apache.http.auth.ChallengeState;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.message.BasicHeaderValueParser;
import org.apache.http.message.HeaderValueParser;
@@ -52,13 +53,21 @@ public abstract class RFC2617Scheme extends AuthSchemeBase {
/**
* Authentication parameter map.
*/
- private Map<String, String> params;
+ private final Map<String, String> params;
/**
- * Default constructor for RFC2617 compliant authentication schemes.
+ * Creates an instance of <tt>RFC2617Scheme</tt> with the given challenge
+ * state.
+ *
+ * @since 4.2
*/
+ public RFC2617Scheme(final ChallengeState challengeState) {
+ super(challengeState);
+ this.params = new HashMap<String, String>();
+ }
+
public RFC2617Scheme() {
- super();
+ this(null);
}
@Override
@@ -70,8 +79,7 @@ public abstract class RFC2617Scheme extends AuthSchemeBase {
if (elements.length == 0) {
throw new MalformedChallengeException("Authentication challenge is empty");
}
-
- this.params = new HashMap<String, String>(elements.length);
+ this.params.clear();
for (HeaderElement element : elements) {
this.params.put(element.getName(), element.getValue());
}
@@ -83,9 +91,6 @@ public abstract class RFC2617Scheme extends AuthSchemeBase {
* @return the map of authentication parameters
*/
protected Map<String, String> getParameters() {
- if (this.params == null) {
- this.params = new HashMap<String, String>();
- }
return this.params;
}
@@ -98,9 +103,6 @@ public abstract class RFC2617Scheme extends AuthSchemeBase {
*/
public String getParameter(final String name) {
if (name == null) {
- throw new IllegalArgumentException("Parameter name may not be null");
- }
- if (this.params == null) {
return null;
}
return this.params.get(name.toLowerCase(Locale.ENGLISH));
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/SPNegoScheme.java b/httpclient/src/main/java/org/apache/http/impl/auth/SPNegoScheme.java
new file mode 100644
index 0000000..66ee8db
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/SPNegoScheme.java
@@ -0,0 +1,115 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.http.impl.auth;
+
+import org.apache.http.Header;
+import org.apache.http.HttpRequest;
+import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.Credentials;
+import org.apache.http.protocol.HttpContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.Oid;
+
+/**
+ * SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication
+ * scheme.
+ *
+ * @since 4.2
+ */
+public class SPNegoScheme extends GGSSchemeBase {
+
+ private static final String SPNEGO_OID = "1.3.6.1.5.5.2";
+
+ public SPNegoScheme(boolean stripPort) {
+ super(stripPort);
+ }
+
+ public SPNegoScheme() {
+ super(false);
+ }
+
+ public String getSchemeName() {
+ return "Negotiate";
+ }
+
+ /**
+ * Produces SPNEGO authorization Header based on token created by
+ * processChallenge.
+ *
+ * @param credentials not used by the SPNEGO scheme.
+ * @param request The request being authenticated
+ *
+ * @throws AuthenticationException if authentication string cannot
+ * be generated due to an authentication failure
+ *
+ * @return SPNEGO authentication Header
+ */
+ @Override
+ public Header authenticate(
+ final Credentials credentials,
+ final HttpRequest request,
+ final HttpContext context) throws AuthenticationException {
+ return super.authenticate(credentials, request, context);
+ }
+
+ @Override
+ protected byte[] generateToken(final byte[] input, final String authServer) throws GSSException {
+ return generateGSSToken(input, new Oid(SPNEGO_OID), authServer);
+ }
+
+ /**
+ * There are no valid parameters for SPNEGO authentication so this
+ * method always returns <code>null</code>.
+ *
+ * @return <code>null</code>
+ */
+ public String getParameter(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("Parameter name may not be null");
+ }
+ return null;
+ }
+
+ /**
+ * The concept of an authentication realm is not supported by the Negotiate
+ * authentication scheme. Always returns <code>null</code>.
+ *
+ * @return <code>null</code>
+ */
+ public String getRealm() {
+ return null;
+ }
+
+ /**
+ * Returns <tt>true</tt>. SPNEGO authentication scheme is connection based.
+ *
+ * @return <tt>true</tt>.
+ */
+ public boolean isConnectionBased() {
+ return true;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/SPNegoSchemeFactory.java b/httpclient/src/main/java/org/apache/http/impl/auth/SPNegoSchemeFactory.java
new file mode 100644
index 0000000..6ee3ac6
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/SPNegoSchemeFactory.java
@@ -0,0 +1,62 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.auth;
+
+import org.apache.http.annotation.Immutable;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthSchemeFactory;
+import org.apache.http.params.HttpParams;
+
+/**
+ * SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication
+ * scheme factory.
+ *
+ * @since 4.2
+ */
+ at Immutable
+public class SPNegoSchemeFactory implements AuthSchemeFactory {
+
+ private final boolean stripPort;
+
+ public SPNegoSchemeFactory(boolean stripPort) {
+ super();
+ this.stripPort = stripPort;
+ }
+
+ public SPNegoSchemeFactory() {
+ this(false);
+ }
+
+ public AuthScheme newInstance(final HttpParams params) {
+ return new SPNegoScheme(this.stripPort);
+ }
+
+ public boolean isStripPort() {
+ return stripPort;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java b/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java
index 3e474bb..154e584 100644
--- a/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/SpnegoTokenGenerator.java
@@ -35,7 +35,11 @@ import java.io.IOException;
* Implementations of this interface are expected to be thread-safe.
*
* @since 4.1
+ *
+ * @deprecated (4.2) subclass {@link KerberosScheme} and override
+ * {@link KerberosScheme#generateGSSToken(byte[], org.ietf.jgss.Oid, String)}
*/
+ at Deprecated
public interface SpnegoTokenGenerator {
byte [] generateSpnegoDERObject(byte [] kerberosTicket) throws IOException;
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/AIMDBackoffManager.java b/httpclient/src/main/java/org/apache/http/impl/client/AIMDBackoffManager.java
new file mode 100644
index 0000000..b372fe4
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/AIMDBackoffManager.java
@@ -0,0 +1,161 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.http.client.BackoffManager;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.pool.ConnPoolControl;
+
+/**
+ * <p>The <code>AIMDBackoffManager</code> applies an additive increase,
+ * multiplicative decrease (AIMD) to managing a dynamic limit to
+ * the number of connections allowed to a given host. You may want
+ * to experiment with the settings for the cooldown periods and the
+ * backoff factor to get the adaptive behavior you want.</p>
+ *
+ * <p>Generally speaking, shorter cooldowns will lead to more steady-state
+ * variability but faster reaction times, while longer cooldowns
+ * will lead to more stable equilibrium behavior but slower reaction
+ * times.</p>
+ *
+ * <p>Similarly, higher backoff factors promote greater
+ * utilization of available capacity at the expense of fairness
+ * among clients. Lower backoff factors allow equal distribution of
+ * capacity among clients (fairness) to happen faster, at the
+ * expense of having more server capacity unused in the short term.</p>
+ *
+ * @since 4.2
+ */
+public class AIMDBackoffManager implements BackoffManager {
+
+ private final ConnPoolControl<HttpRoute> connPerRoute;
+ private final Clock clock;
+ private final Map<HttpRoute,Long> lastRouteProbes;
+ private final Map<HttpRoute,Long> lastRouteBackoffs;
+ private long coolDown = 5 * 1000L;
+ private double backoffFactor = 0.5;
+ private int cap = 2; // Per RFC 2616 sec 8.1.4
+
+ /**
+ * Creates an <code>AIMDBackoffManager</code> to manage
+ * per-host connection pool sizes represented by the
+ * given {@link ConnPoolControl}.
+ * @param connPerRoute per-host routing maximums to
+ * be managed
+ */
+ public AIMDBackoffManager(ConnPoolControl<HttpRoute> connPerRoute) {
+ this(connPerRoute, new SystemClock());
+ }
+
+ AIMDBackoffManager(ConnPoolControl<HttpRoute> connPerRoute, Clock clock) {
+ this.clock = clock;
+ this.connPerRoute = connPerRoute;
+ this.lastRouteProbes = new HashMap<HttpRoute,Long>();
+ this.lastRouteBackoffs = new HashMap<HttpRoute,Long>();
+ }
+
+ public void backOff(HttpRoute route) {
+ synchronized(connPerRoute) {
+ int curr = connPerRoute.getMaxPerRoute(route);
+ Long lastUpdate = getLastUpdate(lastRouteBackoffs, route);
+ long now = clock.getCurrentTime();
+ if (now - lastUpdate.longValue() < coolDown) return;
+ connPerRoute.setMaxPerRoute(route, getBackedOffPoolSize(curr));
+ lastRouteBackoffs.put(route, Long.valueOf(now));
+ }
+ }
+
+ private int getBackedOffPoolSize(int curr) {
+ if (curr <= 1) return 1;
+ return (int)(Math.floor(backoffFactor * curr));
+ }
+
+ public void probe(HttpRoute route) {
+ synchronized(connPerRoute) {
+ int curr = connPerRoute.getMaxPerRoute(route);
+ int max = (curr >= cap) ? cap : curr + 1;
+ Long lastProbe = getLastUpdate(lastRouteProbes, route);
+ Long lastBackoff = getLastUpdate(lastRouteBackoffs, route);
+ long now = clock.getCurrentTime();
+ if (now - lastProbe.longValue() < coolDown || now - lastBackoff.longValue() < coolDown)
+ return;
+ connPerRoute.setMaxPerRoute(route, max);
+ lastRouteProbes.put(route, Long.valueOf(now));
+ }
+ }
+
+ private Long getLastUpdate(Map<HttpRoute,Long> updates, HttpRoute route) {
+ Long lastUpdate = updates.get(route);
+ if (lastUpdate == null) lastUpdate = Long.valueOf(0L);
+ return lastUpdate;
+ }
+
+ /**
+ * Sets the factor to use when backing off; the new
+ * per-host limit will be roughly the current max times
+ * this factor. <code>Math.floor</code> is applied in the
+ * case of non-integer outcomes to ensure we actually
+ * decrease the pool size. Pool sizes are never decreased
+ * below 1, however. Defaults to 0.5.
+ * @param d must be between 0.0 and 1.0, exclusive.
+ */
+ public void setBackoffFactor(double d) {
+ if (d <= 0.0 || d >= 1.0) {
+ throw new IllegalArgumentException("backoffFactor must be 0.0 < f < 1.0");
+ }
+ backoffFactor = d;
+ }
+
+ /**
+ * Sets the amount of time, in milliseconds, to wait between
+ * adjustments in pool sizes for a given host, to allow
+ * enough time for the adjustments to take effect. Defaults
+ * to 5000L (5 seconds).
+ * @param l must be positive
+ */
+ public void setCooldownMillis(long l) {
+ if (coolDown <= 0) {
+ throw new IllegalArgumentException("cooldownMillis must be positive");
+ }
+ coolDown = l;
+ }
+
+ /**
+ * Sets the absolute maximum per-host connection pool size to
+ * probe up to; defaults to 2 (the default per-host max).
+ * @param cap must be >= 1
+ */
+ public void setPerHostConnectionCap(int cap) {
+ if (cap < 1) {
+ throw new IllegalArgumentException("perHostConnectionCap must be >= 1");
+ }
+ this.cap = cap;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/AbstractAuthenticationHandler.java b/httpclient/src/main/java/org/apache/http/impl/client/AbstractAuthenticationHandler.java
index 3a8dbcb..2976f4d 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/AbstractAuthenticationHandler.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/AbstractAuthenticationHandler.java
@@ -46,6 +46,7 @@ import org.apache.http.auth.AuthSchemeRegistry;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.client.AuthenticationHandler;
+import org.apache.http.client.AuthenticationStrategy;
import org.apache.http.client.params.AuthPolicy;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.protocol.HTTP;
@@ -56,7 +57,10 @@ import org.apache.http.util.CharArrayBuffer;
* Base class for {@link AuthenticationHandler} implementations.
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link AuthenticationStrategy}
*/
+ at Deprecated
@Immutable
public abstract class AbstractAuthenticationHandler implements AuthenticationHandler {
@@ -102,7 +106,7 @@ public abstract class AbstractAuthenticationHandler implements AuthenticationHan
}
int endIndex = pos;
String s = buffer.substring(beginIndex, endIndex);
- map.put(s.toLowerCase(Locale.ENGLISH), header);
+ map.put(s.toLowerCase(Locale.US), header);
}
return map;
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java b/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
index 5069e25..0f597d6 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
@@ -45,7 +45,10 @@ import org.apache.http.annotation.GuardedBy;
import org.apache.http.annotation.ThreadSafe;
import org.apache.http.auth.AuthSchemeRegistry;
import org.apache.http.client.AuthenticationHandler;
+import org.apache.http.client.AuthenticationStrategy;
+import org.apache.http.client.BackoffManager;
import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ConnectionBackoffStrategy;
import org.apache.http.client.CookieStore;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
@@ -64,17 +67,19 @@ import org.apache.http.client.utils.URIUtils;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ClientConnectionManagerFactory;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
+import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.cookie.CookieSpecRegistry;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.auth.BasicSchemeFactory;
import org.apache.http.impl.auth.DigestSchemeFactory;
+import org.apache.http.impl.auth.KerberosSchemeFactory;
import org.apache.http.impl.auth.NTLMSchemeFactory;
-import org.apache.http.impl.auth.NegotiateSchemeFactory;
+import org.apache.http.impl.auth.SPNegoSchemeFactory;
+import org.apache.http.impl.conn.BasicClientConnectionManager;
import org.apache.http.impl.conn.DefaultHttpRoutePlanner;
import org.apache.http.impl.conn.SchemeRegistryFactory;
-import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.impl.cookie.BestMatchSpecFactory;
import org.apache.http.impl.cookie.BrowserCompatSpecFactory;
import org.apache.http.impl.cookie.IgnoreSpecFactory;
@@ -137,13 +142,13 @@ import org.apache.http.util.EntityUtils;
* a collection user credentials. The {@link #createCredentialsProvider()}
* must be implemented by concrete super classes to instantiate
* this object.
- * <li>{@link AuthenticationHandler}</li> object used to authenticate
+ * <li>{@link AuthenticationStrategy}</li> object used to authenticate
* against the target host.
- * The {@link #createTargetAuthenticationHandler()} must be implemented
+ * The {@link #createTargetAuthenticationStrategy()} must be implemented
* by concrete super classes to instantiate this object.
- * <li>{@link AuthenticationHandler}</li> object used to authenticate
+ * <li>{@link AuthenticationStrategy}</li> object used to authenticate
* against the proxy host.
- * The {@link #createProxyAuthenticationHandler()} must be implemented
+ * The {@link #createProxyAuthenticationStrategy()} must be implemented
* by concrete super classes to instantiate this object.
* <li>{@link HttpRoutePlanner}</li> object used to calculate a route
* for establishing a connection to the target host. The route
@@ -175,8 +180,8 @@ import org.apache.http.util.EntityUtils;
*
* @since 4.0
*/
- at ThreadSafe
@SuppressWarnings("deprecation")
+ at ThreadSafe
public abstract class AbstractHttpClient implements HttpClient {
private final Log log = LogFactory.getLog(getClass());
@@ -226,11 +231,11 @@ public abstract class AbstractHttpClient implements HttpClient {
/** The target authentication handler. */
@GuardedBy("this")
- private AuthenticationHandler targetAuthHandler;
+ private AuthenticationStrategy targetAuthStrategy;
/** The proxy authentication handler. */
@GuardedBy("this")
- private AuthenticationHandler proxyAuthHandler;
+ private AuthenticationStrategy proxyAuthStrategy;
/** The cookie store. */
@GuardedBy("this")
@@ -248,6 +253,13 @@ public abstract class AbstractHttpClient implements HttpClient {
@GuardedBy("this")
private UserTokenHandler userTokenHandler;
+ /** The connection backoff strategy. */
+ @GuardedBy("this")
+ private ConnectionBackoffStrategy connectionBackoffStrategy;
+
+ /** The backoff manager. */
+ @GuardedBy("this")
+ private BackoffManager backoffManager;
/**
* Creates a new HTTP client.
@@ -315,7 +327,7 @@ public abstract class AbstractHttpClient implements HttpClient {
if (factory != null) {
connManager = factory.newInstance(params, registry);
} else {
- connManager = new SingleClientConnManager(registry);
+ connManager = new BasicClientConnectionManager(registry);
}
return connManager;
@@ -335,7 +347,10 @@ public abstract class AbstractHttpClient implements HttpClient {
new NTLMSchemeFactory());
registry.register(
AuthPolicy.SPNEGO,
- new NegotiateSchemeFactory());
+ new SPNegoSchemeFactory());
+ registry.register(
+ AuthPolicy.KERBEROS,
+ new KerberosSchemeFactory());
return registry;
}
@@ -363,63 +378,70 @@ public abstract class AbstractHttpClient implements HttpClient {
return registry;
}
-
protected HttpRequestExecutor createRequestExecutor() {
return new HttpRequestExecutor();
}
-
protected ConnectionReuseStrategy createConnectionReuseStrategy() {
return new DefaultConnectionReuseStrategy();
}
-
protected ConnectionKeepAliveStrategy createConnectionKeepAliveStrategy() {
return new DefaultConnectionKeepAliveStrategy();
}
-
protected HttpRequestRetryHandler createHttpRequestRetryHandler() {
return new DefaultHttpRequestRetryHandler();
}
-
- @Deprecated
+ /**
+ * @deprecated (4.1) do not use
+ */
+ @Deprecated
protected RedirectHandler createRedirectHandler() {
return new DefaultRedirectHandler();
}
+ protected AuthenticationStrategy createTargetAuthenticationStrategy() {
+ return new TargetAuthenticationStrategy();
+ }
+ /**
+ * @deprecated (4.2) do not use
+ */
+ @Deprecated
protected AuthenticationHandler createTargetAuthenticationHandler() {
return new DefaultTargetAuthenticationHandler();
}
+ protected AuthenticationStrategy createProxyAuthenticationStrategy() {
+ return new ProxyAuthenticationStrategy();
+ }
+ /**
+ * @deprecated (4.2) do not use
+ */
+ @Deprecated
protected AuthenticationHandler createProxyAuthenticationHandler() {
return new DefaultProxyAuthenticationHandler();
}
-
protected CookieStore createCookieStore() {
return new BasicCookieStore();
}
-
protected CredentialsProvider createCredentialsProvider() {
return new BasicCredentialsProvider();
}
-
protected HttpRoutePlanner createHttpRoutePlanner() {
return new DefaultHttpRoutePlanner(getConnectionManager().getSchemeRegistry());
}
-
protected UserTokenHandler createUserTokenHandler() {
return new DefaultUserTokenHandler();
}
-
// non-javadoc, see interface HttpClient
public synchronized final HttpParams getParams() {
if (defaultParams == null) {
@@ -428,7 +450,6 @@ public abstract class AbstractHttpClient implements HttpClient {
return defaultParams;
}
-
/**
* Replaces the parameters.
* The implementation here does not update parameters of dependent objects.
@@ -463,11 +484,17 @@ public abstract class AbstractHttpClient implements HttpClient {
return supportedAuthSchemes;
}
+ public synchronized void setAuthSchemes(final AuthSchemeRegistry registry) {
+ supportedAuthSchemes = registry;
+ }
- public synchronized void setAuthSchemes(final AuthSchemeRegistry authSchemeRegistry) {
- supportedAuthSchemes = authSchemeRegistry;
+ public synchronized final ConnectionBackoffStrategy getConnectionBackoffStrategy() {
+ return connectionBackoffStrategy;
}
+ public synchronized void setConnectionBackoffStrategy(final ConnectionBackoffStrategy strategy) {
+ connectionBackoffStrategy = strategy;
+ }
public synchronized final CookieSpecRegistry getCookieSpecs() {
if (supportedCookieSpecs == null) {
@@ -476,11 +503,17 @@ public abstract class AbstractHttpClient implements HttpClient {
return supportedCookieSpecs;
}
+ public synchronized final BackoffManager getBackoffManager() {
+ return backoffManager;
+ }
- public synchronized void setCookieSpecs(final CookieSpecRegistry cookieSpecRegistry) {
- supportedCookieSpecs = cookieSpecRegistry;
+ public synchronized void setBackoffManager(final BackoffManager manager) {
+ backoffManager = manager;
}
+ public synchronized void setCookieSpecs(final CookieSpecRegistry registry) {
+ supportedCookieSpecs = registry;
+ }
public synchronized final ConnectionReuseStrategy getConnectionReuseStrategy() {
if (reuseStrategy == null) {
@@ -490,8 +523,8 @@ public abstract class AbstractHttpClient implements HttpClient {
}
- public synchronized void setReuseStrategy(final ConnectionReuseStrategy reuseStrategy) {
- this.reuseStrategy = reuseStrategy;
+ public synchronized void setReuseStrategy(final ConnectionReuseStrategy strategy) {
+ this.reuseStrategy = strategy;
}
@@ -503,8 +536,8 @@ public abstract class AbstractHttpClient implements HttpClient {
}
- public synchronized void setKeepAliveStrategy(final ConnectionKeepAliveStrategy keepAliveStrategy) {
- this.keepAliveStrategy = keepAliveStrategy;
+ public synchronized void setKeepAliveStrategy(final ConnectionKeepAliveStrategy strategy) {
+ this.keepAliveStrategy = strategy;
}
@@ -515,21 +548,24 @@ public abstract class AbstractHttpClient implements HttpClient {
return retryHandler;
}
-
- public synchronized void setHttpRequestRetryHandler(final HttpRequestRetryHandler retryHandler) {
- this.retryHandler = retryHandler;
+ public synchronized void setHttpRequestRetryHandler(final HttpRequestRetryHandler handler) {
+ this.retryHandler = handler;
}
-
- @Deprecated
+ /**
+ * @deprecated (4.1) do not use
+ */
+ @Deprecated
public synchronized final RedirectHandler getRedirectHandler() {
return createRedirectHandler();
}
-
- @Deprecated
- public synchronized void setRedirectHandler(final RedirectHandler redirectHandler) {
- this.redirectStrategy = new DefaultRedirectStrategyAdaptor(redirectHandler);
+ /**
+ * @deprecated (4.1) do not use
+ */
+ @Deprecated
+ public synchronized void setRedirectHandler(final RedirectHandler handler) {
+ this.redirectStrategy = new DefaultRedirectStrategyAdaptor(handler);
}
/**
@@ -545,38 +581,75 @@ public abstract class AbstractHttpClient implements HttpClient {
/**
* @since 4.1
*/
- public synchronized void setRedirectStrategy(final RedirectStrategy redirectStrategy) {
- this.redirectStrategy = redirectStrategy;
+ public synchronized void setRedirectStrategy(final RedirectStrategy strategy) {
+ this.redirectStrategy = strategy;
}
-
+ /**
+ * @deprecated (4.2) do not use
+ */
+ @Deprecated
public synchronized final AuthenticationHandler getTargetAuthenticationHandler() {
- if (targetAuthHandler == null) {
- targetAuthHandler = createTargetAuthenticationHandler();
- }
- return targetAuthHandler;
+ return createTargetAuthenticationHandler();
}
+ /**
+ * @deprecated (4.2) do not use
+ */
+ @Deprecated
+ public synchronized void setTargetAuthenticationHandler(final AuthenticationHandler handler) {
+ this.targetAuthStrategy = new AuthenticationStrategyAdaptor(handler);
+ }
- public synchronized void setTargetAuthenticationHandler(
- final AuthenticationHandler targetAuthHandler) {
- this.targetAuthHandler = targetAuthHandler;
+ /**
+ * @since 4.2
+ */
+ public synchronized final AuthenticationStrategy getTargetAuthenticationStrategy() {
+ if (targetAuthStrategy == null) {
+ targetAuthStrategy = createTargetAuthenticationStrategy();
+ }
+ return targetAuthStrategy;
}
+ /**
+ * @since 4.2
+ */
+ public synchronized void setTargetAuthenticationStrategy(final AuthenticationStrategy strategy) {
+ this.targetAuthStrategy = strategy;
+ }
+ /**
+ * @deprecated (4.2) do not use
+ */
+ @Deprecated
public synchronized final AuthenticationHandler getProxyAuthenticationHandler() {
- if (proxyAuthHandler == null) {
- proxyAuthHandler = createProxyAuthenticationHandler();
- }
- return proxyAuthHandler;
+ return createProxyAuthenticationHandler();
}
+ /**
+ * @deprecated (4.2) do not use
+ */
+ @Deprecated
+ public synchronized void setProxyAuthenticationHandler(final AuthenticationHandler handler) {
+ this.proxyAuthStrategy = new AuthenticationStrategyAdaptor(handler);
+ }
- public synchronized void setProxyAuthenticationHandler(
- final AuthenticationHandler proxyAuthHandler) {
- this.proxyAuthHandler = proxyAuthHandler;
+ /**
+ * @since 4.2
+ */
+ public synchronized final AuthenticationStrategy getProxyAuthenticationStrategy() {
+ if (proxyAuthStrategy == null) {
+ proxyAuthStrategy = createProxyAuthenticationStrategy();
+ }
+ return proxyAuthStrategy;
}
+ /**
+ * @since 4.2
+ */
+ public synchronized void setProxyAuthenticationStrategy(final AuthenticationStrategy strategy) {
+ this.proxyAuthStrategy = strategy;
+ }
public synchronized final CookieStore getCookieStore() {
if (cookieStore == null) {
@@ -585,12 +658,10 @@ public abstract class AbstractHttpClient implements HttpClient {
return cookieStore;
}
-
public synchronized void setCookieStore(final CookieStore cookieStore) {
this.cookieStore = cookieStore;
}
-
public synchronized final CredentialsProvider getCredentialsProvider() {
if (credsProvider == null) {
credsProvider = createCredentialsProvider();
@@ -598,12 +669,10 @@ public abstract class AbstractHttpClient implements HttpClient {
return credsProvider;
}
-
public synchronized void setCredentialsProvider(final CredentialsProvider credsProvider) {
this.credsProvider = credsProvider;
}
-
public synchronized final HttpRoutePlanner getRoutePlanner() {
if (this.routePlanner == null) {
this.routePlanner = createHttpRoutePlanner();
@@ -611,12 +680,10 @@ public abstract class AbstractHttpClient implements HttpClient {
return this.routePlanner;
}
-
public synchronized void setRoutePlanner(final HttpRoutePlanner routePlanner) {
this.routePlanner = routePlanner;
}
-
public synchronized final UserTokenHandler getUserTokenHandler() {
if (this.userTokenHandler == null) {
this.userTokenHandler = createUserTokenHandler();
@@ -624,12 +691,10 @@ public abstract class AbstractHttpClient implements HttpClient {
return this.userTokenHandler;
}
-
- public synchronized void setUserTokenHandler(final UserTokenHandler userTokenHandler) {
- this.userTokenHandler = userTokenHandler;
+ public synchronized void setUserTokenHandler(final UserTokenHandler handler) {
+ this.userTokenHandler = handler;
}
-
protected synchronized final BasicHttpProcessor getHttpProcessor() {
if (mutableProcessor == null) {
mutableProcessor = createHttpProcessor();
@@ -637,7 +702,6 @@ public abstract class AbstractHttpClient implements HttpClient {
return mutableProcessor;
}
-
private synchronized final HttpProcessor getProtocolProcessor() {
if (protocolProcessor == null) {
// Get mutable HTTP processor
@@ -658,69 +722,57 @@ public abstract class AbstractHttpClient implements HttpClient {
return protocolProcessor;
}
-
public synchronized int getResponseInterceptorCount() {
return getHttpProcessor().getResponseInterceptorCount();
}
-
public synchronized HttpResponseInterceptor getResponseInterceptor(int index) {
return getHttpProcessor().getResponseInterceptor(index);
}
-
public synchronized HttpRequestInterceptor getRequestInterceptor(int index) {
return getHttpProcessor().getRequestInterceptor(index);
}
-
public synchronized int getRequestInterceptorCount() {
return getHttpProcessor().getRequestInterceptorCount();
}
-
public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp) {
getHttpProcessor().addInterceptor(itcp);
protocolProcessor = null;
}
-
public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp, int index) {
getHttpProcessor().addInterceptor(itcp, index);
protocolProcessor = null;
}
-
public synchronized void clearResponseInterceptors() {
getHttpProcessor().clearResponseInterceptors();
protocolProcessor = null;
}
-
public synchronized void removeResponseInterceptorByClass(Class<? extends HttpResponseInterceptor> clazz) {
getHttpProcessor().removeResponseInterceptorByClass(clazz);
protocolProcessor = null;
}
-
public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp) {
getHttpProcessor().addInterceptor(itcp);
protocolProcessor = null;
}
-
public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp, int index) {
getHttpProcessor().addInterceptor(itcp, index);
protocolProcessor = null;
}
-
public synchronized void clearRequestInterceptors() {
getHttpProcessor().clearRequestInterceptors();
protocolProcessor = null;
}
-
public synchronized void removeRequestInterceptorByClass(Class<? extends HttpRequestInterceptor> clazz) {
getHttpProcessor().removeRequestInterceptorByClass(clazz);
protocolProcessor = null;
@@ -732,7 +784,6 @@ public abstract class AbstractHttpClient implements HttpClient {
return execute(request, (HttpContext) null);
}
-
/**
* Maps to {@link HttpClient#execute(HttpHost,HttpRequest,HttpContext)
* execute(target, request, context)}.
@@ -789,6 +840,9 @@ public abstract class AbstractHttpClient implements HttpClient {
HttpContext execContext = null;
RequestDirector director = null;
+ HttpRoutePlanner routePlanner = null;
+ ConnectionBackoffStrategy connectionBackoffStrategy = null;
+ BackoffManager backoffManager = null;
// Initialize the request execution context making copies of
// all shared objects that are potentially threading unsafe.
@@ -810,19 +864,55 @@ public abstract class AbstractHttpClient implements HttpClient {
getProtocolProcessor(),
getHttpRequestRetryHandler(),
getRedirectStrategy(),
- getTargetAuthenticationHandler(),
- getProxyAuthenticationHandler(),
+ getTargetAuthenticationStrategy(),
+ getProxyAuthenticationStrategy(),
getUserTokenHandler(),
determineParams(request));
+ routePlanner = getRoutePlanner();
+ connectionBackoffStrategy = getConnectionBackoffStrategy();
+ backoffManager = getBackoffManager();
}
try {
- return director.execute(target, request, execContext);
+ if (connectionBackoffStrategy != null && backoffManager != null) {
+ HttpHost targetForRoute = (target != null) ? target
+ : (HttpHost) determineParams(request).getParameter(
+ ClientPNames.DEFAULT_HOST);
+ HttpRoute route = routePlanner.determineRoute(targetForRoute, request, execContext);
+
+ HttpResponse out;
+ try {
+ out = director.execute(target, request, execContext);
+ } catch (RuntimeException re) {
+ if (connectionBackoffStrategy.shouldBackoff(re)) {
+ backoffManager.backOff(route);
+ }
+ throw re;
+ } catch (Exception e) {
+ if (connectionBackoffStrategy.shouldBackoff(e)) {
+ backoffManager.backOff(route);
+ }
+ if (e instanceof HttpException) throw (HttpException)e;
+ if (e instanceof IOException) throw (IOException)e;
+ throw new UndeclaredThrowableException(e);
+ }
+ if (connectionBackoffStrategy.shouldBackoff(out)) {
+ backoffManager.backOff(route);
+ } else {
+ backoffManager.probe(route);
+ }
+ return out;
+ } else {
+ return director.execute(target, request, execContext);
+ }
} catch(HttpException httpException) {
throw new ClientProtocolException(httpException);
}
}
+ /**
+ * @deprecated (4.1) do not use
+ */
@Deprecated
protected RequestDirector createClientRequestDirector(
final HttpRequestExecutor requestExec,
@@ -832,10 +922,10 @@ public abstract class AbstractHttpClient implements HttpClient {
final HttpRoutePlanner rouplan,
final HttpProcessor httpProcessor,
final HttpRequestRetryHandler retryHandler,
- final org.apache.http.client.RedirectHandler redirectHandler,
+ final RedirectHandler redirectHandler,
final AuthenticationHandler targetAuthHandler,
final AuthenticationHandler proxyAuthHandler,
- final UserTokenHandler stateHandler,
+ final UserTokenHandler userTokenHandler,
final HttpParams params) {
return new DefaultRequestDirector(
requestExec,
@@ -848,13 +938,14 @@ public abstract class AbstractHttpClient implements HttpClient {
redirectHandler,
targetAuthHandler,
proxyAuthHandler,
- stateHandler,
+ userTokenHandler,
params);
}
/**
- * @since 4.1
+ * @deprecated (4.2) do not use
*/
+ @Deprecated
protected RequestDirector createClientRequestDirector(
final HttpRequestExecutor requestExec,
final ClientConnectionManager conman,
@@ -866,7 +957,7 @@ public abstract class AbstractHttpClient implements HttpClient {
final RedirectStrategy redirectStrategy,
final AuthenticationHandler targetAuthHandler,
final AuthenticationHandler proxyAuthHandler,
- final UserTokenHandler stateHandler,
+ final UserTokenHandler userTokenHandler,
final HttpParams params) {
return new DefaultRequestDirector(
log,
@@ -880,9 +971,43 @@ public abstract class AbstractHttpClient implements HttpClient {
redirectStrategy,
targetAuthHandler,
proxyAuthHandler,
- stateHandler,
+ userTokenHandler,
params);
}
+
+
+ /**
+ * @since 4.2
+ */
+ protected RequestDirector createClientRequestDirector(
+ final HttpRequestExecutor requestExec,
+ final ClientConnectionManager conman,
+ final ConnectionReuseStrategy reustrat,
+ final ConnectionKeepAliveStrategy kastrat,
+ final HttpRoutePlanner rouplan,
+ final HttpProcessor httpProcessor,
+ final HttpRequestRetryHandler retryHandler,
+ final RedirectStrategy redirectStrategy,
+ final AuthenticationStrategy targetAuthStrategy,
+ final AuthenticationStrategy proxyAuthStrategy,
+ final UserTokenHandler userTokenHandler,
+ final HttpParams params) {
+ return new DefaultRequestDirector(
+ log,
+ requestExec,
+ conman,
+ reustrat,
+ kastrat,
+ rouplan,
+ httpProcessor,
+ retryHandler,
+ redirectStrategy,
+ targetAuthStrategy,
+ proxyAuthStrategy,
+ userTokenHandler,
+ params);
+ }
+
/**
* Obtains parameters for executing a request.
* The default implementation in this class creates a new
@@ -943,7 +1068,7 @@ public abstract class AbstractHttpClient implements HttpClient {
T result;
try {
result = responseHandler.handleResponse(response);
- } catch (Throwable t) {
+ } catch (Exception t) {
HttpEntity entity = response.getEntity();
try {
EntityUtils.consume(entity);
@@ -952,19 +1077,12 @@ public abstract class AbstractHttpClient implements HttpClient {
// important and will be thrown to the caller.
this.log.warn("Error consuming content after an exception.", t2);
}
-
- if (t instanceof Error) {
- throw (Error) t;
- }
-
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
-
if (t instanceof IOException) {
throw (IOException) t;
}
-
throw new UndeclaredThrowableException(t);
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyAdaptor.java b/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyAdaptor.java
new file mode 100644
index 0000000..c7d5346
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyAdaptor.java
@@ -0,0 +1,179 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Queue;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.annotation.Immutable;
+import org.apache.http.auth.AuthOption;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.MalformedChallengeException;
+import org.apache.http.client.AuthCache;
+import org.apache.http.client.AuthenticationHandler;
+import org.apache.http.client.AuthenticationStrategy;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * @deprecated (4.2) do not use
+ */
+ at Immutable
+ at Deprecated
+class AuthenticationStrategyAdaptor implements AuthenticationStrategy {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ private final AuthenticationHandler handler;
+
+ public AuthenticationStrategyAdaptor(final AuthenticationHandler handler) {
+ super();
+ this.handler = handler;
+ }
+
+ public boolean isAuthenticationRequested(
+ final HttpHost authhost,
+ final HttpResponse response,
+ final HttpContext context) {
+ return this.handler.isAuthenticationRequested(response, context);
+ }
+
+ public Map<String, Header> getChallenges(
+ final HttpHost authhost,
+ final HttpResponse response,
+ final HttpContext context) throws MalformedChallengeException {
+ return this.handler.getChallenges(response, context);
+ }
+
+ public Queue<AuthOption> select(
+ final Map<String, Header> challenges,
+ final HttpHost authhost,
+ final HttpResponse response,
+ final HttpContext context) throws MalformedChallengeException {
+ if (challenges == null) {
+ throw new IllegalArgumentException("Map of auth challenges may not be null");
+ }
+ if (authhost == null) {
+ throw new IllegalArgumentException("Host may not be null");
+ }
+ if (response == null) {
+ throw new IllegalArgumentException("HTTP response may not be null");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("HTTP context may not be null");
+ }
+
+ Queue<AuthOption> options = new LinkedList<AuthOption>();
+ CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(
+ ClientContext.CREDS_PROVIDER);
+ if (credsProvider == null) {
+ this.log.debug("Credentials provider not set in the context");
+ return options;
+ }
+
+ AuthScheme authScheme;
+ try {
+ authScheme = this.handler.selectScheme(challenges, response, context);
+ } catch (AuthenticationException ex) {
+ if (this.log.isWarnEnabled()) {
+ this.log.warn(ex.getMessage(), ex);
+ }
+ return options;
+ }
+ String id = authScheme.getSchemeName();
+ Header challenge = challenges.get(id.toLowerCase(Locale.US));
+ authScheme.processChallenge(challenge);
+
+ AuthScope authScope = new AuthScope(
+ authhost.getHostName(),
+ authhost.getPort(),
+ authScheme.getRealm(),
+ authScheme.getSchemeName());
+
+ Credentials credentials = credsProvider.getCredentials(authScope);
+ if (credentials != null) {
+ options.add(new AuthOption(authScheme, credentials));
+ }
+ return options;
+ }
+
+ public void authSucceeded(
+ final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) {
+ AuthCache authCache = (AuthCache) context.getAttribute(ClientContext.AUTH_CACHE);
+ if (isCachable(authScheme)) {
+ if (authCache == null) {
+ authCache = new BasicAuthCache();
+ context.setAttribute(ClientContext.AUTH_CACHE, authCache);
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Caching '" + authScheme.getSchemeName() +
+ "' auth scheme for " + authhost);
+ }
+ authCache.put(authhost, authScheme);
+ }
+ }
+
+ public void authFailed(
+ final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) {
+ AuthCache authCache = (AuthCache) context.getAttribute(ClientContext.AUTH_CACHE);
+ if (authCache == null) {
+ return;
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Removing from cache '" + authScheme.getSchemeName() +
+ "' auth scheme for " + authhost);
+ }
+ authCache.remove(authhost);
+ }
+
+ private boolean isCachable(final AuthScheme authScheme) {
+ if (authScheme == null || !authScheme.isComplete()) {
+ return false;
+ }
+ String schemeName = authScheme.getSchemeName();
+ return schemeName.equalsIgnoreCase(AuthPolicy.BASIC) ||
+ schemeName.equalsIgnoreCase(AuthPolicy.DIGEST);
+ }
+
+ public AuthenticationHandler getHandler() {
+ return this.handler;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyImpl.java b/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyImpl.java
new file mode 100644
index 0000000..3d1ff95
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyImpl.java
@@ -0,0 +1,260 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Queue;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.FormattedHeader;
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.annotation.Immutable;
+import org.apache.http.auth.AuthOption;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthSchemeRegistry;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.MalformedChallengeException;
+import org.apache.http.client.AuthCache;
+import org.apache.http.client.AuthenticationStrategy;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.CharArrayBuffer;
+
+ at Immutable
+class AuthenticationStrategyImpl implements AuthenticationStrategy {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ private static final List<String> DEFAULT_SCHEME_PRIORITY =
+ Collections.unmodifiableList(Arrays.asList(new String[] {
+ AuthPolicy.SPNEGO,
+ AuthPolicy.KERBEROS,
+ AuthPolicy.NTLM,
+ AuthPolicy.DIGEST,
+ AuthPolicy.BASIC
+ }));
+
+ private final int challengeCode;
+ private final String headerName;
+ private final String prefParamName;
+
+ AuthenticationStrategyImpl(int challengeCode, final String headerName, final String prefParamName) {
+ super();
+ this.challengeCode = challengeCode;
+ this.headerName = headerName;
+ this.prefParamName = prefParamName;
+ }
+
+ public boolean isAuthenticationRequested(
+ final HttpHost authhost,
+ final HttpResponse response,
+ final HttpContext context) {
+ if (response == null) {
+ throw new IllegalArgumentException("HTTP response may not be null");
+ }
+ int status = response.getStatusLine().getStatusCode();
+ return status == this.challengeCode;
+ }
+
+ public Map<String, Header> getChallenges(
+ final HttpHost authhost,
+ final HttpResponse response,
+ final HttpContext context) throws MalformedChallengeException {
+ if (response == null) {
+ throw new IllegalArgumentException("HTTP response may not be null");
+ }
+ Header[] headers = response.getHeaders(this.headerName);
+ Map<String, Header> map = new HashMap<String, Header>(headers.length);
+ for (Header header : headers) {
+ CharArrayBuffer buffer;
+ int pos;
+ if (header instanceof FormattedHeader) {
+ buffer = ((FormattedHeader) header).getBuffer();
+ pos = ((FormattedHeader) header).getValuePos();
+ } else {
+ String s = header.getValue();
+ if (s == null) {
+ throw new MalformedChallengeException("Header value is null");
+ }
+ buffer = new CharArrayBuffer(s.length());
+ buffer.append(s);
+ pos = 0;
+ }
+ while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) {
+ pos++;
+ }
+ int beginIndex = pos;
+ while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) {
+ pos++;
+ }
+ int endIndex = pos;
+ String s = buffer.substring(beginIndex, endIndex);
+ map.put(s.toLowerCase(Locale.US), header);
+ }
+ return map;
+ }
+
+ public Queue<AuthOption> select(
+ final Map<String, Header> challenges,
+ final HttpHost authhost,
+ final HttpResponse response,
+ final HttpContext context) throws MalformedChallengeException {
+ if (challenges == null) {
+ throw new IllegalArgumentException("Map of auth challenges may not be null");
+ }
+ if (authhost == null) {
+ throw new IllegalArgumentException("Host may not be null");
+ }
+ if (response == null) {
+ throw new IllegalArgumentException("HTTP response may not be null");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("HTTP context may not be null");
+ }
+
+ Queue<AuthOption> options = new LinkedList<AuthOption>();
+ AuthSchemeRegistry registry = (AuthSchemeRegistry) context.getAttribute(
+ ClientContext.AUTHSCHEME_REGISTRY);
+ if (registry == null) {
+ this.log.debug("Auth scheme registry not set in the context");
+ return options;
+ }
+ CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(
+ ClientContext.CREDS_PROVIDER);
+ if (credsProvider == null) {
+ this.log.debug("Credentials provider not set in the context");
+ return options;
+ }
+
+ @SuppressWarnings("unchecked")
+ List<String> authPrefs = (List<String>) response.getParams().getParameter(this.prefParamName);
+ if (authPrefs == null) {
+ authPrefs = DEFAULT_SCHEME_PRIORITY;
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Authentication schemes in the order of preference: " + authPrefs);
+ }
+
+ for (String id: authPrefs) {
+ Header challenge = challenges.get(id.toLowerCase(Locale.US));
+ if (challenge != null) {
+ try {
+ AuthScheme authScheme = registry.getAuthScheme(id, response.getParams());
+ authScheme.processChallenge(challenge);
+
+ AuthScope authScope = new AuthScope(
+ authhost.getHostName(),
+ authhost.getPort(),
+ authScheme.getRealm(),
+ authScheme.getSchemeName());
+
+ Credentials credentials = credsProvider.getCredentials(authScope);
+ if (credentials != null) {
+ options.add(new AuthOption(authScheme, credentials));
+ }
+ } catch (IllegalStateException e) {
+ if (this.log.isWarnEnabled()) {
+ this.log.warn("Authentication scheme " + id + " not supported");
+ // Try again
+ }
+ }
+ } else {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Challenge for " + id + " authentication scheme not available");
+ // Try again
+ }
+ }
+ }
+ return options;
+ }
+
+ public void authSucceeded(
+ final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) {
+ if (authhost == null) {
+ throw new IllegalArgumentException("Host may not be null");
+ }
+ if (authScheme == null) {
+ throw new IllegalArgumentException("Auth scheme may not be null");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("HTTP context may not be null");
+ }
+ if (isCachable(authScheme)) {
+ AuthCache authCache = (AuthCache) context.getAttribute(ClientContext.AUTH_CACHE);
+ if (authCache == null) {
+ authCache = new BasicAuthCache();
+ context.setAttribute(ClientContext.AUTH_CACHE, authCache);
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Caching '" + authScheme.getSchemeName() +
+ "' auth scheme for " + authhost);
+ }
+ authCache.put(authhost, authScheme);
+ }
+ }
+
+ protected boolean isCachable(final AuthScheme authScheme) {
+ if (authScheme == null || !authScheme.isComplete()) {
+ return false;
+ }
+ String schemeName = authScheme.getSchemeName();
+ return schemeName.equalsIgnoreCase(AuthPolicy.BASIC) ||
+ schemeName.equalsIgnoreCase(AuthPolicy.DIGEST);
+ }
+
+ public void authFailed(
+ final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) {
+ if (authhost == null) {
+ throw new IllegalArgumentException("Host may not be null");
+ }
+ if (context == null) {
+ throw new IllegalArgumentException("HTTP context may not be null");
+ }
+ AuthCache authCache = (AuthCache) context.getAttribute(ClientContext.AUTH_CACHE);
+ if (authCache != null) {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Clearing cached auth scheme for " + authhost);
+ }
+ authCache.remove(authhost);
+ }
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/AutoRetryHttpClient.java b/httpclient/src/main/java/org/apache/http/impl/client/AutoRetryHttpClient.java
new file mode 100644
index 0000000..7eb72c1
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/AutoRetryHttpClient.java
@@ -0,0 +1,179 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.URI;
+
+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.annotation.ThreadSafe;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.ServiceUnavailableRetryStrategy;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * {@link HttpClient} implementation that can automatically retry the request in case of
+ * a non-2xx response using the {@link ServiceUnavailableRetryStrategy} interface.
+ *
+ * @since 4.2
+ */
+ at ThreadSafe
+public class AutoRetryHttpClient implements HttpClient {
+
+ private final HttpClient backend;
+
+ private final ServiceUnavailableRetryStrategy retryStrategy;
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ public AutoRetryHttpClient(
+ final HttpClient client, final ServiceUnavailableRetryStrategy retryStrategy) {
+ super();
+ if (client == null) {
+ throw new IllegalArgumentException("HttpClient may not be null");
+ }
+ if (retryStrategy == null) {
+ throw new IllegalArgumentException(
+ "ServiceUnavailableRetryStrategy may not be null");
+ }
+ this.backend = client;
+ this.retryStrategy = retryStrategy;
+ }
+
+ /**
+ * Constructs a {@code AutoRetryHttpClient} with default caching settings that
+ * stores cache entries in memory and uses a vanilla
+ * {@link DefaultHttpClient} for backend requests.
+ */
+ public AutoRetryHttpClient() {
+ this(new DefaultHttpClient(), new DefaultServiceUnavailableRetryStrategy());
+ }
+
+ /**
+ * Constructs a {@code AutoRetryHttpClient} with the given caching options that
+ * stores cache entries in memory and uses a vanilla
+ * {@link DefaultHttpClient} for backend requests.
+ *
+ * @param config
+ * retry configuration module options
+ */
+ public AutoRetryHttpClient(ServiceUnavailableRetryStrategy config) {
+ this(new DefaultHttpClient(), config);
+ }
+
+ /**
+ * Constructs a {@code AutoRetryHttpClient} with default caching settings that
+ * stores cache entries in memory and uses the given {@link HttpClient} for
+ * backend requests.
+ *
+ * @param client
+ * used to make origin requests
+ */
+ public AutoRetryHttpClient(HttpClient client) {
+ this(client, new DefaultServiceUnavailableRetryStrategy());
+ }
+
+ 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 HttpResponse execute(HttpHost target, HttpRequest request,
+ HttpContext context) throws IOException {
+ for (int c = 1;; c++) {
+ HttpResponse response = backend.execute(target, request, context);
+ if (retryStrategy.retryRequest(response, c, context)) {
+ long nextInterval = retryStrategy.getRetryInterval();
+ try {
+ log.trace("Wait for " + nextInterval);
+ Thread.sleep(nextInterval);
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException(e.getMessage());
+ }
+ } else {
+ return response;
+ }
+ }
+ }
+
+ public ClientConnectionManager getConnectionManager() {
+ return backend.getConnectionManager();
+ }
+
+ public HttpParams getParams() {
+ return backend.getParams();
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/BasicCookieStore.java b/httpclient/src/main/java/org/apache/http/impl/client/BasicCookieStore.java
index 09fc020..7c67aa4 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/BasicCookieStore.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/BasicCookieStore.java
@@ -27,7 +27,11 @@
package org.apache.http.impl.client;
import java.io.Serializable;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeSet;
import org.apache.http.annotation.GuardedBy;
import org.apache.http.annotation.ThreadSafe;
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/BasicResponseHandler.java b/httpclient/src/main/java/org/apache/http/impl/client/BasicResponseHandler.java
index 5ad5427..54df962 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/BasicResponseHandler.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/BasicResponseHandler.java
@@ -42,13 +42,12 @@ import org.apache.http.util.EntityUtils;
* A {@link ResponseHandler} that returns the response body as a String
* for successful (2xx) responses. If the response code was >= 300, the response
* body is consumed and an {@link HttpResponseException} is thrown.
- *
+ * <p/>
* If this is used with
* {@link org.apache.http.client.HttpClient#execute(
* org.apache.http.client.methods.HttpUriRequest, ResponseHandler)},
* HttpClient may handle redirects (3xx responses) internally.
*
- *
* @since 4.0
*/
@Immutable
@@ -63,12 +62,12 @@ public class BasicResponseHandler implements ResponseHandler<String> {
public String handleResponse(final HttpResponse response)
throws HttpResponseException, IOException {
StatusLine statusLine = response.getStatusLine();
+ HttpEntity entity = response.getEntity();
if (statusLine.getStatusCode() >= 300) {
+ EntityUtils.consume(entity);
throw new HttpResponseException(statusLine.getStatusCode(),
statusLine.getReasonPhrase());
}
-
- HttpEntity entity = response.getEntity();
return entity == null ? null : EntityUtils.toString(entity);
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/Clock.java b/httpclient/src/main/java/org/apache/http/impl/client/Clock.java
new file mode 100644
index 0000000..e3ef4bc
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/Clock.java
@@ -0,0 +1,42 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client;
+
+/**
+ * Interface used to enable easier testing of time-related behavior.
+ *
+ * @since 4.2
+ *
+ */
+interface Clock {
+
+ /**
+ * Returns the current time, expressed as the number of
+ * milliseconds since the epoch.
+ * @return current time
+ */
+ long getCurrentTime();
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/ContentEncodingHttpClient.java b/httpclient/src/main/java/org/apache/http/impl/client/ContentEncodingHttpClient.java
index 82e3a68..2a08d99 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/ContentEncodingHttpClient.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/ContentEncodingHttpClient.java
@@ -27,6 +27,7 @@
package org.apache.http.impl.client;
import org.apache.http.annotation.ThreadSafe;
+import org.apache.http.client.HttpClient;
import org.apache.http.client.protocol.RequestAcceptEncoding;
import org.apache.http.client.protocol.ResponseContentEncoding;
import org.apache.http.conn.ClientConnectionManager;
@@ -36,9 +37,18 @@ import org.apache.http.protocol.BasicHttpProcessor;
/**
* {@link DefaultHttpClient} sub-class which includes a {@link RequestAcceptEncoding}
* for the request and response.
+ *
+ * <b>Deprecation note:</b> due to the way this class modifies a response body
+ * without changing the response headers to reflect the entity changes, it cannot
+ * be used as the "backend" for a caching {@link HttpClient} and still
+ * have uncompressed responses be cached. Users are encouraged to use the
+ * {@link DecompressingHttpClient} instead of this class, which can be wired in
+ * either before or after caching, depending on whether you want to cache
+ * responses in compressed or uncompressed form.
*
* @since 4.1
*/
+ at Deprecated
@ThreadSafe // since DefaultHttpClient is
public class ContentEncodingHttpClient extends DefaultHttpClient {
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/DecompressingHttpClient.java b/httpclient/src/main/java/org/apache/http/impl/client/DecompressingHttpClient.java
new file mode 100644
index 0000000..26307a1
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/DecompressingHttpClient.java
@@ -0,0 +1,180 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+package org.apache.http.impl.client;
+
+import java.io.IOException;
+import java.net.URI;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpResponseInterceptor;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.protocol.RequestAcceptEncoding;
+import org.apache.http.client.protocol.ResponseContentEncoding;
+import org.apache.http.client.utils.URIUtils;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+
+/**
+ * <p>Decorator adding support for compressed responses. This class sets
+ * the <code>Accept-Encoding</code> header on requests to indicate
+ * support for the <code>gzip</code> and <code>deflate</code>
+ * compression schemes; it then checks the <code>Content-Encoding</code>
+ * header on the response to uncompress any compressed response bodies.
+ * The {@link java.io.InputStream} of the entity will contain the uncompressed
+ * content.</p>
+ *
+ * <p><b>N.B.</b> Any upstream clients of this class need to be aware that
+ * this effectively obscures visibility into the length of a server
+ * response body, since the <code>Content-Length</code> header will
+ * correspond to the compressed entity length received from the server,
+ * but the content length experienced by reading the response body may
+ * be different (hopefully higher!).</p>
+ *
+ * <p>That said, this decorator is compatible with the
+ * <code>CachingHttpClient</code> in that the two decorators can be added
+ * in either order and still have cacheable responses be cached.</p>
+ *
+ * @since 4.2
+ */
+public class DecompressingHttpClient implements HttpClient {
+
+ private HttpClient backend;
+ private HttpRequestInterceptor acceptEncodingInterceptor;
+ private HttpResponseInterceptor contentEncodingInterceptor;
+
+ /**
+ * Constructs a decorator to ask for and handle compressed
+ * entities on the fly.
+ * @param backend the {@link HttpClient} to use for actually
+ * issuing requests
+ */
+ public DecompressingHttpClient(HttpClient backend) {
+ this(backend, new RequestAcceptEncoding(), new ResponseContentEncoding());
+ }
+
+ DecompressingHttpClient(HttpClient backend,
+ HttpRequestInterceptor requestInterceptor,
+ HttpResponseInterceptor responseInterceptor) {
+ this.backend = backend;
+ this.acceptEncodingInterceptor = requestInterceptor;
+ this.contentEncodingInterceptor = responseInterceptor;
+ }
+
+ public HttpParams getParams() {
+ return backend.getParams();
+ }
+
+ public ClientConnectionManager getConnectionManager() {
+ return backend.getConnectionManager();
+ }
+
+ public HttpResponse execute(HttpUriRequest request) throws IOException,
+ ClientProtocolException {
+ return execute(getHttpHost(request), request, (HttpContext)null);
+ }
+
+ HttpHost getHttpHost(HttpUriRequest request) {
+ URI uri = request.getURI();
+ return URIUtils.extractHost(uri);
+ }
+
+ public HttpResponse execute(HttpUriRequest request, HttpContext context)
+ throws IOException, ClientProtocolException {
+ return execute(getHttpHost(request), request, context);
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request)
+ throws IOException, ClientProtocolException {
+ return execute(target, request, (HttpContext)null);
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request,
+ HttpContext context) throws IOException, ClientProtocolException {
+ try {
+ if (context == null) context = new BasicHttpContext();
+ HttpRequest wrapped;
+ if (request instanceof HttpEntityEnclosingRequest) {
+ wrapped = new EntityEnclosingRequestWrapper((HttpEntityEnclosingRequest) request);
+ } else {
+ wrapped = new RequestWrapper(request);
+ }
+ acceptEncodingInterceptor.process(wrapped, context);
+ HttpResponse response = backend.execute(target, wrapped, context);
+ contentEncodingInterceptor.process(response, context);
+ if (Boolean.TRUE.equals(context.getAttribute(ResponseContentEncoding.UNCOMPRESSED))) {
+ response.removeHeaders("Content-Length");
+ response.removeHeaders("Content-Encoding");
+ response.removeHeaders("Content-MD5");
+ }
+ return response;
+ } catch (HttpException e) {
+ throw new ClientProtocolException(e);
+ }
+ }
+
+ public <T> T execute(HttpUriRequest request,
+ ResponseHandler<? extends T> responseHandler) throws IOException,
+ ClientProtocolException {
+ return execute(getHttpHost(request), request, responseHandler);
+ }
+
+ public <T> T execute(HttpUriRequest request,
+ ResponseHandler<? extends T> responseHandler, HttpContext context)
+ throws IOException, ClientProtocolException {
+ return execute(getHttpHost(request), request, responseHandler, context);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler) throws IOException,
+ ClientProtocolException {
+ return execute(target, request, responseHandler, null);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler, HttpContext context)
+ throws IOException, ClientProtocolException {
+ HttpResponse response = execute(target, request, context);
+ try {
+ return responseHandler.handleResponse(response);
+ } finally {
+ HttpEntity entity = response.getEntity();
+ if (entity != null) EntityUtils.consume(entity);
+ }
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/DefaultBackoffStrategy.java b/httpclient/src/main/java/org/apache/http/impl/client/DefaultBackoffStrategy.java
new file mode 100644
index 0000000..7d45ca4
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/DefaultBackoffStrategy.java
@@ -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.impl.client;
+
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.ConnectionBackoffStrategy;
+
+/**
+ * This {@link ConnectionBackoffStrategy} backs off either for a raw
+ * network socket or connection timeout or if the server explicitly
+ * sends a 503 (Service Unavailable) response.
+ *
+ * @since 4.2
+ */
+public class DefaultBackoffStrategy implements ConnectionBackoffStrategy {
+
+ public boolean shouldBackoff(Throwable t) {
+ return (t instanceof SocketTimeoutException
+ || t instanceof ConnectException);
+ }
+
+ public boolean shouldBackoff(HttpResponse resp) {
+ return (resp.getStatusLine().getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE);
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java b/httpclient/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java
index 872eac8..e4eb54f 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java
@@ -36,7 +36,6 @@ import org.apache.http.client.protocol.RequestClientConnControl;
import org.apache.http.client.protocol.RequestDefaultHeaders;
import org.apache.http.client.protocol.RequestProxyAuthentication;
import org.apache.http.client.protocol.RequestTargetAuthentication;
-import org.apache.http.client.protocol.ResponseAuthCache;
import org.apache.http.client.protocol.ResponseProcessCookies;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.params.CoreConnectionPNames;
@@ -56,19 +55,16 @@ import org.apache.http.util.VersionInfo;
/**
* Default implementation of {@link HttpClient} pre-configured for most common use scenarios.
* <p>
- * This class creates the following chain of protocol interceptors per default:
- * <ul>
- * <li>{@link RequestDefaultHeaders}</li>
- * <li>{@link RequestContent}</li>
- * <li>{@link RequestTargetHost}</li>
- * <li>{@link RequestClientConnControl}</li>
- * <li>{@link RequestUserAgent}</li>
- * <li>{@link RequestExpectContinue}</li>
- * <li>{@link RequestAddCookies}</li>
- * <li>{@link ResponseProcessCookies}</li>
- * <li>{@link RequestTargetAuthentication}</li>
- * <li>{@link RequestProxyAuthentication}</li>
- * </ul>
+ * Please see the Javadoc for {@link #createHttpProcessor()} for the details of the interceptors
+ * that are set up by default.
+ * <p>
+ * Additional interceptors can be added as follows, but
+ * take care not to add the same interceptor more than once.
+ * <pre>
+ * DefaultHttpClient httpclient = new DefaultHttpClient();
+ * httpclient.addRequestInterceptor(new RequestAcceptEncoding());
+ * httpclient.addResponseInterceptor(new ResponseContentEncoding());
+ * </pre>
* <p>
* This class sets up the following parameters if not explicitly set:
* <ul>
@@ -111,7 +107,7 @@ import org.apache.http.util.VersionInfo;
* <li>{@link org.apache.http.client.params.ClientPNames#VIRTUAL_HOST}</li>
* <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HOST}</li>
* <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HEADERS}</li>
- * <li>{@link org.apache.http.client.params.ClientPNames#CONNECTION_MANAGER_FACTORY_CLASS_NAME}</li>
+ * <li>{@link org.apache.http.client.params.ClientPNames#CONN_MANAGER_TIMEOUT}</li>
* </ul>
*
* @since 4.0
@@ -176,7 +172,7 @@ public class DefaultHttpClient extends AbstractHttpClient {
*/
public static void setDefaultHttpParams(HttpParams params) {
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
- HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
+ HttpProtocolParams.setContentCharset(params, HTTP.DEF_CONTENT_CHARSET.name());
HttpConnectionParams.setTcpNoDelay(params, true);
HttpConnectionParams.setSocketBufferSize(params, 8192);
@@ -189,7 +185,24 @@ public class DefaultHttpClient extends AbstractHttpClient {
"Apache-HttpClient/" + release + " (java 1.5)");
}
-
+ /**
+ * Create the processor with the following interceptors:
+ * <ul>
+ * <li>{@link RequestDefaultHeaders}</li>
+ * <li>{@link RequestContent}</li>
+ * <li>{@link RequestTargetHost}</li>
+ * <li>{@link RequestClientConnControl}</li>
+ * <li>{@link RequestUserAgent}</li>
+ * <li>{@link RequestExpectContinue}</li>
+ * <li>{@link RequestAddCookies}</li>
+ * <li>{@link ResponseProcessCookies}</li>
+ * <li>{@link RequestAuthCache}</li>
+ * <li>{@link RequestTargetAuthentication}</li>
+ * <li>{@link RequestProxyAuthentication}</li>
+ * </ul>
+ * <p>
+ * @return the processor with the added interceptors.
+ */
@Override
protected BasicHttpProcessor createHttpProcessor() {
BasicHttpProcessor httpproc = new BasicHttpProcessor();
@@ -206,7 +219,6 @@ public class DefaultHttpClient extends AbstractHttpClient {
httpproc.addInterceptor(new ResponseProcessCookies());
// HTTP authentication interceptors
httpproc.addInterceptor(new RequestAuthCache());
- httpproc.addInterceptor(new ResponseAuthCache());
httpproc.addInterceptor(new RequestTargetAuthentication());
httpproc.addInterceptor(new RequestProxyAuthentication());
return httpproc;
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java b/httpclient/src/main/java/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java
index 9cfecc7..d50e5f6 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java
@@ -41,6 +41,7 @@ import org.apache.http.HttpRequest;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.client.methods.HttpUriRequest;
/**
* The default {@link HttpRequestRetryHandler} used by request executors.
@@ -109,6 +110,11 @@ public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler {
HttpRequest request = (HttpRequest)
context.getAttribute(ExecutionContext.HTTP_REQUEST);
+
+ if(requestIsAborted(request)){
+ return false;
+ }
+
if (handleAsIdempotent(request)) {
// Retry if the request is considered idempotent
return true;
@@ -142,8 +148,22 @@ public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler {
return retryCount;
}
- private boolean handleAsIdempotent(final HttpRequest request) {
+ /**
+ * @since 4.2
+ */
+ protected boolean handleAsIdempotent(final HttpRequest request) {
return !(request instanceof HttpEntityEnclosingRequest);
}
+ /**
+ * @since 4.2
+ */
+ protected boolean requestIsAborted(final HttpRequest request) {
+ HttpRequest req = request;
+ if (request instanceof RequestWrapper) { // does not forward request to original
+ req = ((RequestWrapper) request).getOriginal();
+ }
+ return (req instanceof HttpUriRequest && ((HttpUriRequest)req).isAborted());
+ }
+
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java b/httpclient/src/main/java/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java
index e3a96d1..345c347 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java
@@ -46,7 +46,10 @@ import org.apache.http.protocol.HttpContext;
* authentication.
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link ProxyAuthenticationStrategy}
*/
+ at Deprecated
@Immutable
public class DefaultProxyAuthenticationHandler extends AbstractAuthenticationHandler {
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectHandler.java b/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectHandler.java
index 850a33b..4b1a3f3 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectHandler.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectHandler.java
@@ -55,7 +55,7 @@ import org.apache.http.protocol.ExecutionContext;
*
* @since 4.0
*
- * @deprecated use {@link DefaultRedirectStrategy}.
+ * @deprecated (4.1) use {@link DefaultRedirectStrategy}.
*/
@Immutable
@Deprecated
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectStrategy.java b/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectStrategy.java
index c41ee38..bdec33f 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectStrategy.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectStrategy.java
@@ -52,8 +52,17 @@ import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.ExecutionContext;
/**
- * Default implementation of {@link RedirectStrategy}.
+ * Default implementation of {@link RedirectStrategy}. This strategy honors the restrictions
+ * on automatic redirection of entity enclosing methods such as POST and PUT imposed by the
+ * HTTP specification. <tt>302 Moved Temporarily</tt>, <tt>301 Moved Permanently</tt> and
+ * <tt>307 Temporary Redirect</tt> status codes will result in an automatic redirect of
+ * HEAD and GET methods only. POST and PUT methods will not be automatically redirected
+ * as requiring user confirmation.
+ * <p/>
+ * The restriction on automatic redirection of POST methods can be relaxed by using
+ * {@link LaxRedirectStrategy} instead of {@link DefaultRedirectStrategy}.
*
+ * @see LaxRedirectStrategy
* @since 4.1
*/
@Immutable
@@ -63,6 +72,14 @@ public class DefaultRedirectStrategy implements RedirectStrategy {
public static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations";
+ /**
+ * Redirectable methods.
+ */
+ private static final String[] REDIRECT_METHODS = new String[] {
+ HttpGet.METHOD_NAME,
+ HttpHead.METHOD_NAME
+ };
+
public DefaultRedirectStrategy() {
super();
}
@@ -71,6 +88,9 @@ public class DefaultRedirectStrategy implements RedirectStrategy {
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws ProtocolException {
+ if (request == null) {
+ throw new IllegalArgumentException("HTTP request may not be null");
+ }
if (response == null) {
throw new IllegalArgumentException("HTTP response may not be null");
}
@@ -80,12 +100,10 @@ public class DefaultRedirectStrategy implements RedirectStrategy {
Header locationHeader = response.getFirstHeader("location");
switch (statusCode) {
case HttpStatus.SC_MOVED_TEMPORARILY:
- return (method.equalsIgnoreCase(HttpGet.METHOD_NAME)
- || method.equalsIgnoreCase(HttpHead.METHOD_NAME)) && locationHeader != null;
+ return isRedirectable(method) && locationHeader != null;
case HttpStatus.SC_MOVED_PERMANENTLY:
case HttpStatus.SC_TEMPORARY_REDIRECT:
- return method.equalsIgnoreCase(HttpGet.METHOD_NAME)
- || method.equalsIgnoreCase(HttpHead.METHOD_NAME);
+ return isRedirectable(method);
case HttpStatus.SC_SEE_OTHER:
return true;
default:
@@ -97,9 +115,15 @@ public class DefaultRedirectStrategy implements RedirectStrategy {
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws ProtocolException {
+ if (request == null) {
+ throw new IllegalArgumentException("HTTP request may not be null");
+ }
if (response == null) {
throw new IllegalArgumentException("HTTP response may not be null");
}
+ if (context == null) {
+ throw new IllegalArgumentException("HTTP context may not be null");
+ }
//get the location header to find out where to redirect to
Header locationHeader = response.getFirstHeader("location");
if (locationHeader == null) {
@@ -115,63 +139,43 @@ public class DefaultRedirectStrategy implements RedirectStrategy {
URI uri = createLocationURI(location);
- HttpParams params = response.getParams();
+ HttpParams params = request.getParams();
// rfc2616 demands the location value be a complete URI
// Location = "Location" ":" absoluteURI
- if (!uri.isAbsolute()) {
- if (params.isParameterTrue(ClientPNames.REJECT_RELATIVE_REDIRECT)) {
- throw new ProtocolException("Relative redirect location '"
- + uri + "' not allowed");
- }
- // Adjust location URI
- HttpHost target = (HttpHost) context.getAttribute(
- ExecutionContext.HTTP_TARGET_HOST);
- if (target == null) {
- throw new IllegalStateException("Target host not available " +
- "in the HTTP context");
- }
- try {
+ try {
+ // Drop fragment
+ uri = URIUtils.rewriteURI(uri);
+ if (!uri.isAbsolute()) {
+ if (params.isParameterTrue(ClientPNames.REJECT_RELATIVE_REDIRECT)) {
+ throw new ProtocolException("Relative redirect location '"
+ + uri + "' not allowed");
+ }
+ // Adjust location URI
+ HttpHost target = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
+ if (target == null) {
+ throw new IllegalStateException("Target host not available " +
+ "in the HTTP context");
+ }
URI requestURI = new URI(request.getRequestLine().getUri());
URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target, true);
uri = URIUtils.resolve(absoluteRequestURI, uri);
- } catch (URISyntaxException ex) {
- throw new ProtocolException(ex.getMessage(), ex);
}
+ } catch (URISyntaxException ex) {
+ throw new ProtocolException(ex.getMessage(), ex);
}
+ RedirectLocations redirectLocations = (RedirectLocations) context.getAttribute(
+ REDIRECT_LOCATIONS);
+ if (redirectLocations == null) {
+ redirectLocations = new RedirectLocations();
+ context.setAttribute(REDIRECT_LOCATIONS, redirectLocations);
+ }
if (params.isParameterFalse(ClientPNames.ALLOW_CIRCULAR_REDIRECTS)) {
-
- RedirectLocations redirectLocations = (RedirectLocations) context.getAttribute(
- REDIRECT_LOCATIONS);
-
- if (redirectLocations == null) {
- redirectLocations = new RedirectLocations();
- context.setAttribute(REDIRECT_LOCATIONS, redirectLocations);
- }
-
- URI redirectURI;
- if (uri.getFragment() != null) {
- try {
- HttpHost target = new HttpHost(
- uri.getHost(),
- uri.getPort(),
- uri.getScheme());
- redirectURI = URIUtils.rewriteURI(uri, target, true);
- } catch (URISyntaxException ex) {
- throw new ProtocolException(ex.getMessage(), ex);
- }
- } else {
- redirectURI = uri;
- }
-
- if (redirectLocations.contains(redirectURI)) {
- throw new CircularRedirectException("Circular redirect to '" +
- redirectURI + "'");
- } else {
- redirectLocations.add(redirectURI);
+ if (redirectLocations.contains(uri)) {
+ throw new CircularRedirectException("Circular redirect to '" + uri + "'");
}
}
-
+ redirectLocations.add(uri);
return uri;
}
@@ -180,12 +184,24 @@ public class DefaultRedirectStrategy implements RedirectStrategy {
*/
protected URI createLocationURI(final String location) throws ProtocolException {
try {
- return new URI(location);
+ return new URI(location).normalize();
} catch (URISyntaxException ex) {
throw new ProtocolException("Invalid redirect URI: " + location, ex);
}
}
+ /**
+ * @since 4.2
+ */
+ protected boolean isRedirectable(final String method) {
+ for (String m: REDIRECT_METHODS) {
+ if (m.equalsIgnoreCase(method)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public HttpUriRequest getRedirect(
final HttpRequest request,
final HttpResponse response,
@@ -199,4 +215,4 @@ public class DefaultRedirectStrategy implements RedirectStrategy {
}
}
-}
\ No newline at end of file
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectStrategyAdaptor.java b/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectStrategyAdaptor.java
index a4da7fd..7a592aa 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectStrategyAdaptor.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/DefaultRedirectStrategyAdaptor.java
@@ -33,6 +33,7 @@ import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
import org.apache.http.annotation.Immutable;
+import org.apache.http.client.RedirectHandler;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
@@ -40,16 +41,15 @@ import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.protocol.HttpContext;
/**
- * @since 4.1
+ * @deprecated (4.1) do not use
*/
@Immutable
- at Deprecated
+ at Deprecated
class DefaultRedirectStrategyAdaptor implements RedirectStrategy {
- private final org.apache.http.client.RedirectHandler handler;
+ private final RedirectHandler handler;
- @Deprecated
- public DefaultRedirectStrategyAdaptor(final org.apache.http.client.RedirectHandler handler) {
+ public DefaultRedirectStrategyAdaptor(final RedirectHandler handler) {
super();
this.handler = handler;
}
@@ -74,4 +74,8 @@ class DefaultRedirectStrategyAdaptor implements RedirectStrategy {
}
}
+ public RedirectHandler getHandler() {
+ return this.handler;
+ }
+
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/DefaultRequestDirector.java b/httpclient/src/main/java/org/apache/http/impl/client/DefaultRequestDirector.java
index d9818f1..c2e9558 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/DefaultRequestDirector.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/DefaultRequestDirector.java
@@ -31,16 +31,11 @@ import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.Locale;
-import java.util.Map;
import java.util.concurrent.TimeUnit;
-import org.apache.http.annotation.NotThreadSafe;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.ConnectionReuseStrategy;
-import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
@@ -49,19 +44,19 @@ import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
import org.apache.http.ProtocolVersion;
+import org.apache.http.annotation.NotThreadSafe;
+import org.apache.http.auth.AuthProtocolState;
import org.apache.http.auth.AuthScheme;
-import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
-import org.apache.http.auth.AuthenticationException;
-import org.apache.http.auth.Credentials;
-import org.apache.http.auth.MalformedChallengeException;
+import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthenticationHandler;
-import org.apache.http.client.RedirectStrategy;
-import org.apache.http.client.RequestDirector;
-import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.AuthenticationStrategy;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.NonRepeatableRequestException;
import org.apache.http.client.RedirectException;
+import org.apache.http.client.RedirectHandler;
+import org.apache.http.client.RedirectStrategy;
+import org.apache.http.client.RequestDirector;
import org.apache.http.client.UserTokenHandler;
import org.apache.http.client.methods.AbortableHttpRequest;
import org.apache.http.client.methods.HttpUriRequest;
@@ -80,6 +75,7 @@ import org.apache.http.conn.routing.HttpRouteDirector;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.entity.BufferedHttpEntity;
+import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.conn.ConnectionShutdownException;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.params.HttpConnectionParams;
@@ -126,10 +122,12 @@ import org.apache.http.util.EntityUtils;
* <li>{@link org.apache.http.client.params.ClientPNames#VIRTUAL_HOST}</li>
* <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HOST}</li>
* <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HEADERS}</li>
+ * <li>{@link org.apache.http.client.params.ClientPNames#CONN_MANAGER_TIMEOUT}</li>
* </ul>
*
* @since 4.0
*/
+ at SuppressWarnings("deprecation")
@NotThreadSafe // e.g. managedConn
public class DefaultRequestDirector implements RequestDirector {
@@ -158,17 +156,25 @@ public class DefaultRequestDirector implements RequestDirector {
/** The redirect handler. */
@Deprecated
- protected final org.apache.http.client.RedirectHandler redirectHandler = null;
+ protected final RedirectHandler redirectHandler;
/** The redirect strategy. */
protected final RedirectStrategy redirectStrategy;
/** The target authentication handler. */
+ @Deprecated
protected final AuthenticationHandler targetAuthHandler;
+ /** The target authentication handler. */
+ protected final AuthenticationStrategy targetAuthStrategy;
+
/** The proxy authentication handler. */
+ @Deprecated
protected final AuthenticationHandler proxyAuthHandler;
+ /** The proxy authentication handler. */
+ protected final AuthenticationStrategy proxyAuthStrategy;
+
/** The user token handler. */
protected final UserTokenHandler userTokenHandler;
@@ -182,6 +188,8 @@ public class DefaultRequestDirector implements RequestDirector {
protected final AuthState proxyAuthState;
+ private final HttpAuthenticator authenticator;
+
private int execCount;
private int redirectCount;
@@ -199,7 +207,7 @@ public class DefaultRequestDirector implements RequestDirector {
final HttpRoutePlanner rouplan,
final HttpProcessor httpProcessor,
final HttpRequestRetryHandler retryHandler,
- final org.apache.http.client.RedirectHandler redirectHandler,
+ final RedirectHandler redirectHandler,
final AuthenticationHandler targetAuthHandler,
final AuthenticationHandler proxyAuthHandler,
final UserTokenHandler userTokenHandler,
@@ -207,13 +215,14 @@ public class DefaultRequestDirector implements RequestDirector {
this(LogFactory.getLog(DefaultRequestDirector.class),
requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler,
new DefaultRedirectStrategyAdaptor(redirectHandler),
- targetAuthHandler, proxyAuthHandler, userTokenHandler, params);
+ new AuthenticationStrategyAdaptor(targetAuthHandler),
+ new AuthenticationStrategyAdaptor(proxyAuthHandler),
+ userTokenHandler,
+ params);
}
- /**
- * @since 4.1
- */
+ @Deprecated
public DefaultRequestDirector(
final Log log,
final HttpRequestExecutor requestExec,
@@ -228,6 +237,32 @@ public class DefaultRequestDirector implements RequestDirector {
final AuthenticationHandler proxyAuthHandler,
final UserTokenHandler userTokenHandler,
final HttpParams params) {
+ this(LogFactory.getLog(DefaultRequestDirector.class),
+ requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler,
+ redirectStrategy,
+ new AuthenticationStrategyAdaptor(targetAuthHandler),
+ new AuthenticationStrategyAdaptor(proxyAuthHandler),
+ userTokenHandler,
+ params);
+ }
+
+ /**
+ * @since 4.2
+ */
+ public DefaultRequestDirector(
+ final Log log,
+ final HttpRequestExecutor requestExec,
+ final ClientConnectionManager conman,
+ final ConnectionReuseStrategy reustrat,
+ final ConnectionKeepAliveStrategy kastrat,
+ final HttpRoutePlanner rouplan,
+ final HttpProcessor httpProcessor,
+ final HttpRequestRetryHandler retryHandler,
+ final RedirectStrategy redirectStrategy,
+ final AuthenticationStrategy targetAuthStrategy,
+ final AuthenticationStrategy proxyAuthStrategy,
+ final UserTokenHandler userTokenHandler,
+ final HttpParams params) {
if (log == null) {
throw new IllegalArgumentException
@@ -265,13 +300,13 @@ public class DefaultRequestDirector implements RequestDirector {
throw new IllegalArgumentException
("Redirect strategy may not be null.");
}
- if (targetAuthHandler == null) {
+ if (targetAuthStrategy == null) {
throw new IllegalArgumentException
- ("Target authentication handler may not be null.");
+ ("Target authentication strategy may not be null.");
}
- if (proxyAuthHandler == null) {
+ if (proxyAuthStrategy == null) {
throw new IllegalArgumentException
- ("Proxy authentication handler may not be null.");
+ ("Proxy authentication strategy may not be null.");
}
if (userTokenHandler == null) {
throw new IllegalArgumentException
@@ -282,27 +317,44 @@ public class DefaultRequestDirector implements RequestDirector {
("HTTP parameters may not be null");
}
this.log = log;
- this.requestExec = requestExec;
- this.connManager = conman;
- this.reuseStrategy = reustrat;
- this.keepAliveStrategy = kastrat;
- this.routePlanner = rouplan;
- this.httpProcessor = httpProcessor;
- this.retryHandler = retryHandler;
- this.redirectStrategy = redirectStrategy;
- this.targetAuthHandler = targetAuthHandler;
- this.proxyAuthHandler = proxyAuthHandler;
- this.userTokenHandler = userTokenHandler;
- this.params = params;
-
- this.managedConn = null;
+ this.authenticator = new HttpAuthenticator(log);
+ this.requestExec = requestExec;
+ this.connManager = conman;
+ this.reuseStrategy = reustrat;
+ this.keepAliveStrategy = kastrat;
+ this.routePlanner = rouplan;
+ this.httpProcessor = httpProcessor;
+ this.retryHandler = retryHandler;
+ this.redirectStrategy = redirectStrategy;
+ this.targetAuthStrategy = targetAuthStrategy;
+ this.proxyAuthStrategy = proxyAuthStrategy;
+ this.userTokenHandler = userTokenHandler;
+ this.params = params;
+
+ if (redirectStrategy instanceof DefaultRedirectStrategyAdaptor) {
+ this.redirectHandler = ((DefaultRedirectStrategyAdaptor) redirectStrategy).getHandler();
+ } else {
+ this.redirectHandler = null;
+ }
+ if (targetAuthStrategy instanceof AuthenticationStrategyAdaptor) {
+ this.targetAuthHandler = ((AuthenticationStrategyAdaptor) targetAuthStrategy).getHandler();
+ } else {
+ this.targetAuthHandler = null;
+ }
+ if (proxyAuthStrategy instanceof AuthenticationStrategyAdaptor) {
+ this.proxyAuthHandler = ((AuthenticationStrategyAdaptor) proxyAuthStrategy).getHandler();
+ } else {
+ this.proxyAuthHandler = null;
+ }
+
+ this.managedConn = null;
this.execCount = 0;
this.redirectCount = 0;
- this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100);
this.targetAuthState = new AuthState();
this.proxyAuthState = new AuthState();
- } // constructor
+ this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100);
+ }
private RequestWrapper wrapRequest(
@@ -327,16 +379,19 @@ public class DefaultRequestDirector implements RequestDirector {
// Make sure the request URI is absolute
if (!uri.isAbsolute()) {
HttpHost target = route.getTargetHost();
- uri = URIUtils.rewriteURI(uri, target);
- request.setURI(uri);
+ uri = URIUtils.rewriteURI(uri, target, true);
+ } else {
+ uri = URIUtils.rewriteURI(uri);
}
} else {
// Make sure the request URI is relative
if (uri.isAbsolute()) {
uri = URIUtils.rewriteURI(uri, null);
- request.setURI(uri);
+ } else {
+ uri = URIUtils.rewriteURI(uri);
}
}
+ request.setURI(uri);
} catch (URISyntaxException ex) {
throw new ProtocolException("Invalid URI: " +
@@ -350,17 +405,25 @@ public class DefaultRequestDirector implements RequestDirector {
HttpContext context)
throws HttpException, IOException {
+ context.setAttribute(ClientContext.TARGET_AUTH_STATE, targetAuthState);
+ context.setAttribute(ClientContext.PROXY_AUTH_STATE, proxyAuthState);
+
HttpRequest orig = request;
RequestWrapper origWrapper = wrapRequest(orig);
origWrapper.setParams(params);
HttpRoute origRoute = determineRoute(target, origWrapper, context);
- virtualHost = (HttpHost) orig.getParams().getParameter(
- ClientPNames.VIRTUAL_HOST);
+ virtualHost = (HttpHost) origWrapper.getParams().getParameter(ClientPNames.VIRTUAL_HOST);
- RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute);
+ // HTTPCLIENT-1092 - add the port if necessary
+ if (virtualHost != null && virtualHost.getPort() == -1) {
+ int port = target.getPort();
+ if (port != -1){
+ virtualHost = new HttpHost(virtualHost.getHostName(), port, virtualHost.getSchemeName());
+ }
+ }
- long timeout = HttpConnectionParams.getConnectionTimeout(params);
+ RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute);
boolean reuse = false;
boolean done = false;
@@ -387,6 +450,7 @@ public class DefaultRequestDirector implements RequestDirector {
((AbortableHttpRequest) orig).setConnectionRequest(connRequest);
}
+ long timeout = HttpClientParams.getConnectionManagerTimeout(params);
try {
managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS);
} catch(InterruptedException interrupted) {
@@ -421,6 +485,12 @@ public class DefaultRequestDirector implements RequestDirector {
break;
}
+ String userinfo = wrapper.getURI().getUserInfo();
+ if (userinfo != null) {
+ targetAuthState.update(
+ new BasicScheme(), new UsernamePasswordCredentials(userinfo));
+ }
+
// Reset headers on the request wrapper
wrapper.resetHeaders();
@@ -437,16 +507,9 @@ public class DefaultRequestDirector implements RequestDirector {
HttpHost proxy = route.getProxyHost();
// Populate the execution context
- context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,
- target);
- context.setAttribute(ExecutionContext.HTTP_PROXY_HOST,
- proxy);
- context.setAttribute(ExecutionContext.HTTP_CONNECTION,
- managedConn);
- context.setAttribute(ClientContext.TARGET_AUTH_STATE,
- targetAuthState);
- context.setAttribute(ClientContext.PROXY_AUTH_STATE,
- proxyAuthState);
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
+ context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, proxy);
+ context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn);
// Run request protocol interceptors
requestExec.preProcess(wrapper, httpProcessor, context);
@@ -492,6 +555,18 @@ public class DefaultRequestDirector implements RequestDirector {
managedConn.markReusable();
} else {
managedConn.close();
+ if (proxyAuthState.getState() == AuthProtocolState.SUCCESS
+ && proxyAuthState.getAuthScheme() != null
+ && proxyAuthState.getAuthScheme().isConnectionBased()) {
+ this.log.debug("Resetting proxy auth state");
+ proxyAuthState.reset();
+ }
+ if (targetAuthState.getState() == AuthProtocolState.SUCCESS
+ && targetAuthState.getAuthScheme() != null
+ && targetAuthState.getAuthScheme().isConnectionBased()) {
+ this.log.debug("Resetting target auth state");
+ targetAuthState.reset();
+ }
}
// check if we can use the same connection for the followup
if (!followup.getRoute().equals(roureq.getRoute())) {
@@ -500,9 +575,11 @@ public class DefaultRequestDirector implements RequestDirector {
roureq = followup;
}
- if (managedConn != null && userToken == null) {
- userToken = userTokenHandler.getUserToken(context);
- context.setAttribute(ClientContext.USER_TOKEN, userToken);
+ if (managedConn != null) {
+ if (userToken == null) {
+ userToken = userTokenHandler.getUserToken(context);
+ context.setAttribute(ClientContext.USER_TOKEN, userToken);
+ }
if (userToken != null) {
managedConn.setState(userToken);
}
@@ -551,9 +628,11 @@ public class DefaultRequestDirector implements RequestDirector {
private void tryConnect(
final RoutedRequest req, final HttpContext context) throws HttpException, IOException {
HttpRoute route = req.getRoute();
+ HttpRequest wrapper = req.getRequest();
int connectCount = 0;
for (;;) {
+ context.setAttribute(ExecutionContext.HTTP_REQUEST, wrapper);
// Increment connect count
connectCount++;
try {
@@ -574,11 +653,11 @@ public class DefaultRequestDirector implements RequestDirector {
this.log.info("I/O exception ("+ ex.getClass().getName() +
") caught when connecting to the target host: "
+ ex.getMessage());
+ if (this.log.isDebugEnabled()) {
+ this.log.debug(ex.getMessage(), ex);
+ }
+ this.log.info("Retrying connect");
}
- if (this.log.isDebugEnabled()) {
- this.log.debug(ex.getMessage(), ex);
- }
- this.log.info("Retrying connect");
} else {
throw ex;
}
@@ -801,11 +880,7 @@ public class DefaultRequestDirector implements RequestDirector {
HttpHost target = route.getTargetHost();
HttpResponse response = null;
- boolean done = false;
- while (!done) {
-
- done = true;
-
+ for (;;) {
if (!this.managedConn.isOpen()) {
this.managedConn.open(route, context, this.params);
}
@@ -814,18 +889,10 @@ public class DefaultRequestDirector implements RequestDirector {
connect.setParams(this.params);
// Populate the execution context
- context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,
- target);
- context.setAttribute(ExecutionContext.HTTP_PROXY_HOST,
- proxy);
- context.setAttribute(ExecutionContext.HTTP_CONNECTION,
- managedConn);
- context.setAttribute(ClientContext.TARGET_AUTH_STATE,
- targetAuthState);
- context.setAttribute(ClientContext.PROXY_AUTH_STATE,
- proxyAuthState);
- context.setAttribute(ExecutionContext.HTTP_REQUEST,
- connect);
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
+ context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, proxy);
+ context.setAttribute(ExecutionContext.HTTP_CONNECTION, managedConn);
+ context.setAttribute(ExecutionContext.HTTP_REQUEST, connect);
this.requestExec.preProcess(connect, this.httpProcessor, context);
@@ -840,30 +907,11 @@ public class DefaultRequestDirector implements RequestDirector {
response.getStatusLine());
}
- CredentialsProvider credsProvider = (CredentialsProvider)
- context.getAttribute(ClientContext.CREDS_PROVIDER);
-
- if (credsProvider != null && HttpClientParams.isAuthenticating(params)) {
- if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) {
-
- this.log.debug("Proxy requested authentication");
- Map<String, Header> challenges = this.proxyAuthHandler.getChallenges(
- response, context);
- try {
- processChallenges(
- challenges, this.proxyAuthState, this.proxyAuthHandler,
- response, context);
- } catch (AuthenticationException ex) {
- if (this.log.isWarnEnabled()) {
- this.log.warn("Authentication error: " + ex.getMessage());
- break;
- }
- }
- updateAuthState(this.proxyAuthState, proxy, credsProvider);
-
- if (this.proxyAuthState.getCredentials() != null) {
- done = false;
-
+ if (HttpClientParams.isAuthenticating(this.params)) {
+ if (this.authenticator.isAuthenticationRequested(proxy, response,
+ this.proxyAuthStrategy, this.proxyAuthState, context)) {
+ if (this.authenticator.authenticate(proxy, response,
+ this.proxyAuthStrategy, this.proxyAuthState, context)) {
// Retry request
if (this.reuseStrategy.keepAlive(response, context)) {
this.log.debug("Connection kept alive");
@@ -873,17 +921,16 @@ public class DefaultRequestDirector implements RequestDirector {
} else {
this.managedConn.close();
}
-
+ } else {
+ break;
}
-
} else {
- // Reset proxy auth scope
- this.proxyAuthState.setAuthScope(null);
+ break;
}
}
}
- int status = response.getStatusLine().getStatusCode(); // can't be null
+ int status = response.getStatusLine().getStatusCode();
if (status > 299) {
@@ -1032,16 +1079,14 @@ public class DefaultRequestDirector implements RequestDirector {
uri.getPort(),
uri.getScheme());
- // Unset auth scope
- targetAuthState.setAuthScope(null);
- proxyAuthState.setAuthScope(null);
-
- // Invalidate auth states if redirecting to another host
+ // Reset auth states if redirecting to another host
if (!route.getTargetHost().equals(newTarget)) {
- targetAuthState.invalidate();
+ this.log.debug("Resetting target auth state");
+ targetAuthState.reset();
AuthScheme authScheme = proxyAuthState.getAuthScheme();
if (authScheme != null && authScheme.isConnectionBased()) {
- proxyAuthState.invalidate();
+ this.log.debug("Resetting proxy auth state");
+ proxyAuthState.reset();
}
}
@@ -1058,73 +1103,36 @@ public class DefaultRequestDirector implements RequestDirector {
return newRequest;
}
- CredentialsProvider credsProvider = (CredentialsProvider)
- context.getAttribute(ClientContext.CREDS_PROVIDER);
-
- if (credsProvider != null && HttpClientParams.isAuthenticating(params)) {
-
- if (this.targetAuthHandler.isAuthenticationRequested(response, context)) {
-
- HttpHost target = (HttpHost)
- context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
- if (target == null) {
- target = route.getTargetHost();
- }
-
- this.log.debug("Target requested authentication");
- Map<String, Header> challenges = this.targetAuthHandler.getChallenges(
- response, context);
- try {
- processChallenges(challenges,
- this.targetAuthState, this.targetAuthHandler,
- response, context);
- } catch (AuthenticationException ex) {
- if (this.log.isWarnEnabled()) {
- this.log.warn("Authentication error: " + ex.getMessage());
- return null;
- }
- }
- updateAuthState(this.targetAuthState, target, credsProvider);
-
- if (this.targetAuthState.getCredentials() != null) {
+ if (HttpClientParams.isAuthenticating(params)) {
+ HttpHost target = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
+ if (target == null) {
+ target = route.getTargetHost();
+ }
+ if (target.getPort() < 0) {
+ Scheme scheme = connManager.getSchemeRegistry().getScheme(target);
+ target = new HttpHost(target.getHostName(), scheme.getDefaultPort(), target.getSchemeName());
+ }
+ if (this.authenticator.isAuthenticationRequested(target, response,
+ this.targetAuthStrategy, this.targetAuthState, context)) {
+ if (this.authenticator.authenticate(target, response,
+ this.targetAuthStrategy, this.targetAuthState, context)) {
// Re-try the same request via the same route
return roureq;
} else {
return null;
}
- } else {
- // Reset target auth scope
- this.targetAuthState.setAuthScope(null);
}
- if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) {
-
- HttpHost proxy = route.getProxyHost();
-
- this.log.debug("Proxy requested authentication");
- Map<String, Header> challenges = this.proxyAuthHandler.getChallenges(
- response, context);
- try {
- processChallenges(challenges,
- this.proxyAuthState, this.proxyAuthHandler,
- response, context);
- } catch (AuthenticationException ex) {
- if (this.log.isWarnEnabled()) {
- this.log.warn("Authentication error: " + ex.getMessage());
- return null;
- }
- }
- updateAuthState(this.proxyAuthState, proxy, credsProvider);
-
- if (this.proxyAuthState.getCredentials() != null) {
+ HttpHost proxy = route.getProxyHost();
+ if (this.authenticator.isAuthenticationRequested(proxy, response,
+ this.proxyAuthStrategy, this.proxyAuthState, context)) {
+ if (this.authenticator.authenticate(proxy, response,
+ this.proxyAuthStrategy, this.proxyAuthState, context)) {
// Re-try the same request via the same route
return roureq;
} else {
return null;
}
- } else {
- // Reset proxy auth scope
- this.proxyAuthState.setAuthScope(null);
}
}
return null;
@@ -1159,76 +1167,4 @@ public class DefaultRequestDirector implements RequestDirector {
} // abortConnection
- private void processChallenges(
- final Map<String, Header> challenges,
- final AuthState authState,
- final AuthenticationHandler authHandler,
- final HttpResponse response,
- final HttpContext context)
- throws MalformedChallengeException, AuthenticationException {
-
- AuthScheme authScheme = authState.getAuthScheme();
- if (authScheme == null) {
- // Authentication not attempted before
- authScheme = authHandler.selectScheme(challenges, response, context);
- authState.setAuthScheme(authScheme);
- }
- String id = authScheme.getSchemeName();
-
- Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH));
- if (challenge == null) {
- throw new AuthenticationException(id +
- " authorization challenge expected, but not found");
- }
- authScheme.processChallenge(challenge);
- this.log.debug("Authorization challenge processed");
- }
-
-
- private void updateAuthState(
- final AuthState authState,
- final HttpHost host,
- final CredentialsProvider credsProvider) {
-
- if (!authState.isValid()) {
- return;
- }
-
- String hostname = host.getHostName();
- int port = host.getPort();
- if (port < 0) {
- Scheme scheme = connManager.getSchemeRegistry().getScheme(host);
- port = scheme.getDefaultPort();
- }
-
- AuthScheme authScheme = authState.getAuthScheme();
- AuthScope authScope = new AuthScope(
- hostname,
- port,
- authScheme.getRealm(),
- authScheme.getSchemeName());
-
- if (this.log.isDebugEnabled()) {
- this.log.debug("Authentication scope: " + authScope);
- }
- Credentials creds = authState.getCredentials();
- if (creds == null) {
- creds = credsProvider.getCredentials(authScope);
- if (this.log.isDebugEnabled()) {
- if (creds != null) {
- this.log.debug("Found credentials");
- } else {
- this.log.debug("Credentials not found");
- }
- }
- } else {
- if (authScheme.isComplete()) {
- this.log.debug("Authentication failed");
- creds = null;
- }
- }
- authState.setAuthScope(authScope);
- authState.setCredentials(creds);
- }
-
} // class DefaultClientRequestDirector
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/DefaultServiceUnavailableRetryStrategy.java b/httpclient/src/main/java/org/apache/http/impl/client/DefaultServiceUnavailableRetryStrategy.java
new file mode 100644
index 0000000..7906788
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/DefaultServiceUnavailableRetryStrategy.java
@@ -0,0 +1,83 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.annotation.Immutable;
+import org.apache.http.client.ServiceUnavailableRetryStrategy;
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * Default implementation of the {@link ServiceUnavailableRetryStrategy} interface.
+ * that retries <code>503</code> (Service Unavailable) responses for a fixed number of times
+ * at a fixed interval.
+ *
+ * @since 4.2
+ */
+ at Immutable
+public class DefaultServiceUnavailableRetryStrategy implements ServiceUnavailableRetryStrategy {
+
+ /**
+ * Maximum number of allowed retries if the server responds with a HTTP code
+ * in our retry code list. Default value is 1.
+ */
+ private final int maxRetries;
+
+ /**
+ * Retry interval between subsequent requests, in milliseconds. Default
+ * value is 1 second.
+ */
+ private final long retryInterval;
+
+ public DefaultServiceUnavailableRetryStrategy(int maxRetries, int retryInterval) {
+ super();
+ if (maxRetries < 1) {
+ throw new IllegalArgumentException("MaxRetries must be greater than 1");
+ }
+ if (retryInterval < 1) {
+ throw new IllegalArgumentException("Retry interval must be greater than 1");
+ }
+ this.maxRetries = maxRetries;
+ this.retryInterval = retryInterval;
+ }
+
+ public DefaultServiceUnavailableRetryStrategy() {
+ this(1, 1000);
+ }
+
+ public boolean retryRequest(final HttpResponse response, int executionCount, final HttpContext context) {
+ return executionCount <= maxRetries &&
+ response.getStatusLine().getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE;
+ }
+
+ public long getRetryInterval() {
+ return retryInterval;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java b/httpclient/src/main/java/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java
index 1850c15..17cfc5c 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java
@@ -46,7 +46,10 @@ import org.apache.http.protocol.HttpContext;
* authentication.
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link TargetAuthenticationStrategy}
*/
+ at Deprecated
@Immutable
public class DefaultTargetAuthenticationHandler extends AbstractAuthenticationHandler {
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java b/httpclient/src/main/java/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java
index 3c393c6..c256c10 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java
@@ -89,7 +89,7 @@ public class EntityEnclosingRequestWrapper extends RequestWrapper
super(entity);
}
- @Deprecated
+ @SuppressWarnings("deprecation")
@Override
public void consumeContent() throws IOException {
consumed = true;
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/HttpAuthenticator.java b/httpclient/src/main/java/org/apache/http/impl/client/HttpAuthenticator.java
new file mode 100644
index 0000000..034de00
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/HttpAuthenticator.java
@@ -0,0 +1,159 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.Queue;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthProtocolState;
+import org.apache.http.auth.AuthOption;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthState;
+import org.apache.http.auth.MalformedChallengeException;
+import org.apache.http.client.AuthenticationStrategy;
+import org.apache.http.protocol.HttpContext;
+
+public class HttpAuthenticator {
+
+ private final Log log;
+
+ public HttpAuthenticator(final Log log) {
+ super();
+ this.log = log != null ? log : LogFactory.getLog(getClass());
+ }
+
+ public HttpAuthenticator() {
+ this(null);
+ }
+
+ public boolean isAuthenticationRequested(
+ final HttpHost host,
+ final HttpResponse response,
+ final AuthenticationStrategy authStrategy,
+ final AuthState authState,
+ final HttpContext context) {
+ if (authStrategy.isAuthenticationRequested(host, response, context)) {
+ return true;
+ } else {
+ switch (authState.getState()) {
+ case CHALLENGED:
+ case HANDSHAKE:
+ authState.setState(AuthProtocolState.SUCCESS);
+ authStrategy.authSucceeded(host, authState.getAuthScheme(), context);
+ break;
+ case SUCCESS:
+ break;
+ default:
+ authState.setState(AuthProtocolState.UNCHALLENGED);
+ }
+ return false;
+ }
+ }
+
+ public boolean authenticate(
+ final HttpHost host,
+ final HttpResponse response,
+ final AuthenticationStrategy authStrategy,
+ final AuthState authState,
+ final HttpContext context) {
+ try {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug(host.toHostString() + " requested authentication");
+ }
+ Map<String, Header> challenges = authStrategy.getChallenges(host, response, context);
+ if (challenges.isEmpty()) {
+ this.log.debug("Response contains no authentication challenges");
+ return false;
+ }
+
+ AuthScheme authScheme = authState.getAuthScheme();
+ switch (authState.getState()) {
+ case FAILURE:
+ return false;
+ case SUCCESS:
+ authState.reset();
+ break;
+ case CHALLENGED:
+ case HANDSHAKE:
+ if (authScheme == null) {
+ this.log.debug("Auth scheme is null");
+ authStrategy.authFailed(host, null, context);
+ authState.reset();
+ authState.setState(AuthProtocolState.FAILURE);
+ return false;
+ }
+ case UNCHALLENGED:
+ if (authScheme != null) {
+ String id = authScheme.getSchemeName();
+ Header challenge = challenges.get(id.toLowerCase(Locale.US));
+ if (challenge != null) {
+ this.log.debug("Authorization challenge processed");
+ authScheme.processChallenge(challenge);
+ if (authScheme.isComplete()) {
+ this.log.debug("Authentication failed");
+ authStrategy.authFailed(host, authState.getAuthScheme(), context);
+ authState.reset();
+ authState.setState(AuthProtocolState.FAILURE);
+ return false;
+ } else {
+ authState.setState(AuthProtocolState.HANDSHAKE);
+ return true;
+ }
+ } else {
+ authState.reset();
+ // Retry authentication with a different scheme
+ }
+ }
+ }
+ Queue<AuthOption> authOptions = authStrategy.select(challenges, host, response, context);
+ if (authOptions != null && !authOptions.isEmpty()) {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Selected authentication options: " + authOptions);
+ }
+ authState.setState(AuthProtocolState.CHALLENGED);
+ authState.update(authOptions);
+ return true;
+ } else {
+ return false;
+ }
+ } catch (MalformedChallengeException ex) {
+ if (this.log.isWarnEnabled()) {
+ this.log.warn("Malformed challenge: " + ex.getMessage());
+ }
+ authState.reset();
+ return false;
+ }
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/LaxRedirectStrategy.java b/httpclient/src/main/java/org/apache/http/impl/client/LaxRedirectStrategy.java
new file mode 100644
index 0000000..add0be3
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/LaxRedirectStrategy.java
@@ -0,0 +1,65 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import org.apache.http.annotation.Immutable;
+import org.apache.http.client.RedirectStrategy;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpPost;
+
+/**
+ * Lax {@link RedirectStrategy} implementation that automatically redirects all HEAD, GET and POST
+ * requests. This strategy relaxes restrictions on automatic redirection of POST methods imposed
+ * by the HTTP specification.
+ *
+ * @since 4.2
+ */
+ at Immutable
+public class LaxRedirectStrategy extends DefaultRedirectStrategy {
+
+ /**
+ * Redirectable methods.
+ */
+ private static final String[] REDIRECT_METHODS = new String[] {
+ HttpGet.METHOD_NAME,
+ HttpPost.METHOD_NAME,
+ HttpHead.METHOD_NAME
+ };
+
+ @Override
+ protected boolean isRedirectable(String method) {
+ for (String m: REDIRECT_METHODS) {
+ if (m.equalsIgnoreCase(method)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/NullBackoffStrategy.java b/httpclient/src/main/java/org/apache/http/impl/client/NullBackoffStrategy.java
new file mode 100644
index 0000000..e7ff0b2
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/NullBackoffStrategy.java
@@ -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.impl.client;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ConnectionBackoffStrategy;
+
+/**
+ * This is a {@link ConnectionBackoffStrategy} that never backs off,
+ * for compatibility with existing behavior.
+ *
+ * @since 4.2
+ */
+public class NullBackoffStrategy implements ConnectionBackoffStrategy {
+
+ public boolean shouldBackoff(Throwable t) {
+ return false;
+ }
+
+ public boolean shouldBackoff(HttpResponse resp) {
+ return false;
+ }
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/ProxyAuthenticationStrategy.java b/httpclient/src/main/java/org/apache/http/impl/client/ProxyAuthenticationStrategy.java
new file mode 100644
index 0000000..ff74c39
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/ProxyAuthenticationStrategy.java
@@ -0,0 +1,48 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.annotation.Immutable;
+import org.apache.http.auth.AUTH;
+import org.apache.http.auth.params.AuthPNames;
+import org.apache.http.client.AuthenticationStrategy;
+
+/**
+ * Default {@link AuthenticationStrategy} implementation for proxy host authentication.
+ *
+ * @since 4.2
+ */
+ at Immutable
+public class ProxyAuthenticationStrategy extends AuthenticationStrategyImpl {
+
+ public ProxyAuthenticationStrategy() {
+ super(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED, AUTH.PROXY_AUTH, AuthPNames.PROXY_AUTH_PREF);
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/ProxyClient.java b/httpclient/src/main/java/org/apache/http/impl/client/ProxyClient.java
new file mode 100644
index 0000000..19771a4
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/ProxyClient.java
@@ -0,0 +1,247 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import java.io.IOException;
+import java.net.Socket;
+
+import javax.net.ssl.SSLSession;
+
+import org.apache.http.ConnectionReuseStrategy;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.HttpResponse;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.auth.AuthSchemeRegistry;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.AuthState;
+import org.apache.http.auth.Credentials;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.client.protocol.RequestClientConnControl;
+import org.apache.http.client.protocol.RequestProxyAuthentication;
+import org.apache.http.conn.HttpRoutedConnection;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.entity.BufferedHttpEntity;
+import org.apache.http.impl.DefaultConnectionReuseStrategy;
+import org.apache.http.impl.DefaultHttpClientConnection;
+import org.apache.http.impl.auth.BasicSchemeFactory;
+import org.apache.http.impl.auth.DigestSchemeFactory;
+import org.apache.http.impl.auth.KerberosSchemeFactory;
+import org.apache.http.impl.auth.NTLMSchemeFactory;
+import org.apache.http.impl.auth.SPNegoSchemeFactory;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpProcessor;
+import org.apache.http.protocol.HttpRequestExecutor;
+import org.apache.http.protocol.ImmutableHttpProcessor;
+import org.apache.http.protocol.RequestContent;
+import org.apache.http.protocol.RequestTargetHost;
+import org.apache.http.protocol.RequestUserAgent;
+import org.apache.http.util.EntityUtils;
+
+public class ProxyClient {
+
+ private final HttpProcessor httpProcessor;
+ private final HttpRequestExecutor requestExec;
+ private final ProxyAuthenticationStrategy proxyAuthStrategy;
+ private final HttpAuthenticator authenticator;
+ private final AuthState proxyAuthState;
+ private final AuthSchemeRegistry authSchemeRegistry;
+ private final ConnectionReuseStrategy reuseStrategy;
+ private final HttpParams params;
+
+ public ProxyClient(final HttpParams params) {
+ super();
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ this.httpProcessor = new ImmutableHttpProcessor(new HttpRequestInterceptor[] {
+ new RequestContent(),
+ new RequestTargetHost(),
+ new RequestClientConnControl(),
+ new RequestUserAgent(),
+ new RequestProxyAuthentication()
+ } );
+ this.requestExec = new HttpRequestExecutor();
+ this.proxyAuthStrategy = new ProxyAuthenticationStrategy();
+ this.authenticator = new HttpAuthenticator();
+ this.proxyAuthState = new AuthState();
+ this.authSchemeRegistry = new AuthSchemeRegistry();
+ this.authSchemeRegistry.register(AuthPolicy.BASIC, new BasicSchemeFactory());
+ this.authSchemeRegistry.register(AuthPolicy.DIGEST, new DigestSchemeFactory());
+ this.authSchemeRegistry.register(AuthPolicy.NTLM, new NTLMSchemeFactory());
+ this.authSchemeRegistry.register(AuthPolicy.SPNEGO, new SPNegoSchemeFactory());
+ this.authSchemeRegistry.register(AuthPolicy.KERBEROS, new KerberosSchemeFactory());
+ this.reuseStrategy = new DefaultConnectionReuseStrategy();
+ this.params = params;
+ }
+
+ public ProxyClient() {
+ this(new BasicHttpParams());
+ }
+
+ public HttpParams getParams() {
+ return this.params;
+ }
+
+ public AuthSchemeRegistry getAuthSchemeRegistry() {
+ return this.authSchemeRegistry;
+ }
+
+ public Socket tunnel(
+ final HttpHost proxy,
+ final HttpHost target,
+ final Credentials credentials) throws IOException, HttpException {
+ ProxyConnection conn = new ProxyConnection(new HttpRoute(proxy));
+ HttpContext context = new BasicHttpContext();
+ HttpResponse response = null;
+
+ for (;;) {
+ if (!conn.isOpen()) {
+ Socket socket = new Socket(proxy.getHostName(), proxy.getPort());
+ conn.bind(socket, this.params);
+ }
+ String host = target.getHostName();
+ int port = target.getPort();
+ if (port < 0) {
+ port = 80;
+ }
+
+ StringBuilder buffer = new StringBuilder(host.length() + 6);
+ buffer.append(host);
+ buffer.append(':');
+ buffer.append(Integer.toString(port));
+
+ String authority = buffer.toString();
+ ProtocolVersion ver = HttpProtocolParams.getVersion(this.params);
+ HttpRequest connect = new BasicHttpRequest("CONNECT", authority, ver);
+ connect.setParams(this.params);
+
+ BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
+ credsProvider.setCredentials(new AuthScope(proxy), credentials);
+
+ // Populate the execution context
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, target);
+ context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, proxy);
+ context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
+ context.setAttribute(ExecutionContext.HTTP_REQUEST, connect);
+ context.setAttribute(ClientContext.PROXY_AUTH_STATE, this.proxyAuthState);
+ context.setAttribute(ClientContext.CREDS_PROVIDER, credsProvider);
+ context.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry);
+
+ this.requestExec.preProcess(connect, this.httpProcessor, context);
+
+ response = this.requestExec.execute(connect, conn, context);
+
+ response.setParams(this.params);
+ this.requestExec.postProcess(response, this.httpProcessor, context);
+
+ int status = response.getStatusLine().getStatusCode();
+ if (status < 200) {
+ throw new HttpException("Unexpected response to CONNECT request: " +
+ response.getStatusLine());
+ }
+
+ if (HttpClientParams.isAuthenticating(this.params)) {
+ if (this.authenticator.isAuthenticationRequested(proxy, response,
+ this.proxyAuthStrategy, this.proxyAuthState, context)) {
+ if (this.authenticator.authenticate(proxy, response,
+ this.proxyAuthStrategy, this.proxyAuthState, context)) {
+ // Retry request
+ if (this.reuseStrategy.keepAlive(response, context)) {
+ // Consume response content
+ HttpEntity entity = response.getEntity();
+ EntityUtils.consume(entity);
+ } else {
+ conn.close();
+ }
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ int status = response.getStatusLine().getStatusCode();
+
+ if (status > 299) {
+
+ // Buffer response content
+ HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ response.setEntity(new BufferedHttpEntity(entity));
+ }
+
+ conn.close();
+ throw new TunnelRefusedException("CONNECT refused by proxy: " +
+ response.getStatusLine(), response);
+ }
+ return conn.getSocket();
+ }
+
+ static class ProxyConnection extends DefaultHttpClientConnection implements HttpRoutedConnection {
+
+ private final HttpRoute route;
+
+ ProxyConnection(final HttpRoute route) {
+ super();
+ this.route = route;
+ }
+
+ public HttpRoute getRoute() {
+ return this.route;
+ }
+
+ public boolean isSecure() {
+ return false;
+ }
+
+ public SSLSession getSSLSession() {
+ return null;
+ }
+
+ @Override
+ public Socket getSocket() {
+ return super.getSocket();
+ }
+
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/StandardHttpRequestRetryHandler.java b/httpclient/src/main/java/org/apache/http/impl/client/StandardHttpRequestRetryHandler.java
new file mode 100644
index 0000000..0ebac0d
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/StandardHttpRequestRetryHandler.java
@@ -0,0 +1,81 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.http.HttpRequest;
+import org.apache.http.annotation.Immutable;
+import org.apache.http.client.HttpRequestRetryHandler;
+
+/**
+ * A {@link HttpRequestRetryHandler} which assumes that all requested
+ * HTTP methods which should be idempotent according to RFC-2616 are
+ * in fact idempotent and can be retried.
+ *
+ * According to RFC-2616 section 9.1.2 the idempotent HTTP methods are:
+ * GET, HEAD, PUT, DELETE, OPTIONS, and TRACE
+ *
+ * @since 4.2
+ */
+ at Immutable
+public class StandardHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
+
+ private final Map<String, Boolean> idempotentMethods;
+
+ /**
+ * Default constructor
+ */
+ public StandardHttpRequestRetryHandler(int retryCount, boolean requestSentRetryEnabled) {
+ super(retryCount, requestSentRetryEnabled);
+ this.idempotentMethods = new ConcurrentHashMap<String, Boolean>();
+ this.idempotentMethods.put("GET", Boolean.TRUE);
+ this.idempotentMethods.put("HEAD", Boolean.TRUE);
+ this.idempotentMethods.put("PUT", Boolean.TRUE);
+ this.idempotentMethods.put("DELETE", Boolean.TRUE);
+ this.idempotentMethods.put("OPTIONS", Boolean.TRUE);
+ this.idempotentMethods.put("TRACE", Boolean.TRUE);
+ }
+
+ /**
+ * Default constructor
+ */
+ public StandardHttpRequestRetryHandler() {
+ this(3, false);
+ }
+
+ @Override
+ protected boolean handleAsIdempotent(final HttpRequest request) {
+ String method = request.getRequestLine().getMethod().toUpperCase(Locale.US);
+ Boolean b = this.idempotentMethods.get(method);
+ return b != null && b.booleanValue();
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/SystemClock.java b/httpclient/src/main/java/org/apache/http/impl/client/SystemClock.java
new file mode 100644
index 0000000..18f0992
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/SystemClock.java
@@ -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.impl.client;
+
+/**
+ * The actual system clock.
+ *
+ * @since 4.2
+ */
+class SystemClock implements Clock {
+
+ public long getCurrentTime() {
+ return System.currentTimeMillis();
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/SystemDefaultHttpClient.java b/httpclient/src/main/java/org/apache/http/impl/client/SystemDefaultHttpClient.java
new file mode 100644
index 0000000..43b7855
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/SystemDefaultHttpClient.java
@@ -0,0 +1,146 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import java.net.ProxySelector;
+
+import org.apache.http.ConnectionReuseStrategy;
+import org.apache.http.annotation.ThreadSafe;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.routing.HttpRoutePlanner;
+import org.apache.http.impl.DefaultConnectionReuseStrategy;
+import org.apache.http.impl.NoConnectionReuseStrategy;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
+import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
+import org.apache.http.impl.conn.SchemeRegistryFactory;
+import org.apache.http.params.HttpParams;
+
+/**
+ * An extension of {@link DefaultHttpClient} pre-configured using system properties.
+ * <p>
+ * The following system properties are taken into account by this class:
+ * <ul>
+ * <li>ssl.TrustManagerFactory.algorithm</li>
+ * <li>javax.net.ssl.trustStoreType</li>
+ * <li>javax.net.ssl.trustStore</li>
+ * <li>javax.net.ssl.trustStoreProvider</li>
+ * <li>javax.net.ssl.trustStorePassword</li>
+ * <li>java.home</li>
+ * <li>ssl.KeyManagerFactory.algorithm</li>
+ * <li>javax.net.ssl.keyStoreType</li>
+ * <li>javax.net.ssl.keyStore</li>
+ * <li>javax.net.ssl.keyStoreProvider</li>
+ * <li>javax.net.ssl.keyStorePassword</li>
+ * <li>http.proxyHost</li>
+ * <li>http.proxyPort</li>
+ * <li>http.nonProxyHosts</li>
+ * <li>http.keepAlive</li>
+ * <li>http.maxConnections</li>
+ * </ul>
+ * <p>
+ * <p>
+ * The following parameters can be used to customize the behavior of this
+ * class:
+ * <ul>
+ * <li>{@link org.apache.http.params.CoreProtocolPNames#PROTOCOL_VERSION}</li>
+ * <li>{@link org.apache.http.params.CoreProtocolPNames#STRICT_TRANSFER_ENCODING}</li>
+ * <li>{@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li>
+ * <li>{@link org.apache.http.params.CoreProtocolPNames#USE_EXPECT_CONTINUE}</li>
+ * <li>{@link org.apache.http.params.CoreProtocolPNames#WAIT_FOR_CONTINUE}</li>
+ * <li>{@link org.apache.http.params.CoreProtocolPNames#USER_AGENT}</li>
+ * <li>{@link org.apache.http.params.CoreConnectionPNames#TCP_NODELAY}</li>
+ * <li>{@link org.apache.http.params.CoreConnectionPNames#SO_TIMEOUT}</li>
+ * <li>{@link org.apache.http.params.CoreConnectionPNames#SO_LINGER}</li>
+ * <li>{@link org.apache.http.params.CoreConnectionPNames#SO_REUSEADDR}</li>
+ * <li>{@link org.apache.http.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li>
+ * <li>{@link org.apache.http.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li>
+ * <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li>
+ * <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li>
+ * <li>{@link org.apache.http.params.CoreConnectionPNames#STALE_CONNECTION_CHECK}</li>
+ * <li>{@link org.apache.http.conn.params.ConnRoutePNames#FORCED_ROUTE}</li>
+ * <li>{@link org.apache.http.conn.params.ConnRoutePNames#LOCAL_ADDRESS}</li>
+ * <li>{@link org.apache.http.conn.params.ConnRoutePNames#DEFAULT_PROXY}</li>
+ * <li>{@link org.apache.http.cookie.params.CookieSpecPNames#DATE_PATTERNS}</li>
+ * <li>{@link org.apache.http.cookie.params.CookieSpecPNames#SINGLE_COOKIE_HEADER}</li>
+ * <li>{@link org.apache.http.auth.params.AuthPNames#CREDENTIAL_CHARSET}</li>
+ * <li>{@link org.apache.http.client.params.ClientPNames#COOKIE_POLICY}</li>
+ * <li>{@link org.apache.http.client.params.ClientPNames#HANDLE_AUTHENTICATION}</li>
+ * <li>{@link org.apache.http.client.params.ClientPNames#HANDLE_REDIRECTS}</li>
+ * <li>{@link org.apache.http.client.params.ClientPNames#MAX_REDIRECTS}</li>
+ * <li>{@link org.apache.http.client.params.ClientPNames#ALLOW_CIRCULAR_REDIRECTS}</li>
+ * <li>{@link org.apache.http.client.params.ClientPNames#VIRTUAL_HOST}</li>
+ * <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HOST}</li>
+ * <li>{@link org.apache.http.client.params.ClientPNames#DEFAULT_HEADERS}</li>
+ * <li>{@link org.apache.http.client.params.ClientPNames#CONN_MANAGER_TIMEOUT}</li>
+ * </ul>
+ * </p>
+ *
+ * @since 4.2
+ */
+ at ThreadSafe
+public class SystemDefaultHttpClient extends DefaultHttpClient {
+
+ public SystemDefaultHttpClient(final HttpParams params) {
+ super(null, params);
+ }
+
+ public SystemDefaultHttpClient() {
+ super(null, null);
+ }
+
+ @Override
+ protected ClientConnectionManager createClientConnectionManager() {
+ PoolingClientConnectionManager connmgr = new PoolingClientConnectionManager(
+ SchemeRegistryFactory.createSystemDefault());
+ String s = System.getProperty("http.keepAlive");
+ if ("true".equalsIgnoreCase(s)) {
+ s = System.getProperty("http.maxConnections", "5");
+ int max = Integer.parseInt(s);
+ connmgr.setDefaultMaxPerRoute(max);
+ connmgr.setMaxTotal(2 * max);
+ }
+ return connmgr;
+ }
+
+ @Override
+ protected HttpRoutePlanner createHttpRoutePlanner() {
+ return new ProxySelectorRoutePlanner(getConnectionManager().getSchemeRegistry(),
+ ProxySelector.getDefault());
+ }
+
+ @Override
+ protected ConnectionReuseStrategy createConnectionReuseStrategy() {
+ String s = System.getProperty("http.keepAlive");
+ if ("true".equalsIgnoreCase(s)) {
+ return new DefaultConnectionReuseStrategy();
+ } else {
+ return new NoConnectionReuseStrategy();
+ }
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/TargetAuthenticationStrategy.java b/httpclient/src/main/java/org/apache/http/impl/client/TargetAuthenticationStrategy.java
new file mode 100644
index 0000000..eb2cc8d
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/client/TargetAuthenticationStrategy.java
@@ -0,0 +1,48 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.annotation.Immutable;
+import org.apache.http.auth.AUTH;
+import org.apache.http.auth.params.AuthPNames;
+import org.apache.http.client.AuthenticationStrategy;
+
+/**
+ * Default {@link AuthenticationStrategy} implementation for proxy host authentication.
+ *
+ * @since 4.2
+ */
+ at Immutable
+public class TargetAuthenticationStrategy extends AuthenticationStrategyImpl {
+
+ public TargetAuthenticationStrategy() {
+ super(HttpStatus.SC_UNAUTHORIZED, AUTH.WWW_AUTH, AuthPNames.TARGET_AUTH_PREF);
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/AbstractClientConnAdapter.java b/httpclient/src/main/java/org/apache/http/impl/conn/AbstractClientConnAdapter.java
index f0537e8..4d5718b 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/AbstractClientConnAdapter.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/AbstractClientConnAdapter.java
@@ -40,6 +40,7 @@ import org.apache.http.HttpRequest;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpConnectionMetrics;
+import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.conn.OperatedClientConnection;
import org.apache.http.conn.ManagedClientConnection;
import org.apache.http.conn.ClientConnectionManager;
@@ -65,16 +66,17 @@ import org.apache.http.protocol.HttpContext;
* expected to tolerate multiple calls to the release method.
*
* @since 4.0
+ *
+ * @deprecated (4.2) do not use
*/
-public abstract class AbstractClientConnAdapter
- implements ManagedClientConnection, HttpContext {
+ at Deprecated
+ at NotThreadSafe
+public abstract class AbstractClientConnAdapter implements ManagedClientConnection, HttpContext {
/**
- * The connection manager, if any.
- * This attribute MUST NOT be final, so the adapter can be detached
- * from the connection manager without keeping a hard reference there.
+ * The connection manager.
*/
- private volatile ClientConnectionManager connManager;
+ private final ClientConnectionManager connManager;
/** The wrapped connection. */
private volatile OperatedClientConnection wrappedConnection;
@@ -112,7 +114,6 @@ public abstract class AbstractClientConnAdapter
*/
protected synchronized void detach() {
wrappedConnection = null;
- connManager = null; // base class attribute
duration = Long.MAX_VALUE;
}
@@ -125,9 +126,8 @@ public abstract class AbstractClientConnAdapter
}
/**
- * @deprecated use {@link #assertValid(OperatedClientConnection)}
+ * @deprecated (4.1) use {@link #assertValid(OperatedClientConnection)}
*/
- @Deprecated
protected final void assertNotAborted() throws InterruptedIOException {
if (isReleased()) {
throw new InterruptedIOException("Connection has been shut down");
@@ -304,9 +304,7 @@ public abstract class AbstractClientConnAdapter
return;
}
released = true;
- if (connManager != null) {
- connManager.releaseConnection(this, duration, TimeUnit.MILLISECONDS);
- }
+ connManager.releaseConnection(this, duration, TimeUnit.MILLISECONDS);
}
public synchronized void abortConnection() {
@@ -319,12 +317,10 @@ public abstract class AbstractClientConnAdapter
shutdown();
} catch (IOException ignore) {
}
- if (connManager != null) {
- connManager.releaseConnection(this, duration, TimeUnit.MILLISECONDS);
- }
+ connManager.releaseConnection(this, duration, TimeUnit.MILLISECONDS);
}
- public synchronized Object getAttribute(final String id) {
+ public Object getAttribute(final String id) {
OperatedClientConnection conn = getWrappedConnection();
assertValid(conn);
if (conn instanceof HttpContext) {
@@ -334,7 +330,7 @@ public abstract class AbstractClientConnAdapter
}
}
- public synchronized Object removeAttribute(final String id) {
+ public Object removeAttribute(final String id) {
OperatedClientConnection conn = getWrappedConnection();
assertValid(conn);
if (conn instanceof HttpContext) {
@@ -344,7 +340,7 @@ public abstract class AbstractClientConnAdapter
}
}
- public synchronized void setAttribute(final String id, final Object obj) {
+ public void setAttribute(final String id, final Object obj) {
OperatedClientConnection conn = getWrappedConnection();
assertValid(conn);
if (conn instanceof HttpContext) {
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/AbstractPoolEntry.java b/httpclient/src/main/java/org/apache/http/impl/conn/AbstractPoolEntry.java
index 4e8ad0d..8ae374a 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/AbstractPoolEntry.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/AbstractPoolEntry.java
@@ -27,11 +27,11 @@
package org.apache.http.impl.conn;
import java.io.IOException;
+import java.io.InterruptedIOException;
import org.apache.http.HttpHost;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
-import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.routing.RouteTracker;
import org.apache.http.conn.ClientConnectionOperator;
@@ -52,8 +52,10 @@ import org.apache.http.conn.OperatedClientConnection;
* underlying connection and the established route.
*
* @since 4.0
+ *
+ * @deprecated (4.2) do not use
*/
- at NotThreadSafe
+ at Deprecated
public abstract class AbstractPoolEntry {
/** The connection operator. */
@@ -157,7 +159,7 @@ public abstract class AbstractPoolEntry {
// If this tracker was reset while connecting,
// fail early.
if (localTracker == null) {
- throw new IOException("Request aborted");
+ throw new InterruptedIOException("Request aborted");
}
if (proxy == null) {
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/AbstractPooledConnAdapter.java b/httpclient/src/main/java/org/apache/http/impl/conn/AbstractPooledConnAdapter.java
index 70212da..db98e84 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/AbstractPooledConnAdapter.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/AbstractPooledConnAdapter.java
@@ -46,7 +46,10 @@ import org.apache.http.conn.OperatedClientConnection;
* respective method of the wrapped connection.
*
* @since 4.0
+ *
+ * @deprecated (4.2) do not use
*/
+ at Deprecated
public abstract class AbstractPooledConnAdapter extends AbstractClientConnAdapter {
/** The wrapped pool entry. */
@@ -68,6 +71,8 @@ public abstract class AbstractPooledConnAdapter extends AbstractClientConnAdapte
* Obtains the pool entry.
*
* @return the pool entry, or <code>null</code> if detached
+ *
+ * @deprecated (4.0.1)
*/
protected AbstractPoolEntry getPoolEntry() {
return this.poolEntry;
@@ -88,9 +93,8 @@ public abstract class AbstractPooledConnAdapter extends AbstractClientConnAdapte
}
/**
- * @deprecated use {@link #assertValid(AbstractPoolEntry)}
+ * @deprecated (4.1) use {@link #assertValid(AbstractPoolEntry)}
*/
- @Deprecated
protected final void assertAttached() {
if (poolEntry == null) {
throw new ConnectionShutdownException();
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/BasicClientConnectionManager.java b/httpclient/src/main/java/org/apache/http/impl/conn/BasicClientConnectionManager.java
new file mode 100644
index 0000000..b4d645c
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/BasicClientConnectionManager.java
@@ -0,0 +1,275 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.conn;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.annotation.GuardedBy;
+import org.apache.http.annotation.ThreadSafe;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.ClientConnectionOperator;
+import org.apache.http.conn.ClientConnectionRequest;
+import org.apache.http.conn.ManagedClientConnection;
+import org.apache.http.conn.OperatedClientConnection;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.scheme.SchemeRegistry;
+
+/**
+ * A connection manager for a single connection. This connection manager maintains only one active
+ * connection at a time. Even though this class is thread-safe it ought to be used by one execution
+ * thread only.
+ * <p/>
+ * BasicClientConnManager will make an effort to reuse the connection for subsequent requests
+ * with the same {@link HttpRoute route}. It will, however, close the existing connection and
+ * open it for the given route, if the route of the persistent connection does not match that
+ * of the connection request. If the connection has been already been allocated
+ * {@link IllegalStateException} is thrown.
+ * <p/>
+ * This connection manager implementation can be used inside a EJB container instead of
+ * {@link PoolingClientConnectionManager}.
+ *
+ * @since 4.2
+ */
+ at ThreadSafe
+public class BasicClientConnectionManager implements ClientConnectionManager {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ private static final AtomicLong COUNTER = new AtomicLong();
+
+ /** The message to be logged on multiple allocation. */
+ public final static String MISUSE_MESSAGE =
+ "Invalid use of BasicClientConnManager: connection still allocated.\n" +
+ "Make sure to release the connection before allocating another one.";
+
+ /** The schemes supported by this connection manager. */
+ private final SchemeRegistry schemeRegistry;
+
+ /** The operator for opening and updating connections. */
+ private final ClientConnectionOperator connOperator;
+
+ /** The one and only entry in this pool. */
+ @GuardedBy("this")
+ private HttpPoolEntry poolEntry;
+
+ /** The currently issued managed connection, if any. */
+ @GuardedBy("this")
+ private ManagedClientConnectionImpl conn;
+
+ /** Indicates whether this connection manager is shut down. */
+ @GuardedBy("this")
+ private volatile boolean shutdown;
+
+ /**
+ * Creates a new simple connection manager.
+ *
+ * @param schreg the scheme registry
+ */
+ public BasicClientConnectionManager(final SchemeRegistry schreg) {
+ if (schreg == null) {
+ throw new IllegalArgumentException("Scheme registry may not be null");
+ }
+ this.schemeRegistry = schreg;
+ this.connOperator = createConnectionOperator(schreg);
+ }
+
+ public BasicClientConnectionManager() {
+ this(SchemeRegistryFactory.createDefault());
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ shutdown();
+ } finally { // Make sure we call overridden method even if shutdown barfs
+ super.finalize();
+ }
+ }
+
+ public SchemeRegistry getSchemeRegistry() {
+ return this.schemeRegistry;
+ }
+
+ protected ClientConnectionOperator createConnectionOperator(final SchemeRegistry schreg) {
+ return new DefaultClientConnectionOperator(schreg);
+ }
+
+ public final ClientConnectionRequest requestConnection(
+ final HttpRoute route,
+ final Object state) {
+
+ return new ClientConnectionRequest() {
+
+ public void abortRequest() {
+ // Nothing to abort, since requests are immediate.
+ }
+
+ public ManagedClientConnection getConnection(
+ long timeout, TimeUnit tunit) {
+ return BasicClientConnectionManager.this.getConnection(
+ route, state);
+ }
+
+ };
+ }
+
+ private void assertNotShutdown() {
+ if (this.shutdown) {
+ throw new IllegalStateException("Connection manager has been shut down");
+ }
+ }
+
+ ManagedClientConnection getConnection(final HttpRoute route, final Object state) {
+ if (route == null) {
+ throw new IllegalArgumentException("Route may not be null.");
+ }
+ assertNotShutdown();
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Get connection for route " + route);
+ }
+ synchronized (this) {
+ if (this.conn != null) {
+ throw new IllegalStateException(MISUSE_MESSAGE);
+ }
+ if (this.poolEntry != null && !this.poolEntry.getPlannedRoute().equals(route)) {
+ this.poolEntry.close();
+ this.poolEntry = null;
+ }
+ if (this.poolEntry == null) {
+ String id = Long.toString(COUNTER.getAndIncrement());
+ OperatedClientConnection conn = this.connOperator.createConnection();
+ this.poolEntry = new HttpPoolEntry(this.log, id, route, conn, 0, TimeUnit.MILLISECONDS);
+ }
+ long now = System.currentTimeMillis();
+ if (this.poolEntry.isExpired(now)) {
+ this.poolEntry.close();
+ this.poolEntry.getTracker().reset();
+ }
+ this.conn = new ManagedClientConnectionImpl(this, this.connOperator, this.poolEntry);
+ return this.conn;
+ }
+ }
+
+ public void releaseConnection(final ManagedClientConnection conn, long keepalive, TimeUnit tunit) {
+ assertNotShutdown();
+ if (!(conn instanceof ManagedClientConnectionImpl)) {
+ throw new IllegalArgumentException("Connection class mismatch, " +
+ "connection not obtained from this manager");
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Releasing connection " + conn);
+ }
+ ManagedClientConnectionImpl managedConn = (ManagedClientConnectionImpl) conn;
+ synchronized (managedConn) {
+ if (managedConn.getPoolEntry() == null) {
+ return; // already released
+ }
+ ClientConnectionManager manager = managedConn.getManager();
+ if (manager != null && manager != this) {
+ throw new IllegalStateException("Connection not obtained from this manager");
+ }
+ synchronized (this) {
+ try {
+ if (managedConn.isOpen() && !managedConn.isMarkedReusable()) {
+ try {
+ managedConn.shutdown();
+ } catch (IOException iox) {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("I/O exception shutting down released connection", iox);
+ }
+ }
+ }
+ this.poolEntry.updateExpiry(keepalive, tunit != null ? tunit : TimeUnit.MILLISECONDS);
+ if (this.log.isDebugEnabled()) {
+ String s;
+ if (keepalive > 0) {
+ s = "for " + keepalive + " " + tunit;
+ } else {
+ s = "indefinitely";
+ }
+ this.log.debug("Connection can be kept alive " + s);
+ }
+ } finally {
+ managedConn.detach();
+ this.conn = null;
+ if (this.poolEntry.isClosed()) {
+ this.poolEntry = null;
+ }
+ }
+ }
+ }
+ }
+
+ public void closeExpiredConnections() {
+ assertNotShutdown();
+ synchronized (this) {
+ long now = System.currentTimeMillis();
+ if (this.poolEntry != null && this.poolEntry.isExpired(now)) {
+ this.poolEntry.close();
+ this.poolEntry.getTracker().reset();
+ }
+ }
+ }
+
+ public void closeIdleConnections(long idletime, TimeUnit tunit) {
+ if (tunit == null) {
+ throw new IllegalArgumentException("Time unit must not be null.");
+ }
+ assertNotShutdown();
+ synchronized (this) {
+ long time = tunit.toMillis(idletime);
+ if (time < 0) {
+ time = 0;
+ }
+ long deadline = System.currentTimeMillis() - time;
+ if (this.poolEntry != null && this.poolEntry.getUpdated() <= deadline) {
+ this.poolEntry.close();
+ this.poolEntry.getTracker().reset();
+ }
+ }
+ }
+
+ public void shutdown() {
+ this.shutdown = true;
+ synchronized (this) {
+ try {
+ if (this.poolEntry != null) {
+ this.poolEntry.close();
+ }
+ } finally {
+ this.poolEntry = null;
+ this.conn = null;
+ }
+ }
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnection.java b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnection.java
index a45133b..4b612d9 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnection.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnection.java
@@ -28,6 +28,7 @@
package org.apache.http.impl.conn;
import java.io.IOException;
+import java.io.InterruptedIOException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
@@ -117,7 +118,7 @@ public class DefaultClientConnection extends SocketHttpClientConnection
if (this.shutdown) {
sock.close(); // allow this to throw...
// ...but if it doesn't, explicitly throw one ourselves.
- throw new IOException("Connection already shutdown");
+ throw new InterruptedIOException("Connection already shutdown");
}
}
@@ -149,7 +150,9 @@ public class DefaultClientConnection extends SocketHttpClientConnection
shutdown = true;
try {
super.shutdown();
- log.debug("Connection shut down");
+ if (log.isDebugEnabled()) {
+ log.debug("Connection " + this + " shut down");
+ }
Socket sock = this.socket; // copy volatile attribute
if (sock != null)
sock.close();
@@ -162,7 +165,9 @@ public class DefaultClientConnection extends SocketHttpClientConnection
public void close() throws IOException {
try {
super.close();
- log.debug("Connection closed");
+ if (log.isDebugEnabled()) {
+ log.debug("Connection " + this + " closed");
+ }
} catch (IOException ex) {
log.debug("I/O error closing connection", ex);
}
@@ -211,12 +216,12 @@ public class DefaultClientConnection extends SocketHttpClientConnection
}
@Override
- protected HttpMessageParser createResponseParser(
+ protected HttpMessageParser<HttpResponse> createResponseParser(
final SessionInputBuffer buffer,
final HttpResponseFactory responseFactory,
final HttpParams params) {
// override in derived class to specify a line parser
- return new DefaultResponseParser
+ return new DefaultHttpResponseParser
(buffer, null, responseFactory, params);
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java
index 9dfabce..19d40b6 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java
@@ -45,13 +45,16 @@ import org.apache.http.protocol.HttpContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpHostConnectException;
+import org.apache.http.conn.HttpInetSocketAddress;
import org.apache.http.conn.OperatedClientConnection;
import org.apache.http.conn.ClientConnectionOperator;
-import org.apache.http.conn.scheme.LayeredSchemeSocketFactory;
+import org.apache.http.conn.scheme.SchemeLayeredSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SchemeSocketFactory;
+import org.apache.http.conn.DnsResolver;
+
/**
* Default implementation of a {@link ClientConnectionOperator}. It uses a {@link SchemeRegistry}
* to look up {@link SchemeSocketFactory} objects.
@@ -89,16 +92,45 @@ public class DefaultClientConnectionOperator implements ClientConnectionOperator
/** The scheme registry for looking up socket factories. */
protected final SchemeRegistry schemeRegistry; // @ThreadSafe
+ /** the custom-configured DNS lookup mechanism. */
+ protected final DnsResolver dnsResolver;
+
/**
* Creates a new client connection operator for the given scheme registry.
*
* @param schemes the scheme registry
+ *
+ * @since 4.2
*/
public DefaultClientConnectionOperator(final SchemeRegistry schemes) {
if (schemes == null) {
throw new IllegalArgumentException("Scheme registry amy not be null");
}
this.schemeRegistry = schemes;
+ this.dnsResolver = new SystemDefaultDnsResolver();
+ }
+
+ /**
+ * Creates a new client connection operator for the given scheme registry
+ * and the given custom DNS lookup mechanism.
+ *
+ * @param schemes
+ * the scheme registry
+ * @param dnsResolver
+ * the custom DNS lookup mechanism
+ */
+ public DefaultClientConnectionOperator(final SchemeRegistry schemes,final DnsResolver dnsResolver) {
+ if (schemes == null) {
+ throw new IllegalArgumentException(
+ "Scheme registry may not be null");
+ }
+
+ if(dnsResolver == null){
+ throw new IllegalArgumentException("DNS resolver may not be null");
+ }
+
+ this.schemeRegistry = schemes;
+ this.dnsResolver = dnsResolver;
}
public OperatedClientConnection createConnection() {
@@ -136,7 +168,7 @@ public class DefaultClientConnectionOperator implements ClientConnectionOperator
Socket sock = sf.createSocket(params);
conn.opening(sock, target);
- InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
+ InetSocketAddress remoteAddress = new HttpInetSocketAddress(target, address, port);
InetSocketAddress localAddress = null;
if (local != null) {
localAddress = new InetSocketAddress(local, 0);
@@ -188,17 +220,17 @@ public class DefaultClientConnectionOperator implements ClientConnectionOperator
}
final Scheme schm = schemeRegistry.getScheme(target.getSchemeName());
- if (!(schm.getSchemeSocketFactory() instanceof LayeredSchemeSocketFactory)) {
+ if (!(schm.getSchemeSocketFactory() instanceof SchemeLayeredSocketFactory)) {
throw new IllegalArgumentException
("Target scheme (" + schm.getName() +
") must have layered socket factory.");
}
- LayeredSchemeSocketFactory lsf = (LayeredSchemeSocketFactory) schm.getSchemeSocketFactory();
+ SchemeLayeredSocketFactory lsf = (SchemeLayeredSocketFactory) schm.getSchemeSocketFactory();
Socket sock;
try {
sock = lsf.createLayeredSocket(
- conn.getSocket(), target.getHostName(), target.getPort(), true);
+ conn.getSocket(), target.getHostName(), target.getPort(), params);
} catch (ConnectException ex) {
throw new HttpHostConnectException(target, ex);
}
@@ -230,16 +262,20 @@ public class DefaultClientConnectionOperator implements ClientConnectionOperator
/**
* Resolves the given host name to an array of corresponding IP addresses, based on the
- * configured name service on the system.
+ * configured name service on the provided DNS resolver. If one wasn't provided, the system
+ * configuration is used.
*
* @param host host name to resolve
* @return array of IP addresses
* @exception UnknownHostException if no IP address for the host could be determined.
*
+ * @see DnsResolver
+ * @see SystemDefaultDnsResolver
+ *
* @since 4.1
*/
protected InetAddress[] resolveHostname(final String host) throws UnknownHostException {
- return InetAddress.getAllByName(host);
+ return dnsResolver.resolve(host);
}
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpResponseParser.java b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpResponseParser.java
new file mode 100644
index 0000000..98b9831
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpResponseParser.java
@@ -0,0 +1,120 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.conn;
+
+import java.io.IOException;
+
+import org.apache.http.annotation.ThreadSafe;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.HttpException;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpResponseFactory;
+import org.apache.http.NoHttpResponseException;
+import org.apache.http.ProtocolException;
+import org.apache.http.StatusLine;
+import org.apache.http.impl.io.AbstractMessageParser;
+import org.apache.http.io.SessionInputBuffer;
+import org.apache.http.message.LineParser;
+import org.apache.http.message.ParserCursor;
+import org.apache.http.params.HttpParams;
+import org.apache.http.util.CharArrayBuffer;
+
+/**
+ * Default HTTP response parser implementation.
+ * <p>
+ * The following parameters can be used to customize the behavior of this
+ * class:
+ * <ul>
+ * <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_HEADER_COUNT}</li>
+ * <li>{@link org.apache.http.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li>
+ * </ul>
+ *
+ * @since 4.2
+ */
+ at ThreadSafe // no public methods
+public class DefaultHttpResponseParser extends AbstractMessageParser<HttpResponse> {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ private final HttpResponseFactory responseFactory;
+ private final CharArrayBuffer lineBuf;
+
+ public DefaultHttpResponseParser(
+ final SessionInputBuffer buffer,
+ final LineParser parser,
+ final HttpResponseFactory responseFactory,
+ final HttpParams params) {
+ super(buffer, parser, params);
+ if (responseFactory == null) {
+ throw new IllegalArgumentException
+ ("Response factory may not be null");
+ }
+ this.responseFactory = responseFactory;
+ this.lineBuf = new CharArrayBuffer(128);
+ }
+
+ @Override
+ protected HttpResponse parseHead(
+ final SessionInputBuffer sessionBuffer) throws IOException, HttpException {
+ //read out the HTTP status string
+ int count = 0;
+ ParserCursor cursor = null;
+ do {
+ // clear the buffer
+ this.lineBuf.clear();
+ int i = sessionBuffer.readLine(this.lineBuf);
+ if (i == -1 && count == 0) {
+ // The server just dropped connection on us
+ throw new NoHttpResponseException("The target server failed to respond");
+ }
+ cursor = new ParserCursor(0, this.lineBuf.length());
+ if (lineParser.hasProtocolVersion(this.lineBuf, cursor)) {
+ // Got one
+ break;
+ } else if (i == -1 || reject(this.lineBuf, count)) {
+ // Giving up
+ throw new ProtocolException("The server failed to respond with a " +
+ "valid HTTP response");
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Garbage in response: " + this.lineBuf.toString());
+ }
+ count++;
+ } while(true);
+ //create the status line from the status string
+ StatusLine statusline = lineParser.parseStatusLine(this.lineBuf, cursor);
+ return this.responseFactory.newHttpResponse(statusline, null);
+ }
+
+ protected boolean reject(CharArrayBuffer line, int count) {
+ return false;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java
index ba61ad0..4d0c2d1 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java
@@ -108,7 +108,12 @@ public class DefaultHttpRoutePlanner implements HttpRoutePlanner {
final HttpHost proxy =
ConnRouteParams.getDefaultProxy(request.getParams());
- final Scheme schm = schemeRegistry.getScheme(target.getSchemeName());
+ final Scheme schm;
+ try {
+ schm = schemeRegistry.getScheme(target.getSchemeName());
+ } catch (IllegalStateException ex) {
+ throw new HttpException(ex.getMessage());
+ }
// as it is typically used for TLS/SSL, we assume that
// a layered scheme implies a secure connection
final boolean secure = schm.isLayered();
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultResponseParser.java b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultResponseParser.java
index 43f296f..894fa13 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/DefaultResponseParser.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/DefaultResponseParser.java
@@ -39,7 +39,6 @@ import org.apache.http.HttpResponseFactory;
import org.apache.http.NoHttpResponseException;
import org.apache.http.ProtocolException;
import org.apache.http.StatusLine;
-import org.apache.http.conn.params.ConnConnectionPNames;
import org.apache.http.impl.io.AbstractMessageParser;
import org.apache.http.io.SessionInputBuffer;
import org.apache.http.message.LineParser;
@@ -59,9 +58,11 @@ import org.apache.http.util.CharArrayBuffer;
* </ul>
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link DefaultHttpResponseParser}
*/
@ThreadSafe // no public methods
-public class DefaultResponseParser extends AbstractMessageParser {
+public class DefaultResponseParser extends AbstractMessageParser<HttpMessage> {
private final Log log = LogFactory.getLog(getClass());
@@ -81,10 +82,14 @@ public class DefaultResponseParser extends AbstractMessageParser {
}
this.responseFactory = responseFactory;
this.lineBuf = new CharArrayBuffer(128);
- this.maxGarbageLines = params.getIntParameter(
- ConnConnectionPNames.MAX_STATUS_LINE_GARBAGE, Integer.MAX_VALUE);
+ this.maxGarbageLines = getMaxGarbageLines(params);
}
+ protected int getMaxGarbageLines(final HttpParams params) {
+ return params.getIntParameter(
+ org.apache.http.conn.params.ConnConnectionPNames.MAX_STATUS_LINE_GARBAGE,
+ Integer.MAX_VALUE);
+ }
@Override
protected HttpMessage parseHead(
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/HttpConnPool.java b/httpclient/src/main/java/org/apache/http/impl/conn/HttpConnPool.java
new file mode 100644
index 0000000..ee27b75
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/HttpConnPool.java
@@ -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.impl.conn;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.commons.logging.Log;
+import org.apache.http.conn.OperatedClientConnection;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.pool.AbstractConnPool;
+import org.apache.http.pool.ConnFactory;
+
+/**
+ * @since 4.2
+ */
+class HttpConnPool extends AbstractConnPool<HttpRoute, OperatedClientConnection, HttpPoolEntry> {
+
+ private static AtomicLong COUNTER = new AtomicLong();
+
+ private final Log log;
+ private final long timeToLive;
+ private final TimeUnit tunit;
+
+ public HttpConnPool(final Log log,
+ final int defaultMaxPerRoute, final int maxTotal,
+ final long timeToLive, final TimeUnit tunit) {
+ super(new InternalConnFactory(), defaultMaxPerRoute, maxTotal);
+ this.log = log;
+ this.timeToLive = timeToLive;
+ this.tunit = tunit;
+ }
+
+ @Override
+ protected HttpPoolEntry createEntry(final HttpRoute route, final OperatedClientConnection conn) {
+ String id = Long.toString(COUNTER.getAndIncrement());
+ return new HttpPoolEntry(this.log, id, route, conn, this.timeToLive, this.tunit);
+ }
+
+ static class InternalConnFactory implements ConnFactory<HttpRoute, OperatedClientConnection> {
+
+ public OperatedClientConnection create(final HttpRoute route) throws IOException {
+ return new DefaultClientConnection();
+ }
+
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/HttpPoolEntry.java b/httpclient/src/main/java/org/apache/http/impl/conn/HttpPoolEntry.java
new file mode 100644
index 0000000..71ac85b
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/HttpPoolEntry.java
@@ -0,0 +1,95 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.conn;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.logging.Log;
+import org.apache.http.conn.OperatedClientConnection;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.routing.RouteTracker;
+import org.apache.http.pool.PoolEntry;
+
+/**
+ * @since 4.2
+ */
+class HttpPoolEntry extends PoolEntry<HttpRoute, OperatedClientConnection> {
+
+ private final Log log;
+ private final RouteTracker tracker;
+
+ public HttpPoolEntry(
+ final Log log,
+ final String id,
+ final HttpRoute route,
+ final OperatedClientConnection conn,
+ final long timeToLive, final TimeUnit tunit) {
+ super(id, route, conn, timeToLive, tunit);
+ this.log = log;
+ this.tracker = new RouteTracker(route);
+ }
+
+ @Override
+ public boolean isExpired(long now) {
+ boolean expired = super.isExpired(now);
+ if (expired && this.log.isDebugEnabled()) {
+ this.log.debug("Connection " + this + " expired @ " + new Date(getExpiry()));
+ }
+ return expired;
+ }
+
+ RouteTracker getTracker() {
+ return this.tracker;
+ }
+
+ HttpRoute getPlannedRoute() {
+ return getRoute();
+ }
+
+ HttpRoute getEffectiveRoute() {
+ return this.tracker.toRoute();
+ }
+
+ @Override
+ public boolean isClosed() {
+ OperatedClientConnection conn = getConnection();
+ return !conn.isOpen();
+ }
+
+ @Override
+ public void close() {
+ OperatedClientConnection conn = getConnection();
+ try {
+ conn.close();
+ } catch (IOException ex) {
+ this.log.debug("I/O error closing connection", ex);
+ }
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/IdleConnectionHandler.java b/httpclient/src/main/java/org/apache/http/impl/conn/IdleConnectionHandler.java
index aa5813a..ac5ce51 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/IdleConnectionHandler.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/IdleConnectionHandler.java
@@ -45,7 +45,7 @@ import org.apache.http.HttpConnection;
*
* @since 4.0
*
- * @deprecated no longer used
+ * @deprecated (4.1) no longer used
*/
@Deprecated
public class IdleConnectionHandler {
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/InMemoryDnsResolver.java b/httpclient/src/main/java/org/apache/http/impl/conn/InMemoryDnsResolver.java
new file mode 100644
index 0000000..fc06da3
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/InMemoryDnsResolver.java
@@ -0,0 +1,97 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.conn;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.conn.DnsResolver;
+
+/**
+ * In-memory DNS resolver implementation.
+ *
+ * @since 4.2
+ */
+public class InMemoryDnsResolver implements DnsResolver {
+
+ /** Logger associated to this class. */
+ private final Log log = LogFactory.getLog(InMemoryDnsResolver.class);
+
+ /**
+ * In-memory collection that will hold the associations between a host name
+ * and an array of InetAddress instances.
+ */
+ private Map<String, InetAddress[]> dnsMap;
+
+ /**
+ * Builds a DNS resolver that will resolve the host names against a
+ * collection held in-memory.
+ */
+ public InMemoryDnsResolver() {
+ dnsMap = new ConcurrentHashMap<String, InetAddress[]>();
+ }
+
+ /**
+ * Associates the given array of IP addresses to the given host in this DNS overrider.
+ * The IP addresses are assumed to be already resolved.
+ *
+ * @param host
+ * The host name to be associated with the given IP.
+ * @param ips
+ * array of IP addresses to be resolved by this DNS overrider to the given
+ * host name.
+ */
+ public void add(final String host, final InetAddress... ips) {
+ if (host == null) {
+ throw new IllegalArgumentException("Host name may not be null");
+ }
+ if (ips == null) {
+ throw new IllegalArgumentException("Array of IP addresses may not be null");
+ }
+ dnsMap.put(host, ips);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public InetAddress[] resolve(String host) throws UnknownHostException {
+ InetAddress[] resolvedAddresses = dnsMap.get(host);
+ if (log.isInfoEnabled()) {
+ log.info("Resolving " + host + " to " + Arrays.deepToString(resolvedAddresses));
+ }
+ if(resolvedAddresses == null){
+ throw new UnknownHostException(host + " cannot be resolved");
+ }
+ return resolvedAddresses;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/LoggingSessionInputBuffer.java b/httpclient/src/main/java/org/apache/http/impl/conn/LoggingSessionInputBuffer.java
index 0562075..ff6cfa2 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/LoggingSessionInputBuffer.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/LoggingSessionInputBuffer.java
@@ -28,12 +28,12 @@ package org.apache.http.impl.conn;
import java.io.IOException;
+import org.apache.http.Consts;
import org.apache.http.annotation.Immutable;
import org.apache.http.io.EofSensor;
import org.apache.http.io.HttpTransportMetrics;
import org.apache.http.io.SessionInputBuffer;
-import org.apache.http.protocol.HTTP;
import org.apache.http.util.CharArrayBuffer;
/**
@@ -67,7 +67,7 @@ public class LoggingSessionInputBuffer implements SessionInputBuffer, EofSensor
this.in = in;
this.eofSensor = in instanceof EofSensor ? (EofSensor) in : null;
this.wire = wire;
- this.charset = charset != null ? charset : HTTP.ASCII;
+ this.charset = charset != null ? charset : Consts.ASCII.name();
}
public LoggingSessionInputBuffer(final SessionInputBuffer in, final Wire wire) {
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java b/httpclient/src/main/java/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java
index 803853b..d6a332e 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java
@@ -28,11 +28,11 @@ package org.apache.http.impl.conn;
import java.io.IOException;
+import org.apache.http.Consts;
import org.apache.http.annotation.Immutable;
import org.apache.http.io.HttpTransportMetrics;
import org.apache.http.io.SessionOutputBuffer;
-import org.apache.http.protocol.HTTP;
import org.apache.http.util.CharArrayBuffer;
/**
@@ -63,7 +63,7 @@ public class LoggingSessionOutputBuffer implements SessionOutputBuffer {
super();
this.out = out;
this.wire = wire;
- this.charset = charset != null ? charset : HTTP.ASCII;
+ this.charset = charset != null ? charset : Consts.ASCII.name();
}
public LoggingSessionOutputBuffer(final SessionOutputBuffer out, final Wire wire) {
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/ManagedClientConnectionImpl.java b/httpclient/src/main/java/org/apache/http/impl/conn/ManagedClientConnectionImpl.java
new file mode 100644
index 0000000..83df90e
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/ManagedClientConnectionImpl.java
@@ -0,0 +1,468 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.conn;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+import org.apache.http.HttpConnectionMetrics;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.annotation.NotThreadSafe;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.ClientConnectionOperator;
+import org.apache.http.conn.ManagedClientConnection;
+import org.apache.http.conn.OperatedClientConnection;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.routing.RouteTracker;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+
+ at NotThreadSafe
+class ManagedClientConnectionImpl implements ManagedClientConnection {
+
+ private final ClientConnectionManager manager;
+ private final ClientConnectionOperator operator;
+ private volatile HttpPoolEntry poolEntry;
+ private volatile boolean reusable;
+ private volatile long duration;
+
+ ManagedClientConnectionImpl(
+ final ClientConnectionManager manager,
+ final ClientConnectionOperator operator,
+ final HttpPoolEntry entry) {
+ super();
+ if (manager == null) {
+ throw new IllegalArgumentException("Connection manager may not be null");
+ }
+ if (operator == null) {
+ throw new IllegalArgumentException("Connection operator may not be null");
+ }
+ if (entry == null) {
+ throw new IllegalArgumentException("HTTP pool entry may not be null");
+ }
+ this.manager = manager;
+ this.operator = operator;
+ this.poolEntry = entry;
+ this.reusable = false;
+ this.duration = Long.MAX_VALUE;
+ }
+
+ HttpPoolEntry getPoolEntry() {
+ return this.poolEntry;
+ }
+
+ HttpPoolEntry detach() {
+ HttpPoolEntry local = this.poolEntry;
+ this.poolEntry = null;
+ return local;
+ }
+
+ public ClientConnectionManager getManager() {
+ return this.manager;
+ }
+
+ private OperatedClientConnection getConnection() {
+ HttpPoolEntry local = this.poolEntry;
+ if (local == null) {
+ return null;
+ }
+ return local.getConnection();
+ }
+
+ private OperatedClientConnection ensureConnection() {
+ HttpPoolEntry local = this.poolEntry;
+ if (local == null) {
+ throw new ConnectionShutdownException();
+ }
+ return local.getConnection();
+ }
+
+ private HttpPoolEntry ensurePoolEntry() {
+ HttpPoolEntry local = this.poolEntry;
+ if (local == null) {
+ throw new ConnectionShutdownException();
+ }
+ return local;
+ }
+
+ public void close() throws IOException {
+ HttpPoolEntry local = this.poolEntry;
+ if (local != null) {
+ OperatedClientConnection conn = local.getConnection();
+ local.getTracker().reset();
+ conn.close();
+ }
+ }
+
+ public void shutdown() throws IOException {
+ HttpPoolEntry local = this.poolEntry;
+ if (local != null) {
+ OperatedClientConnection conn = local.getConnection();
+ local.getTracker().reset();
+ conn.shutdown();
+ }
+ }
+
+ public boolean isOpen() {
+ OperatedClientConnection conn = getConnection();
+ if (conn != null) {
+ return conn.isOpen();
+ } else {
+ return false;
+ }
+ }
+
+ public boolean isStale() {
+ OperatedClientConnection conn = getConnection();
+ if (conn != null) {
+ return conn.isStale();
+ } else {
+ return true;
+ }
+ }
+
+ public void setSocketTimeout(int timeout) {
+ OperatedClientConnection conn = ensureConnection();
+ conn.setSocketTimeout(timeout);
+ }
+
+ public int getSocketTimeout() {
+ OperatedClientConnection conn = ensureConnection();
+ return conn.getSocketTimeout();
+ }
+
+ public HttpConnectionMetrics getMetrics() {
+ OperatedClientConnection conn = ensureConnection();
+ return conn.getMetrics();
+ }
+
+ public void flush() throws IOException {
+ OperatedClientConnection conn = ensureConnection();
+ conn.flush();
+ }
+
+ public boolean isResponseAvailable(int timeout) throws IOException {
+ OperatedClientConnection conn = ensureConnection();
+ return conn.isResponseAvailable(timeout);
+ }
+
+ public void receiveResponseEntity(
+ final HttpResponse response) throws HttpException, IOException {
+ OperatedClientConnection conn = ensureConnection();
+ conn.receiveResponseEntity(response);
+ }
+
+ public HttpResponse receiveResponseHeader() throws HttpException, IOException {
+ OperatedClientConnection conn = ensureConnection();
+ return conn.receiveResponseHeader();
+ }
+
+ public void sendRequestEntity(
+ final HttpEntityEnclosingRequest request) throws HttpException, IOException {
+ OperatedClientConnection conn = ensureConnection();
+ conn.sendRequestEntity(request);
+ }
+
+ public void sendRequestHeader(
+ final HttpRequest request) throws HttpException, IOException {
+ OperatedClientConnection conn = ensureConnection();
+ conn.sendRequestHeader(request);
+ }
+
+ public InetAddress getLocalAddress() {
+ OperatedClientConnection conn = ensureConnection();
+ return conn.getLocalAddress();
+ }
+
+ public int getLocalPort() {
+ OperatedClientConnection conn = ensureConnection();
+ return conn.getLocalPort();
+ }
+
+ public InetAddress getRemoteAddress() {
+ OperatedClientConnection conn = ensureConnection();
+ return conn.getRemoteAddress();
+ }
+
+ public int getRemotePort() {
+ OperatedClientConnection conn = ensureConnection();
+ return conn.getRemotePort();
+ }
+
+ public boolean isSecure() {
+ OperatedClientConnection conn = ensureConnection();
+ return conn.isSecure();
+ }
+
+ public SSLSession getSSLSession() {
+ OperatedClientConnection conn = ensureConnection();
+ SSLSession result = null;
+ Socket sock = conn.getSocket();
+ if (sock instanceof SSLSocket) {
+ result = ((SSLSocket)sock).getSession();
+ }
+ return result;
+ }
+
+ public Object getAttribute(final String id) {
+ OperatedClientConnection conn = ensureConnection();
+ if (conn instanceof HttpContext) {
+ return ((HttpContext) conn).getAttribute(id);
+ } else {
+ return null;
+ }
+ }
+
+ public Object removeAttribute(final String id) {
+ OperatedClientConnection conn = ensureConnection();
+ if (conn instanceof HttpContext) {
+ return ((HttpContext) conn).removeAttribute(id);
+ } else {
+ return null;
+ }
+ }
+
+ public void setAttribute(final String id, final Object obj) {
+ OperatedClientConnection conn = ensureConnection();
+ if (conn instanceof HttpContext) {
+ ((HttpContext) conn).setAttribute(id, obj);
+ }
+ }
+
+ public HttpRoute getRoute() {
+ HttpPoolEntry local = ensurePoolEntry();
+ return local.getEffectiveRoute();
+ }
+
+ public void open(
+ final HttpRoute route,
+ final HttpContext context,
+ final HttpParams params) throws IOException {
+ if (route == null) {
+ throw new IllegalArgumentException("Route may not be null");
+ }
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ OperatedClientConnection conn;
+ synchronized (this) {
+ if (this.poolEntry == null) {
+ throw new ConnectionShutdownException();
+ }
+ RouteTracker tracker = this.poolEntry.getTracker();
+ if (tracker.isConnected()) {
+ throw new IllegalStateException("Connection already open");
+ }
+ conn = this.poolEntry.getConnection();
+ }
+
+ HttpHost proxy = route.getProxyHost();
+ this.operator.openConnection(
+ conn,
+ (proxy != null) ? proxy : route.getTargetHost(),
+ route.getLocalAddress(),
+ context, params);
+
+ synchronized (this) {
+ if (this.poolEntry == null) {
+ throw new InterruptedIOException();
+ }
+ RouteTracker tracker = this.poolEntry.getTracker();
+ if (proxy == null) {
+ tracker.connectTarget(conn.isSecure());
+ } else {
+ tracker.connectProxy(proxy, conn.isSecure());
+ }
+ }
+ }
+
+ public void tunnelTarget(
+ boolean secure, final HttpParams params) throws IOException {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ HttpHost target;
+ OperatedClientConnection conn;
+ synchronized (this) {
+ if (this.poolEntry == null) {
+ throw new ConnectionShutdownException();
+ }
+ RouteTracker tracker = this.poolEntry.getTracker();
+ if (!tracker.isConnected()) {
+ throw new IllegalStateException("Connection not open");
+ }
+ if (tracker.isTunnelled()) {
+ throw new IllegalStateException("Connection is already tunnelled");
+ }
+ target = tracker.getTargetHost();
+ conn = this.poolEntry.getConnection();
+ }
+
+ conn.update(null, target, secure, params);
+
+ synchronized (this) {
+ if (this.poolEntry == null) {
+ throw new InterruptedIOException();
+ }
+ RouteTracker tracker = this.poolEntry.getTracker();
+ tracker.tunnelTarget(secure);
+ }
+ }
+
+ public void tunnelProxy(
+ final HttpHost next, boolean secure, final HttpParams params) throws IOException {
+ if (next == null) {
+ throw new IllegalArgumentException("Next proxy amy not be null");
+ }
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ OperatedClientConnection conn;
+ synchronized (this) {
+ if (this.poolEntry == null) {
+ throw new ConnectionShutdownException();
+ }
+ RouteTracker tracker = this.poolEntry.getTracker();
+ if (!tracker.isConnected()) {
+ throw new IllegalStateException("Connection not open");
+ }
+ conn = this.poolEntry.getConnection();
+ }
+
+ conn.update(null, next, secure, params);
+
+ synchronized (this) {
+ if (this.poolEntry == null) {
+ throw new InterruptedIOException();
+ }
+ RouteTracker tracker = this.poolEntry.getTracker();
+ tracker.tunnelProxy(next, secure);
+ }
+ }
+
+ public void layerProtocol(
+ final HttpContext context, final HttpParams params) throws IOException {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ HttpHost target;
+ OperatedClientConnection conn;
+ synchronized (this) {
+ if (this.poolEntry == null) {
+ throw new ConnectionShutdownException();
+ }
+ RouteTracker tracker = this.poolEntry.getTracker();
+ if (!tracker.isConnected()) {
+ throw new IllegalStateException("Connection not open");
+ }
+ if (!tracker.isTunnelled()) {
+ throw new IllegalStateException("Protocol layering without a tunnel not supported");
+ }
+ if (tracker.isLayered()) {
+ throw new IllegalStateException("Multiple protocol layering not supported");
+ }
+ target = tracker.getTargetHost();
+ conn = this.poolEntry.getConnection();
+ }
+ this.operator.updateSecureConnection(conn, target, context, params);
+
+ synchronized (this) {
+ if (this.poolEntry == null) {
+ throw new InterruptedIOException();
+ }
+ RouteTracker tracker = this.poolEntry.getTracker();
+ tracker.layerProtocol(conn.isSecure());
+ }
+ }
+
+ public Object getState() {
+ HttpPoolEntry local = ensurePoolEntry();
+ return local.getState();
+ }
+
+ public void setState(final Object state) {
+ HttpPoolEntry local = ensurePoolEntry();
+ local.setState(state);
+ }
+
+ public void markReusable() {
+ this.reusable = true;
+ }
+
+ public void unmarkReusable() {
+ this.reusable = false;
+ }
+
+ public boolean isMarkedReusable() {
+ return this.reusable;
+ }
+
+ public void setIdleDuration(long duration, TimeUnit unit) {
+ if(duration > 0) {
+ this.duration = unit.toMillis(duration);
+ } else {
+ this.duration = -1;
+ }
+ }
+
+ public void releaseConnection() {
+ synchronized (this) {
+ if (this.poolEntry == null) {
+ return;
+ }
+ this.manager.releaseConnection(this, this.duration, TimeUnit.MILLISECONDS);
+ this.poolEntry = null;
+ }
+ }
+
+ public void abortConnection() {
+ synchronized (this) {
+ if (this.poolEntry == null) {
+ return;
+ }
+ this.reusable = false;
+ OperatedClientConnection conn = this.poolEntry.getConnection();
+ try {
+ conn.shutdown();
+ } catch (IOException ignore) {
+ }
+ this.manager.releaseConnection(this, this.duration, TimeUnit.MILLISECONDS);
+ this.poolEntry = null;
+ }
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/PoolingClientConnectionManager.java b/httpclient/src/main/java/org/apache/http/impl/conn/PoolingClientConnectionManager.java
new file mode 100644
index 0000000..48796cf
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/PoolingClientConnectionManager.java
@@ -0,0 +1,338 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.conn;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.annotation.ThreadSafe;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.ClientConnectionOperator;
+import org.apache.http.conn.ClientConnectionRequest;
+import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.conn.ManagedClientConnection;
+import org.apache.http.conn.OperatedClientConnection;
+import org.apache.http.pool.ConnPoolControl;
+import org.apache.http.pool.PoolStats;
+import org.apache.http.impl.conn.DefaultClientConnectionOperator;
+import org.apache.http.impl.conn.SchemeRegistryFactory;
+import org.apache.http.conn.DnsResolver;
+
+/**
+ * Manages a pool of {@link OperatedClientConnection client connections} and
+ * is able to service connection requests from multiple execution threads.
+ * Connections are pooled on a per route basis. A request for a route which
+ * already the manager has persistent connections for available in the pool
+ * will be services by leasing a connection from the pool rather than
+ * creating a brand new connection.
+ * <p>
+ * PoolingConnectionManager maintains a maximum limit of connection on
+ * a per route basis and in total. Per default this implementation will
+ * create no more than than 2 concurrent connections per given route
+ * and no more 20 connections in total. For many real-world applications
+ * these limits may prove too constraining, especially if they use HTTP
+ * as a transport protocol for their services. Connection limits, however,
+ * can be adjusted using HTTP parameters.
+ *
+ * @since 4.2
+ */
+ at ThreadSafe
+public class PoolingClientConnectionManager implements ClientConnectionManager, ConnPoolControl<HttpRoute> {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ private final SchemeRegistry schemeRegistry;
+
+ private final HttpConnPool pool;
+
+ private final ClientConnectionOperator operator;
+
+ /** the custom-configured DNS lookup mechanism. */
+ private final DnsResolver dnsResolver;
+
+ public PoolingClientConnectionManager(final SchemeRegistry schreg) {
+ this(schreg, -1, TimeUnit.MILLISECONDS);
+ }
+
+ public PoolingClientConnectionManager(final SchemeRegistry schreg,final DnsResolver dnsResolver) {
+ this(schreg, -1, TimeUnit.MILLISECONDS,dnsResolver);
+ }
+
+ public PoolingClientConnectionManager() {
+ this(SchemeRegistryFactory.createDefault());
+ }
+
+ public PoolingClientConnectionManager(
+ final SchemeRegistry schemeRegistry,
+ final long timeToLive, final TimeUnit tunit) {
+ this(schemeRegistry, timeToLive, tunit, new SystemDefaultDnsResolver());
+ }
+
+ public PoolingClientConnectionManager(final SchemeRegistry schemeRegistry,
+ final long timeToLive, final TimeUnit tunit,
+ final DnsResolver dnsResolver) {
+ super();
+ if (schemeRegistry == null) {
+ throw new IllegalArgumentException("Scheme registry may not be null");
+ }
+ if (dnsResolver == null) {
+ throw new IllegalArgumentException("DNS resolver may not be null");
+ }
+ this.schemeRegistry = schemeRegistry;
+ this.dnsResolver = dnsResolver;
+ this.operator = createConnectionOperator(schemeRegistry);
+ this.pool = new HttpConnPool(this.log, 2, 20, timeToLive, tunit);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ shutdown();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Hook for creating the connection operator.
+ * It is called by the constructor.
+ * Derived classes can override this method to change the
+ * instantiation of the operator.
+ * The default implementation here instantiates
+ * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}.
+ *
+ * @param schreg the scheme registry.
+ *
+ * @return the connection operator to use
+ */
+ protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg) {
+ return new DefaultClientConnectionOperator(schreg, this.dnsResolver);
+ }
+
+ public SchemeRegistry getSchemeRegistry() {
+ return this.schemeRegistry;
+ }
+
+ private String format(final HttpRoute route, final Object state) {
+ StringBuilder buf = new StringBuilder();
+ buf.append("[route: ").append(route).append("]");
+ if (state != null) {
+ buf.append("[state: ").append(state).append("]");
+ }
+ return buf.toString();
+ }
+
+ private String formatStats(final HttpRoute route) {
+ StringBuilder buf = new StringBuilder();
+ PoolStats totals = this.pool.getTotalStats();
+ PoolStats stats = this.pool.getStats(route);
+ buf.append("[total kept alive: ").append(totals.getAvailable()).append("; ");
+ buf.append("route allocated: ").append(stats.getLeased() + stats.getAvailable());
+ buf.append(" of ").append(stats.getMax()).append("; ");
+ buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable());
+ buf.append(" of ").append(totals.getMax()).append("]");
+ return buf.toString();
+ }
+
+ private String format(final HttpPoolEntry entry) {
+ StringBuilder buf = new StringBuilder();
+ buf.append("[id: ").append(entry.getId()).append("]");
+ buf.append("[route: ").append(entry.getRoute()).append("]");
+ Object state = entry.getState();
+ if (state != null) {
+ buf.append("[state: ").append(state).append("]");
+ }
+ return buf.toString();
+ }
+
+ public ClientConnectionRequest requestConnection(
+ final HttpRoute route,
+ final Object state) {
+ if (route == null) {
+ throw new IllegalArgumentException("HTTP route may not be null");
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Connection request: " + format(route, state) + formatStats(route));
+ }
+ final Future<HttpPoolEntry> future = this.pool.lease(route, state);
+
+ return new ClientConnectionRequest() {
+
+ public void abortRequest() {
+ future.cancel(true);
+ }
+
+ public ManagedClientConnection getConnection(
+ final long timeout,
+ final TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException {
+ return leaseConnection(future, timeout, tunit);
+ }
+
+ };
+
+ }
+
+ ManagedClientConnection leaseConnection(
+ final Future<HttpPoolEntry> future,
+ final long timeout,
+ final TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException {
+ HttpPoolEntry entry;
+ try {
+ entry = future.get(timeout, tunit);
+ if (entry == null || future.isCancelled()) {
+ throw new InterruptedException();
+ }
+ if (entry.getConnection() == null) {
+ throw new IllegalStateException("Pool entry with no connection");
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Connection leased: " + format(entry) + formatStats(entry.getRoute()));
+ }
+ return new ManagedClientConnectionImpl(this, this.operator, entry);
+ } catch (ExecutionException ex) {
+ Throwable cause = ex.getCause();
+ if (cause == null) {
+ cause = ex;
+ }
+ this.log.error("Unexpected exception leasing connection from pool", cause);
+ // Should never happen
+ throw new InterruptedException();
+ } catch (TimeoutException ex) {
+ throw new ConnectionPoolTimeoutException("Timeout waiting for connection");
+ }
+ }
+
+ public void releaseConnection(
+ final ManagedClientConnection conn, final long keepalive, final TimeUnit tunit) {
+
+ if (!(conn instanceof ManagedClientConnectionImpl)) {
+ throw new IllegalArgumentException
+ ("Connection class mismatch, " +
+ "connection not obtained from this manager.");
+ }
+ ManagedClientConnectionImpl managedConn = (ManagedClientConnectionImpl) conn;
+ if (managedConn.getManager() != this) {
+ throw new IllegalStateException("Connection not obtained from this manager.");
+ }
+
+ synchronized (managedConn) {
+ HttpPoolEntry entry = managedConn.detach();
+ if (entry == null) {
+ return;
+ }
+ try {
+ if (managedConn.isOpen() && !managedConn.isMarkedReusable()) {
+ try {
+ managedConn.shutdown();
+ } catch (IOException iox) {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("I/O exception shutting down released connection", iox);
+ }
+ }
+ }
+ entry.updateExpiry(keepalive, tunit != null ? tunit : TimeUnit.MILLISECONDS);
+ if (this.log.isDebugEnabled()) {
+ String s;
+ if (keepalive > 0) {
+ s = "for " + keepalive + " " + tunit;
+ } else {
+ s = "indefinitely";
+ }
+ this.log.debug("Connection " + format(entry) + " can be kept alive " + s);
+ }
+ } finally {
+ this.pool.release(entry, managedConn.isMarkedReusable());
+ }
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Connection released: " + format(entry) + formatStats(entry.getRoute()));
+ }
+ }
+ }
+
+ public void shutdown() {
+ this.log.debug("Connection manager is shutting down");
+ try {
+ this.pool.shutdown();
+ } catch (IOException ex) {
+ this.log.debug("I/O exception shutting down connection manager", ex);
+ }
+ this.log.debug("Connection manager shut down");
+ }
+
+ public void closeIdleConnections(long idleTimeout, TimeUnit tunit) {
+ if (this.log.isDebugEnabled()) {
+ this.log.debug("Closing connections idle longer than " + idleTimeout + " " + tunit);
+ }
+ this.pool.closeIdle(idleTimeout, tunit);
+ }
+
+ public void closeExpiredConnections() {
+ this.log.debug("Closing expired connections");
+ this.pool.closeExpired();
+ }
+
+ public int getMaxTotal() {
+ return this.pool.getMaxTotal();
+ }
+
+ public void setMaxTotal(int max) {
+ this.pool.setMaxTotal(max);
+ }
+
+ public int getDefaultMaxPerRoute() {
+ return this.pool.getDefaultMaxPerRoute();
+ }
+
+ public void setDefaultMaxPerRoute(int max) {
+ this.pool.setDefaultMaxPerRoute(max);
+ }
+
+ public int getMaxPerRoute(final HttpRoute route) {
+ return this.pool.getMaxPerRoute(route);
+ }
+
+ public void setMaxPerRoute(final HttpRoute route, int max) {
+ this.pool.setMaxPerRoute(route, max);
+ }
+
+ public PoolStats getTotalStats() {
+ return this.pool.getTotalStats();
+ }
+
+ public PoolStats getStats(final HttpRoute route) {
+ return this.pool.getStats(route);
+ }
+
+}
+
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/SchemeRegistryFactory.java b/httpclient/src/main/java/org/apache/http/impl/conn/SchemeRegistryFactory.java
index 52ee804..b94c038 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/SchemeRegistryFactory.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/SchemeRegistryFactory.java
@@ -38,6 +38,10 @@ import org.apache.http.conn.ssl.SSLSocketFactory;
@ThreadSafe
public final class SchemeRegistryFactory {
+ /**
+ * Initializes default scheme registry based on JSSE defaults. System properties will
+ * not be taken into consideration.
+ */
public static SchemeRegistry createDefault() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(
@@ -47,5 +51,37 @@ public final class SchemeRegistryFactory {
return registry;
}
+ /**
+ * Initializes default scheme registry using system properties as described in
+ * <a href="http://download.oracle.com/javase/1,5.0/docs/guide/security/jsse/JSSERefGuide.html">
+ * "JavaTM Secure Socket Extension (JSSE) Reference Guide for the JavaTM 2 Platform
+ * Standard Edition 5</a>
+ * <p>
+ * The following system properties are taken into account by this method:
+ * <ul>
+ * <li>ssl.TrustManagerFactory.algorithm</li>
+ * <li>javax.net.ssl.trustStoreType</li>
+ * <li>javax.net.ssl.trustStore</li>
+ * <li>javax.net.ssl.trustStoreProvider</li>
+ * <li>javax.net.ssl.trustStorePassword</li>
+ * <li>java.home</li>
+ * <li>ssl.KeyManagerFactory.algorithm</li>
+ * <li>javax.net.ssl.keyStoreType</li>
+ * <li>javax.net.ssl.keyStore</li>
+ * <li>javax.net.ssl.keyStoreProvider</li>
+ * <li>javax.net.ssl.keyStorePassword</li>
+ * </ul>
+ * <p>
+ *
+ * @since 4.2
+ */
+ public static SchemeRegistry createSystemDefault() {
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(
+ new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
+ registry.register(
+ new Scheme("https", 443, SSLSocketFactory.getSystemSocketFactory()));
+ return registry;
+ }
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java b/httpclient/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java
index b194381..1140640 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java
@@ -57,8 +57,11 @@ import org.apache.http.params.HttpParams;
* already been allocated {@link IllegalStateException} is thrown.
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link BasicClientConnectionManager}
*/
@ThreadSafe
+ at Deprecated
public class SingleClientConnManager implements ClientConnectionManager {
private final Log log = LogFactory.getLog(getClass());
@@ -79,19 +82,19 @@ public class SingleClientConnManager implements ClientConnectionManager {
/** The one and only entry in this pool. */
@GuardedBy("this")
- protected PoolEntry uniquePoolEntry;
+ protected volatile PoolEntry uniquePoolEntry;
/** The currently issued managed connection, if any. */
@GuardedBy("this")
- protected ConnAdapter managedConn;
+ protected volatile ConnAdapter managedConn;
/** The time of the last connection release, or -1. */
@GuardedBy("this")
- protected long lastReleaseTime;
+ protected volatile long lastReleaseTime;
/** The time the last released connection expires and shouldn't be reused. */
@GuardedBy("this")
- protected long connectionExpiresTime;
+ protected volatile long connectionExpiresTime;
/** Indicates whether this connection manager is shut down. */
protected volatile boolean isShutDown;
@@ -102,9 +105,8 @@ public class SingleClientConnManager implements ClientConnectionManager {
* @param params the parameters for this manager
* @param schreg the scheme registry
*
- * @deprecated use {@link SingleClientConnManager#SingleClientConnManager(SchemeRegistry)}
+ * @deprecated (4.1) use {@link SingleClientConnManager#SingleClientConnManager(SchemeRegistry)}
*/
- @Deprecated
public SingleClientConnManager(HttpParams params,
SchemeRegistry schreg) {
this(schreg);
@@ -202,7 +204,7 @@ public class SingleClientConnManager implements ClientConnectionManager {
* @return a connection that can be used to communicate
* along the given route
*/
- public synchronized ManagedClientConnection getConnection(HttpRoute route, Object state) {
+ public ManagedClientConnection getConnection(HttpRoute route, Object state) {
if (route == null) {
throw new IllegalArgumentException("Route may not be null.");
}
@@ -212,47 +214,49 @@ public class SingleClientConnManager implements ClientConnectionManager {
log.debug("Get connection for route " + route);
}
- if (managedConn != null)
- throw new IllegalStateException(MISUSE_MESSAGE);
-
- // check re-usability of the connection
- boolean recreate = false;
- boolean shutdown = false;
-
- // Kill the connection if it expired.
- closeExpiredConnections();
-
- if (uniquePoolEntry.connection.isOpen()) {
- RouteTracker tracker = uniquePoolEntry.tracker;
- shutdown = (tracker == null || // can happen if method is aborted
- !tracker.toRoute().equals(route));
- } else {
- // If the connection is not open, create a new PoolEntry,
- // as the connection may have been marked not reusable,
- // due to aborts -- and the PoolEntry should not be reused
- // either. There's no harm in recreating an entry if
- // the connection is closed.
- recreate = true;
- }
+ synchronized (this) {
+ if (managedConn != null)
+ throw new IllegalStateException(MISUSE_MESSAGE);
+
+ // check re-usability of the connection
+ boolean recreate = false;
+ boolean shutdown = false;
+
+ // Kill the connection if it expired.
+ closeExpiredConnections();
+
+ if (uniquePoolEntry.connection.isOpen()) {
+ RouteTracker tracker = uniquePoolEntry.tracker;
+ shutdown = (tracker == null || // can happen if method is aborted
+ !tracker.toRoute().equals(route));
+ } else {
+ // If the connection is not open, create a new PoolEntry,
+ // as the connection may have been marked not reusable,
+ // due to aborts -- and the PoolEntry should not be reused
+ // either. There's no harm in recreating an entry if
+ // the connection is closed.
+ recreate = true;
+ }
- if (shutdown) {
- recreate = true;
- try {
- uniquePoolEntry.shutdown();
- } catch (IOException iox) {
- log.debug("Problem shutting down connection.", iox);
+ if (shutdown) {
+ recreate = true;
+ try {
+ uniquePoolEntry.shutdown();
+ } catch (IOException iox) {
+ log.debug("Problem shutting down connection.", iox);
+ }
}
- }
- if (recreate)
- uniquePoolEntry = new PoolEntry();
+ if (recreate)
+ uniquePoolEntry = new PoolEntry();
- managedConn = new ConnAdapter(uniquePoolEntry, route);
+ managedConn = new ConnAdapter(uniquePoolEntry, route);
- return managedConn;
+ return managedConn;
+ }
}
- public synchronized void releaseConnection(
+ public void releaseConnection(
ManagedClientConnection conn,
long validDuration, TimeUnit timeUnit) {
assertStillUp();
@@ -268,51 +272,55 @@ public class SingleClientConnManager implements ClientConnectionManager {
}
ConnAdapter sca = (ConnAdapter) conn;
- if (sca.poolEntry == null)
- return; // already released
- ClientConnectionManager manager = sca.getManager();
- if (manager != null && manager != this) {
- throw new IllegalArgumentException
- ("Connection not obtained from this manager.");
- }
-
- try {
- // make sure that the response has been read completely
- if (sca.isOpen() && (this.alwaysShutDown ||
- !sca.isMarkedReusable())
- ) {
- if (log.isDebugEnabled()) {
- log.debug
- ("Released connection open but not reusable.");
+ synchronized (sca) {
+ if (sca.poolEntry == null)
+ return; // already released
+ ClientConnectionManager manager = sca.getManager();
+ if (manager != null && manager != this) {
+ throw new IllegalArgumentException
+ ("Connection not obtained from this manager.");
+ }
+ try {
+ // make sure that the response has been read completely
+ if (sca.isOpen() && (this.alwaysShutDown ||
+ !sca.isMarkedReusable())
+ ) {
+ if (log.isDebugEnabled()) {
+ log.debug
+ ("Released connection open but not reusable.");
+ }
+
+ // make sure this connection will not be re-used
+ // we might have gotten here because of a shutdown trigger
+ // shutdown of the adapter also clears the tracked route
+ sca.shutdown();
+ }
+ } catch (IOException iox) {
+ if (log.isDebugEnabled())
+ log.debug("Exception shutting down released connection.",
+ iox);
+ } finally {
+ sca.detach();
+ synchronized (this) {
+ managedConn = null;
+ lastReleaseTime = System.currentTimeMillis();
+ if(validDuration > 0)
+ connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime;
+ else
+ connectionExpiresTime = Long.MAX_VALUE;
}
-
- // make sure this connection will not be re-used
- // we might have gotten here because of a shutdown trigger
- // shutdown of the adapter also clears the tracked route
- sca.shutdown();
}
- } catch (IOException iox) {
- if (log.isDebugEnabled())
- log.debug("Exception shutting down released connection.",
- iox);
- } finally {
- sca.detach();
- managedConn = null;
- lastReleaseTime = System.currentTimeMillis();
- if(validDuration > 0)
- connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime;
- else
- connectionExpiresTime = Long.MAX_VALUE;
}
}
- public synchronized void closeExpiredConnections() {
- if(System.currentTimeMillis() >= connectionExpiresTime) {
+ public void closeExpiredConnections() {
+ long time = connectionExpiresTime;
+ if (System.currentTimeMillis() >= time) {
closeIdleConnections(0, TimeUnit.MILLISECONDS);
}
}
- public synchronized void closeIdleConnections(long idletime, TimeUnit tunit) {
+ public void closeIdleConnections(long idletime, TimeUnit tunit) {
assertStillUp();
// idletime can be 0 or negative, no problem there
@@ -320,51 +328,51 @@ public class SingleClientConnManager implements ClientConnectionManager {
throw new IllegalArgumentException("Time unit must not be null.");
}
- if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) {
- final long cutoff =
- System.currentTimeMillis() - tunit.toMillis(idletime);
- if (lastReleaseTime <= cutoff) {
- try {
- uniquePoolEntry.close();
- } catch (IOException iox) {
- // ignore
- log.debug("Problem closing idle connection.", iox);
+ synchronized (this) {
+ if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) {
+ final long cutoff =
+ System.currentTimeMillis() - tunit.toMillis(idletime);
+ if (lastReleaseTime <= cutoff) {
+ try {
+ uniquePoolEntry.close();
+ } catch (IOException iox) {
+ // ignore
+ log.debug("Problem closing idle connection.", iox);
+ }
}
}
}
}
- public synchronized void shutdown() {
-
+ public void shutdown() {
this.isShutDown = true;
-
- if (managedConn != null)
- managedConn.detach();
-
- try {
- if (uniquePoolEntry != null) // and connection open?
- uniquePoolEntry.shutdown();
- } catch (IOException iox) {
- // ignore
- log.debug("Problem while shutting down manager.", iox);
- } finally {
- uniquePoolEntry = null;
+ synchronized (this) {
+ try {
+ if (uniquePoolEntry != null) // and connection open?
+ uniquePoolEntry.shutdown();
+ } catch (IOException iox) {
+ // ignore
+ log.debug("Problem while shutting down manager.", iox);
+ } finally {
+ uniquePoolEntry = null;
+ managedConn = null;
+ }
}
}
- /**
- * @deprecated no longer used
- */
- @Deprecated
- protected synchronized void revokeConnection() {
- if (managedConn == null)
+ protected void revokeConnection() {
+ ConnAdapter conn = managedConn;
+ if (conn == null)
return;
- managedConn.detach();
- try {
- uniquePoolEntry.shutdown();
- } catch (IOException iox) {
- // ignore
- log.debug("Problem while shutting down connection.", iox);
+ conn.detach();
+
+ synchronized (this) {
+ try {
+ uniquePoolEntry.shutdown();
+ } catch (IOException iox) {
+ // ignore
+ log.debug("Problem while shutting down connection.", iox);
+ }
}
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/SystemDefaultDnsResolver.java b/httpclient/src/main/java/org/apache/http/impl/conn/SystemDefaultDnsResolver.java
new file mode 100644
index 0000000..1196d95
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/SystemDefaultDnsResolver.java
@@ -0,0 +1,48 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.conn;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.apache.http.conn.DnsResolver;
+
+/**
+ * DNS resolver that uses the default OS implementation for resolving host names.
+ *
+ * @since 4.2
+ */
+public class SystemDefaultDnsResolver implements DnsResolver {
+
+ /**
+ * {@inheritDoc}
+ */
+ public InetAddress[] resolve(String host) throws UnknownHostException {
+ return InetAddress.getAllByName(host);
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/Wire.java b/httpclient/src/main/java/org/apache/http/impl/conn/Wire.java
index bc0b9e2..11cb492 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/Wire.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/Wire.java
@@ -141,7 +141,10 @@ public class Wire {
input(new byte[] {(byte) b});
}
- @Deprecated
+ /**
+ * @deprecated (4.1) do not use
+ */
+ @Deprecated
public void output(final String s)
throws IOException {
if (s == null) {
@@ -150,7 +153,10 @@ public class Wire {
output(s.getBytes());
}
- @Deprecated
+ /**
+ * @deprecated (4.1) do not use
+ */
+ @Deprecated
public void input(final String s)
throws IOException {
if (s == null) {
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/AbstractConnPool.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/AbstractConnPool.java
index a687bdf..1892d38 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/AbstractConnPool.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/AbstractConnPool.java
@@ -53,10 +53,11 @@ import org.apache.http.impl.conn.IdleConnectionHandler;
* Don't use <code>synchronized</code> for that purpose!
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link org.apache.http.pool.AbstractConnPool}
*/
-
@Deprecated
-public abstract class AbstractConnPool implements RefQueueHandler {
+public abstract class AbstractConnPool {
private final Log log;
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java
index 5aeb539..ea270da 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java
@@ -29,7 +29,6 @@ package org.apache.http.impl.conn.tsccm;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.TimeUnit;
-import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.conn.OperatedClientConnection;
import org.apache.http.conn.ClientConnectionOperator;
import org.apache.http.conn.routing.HttpRoute;
@@ -39,8 +38,10 @@ import org.apache.http.impl.conn.AbstractPoolEntry;
* Basic implementation of a connection pool entry.
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link org.apache.http.pool.PoolEntry}
*/
- at NotThreadSafe
+ at Deprecated
public class BasicPoolEntry extends AbstractPoolEntry {
private final long created;
@@ -49,10 +50,6 @@ public class BasicPoolEntry extends AbstractPoolEntry {
private long validUntil;
private long expiry;
- /**
- * @deprecated do not use
- */
- @Deprecated
public BasicPoolEntry(ClientConnectionOperator op,
HttpRoute route,
ReferenceQueue<Object> queue) {
@@ -109,7 +106,6 @@ public class BasicPoolEntry extends AbstractPoolEntry {
return super.route;
}
- @Deprecated
protected final BasicPoolEntryRef getWeakRef() {
return null;
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java
index b565876..ce23ee6 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java
@@ -26,24 +26,21 @@
package org.apache.http.impl.conn.tsccm;
-
import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;
-import org.apache.http.annotation.Immutable;
-
import org.apache.http.conn.routing.HttpRoute;
-
-
/**
* A weak reference to a {@link BasicPoolEntry BasicPoolEntry}.
* This reference explicitly keeps the planned route, so the connection
* can be reclaimed if it is lost to garbage collection.
*
* @since 4.0
+ *
+ * @deprecated (4.2) do not use
*/
- at Immutable
+ at Deprecated
public class BasicPoolEntryRef extends WeakReference<BasicPoolEntry> {
/** The planned route of the entry. */
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java
index 607fde7..5f20a96 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java
@@ -36,7 +36,10 @@ import org.apache.http.impl.conn.AbstractPooledConnAdapter;
* can be {@link #detach detach}ed to prevent further use on release.
*
* @since 4.0
+ *
+ * @deprecated (4.2) do not use
*/
+ at Deprecated
public class BasicPooledConnAdapter extends AbstractPooledConnAdapter {
/**
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java
index b0a5394..864c6d6 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java
@@ -40,7 +40,6 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.http.annotation.ThreadSafe;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.ClientConnectionOperator;
import org.apache.http.conn.ConnectionPoolTimeoutException;
@@ -63,10 +62,11 @@ import org.apache.http.params.HttpParams;
* not via <code>synchronized</code> methods.
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link org.apache.http.pool.AbstractConnPool}
*/
- at ThreadSafe
- at SuppressWarnings("deprecation")
-public class ConnPoolByRoute extends AbstractConnPool { //TODO: remove dependency on AbstractConnPool
+ at Deprecated
+public class ConnPoolByRoute extends AbstractConnPool {
private final Log log = LogFactory.getLog(getClass());
@@ -147,9 +147,8 @@ public class ConnPoolByRoute extends AbstractConnPool { //TODO: remove dependenc
/**
* Creates a new connection pool, managed by route.
*
- * @deprecated use {@link ConnPoolByRoute#ConnPoolByRoute(ClientConnectionOperator, ConnPerRoute, int)}
+ * @deprecated (4.1) use {@link ConnPoolByRoute#ConnPoolByRoute(ClientConnectionOperator, ConnPerRoute, int)}
*/
- @Deprecated
public ConnPoolByRoute(final ClientConnectionOperator operator, final HttpParams params) {
this(operator,
ConnManagerParams.getMaxConnectionsPerRoute(params),
@@ -449,7 +448,7 @@ public class ConnPoolByRoute extends AbstractConnPool { //TODO: remove dependenc
RouteSpecificPool rospl = getRoutePool(route, true);
- if (reusable) {
+ if (reusable && rospl.getCapacity() >= 0) {
if (log.isDebugEnabled()) {
String s;
if (validDuration > 0) {
@@ -464,6 +463,7 @@ public class ConnPoolByRoute extends AbstractConnPool { //TODO: remove dependenc
entry.updateExpiry(validDuration, timeUnit);
freeConnections.add(entry);
} else {
+ closeConnection(entry);
rospl.dropEntry();
numConnections--;
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java
index c96e245..a27dae7 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java
@@ -26,6 +26,7 @@
package org.apache.http.impl.conn.tsccm;
+import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.apache.http.conn.ConnectionPoolTimeoutException;
@@ -34,7 +35,10 @@ import org.apache.http.conn.ConnectionPoolTimeoutException;
* Encapsulates a request for a {@link BasicPoolEntry}.
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link Future}
*/
+ at Deprecated
public interface PoolEntryRequest {
/**
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueHandler.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueHandler.java
deleted file mode 100644
index f4666bc..0000000
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueHandler.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.impl.conn.tsccm;
-
-import java.lang.ref.Reference;
-
-/**
- * @deprecated do not use
- *
- * @since 4.0
- */
- at Deprecated
-public interface RefQueueHandler {
-
- /**
- * Invoked when a reference is found on the queue.
- *
- * @param ref the reference to handle
- */
- public void handleReference(Reference<?> ref)
- ;
-}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueWorker.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueWorker.java
deleted file mode 100644
index 1d65c06..0000000
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueWorker.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.impl.conn.tsccm;
-
-import java.lang.ref.Reference;
-import java.lang.ref.ReferenceQueue;
-
-/**
- * A worker thread for processing queued references.
- * {@link Reference Reference}s can be
- * {@link ReferenceQueue queued}
- * automatically by the garbage collector.
- * If that feature is used, a daemon thread should be executing
- * this worker. It will pick up the queued references and pass them
- * on to a handler for appropriate processing.
- *
- * @deprecated do not use
- */
- at Deprecated
-public class RefQueueWorker implements Runnable {
-
- /** The reference queue to monitor. */
- protected final ReferenceQueue<?> refQueue;
-
- /** The handler for the references found. */
- protected final RefQueueHandler refHandler;
-
-
- /**
- * The thread executing this handler.
- * This attribute is also used as a shutdown indicator.
- */
- protected volatile Thread workerThread;
-
-
- /**
- * Instantiates a new worker to listen for lost connections.
- *
- * @param queue the queue on which to wait for references
- * @param handler the handler to pass the references to
- */
- public RefQueueWorker(ReferenceQueue<?> queue, RefQueueHandler handler) {
- if (queue == null) {
- throw new IllegalArgumentException("Queue must not be null.");
- }
- if (handler == null) {
- throw new IllegalArgumentException("Handler must not be null.");
- }
-
- refQueue = queue;
- refHandler = handler;
- }
-
-
- /**
- * The main loop of this worker.
- * If initialization succeeds, this method will only return
- * after {@link #shutdown shutdown()}. Only one thread can
- * execute the main loop at any time.
- */
- public void run() {
-
- if (this.workerThread == null) {
- this.workerThread = Thread.currentThread();
- }
-
- while (this.workerThread == Thread.currentThread()) {
- try {
- // remove the next reference and process it
- Reference<?> ref = refQueue.remove();
- refHandler.handleReference(ref);
- } catch (InterruptedException ignore) {
- }
- }
- }
-
-
- /**
- * Shuts down this worker.
- * It can be re-started afterwards by another call to {@link #run run()}.
- */
- public void shutdown() {
- Thread wt = this.workerThread;
- if (wt != null) {
- this.workerThread = null; // indicate shutdown
- wt.interrupt();
- }
- }
-
-
- /**
- * Obtains a description of this worker.
- *
- * @return a descriptive string for this worker
- */
- @Override
- public String toString() {
- return "RefQueueWorker::" + this.workerThread;
- }
-
-} // class RefQueueWorker
-
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java
index be67f03..3283ad1 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java
@@ -31,8 +31,6 @@ import java.util.ListIterator;
import java.util.Queue;
import java.util.LinkedList;
-import org.apache.http.annotation.NotThreadSafe;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.conn.OperatedClientConnection;
@@ -47,8 +45,10 @@ import org.apache.http.util.LangUtils;
* containing pool takes care of synchronization.
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link org.apache.http.pool.AbstractConnPool}
*/
- at NotThreadSafe // e.g. numEntries, freeEntries,
+ at Deprecated
public class RouteSpecificPool {
private final Log log = LogFactory.getLog(getClass());
@@ -56,7 +56,6 @@ public class RouteSpecificPool {
/** The route this pool is for. */
protected final HttpRoute route; //Immutable
- @Deprecated
protected final int maxEntries;
/** Connections per route */
@@ -75,11 +74,9 @@ public class RouteSpecificPool {
/** The number of created entries. */
protected int numEntries;
-
/**
- * @deprecated use {@link RouteSpecificPool#RouteSpecificPool(HttpRoute, ConnPerRoute)}
+ * @deprecated (4.1) use {@link RouteSpecificPool#RouteSpecificPool(HttpRoute, ConnPerRoute)}
*/
- @Deprecated
public RouteSpecificPool(HttpRoute route, int maxEntries) {
this.route = route;
this.maxEntries = maxEntries;
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java
index f884654..022794a 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java
@@ -43,6 +43,7 @@ import org.apache.http.conn.ManagedClientConnection;
import org.apache.http.conn.OperatedClientConnection;
import org.apache.http.params.HttpParams;
import org.apache.http.impl.conn.DefaultClientConnectionOperator;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.impl.conn.SchemeRegistryFactory;
/**
@@ -62,8 +63,11 @@ import org.apache.http.impl.conn.SchemeRegistryFactory;
* can be adjusted using HTTP parameters.
*
* @since 4.0
+ *
+ * @deprecated (4.2) use {@link PoolingClientConnectionManager}
*/
@ThreadSafe
+ at Deprecated
public class ThreadSafeClientConnManager implements ClientConnectionManager {
private final Log log;
@@ -71,7 +75,6 @@ public class ThreadSafeClientConnManager implements ClientConnectionManager {
/** The schemes supported by this connection manager. */
protected final SchemeRegistry schemeRegistry; // @ThreadSafe
- @Deprecated
protected final AbstractConnPool connectionPool;
/** The pool of connections being managed. */
@@ -109,13 +112,30 @@ public class ThreadSafeClientConnManager implements ClientConnectionManager {
*/
public ThreadSafeClientConnManager(final SchemeRegistry schreg,
long connTTL, TimeUnit connTTLTimeUnit) {
+ this(schreg, connTTL, connTTLTimeUnit, new ConnPerRouteBean());
+ }
+
+ /**
+ * Creates a new thread safe connection manager.
+ *
+ * @param schreg the scheme registry.
+ * @param connTTL max connection lifetime, <=0 implies "infinity"
+ * @param connTTLTimeUnit TimeUnit of connTTL
+ * @param connPerRoute mapping of maximum connections per route,
+ * provided as a dependency so it can be managed externally, e.g.
+ * for dynamic connection pool size management.
+ *
+ * @since 4.2
+ */
+ public ThreadSafeClientConnManager(final SchemeRegistry schreg,
+ long connTTL, TimeUnit connTTLTimeUnit, ConnPerRouteBean connPerRoute) {
super();
if (schreg == null) {
throw new IllegalArgumentException("Scheme registry may not be null");
}
this.log = LogFactory.getLog(getClass());
this.schemeRegistry = schreg;
- this.connPerRoute = new ConnPerRouteBean();
+ this.connPerRoute = connPerRoute;
this.connOperator = createConnectionOperator(schreg);
this.pool = createConnectionPool(connTTL, connTTLTimeUnit) ;
this.connectionPool = this.pool;
@@ -127,9 +147,8 @@ public class ThreadSafeClientConnManager implements ClientConnectionManager {
* @param params the parameters for this manager.
* @param schreg the scheme registry.
*
- * @deprecated use {@link ThreadSafeClientConnManager#ThreadSafeClientConnManager(SchemeRegistry)}
+ * @deprecated (4.1) use {@link ThreadSafeClientConnManager#ThreadSafeClientConnManager(SchemeRegistry)}
*/
- @Deprecated
public ThreadSafeClientConnManager(HttpParams params,
SchemeRegistry schreg) {
if (schreg == null) {
@@ -157,9 +176,8 @@ public class ThreadSafeClientConnManager implements ClientConnectionManager {
*
* @return the connection pool to use
*
- * @deprecated use #createConnectionPool(long, TimeUnit))
+ * @deprecated (4.1) use #createConnectionPool(long, TimeUnit))
*/
- @Deprecated
protected AbstractConnPool createConnectionPool(final HttpParams params) {
return new ConnPoolByRoute(connOperator, params);
}
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThread.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThread.java
index bab3e2c..47bafd6 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThread.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThread.java
@@ -30,8 +30,6 @@ package org.apache.http.impl.conn.tsccm;
import java.util.Date;
import java.util.concurrent.locks.Condition;
-import org.apache.http.annotation.NotThreadSafe;
-
/**
* Represents a thread waiting for a connection.
* This class implements throwaway objects. It is instantiated whenever
@@ -44,8 +42,10 @@ import org.apache.http.annotation.NotThreadSafe;
*
*
* @since 4.0
+ *
+ * @deprecated (4.2) do not use
*/
- at NotThreadSafe
+ at Deprecated
public class WaitingThread {
/** The condition on which the thread is waiting. */
diff --git a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java
index f0aee20..ce06894 100644
--- a/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java
+++ b/httpclient/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java
@@ -26,17 +26,16 @@
package org.apache.http.impl.conn.tsccm;
-import org.apache.http.annotation.NotThreadSafe;
-
-// TODO - only called from ConnPoolByRoute currently; consider adding it as nested class
/**
* A simple class that can interrupt a {@link WaitingThread}.
*
* Must be called with the pool lock held.
*
* @since 4.0
+ *
+ * @deprecated (4.2) do not use
*/
- at NotThreadSafe
+ at Deprecated
public class WaitingThreadAborter {
private WaitingThread waitingThread;
diff --git a/httpclient/src/main/java/org/apache/http/impl/cookie/BestMatchSpec.java b/httpclient/src/main/java/org/apache/http/impl/cookie/BestMatchSpec.java
index ce6211e..1e9704b 100644
--- a/httpclient/src/main/java/org/apache/http/impl/cookie/BestMatchSpec.java
+++ b/httpclient/src/main/java/org/apache/http/impl/cookie/BestMatchSpec.java
@@ -182,7 +182,7 @@ public class BestMatchSpec implements CookieSpec {
public List<Header> formatCookies(final List<Cookie> cookies) {
if (cookies == null) {
- throw new IllegalArgumentException("List of cookie may not be null");
+ throw new IllegalArgumentException("List of cookies may not be null");
}
int version = Integer.MAX_VALUE;
boolean isSetCookie2 = true;
@@ -218,4 +218,4 @@ public class BestMatchSpec implements CookieSpec {
return "best-match";
}
-}
\ No newline at end of file
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpec.java b/httpclient/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpec.java
index 018619f..81678fc 100644
--- a/httpclient/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpec.java
+++ b/httpclient/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpec.java
@@ -55,24 +55,6 @@ import org.apache.http.util.CharArrayBuffer;
@NotThreadSafe // superclass is @NotThreadSafe
public class BrowserCompatSpec extends CookieSpecBase {
- @Deprecated
- protected static final String[] DATE_PATTERNS = new String[] {
- DateUtils.PATTERN_RFC1123,
- DateUtils.PATTERN_RFC1036,
- DateUtils.PATTERN_ASCTIME,
- "EEE, dd-MMM-yyyy HH:mm:ss z",
- "EEE, dd-MMM-yyyy HH-mm-ss z",
- "EEE, dd MMM yy HH:mm:ss z",
- "EEE dd-MMM-yyyy HH:mm:ss z",
- "EEE dd MMM yyyy HH:mm:ss z",
- "EEE dd-MMM-yyyy HH-mm-ss z",
- "EEE dd-MMM-yy HH:mm:ss z",
- "EEE dd MMM yy HH:mm:ss z",
- "EEE,dd-MMM-yy HH:mm:ss z",
- "EEE,dd-MMM-yyyy HH:mm:ss z",
- "EEE, dd-MM-yyyy HH:mm:ss z",
- };
-
private static final String[] DEFAULT_DATE_PATTERNS = new String[] {
DateUtils.PATTERN_RFC1123,
DateUtils.PATTERN_RFC1036,
diff --git a/httpclient/src/main/java/org/apache/http/impl/cookie/IgnoreSpecFactory.java b/httpclient/src/main/java/org/apache/http/impl/cookie/IgnoreSpecFactory.java
index b1adb55..254861d 100644
--- a/httpclient/src/main/java/org/apache/http/impl/cookie/IgnoreSpecFactory.java
+++ b/httpclient/src/main/java/org/apache/http/impl/cookie/IgnoreSpecFactory.java
@@ -34,7 +34,7 @@ import org.apache.http.params.HttpParams;
/**
* {@link CookieSpecFactory} implementation that ignores all cookies.
- *
+ *
* @since 4.1
*/
@Immutable
diff --git a/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java b/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java
index 90919cd..f8fc034 100644
--- a/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java
+++ b/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java
@@ -64,4 +64,4 @@ public class RFC2965CommentUrlAttributeHandler implements CookieAttributeHandler
return true;
}
- }
\ No newline at end of file
+ }
diff --git a/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java b/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java
index 67b9237..01a3a35 100644
--- a/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java
+++ b/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java
@@ -64,4 +64,4 @@ public class RFC2965DiscardAttributeHandler implements CookieAttributeHandler {
return true;
}
- }
\ No newline at end of file
+ }
diff --git a/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java b/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java
index 2789e64..076745b 100644
--- a/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java
+++ b/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java
@@ -191,4 +191,4 @@ public class RFC2965DomainAttributeHandler implements CookieAttributeHandler {
return effectiveHostWithoutDomain.indexOf('.') == -1;
}
-}
\ No newline at end of file
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java b/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java
index 5f73b4f..6056330 100644
--- a/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java
+++ b/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java
@@ -95,4 +95,4 @@ public class RFC2965VersionAttributeHandler implements CookieAttributeHandler {
return true;
}
-}
\ No newline at end of file
+}
diff --git a/httpclient/src/site/site.xml b/httpclient/src/site/site.xml
index 463453f..02ea590 100644
--- a/httpclient/src/site/site.xml
+++ b/httpclient/src/site/site.xml
@@ -30,7 +30,7 @@
<body>
<menu name="HttpClient Overview">
<item name="Description" href="../index.html"/>
- <item name="Examples" href="../examples.html"/>
+ <item name="Quick Start" href="../quickstart.html"/>
</menu>
<menu ref="modules" />
<menu ref="reports"/>
diff --git a/httpclient/src/test/java/org/apache/http/client/entity/TestDecompressingEntity.java b/httpclient/src/test/java/org/apache/http/client/entity/TestDecompressingEntity.java
new file mode 100644
index 0000000..e3bf408
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/client/entity/TestDecompressingEntity.java
@@ -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.entity;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.CRC32;
+import java.util.zip.CheckedInputStream;
+import java.util.zip.Checksum;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.util.EntityUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestDecompressingEntity {
+
+ @Test
+ public void testNonStreaming() throws Exception {
+ CRC32 crc32 = new CRC32();
+ StringEntity wrapped = new StringEntity("1234567890", "ASCII");
+ ChecksumEntity entity = new ChecksumEntity(wrapped, crc32);
+ Assert.assertFalse(entity.isStreaming());
+ String s = EntityUtils.toString(entity);
+ Assert.assertEquals("1234567890", s);
+ Assert.assertEquals(639479525L, crc32.getValue());
+ InputStream in1 = entity.getContent();
+ InputStream in2 = entity.getContent();
+ Assert.assertTrue(in1 != in2);
+ }
+
+ @Test
+ public void testStreaming() throws Exception {
+ CRC32 crc32 = new CRC32();
+ ByteArrayInputStream in = new ByteArrayInputStream("1234567890".getBytes("ASCII"));
+ InputStreamEntity wrapped = new InputStreamEntity(in, -1);
+ ChecksumEntity entity = new ChecksumEntity(wrapped, crc32);
+ Assert.assertTrue(entity.isStreaming());
+ String s = EntityUtils.toString(entity);
+ Assert.assertEquals("1234567890", s);
+ Assert.assertEquals(639479525L, crc32.getValue());
+ InputStream in1 = entity.getContent();
+ InputStream in2 = entity.getContent();
+ Assert.assertTrue(in1 == in2);
+ EntityUtils.consume(entity);
+ EntityUtils.consume(entity);
+ }
+
+ @Test
+ public void testWriteToStream() throws Exception {
+ CRC32 crc32 = new CRC32();
+ StringEntity wrapped = new StringEntity("1234567890", "ASCII");
+ ChecksumEntity entity = new ChecksumEntity(wrapped, crc32);
+ Assert.assertFalse(entity.isStreaming());
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ entity.writeTo(out);
+
+ String s = new String(out.toByteArray(), "ASCII");
+ Assert.assertEquals("1234567890", s);
+ Assert.assertEquals(639479525L, crc32.getValue());
+ }
+
+ static class ChecksumEntity extends DecompressingEntity {
+
+ private final Checksum checksum;
+
+ public ChecksumEntity(final HttpEntity wrapped, final Checksum checksum) {
+ super(wrapped);
+ this.checksum = checksum;
+ }
+
+ @Override
+ InputStream getDecompressingInputStream(final InputStream wrapped) throws IOException {
+ return new CheckedInputStream(wrapped, this.checksum);
+ }
+
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/client/protocol/TestCookie2Support.java b/httpclient/src/test/java/org/apache/http/client/protocol/TestCookie2Support.java
index 573345a..704426d 100644
--- a/httpclient/src/test/java/org/apache/http/client/protocol/TestCookie2Support.java
+++ b/httpclient/src/test/java/org/apache/http/client/protocol/TestCookie2Support.java
@@ -64,9 +64,10 @@ public class TestCookie2Support extends BasicServerTestBase {
@Before
public void setUp() throws Exception {
- localServer = new LocalTestServer(null, null);
- localServer.registerDefaultHandlers();
- localServer.start();
+ this.localServer = new LocalTestServer(null, null);
+ this.localServer.registerDefaultHandlers();
+ this.localServer.start();
+ this.httpclient = new DefaultHttpClient();
}
private static class CookieVer0Service implements HttpRequestHandler {
@@ -88,8 +89,7 @@ public class TestCookie2Support extends BasicServerTestBase {
public void testCookieVersionSupportHeader1() throws Exception {
this.localServer.register("*", new CookieVer0Service());
- DefaultHttpClient client = new DefaultHttpClient();
- client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH);
+ this.httpclient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH);
CookieStore cookieStore = new BasicCookieStore();
HttpContext context = new BasicHttpContext();
@@ -97,7 +97,7 @@ public class TestCookie2Support extends BasicServerTestBase {
HttpGet httpget = new HttpGet("/test/");
- HttpResponse response1 = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response1 = this.httpclient.execute(getServerHttp(), httpget, context);
HttpEntity e1 = response1.getEntity();
EntityUtils.consume(e1);
@@ -105,7 +105,7 @@ public class TestCookie2Support extends BasicServerTestBase {
Assert.assertNotNull(cookies);
Assert.assertEquals(1, cookies.size());
- HttpResponse response2 = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response2 = this.httpclient.execute(getServerHttp(), httpget, context);
HttpEntity e2 = response2.getEntity();
EntityUtils.consume(e2);
@@ -136,8 +136,7 @@ public class TestCookie2Support extends BasicServerTestBase {
public void testCookieVersionSupportHeader2() throws Exception {
this.localServer.register("*", new CookieVer1Service());
- DefaultHttpClient client = new DefaultHttpClient();
- client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH);
+ this.httpclient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH);
CookieStore cookieStore = new BasicCookieStore();
HttpContext context = new BasicHttpContext();
@@ -145,7 +144,7 @@ public class TestCookie2Support extends BasicServerTestBase {
HttpGet httpget = new HttpGet("/test/");
- HttpResponse response1 = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response1 = this.httpclient.execute(getServerHttp(), httpget, context);
HttpEntity e1 = response1.getEntity();
EntityUtils.consume(e1);
@@ -153,7 +152,7 @@ public class TestCookie2Support extends BasicServerTestBase {
Assert.assertNotNull(cookies);
Assert.assertEquals(2, cookies.size());
- HttpResponse response2 = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response2 = this.httpclient.execute(getServerHttp(), httpget, context);
HttpEntity e2 = response2.getEntity();
EntityUtils.consume(e2);
@@ -183,8 +182,7 @@ public class TestCookie2Support extends BasicServerTestBase {
public void testCookieVersionSupportHeader3() throws Exception {
this.localServer.register("*", new CookieVer2Service());
- DefaultHttpClient client = new DefaultHttpClient();
- client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH);
+ this.httpclient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH);
CookieStore cookieStore = new BasicCookieStore();
HttpContext context = new BasicHttpContext();
@@ -192,7 +190,7 @@ public class TestCookie2Support extends BasicServerTestBase {
HttpGet httpget = new HttpGet("/test/");
- HttpResponse response1 = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response1 = this.httpclient.execute(getServerHttp(), httpget, context);
HttpEntity e1 = response1.getEntity();
EntityUtils.consume(e1);
@@ -200,7 +198,7 @@ public class TestCookie2Support extends BasicServerTestBase {
Assert.assertNotNull(cookies);
Assert.assertEquals(1, cookies.size());
- HttpResponse response2 = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response2 = this.httpclient.execute(getServerHttp(), httpget, context);
HttpEntity e2 = response2.getEntity();
EntityUtils.consume(e2);
@@ -231,8 +229,7 @@ public class TestCookie2Support extends BasicServerTestBase {
public void testSetCookieVersionMix() throws Exception {
this.localServer.register("*", new SetCookieVersionMixService());
- DefaultHttpClient client = new DefaultHttpClient();
- client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH);
+ this.httpclient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH);
CookieStore cookieStore = new BasicCookieStore();
HttpContext context = new BasicHttpContext();
@@ -240,7 +237,7 @@ public class TestCookie2Support extends BasicServerTestBase {
HttpGet httpget = new HttpGet("/test/");
- HttpResponse response1 = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response1 = this.httpclient.execute(getServerHttp(), httpget, context);
HttpEntity e1 = response1.getEntity();
EntityUtils.consume(e1);
diff --git a/httpclient/src/test/java/org/apache/http/client/protocol/TestRedirects.java b/httpclient/src/test/java/org/apache/http/client/protocol/TestRedirects.java
index 611ef34..4238a32 100644
--- a/httpclient/src/test/java/org/apache/http/client/protocol/TestRedirects.java
+++ b/httpclient/src/test/java/org/apache/http/client/protocol/TestRedirects.java
@@ -71,9 +71,10 @@ public class TestRedirects extends BasicServerTestBase {
@Before
public void setUp() throws Exception {
- localServer = new LocalTestServer(null, null);
- localServer.registerDefaultHandlers();
- localServer.start();
+ this.localServer = new LocalTestServer(null, null);
+ this.localServer.registerDefaultHandlers();
+ this.localServer.start();
+ this.httpclient = new DefaultHttpClient();
}
private static class BasicRedirectService implements HttpRequestHandler {
@@ -225,12 +226,11 @@ public class TestRedirects extends BasicServerTestBase {
this.localServer.register("*",
new BasicRedirectService(host, port, HttpStatus.SC_MULTIPLE_CHOICES));
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpGet httpget = new HttpGet("/oldlocation/");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -248,12 +248,11 @@ public class TestRedirects extends BasicServerTestBase {
this.localServer.register("*",
new BasicRedirectService(host, port, HttpStatus.SC_MOVED_PERMANENTLY));
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpGet httpget = new HttpGet("/oldlocation/");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -275,12 +274,11 @@ public class TestRedirects extends BasicServerTestBase {
this.localServer.register("*",
new BasicRedirectService(host, port, HttpStatus.SC_MOVED_TEMPORARILY));
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpGet httpget = new HttpGet("/oldlocation/");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -310,12 +308,11 @@ public class TestRedirects extends BasicServerTestBase {
});
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpGet httpget = new HttpGet("/oldlocation/");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -337,12 +334,11 @@ public class TestRedirects extends BasicServerTestBase {
this.localServer.register("*",
new BasicRedirectService(host, port, HttpStatus.SC_SEE_OTHER));
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpGet httpget = new HttpGet("/oldlocation/");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -364,12 +360,11 @@ public class TestRedirects extends BasicServerTestBase {
this.localServer.register("*",
new BasicRedirectService(host, port, HttpStatus.SC_NOT_MODIFIED));
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpGet httpget = new HttpGet("/oldlocation/");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -387,12 +382,11 @@ public class TestRedirects extends BasicServerTestBase {
this.localServer.register("*",
new BasicRedirectService(host, port, HttpStatus.SC_USE_PROXY));
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpGet httpget = new HttpGet("/oldlocation/");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -410,12 +404,11 @@ public class TestRedirects extends BasicServerTestBase {
this.localServer.register("*",
new BasicRedirectService(host, port, HttpStatus.SC_TEMPORARY_REDIRECT));
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpGet httpget = new HttpGet("/oldlocation/");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -433,13 +426,12 @@ public class TestRedirects extends BasicServerTestBase {
public void testMaxRedirectCheck() throws Exception {
this.localServer.register("*", new CircularRedirectService());
- DefaultHttpClient client = new DefaultHttpClient();
- client.getParams().setBooleanParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true);
- client.getParams().setIntParameter(ClientPNames.MAX_REDIRECTS, 5);
+ this.httpclient.getParams().setBooleanParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true);
+ this.httpclient.getParams().setIntParameter(ClientPNames.MAX_REDIRECTS, 5);
HttpGet httpget = new HttpGet("/circular-oldlocation/");
try {
- client.execute(getServerHttp(), httpget);
+ this.httpclient.execute(getServerHttp(), httpget);
} catch (ClientProtocolException e) {
Assert.assertTrue(e.getCause() instanceof RedirectException);
throw e;
@@ -450,13 +442,12 @@ public class TestRedirects extends BasicServerTestBase {
public void testCircularRedirect() throws Exception {
this.localServer.register("*", new CircularRedirectService());
- DefaultHttpClient client = new DefaultHttpClient();
- client.getParams().setBooleanParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, false);
+ this.httpclient.getParams().setBooleanParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, false);
HttpGet httpget = new HttpGet("/circular-oldlocation/");
try {
- client.execute(getServerHttp(), httpget);
+ this.httpclient.execute(getServerHttp(), httpget);
} catch (ClientProtocolException e) {
Assert.assertTrue(e.getCause() instanceof CircularRedirectException);
throw e;
@@ -470,13 +461,12 @@ public class TestRedirects extends BasicServerTestBase {
String host = address.getHostName();
this.localServer.register("*", new BasicRedirectService(host, port));
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpPost httppost = new HttpPost("/oldlocation/");
httppost.setEntity(new StringEntity("stuff"));
- HttpResponse response = client.execute(getServerHttp(), httppost, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httppost, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -495,13 +485,12 @@ public class TestRedirects extends BasicServerTestBase {
this.localServer.register("*", new BasicRedirectService(host, port,
HttpStatus.SC_SEE_OTHER));
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
HttpPost httppost = new HttpPost("/oldlocation/");
httppost.setEntity(new StringEntity("stuff"));
- HttpResponse response = client.execute(getServerHttp(), httppost, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httppost, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -519,14 +508,13 @@ public class TestRedirects extends BasicServerTestBase {
String host = address.getHostName();
this.localServer.register("*", new RelativeRedirectService());
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
- client.getParams().setBooleanParameter(
+ this.httpclient.getParams().setBooleanParameter(
ClientPNames.REJECT_RELATIVE_REDIRECT, false);
HttpGet httpget = new HttpGet("/oldlocation/");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -547,14 +535,13 @@ public class TestRedirects extends BasicServerTestBase {
String host = address.getHostName();
this.localServer.register("*", new RelativeRedirectService2());
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
- client.getParams().setBooleanParameter(
+ this.httpclient.getParams().setBooleanParameter(
ClientPNames.REJECT_RELATIVE_REDIRECT, false);
HttpGet httpget = new HttpGet("/test/oldlocation");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -572,29 +559,25 @@ public class TestRedirects extends BasicServerTestBase {
public void testRejectRelativeRedirect() throws Exception {
this.localServer.register("*", new RelativeRedirectService());
- DefaultHttpClient client = new DefaultHttpClient();
-
- client.getParams().setBooleanParameter(
+ this.httpclient.getParams().setBooleanParameter(
ClientPNames.REJECT_RELATIVE_REDIRECT, true);
HttpGet httpget = new HttpGet("/oldlocation/");
try {
- client.execute(getServerHttp(), httpget);
+ this.httpclient.execute(getServerHttp(), httpget);
} catch (ClientProtocolException e) {
Assert.assertTrue(e.getCause() instanceof ProtocolException);
throw e;
}
}
- @Test(expected=IllegalStateException.class)
+ @Test(expected=ClientProtocolException.class)
public void testRejectBogusRedirectLocation() throws Exception {
this.localServer.register("*", new BogusRedirectService("xxx://bogus"));
- DefaultHttpClient client = new DefaultHttpClient();
-
HttpGet httpget = new HttpGet("/oldlocation/");
- client.execute(getServerHttp(), httpget);
+ this.httpclient.execute(getServerHttp(), httpget);
}
@Test(expected=ClientProtocolException.class)
@@ -605,12 +588,10 @@ public class TestRedirects extends BasicServerTestBase {
this.localServer.register("*",
new BogusRedirectService("http://"+ host +":"+ port +"/newlocation/?p=I have spaces"));
- DefaultHttpClient client = new DefaultHttpClient();
-
HttpGet httpget = new HttpGet("/oldlocation/");
try {
- client.execute(getServerHttp(), httpget);
+ this.httpclient.execute(getServerHttp(), httpget);
} catch (ClientProtocolException e) {
Assert.assertTrue(e.getCause() instanceof ProtocolException);
throw e;
@@ -626,10 +607,8 @@ public class TestRedirects extends BasicServerTestBase {
this.localServer.register("*",
new BasicRedirectService(host, port));
- DefaultHttpClient client = new DefaultHttpClient();
-
CookieStore cookieStore = new BasicCookieStore();
- client.setCookieStore(cookieStore);
+ this.httpclient.setCookieStore(cookieStore);
BasicClientCookie cookie = new BasicClientCookie("name", "value");
cookie.setDomain(host);
@@ -641,7 +620,7 @@ public class TestRedirects extends BasicServerTestBase {
HttpGet httpget = new HttpGet("/oldlocation/");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -663,18 +642,17 @@ public class TestRedirects extends BasicServerTestBase {
this.localServer.register("*",
new BasicRedirectService(host, port));
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
List<Header> defaultHeaders = new ArrayList<Header>(1);
defaultHeaders.add(new BasicHeader(HTTP.USER_AGENT, "my-test-client"));
- client.getParams().setParameter(ClientPNames.DEFAULT_HEADERS, defaultHeaders);
+ this.httpclient.getParams().setParameter(ClientPNames.DEFAULT_HEADERS, defaultHeaders);
HttpGet httpget = new HttpGet("/oldlocation/");
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
diff --git a/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAcceptEncoding.java b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAcceptEncoding.java
index 7677aa4..baca22d 100644
--- a/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAcceptEncoding.java
+++ b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAcceptEncoding.java
@@ -50,4 +50,17 @@ public class TestRequestAcceptEncoding {
Assert.assertEquals("gzip,deflate", header.getValue());
}
+ @Test
+ public void testAcceptEncodingAlreadyPResent() throws Exception {
+ HttpRequest request = new BasicHttpRequest("GET", "/");
+ request.addHeader("Accept-Encoding", "stuff");
+ HttpContext context = new BasicHttpContext();
+
+ HttpRequestInterceptor interceptor = new RequestAcceptEncoding();
+ interceptor.process(request, context);
+ Header header = request.getFirstHeader("Accept-Encoding");
+ Assert.assertNotNull(header);
+ Assert.assertEquals("stuff", header.getValue());
+ }
+
}
diff --git a/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAddCookies.java b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAddCookies.java
index eca8dcb..6311411 100644
--- a/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAddCookies.java
+++ b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAddCookies.java
@@ -64,6 +64,7 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
+ at SuppressWarnings("boxing")
public class TestRequestAddCookies {
private HttpHost target;
@@ -499,6 +500,48 @@ public class TestRequestAddCookies {
Assert.assertEquals(0, headers2.length);
}
+ // Helper method
+ private BasicClientCookie makeCookie(String name, String value, String domain, String path) {
+ BasicClientCookie cookie = new BasicClientCookie(name, value);
+ cookie.setDomain(domain);
+ cookie.setPath(path);
+ return cookie;
+ }
+
+ @Test
+ // Test for ordering adapted from test in Commons HC 3.1
+ public void testCookieOrder() throws Exception {
+ HttpRequest request = new BasicHttpRequest("GET", "/foobar/yada/yada");
+
+ this.cookieStore.clear();
+
+ cookieStore.addCookie(makeCookie("nomatch", "value", "localhost.local", "/noway"));
+ cookieStore.addCookie(makeCookie("name2", "value", "localhost.local", "/foobar/yada"));
+ cookieStore.addCookie(makeCookie("name3", "value", "localhost.local", "/foobar"));
+ cookieStore.addCookie(makeCookie("name1", "value", "localhost.local", "/foobar/yada/yada"));
+
+ HttpRoute route = new HttpRoute(this.target, null, false);
+
+ HttpRoutedConnection conn = Mockito.mock(HttpRoutedConnection.class);
+ Mockito.when(conn.getRoute()).thenReturn(route);
+ Mockito.when(conn.isSecure()).thenReturn(Boolean.FALSE);
+
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, this.target);
+ context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
+ context.setAttribute(ClientContext.COOKIE_STORE, this.cookieStore);
+ context.setAttribute(ClientContext.COOKIESPEC_REGISTRY, this.cookieSpecRegistry);
+
+ HttpRequestInterceptor interceptor = new RequestAddCookies();
+ interceptor.process(request, context);
+
+ Header[] headers1 = request.getHeaders(SM.COOKIE);
+ Assert.assertNotNull(headers1);
+ Assert.assertEquals(1, headers1.length);
+
+ Assert.assertEquals("name1=value; name2=value; name3=value", headers1[0].getValue());
+ }
+
@Test
public void testAddSpecVersionHeader() throws Exception {
HttpRequest request = new BasicHttpRequest("GET", "/");
diff --git a/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAuthCache.java b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAuthCache.java
index 6f6742f..d499432 100644
--- a/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAuthCache.java
+++ b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAuthCache.java
@@ -31,6 +31,7 @@ import junit.framework.Assert;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.auth.AuthProtocolState;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
@@ -68,8 +69,8 @@ public class TestRequestAuthCache {
this.credProvider = new BasicCredentialsProvider();
this.creds1 = new UsernamePasswordCredentials("user1", "secret1");
this.creds2 = new UsernamePasswordCredentials("user2", "secret2");
- this.authscope1 = new AuthScope(this.target.getHostName(), this.target.getPort());
- this.authscope2 = new AuthScope(this.proxy.getHostName(), this.proxy.getPort());
+ this.authscope1 = new AuthScope(this.target);
+ this.authscope2 = new AuthScope(this.proxy);
this.authscheme1 = new BasicScheme();
this.authscheme2 = new BasicScheme();
@@ -230,10 +231,10 @@ public class TestRequestAuthCache {
context.setAttribute(ClientContext.AUTH_CACHE, authCache);
- this.targetState.setAuthScheme(new BasicScheme());
- this.targetState.setCredentials(new UsernamePasswordCredentials("user3", "secret3"));
- this.proxyState.setAuthScheme(new BasicScheme());
- this.proxyState.setCredentials(new UsernamePasswordCredentials("user4", "secret4"));
+ this.targetState.setState(AuthProtocolState.CHALLENGED);
+ this.targetState.update(new BasicScheme(), new UsernamePasswordCredentials("user3", "secret3"));
+ this.proxyState.setState(AuthProtocolState.CHALLENGED);
+ this.proxyState.update(new BasicScheme(), new UsernamePasswordCredentials("user4", "secret4"));
HttpRequestInterceptor interceptor = new RequestAuthCache();
interceptor.process(request, context);
diff --git a/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAuthenticationBase.java b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAuthenticationBase.java
new file mode 100644
index 0000000..f792e51
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestAuthenticationBase.java
@@ -0,0 +1,216 @@
+/*
+ * ====================================================================
+ *
+ * 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.protocol;
+
+import java.io.IOException;
+import java.util.LinkedList;
+
+import junit.framework.Assert;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.auth.AUTH;
+import org.apache.http.auth.AuthOption;
+import org.apache.http.auth.AuthProtocolState;
+import org.apache.http.auth.AuthState;
+import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.ContextAwareAuthScheme;
+import org.apache.http.auth.Credentials;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class TestRequestAuthenticationBase {
+
+ static class TestRequestAuthentication extends RequestAuthenticationBase {
+
+ public void process(
+ final HttpRequest request,
+ final HttpContext context) throws HttpException, IOException {
+ AuthState authState = (AuthState) context.getAttribute("test-auth-state");
+ super.process(authState, request, context);
+ }
+
+ }
+
+ private ContextAwareAuthScheme authScheme;
+ private Credentials credentials;
+ private AuthState authState;
+ private HttpContext context;
+ private HttpRequestInterceptor interceptor;
+
+ @Before
+ public void setUp() throws Exception {
+ this.authScheme = Mockito.mock(ContextAwareAuthScheme.class);
+ this.credentials = Mockito.mock(Credentials.class);
+ this.authState = new AuthState();
+ this.context = new BasicHttpContext();
+ this.context.setAttribute("test-auth-state", this.authState);
+ this.interceptor = new TestRequestAuthentication();
+ }
+
+ @Test
+ public void testAuthFailureState() throws Exception {
+ HttpRequest request = new BasicHttpRequest("GET", "/");
+ this.authState.setState(AuthProtocolState.FAILURE);
+ this.authState.update(this.authScheme, this.credentials);
+
+ this.interceptor.process(request, this.context);
+
+ Assert.assertFalse(request.containsHeader(AUTH.WWW_AUTH_RESP));
+
+ Mockito.verify(this.authScheme, Mockito.never()).authenticate(
+ Mockito.any(Credentials.class),
+ Mockito.any(HttpRequest.class),
+ Mockito.any(HttpContext.class));
+ }
+
+ @Test
+ public void testAuthChallengeStateNoOption() throws Exception {
+ HttpRequest request = new BasicHttpRequest("GET", "/");
+ this.authState.setState(AuthProtocolState.CHALLENGED);
+ this.authState.update(this.authScheme, this.credentials);
+
+ Mockito.when(this.authScheme.authenticate(
+ Mockito.any(Credentials.class),
+ Mockito.any(HttpRequest.class),
+ Mockito.any(HttpContext.class))).thenReturn(new BasicHeader(AUTH.WWW_AUTH_RESP, "stuff"));
+
+ this.interceptor.process(request, this.context);
+
+ Assert.assertTrue(request.containsHeader(AUTH.WWW_AUTH_RESP));
+
+ Mockito.verify(this.authScheme).authenticate(this.credentials, request, this.context);
+ }
+
+ @Test
+ public void testAuthChallengeStateOneOptions() throws Exception {
+ HttpRequest request = new BasicHttpRequest("GET", "/");
+ this.authState.setState(AuthProtocolState.CHALLENGED);
+ LinkedList<AuthOption> authOptions = new LinkedList<AuthOption>();
+ authOptions.add(new AuthOption(this.authScheme, this.credentials));
+ this.authState.update(authOptions);
+
+ Mockito.when(this.authScheme.authenticate(
+ Mockito.any(Credentials.class),
+ Mockito.any(HttpRequest.class),
+ Mockito.any(HttpContext.class))).thenReturn(new BasicHeader(AUTH.WWW_AUTH_RESP, "stuff"));
+
+ this.interceptor.process(request, this.context);
+
+ Assert.assertSame(this.authScheme, this.authState.getAuthScheme());
+ Assert.assertSame(this.credentials, this.authState.getCredentials());
+ Assert.assertNull(this.authState.getAuthOptions());
+
+ Assert.assertTrue(request.containsHeader(AUTH.WWW_AUTH_RESP));
+
+ Mockito.verify(this.authScheme).authenticate(this.credentials, request, this.context);
+ }
+
+ @Test
+ public void testAuthChallengeStateMultipleOption() throws Exception {
+ HttpRequest request = new BasicHttpRequest("GET", "/");
+ this.authState.setState(AuthProtocolState.CHALLENGED);
+
+ LinkedList<AuthOption> authOptions = new LinkedList<AuthOption>();
+ ContextAwareAuthScheme authScheme1 = Mockito.mock(ContextAwareAuthScheme.class);
+ Mockito.doThrow(new AuthenticationException()).when(authScheme1).authenticate(
+ Mockito.any(Credentials.class),
+ Mockito.any(HttpRequest.class),
+ Mockito.any(HttpContext.class));
+ ContextAwareAuthScheme authScheme2 = Mockito.mock(ContextAwareAuthScheme.class);
+ Mockito.when(authScheme2.authenticate(
+ Mockito.any(Credentials.class),
+ Mockito.any(HttpRequest.class),
+ Mockito.any(HttpContext.class))).thenReturn(new BasicHeader(AUTH.WWW_AUTH_RESP, "stuff"));
+ authOptions.add(new AuthOption(authScheme1, this.credentials));
+ authOptions.add(new AuthOption(authScheme2, this.credentials));
+ this.authState.update(authOptions);
+
+ this.interceptor.process(request, this.context);
+
+ Assert.assertSame(authScheme2, this.authState.getAuthScheme());
+ Assert.assertSame(this.credentials, this.authState.getCredentials());
+ Assert.assertNull(this.authState.getAuthOptions());
+
+ Assert.assertTrue(request.containsHeader(AUTH.WWW_AUTH_RESP));
+
+ Mockito.verify(authScheme1, Mockito.times(1)).authenticate(this.credentials, request, this.context);
+ Mockito.verify(authScheme2, Mockito.times(1)).authenticate(this.credentials, request, this.context);
+ }
+
+ @Test
+ public void testAuthSuccess() throws Exception {
+ HttpRequest request = new BasicHttpRequest("GET", "/");
+ this.authState.setState(AuthProtocolState.SUCCESS);
+ this.authState.update(this.authScheme, this.credentials);
+
+ Mockito.when(this.authScheme.isConnectionBased()).thenReturn(Boolean.FALSE);
+ Mockito.when(this.authScheme.authenticate(
+ Mockito.any(Credentials.class),
+ Mockito.any(HttpRequest.class),
+ Mockito.any(HttpContext.class))).thenReturn(new BasicHeader(AUTH.WWW_AUTH_RESP, "stuff"));
+
+ this.interceptor.process(request, this.context);
+
+ Assert.assertSame(this.authScheme, this.authState.getAuthScheme());
+ Assert.assertSame(this.credentials, this.authState.getCredentials());
+ Assert.assertNull(this.authState.getAuthOptions());
+
+ Assert.assertTrue(request.containsHeader(AUTH.WWW_AUTH_RESP));
+
+ Mockito.verify(this.authScheme).authenticate(this.credentials, request, this.context);
+ }
+
+ @Test
+ public void testAuthSuccessConnectionBased() throws Exception {
+ HttpRequest request = new BasicHttpRequest("GET", "/");
+ this.authState.setState(AuthProtocolState.SUCCESS);
+ this.authState.update(this.authScheme, this.credentials);
+
+ Mockito.when(this.authScheme.isConnectionBased()).thenReturn(Boolean.TRUE);
+ Mockito.when(this.authScheme.authenticate(
+ Mockito.any(Credentials.class),
+ Mockito.any(HttpRequest.class),
+ Mockito.any(HttpContext.class))).thenReturn(new BasicHeader(AUTH.WWW_AUTH_RESP, "stuff"));
+
+ this.interceptor.process(request, this.context);
+
+ Assert.assertFalse(request.containsHeader(AUTH.WWW_AUTH_RESP));
+
+ Mockito.verify(this.authScheme, Mockito.never()).authenticate(
+ Mockito.any(Credentials.class),
+ Mockito.any(HttpRequest.class),
+ Mockito.any(HttpContext.class));
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestProxyAuthentication.java b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestProxyAuthentication.java
index b255df1..c3dc228 100644
--- a/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestProxyAuthentication.java
+++ b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestProxyAuthentication.java
@@ -33,7 +33,7 @@ import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AUTH;
-import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.AuthProtocolState;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
@@ -81,14 +81,11 @@ public class TestRequestProxyAuthentication {
BasicScheme authscheme = new BasicScheme();
Credentials creds = new UsernamePasswordCredentials("user", "secret");
- AuthScope authscope = new AuthScope("localhost", 8080, "auth-realm", "http");
BasicHeader challenge = new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=auth-realm");
authscheme.processChallenge(challenge);
AuthState authstate = new AuthState();
- authstate.setAuthScheme(authscheme);
- authstate.setAuthScope(authscope);
- authstate.setCredentials(creds);
+ authstate.update(authscheme, creds);
context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
context.setAttribute(ClientContext.PROXY_AUTH_STATE, authstate);
@@ -115,15 +112,12 @@ public class TestRequestProxyAuthentication {
BasicScheme authscheme = new BasicScheme();
Credentials creds = new UsernamePasswordCredentials("user", "secret");
- AuthScope authscope = new AuthScope("localhost", 8080, "auth-realm", "http");
BasicHeader challenge = new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=auth-realm");
authscheme.processChallenge(challenge);
AuthState authstate = new AuthState();
- authstate.setAuthScheme(authscheme);
- authstate.setAuthScope(authscope);
- authstate.setCredentials(creds);
+ authstate.update(authscheme, creds);
context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
context.setAttribute(ClientContext.PROXY_AUTH_STATE, authstate);
@@ -150,15 +144,12 @@ public class TestRequestProxyAuthentication {
BasicScheme authscheme = new BasicScheme();
Credentials creds = new UsernamePasswordCredentials("user", "secret");
- AuthScope authscope = new AuthScope("localhost", 8080, "auth-realm", "http");
BasicHeader challenge = new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=auth-realm");
authscheme.processChallenge(challenge);
AuthState authstate = new AuthState();
- authstate.setAuthScheme(authscheme);
- authstate.setAuthScope(authscope);
- authstate.setCredentials(creds);
+ authstate.update(authscheme, creds);
context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
context.setAttribute(ClientContext.PROXY_AUTH_STATE, authstate);
@@ -217,36 +208,6 @@ public class TestRequestProxyAuthentication {
}
@Test
- public void testAuthCredentialsNotSet() throws Exception {
- HttpRequest request = new BasicHttpRequest("GET", "/");
- HttpContext context = new BasicHttpContext();
-
- HttpHost target = new HttpHost("localhost", 80, "http");
- HttpHost proxy = new HttpHost("localhost", 8080);
- HttpRoute route = new HttpRoute(target, null, proxy, false,
- TunnelType.PLAIN, LayerType.PLAIN);
-
- HttpRoutedConnection conn = Mockito.mock(HttpRoutedConnection.class);
- Mockito.when(conn.getRoute()).thenReturn(route);
-
- AuthState authstate = new AuthState();
-
- BasicScheme authscheme = new BasicScheme();
- BasicHeader challenge = new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=auth-realm");
- authscheme.processChallenge(challenge);
-
- authstate.setAuthScheme(authscheme);
-
- context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
- context.setAttribute(ClientContext.PROXY_AUTH_STATE, authstate);
-
- HttpRequestInterceptor interceptor = new RequestProxyAuthentication();
- interceptor.process(request, context);
- Header header = request.getFirstHeader(AUTH.PROXY_AUTH_RESP);
- Assert.assertNull(header);
- }
-
- @Test
public void testConnectionBasedAuthOnlyIfChallenged() throws Exception {
HttpRequest request = new BasicHttpRequest("GET", "/");
HttpContext context = new BasicHttpContext();
@@ -275,10 +236,8 @@ public class TestRequestProxyAuthentication {
Credentials creds = new UsernamePasswordCredentials("user", "secret");
- authstate.setAuthScheme(authscheme);
- authstate.setCredentials(creds);
- // No challenge
- authstate.setAuthScope(null);
+ authstate.setState(AuthProtocolState.SUCCESS);
+ authstate.update(authscheme, creds);
context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
context.setAttribute(ClientContext.PROXY_AUTH_STATE, authstate);
diff --git a/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestTargetAuthentication.java b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestTargetAuthentication.java
index 7c4d55a..04c2623 100644
--- a/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestTargetAuthentication.java
+++ b/httpclient/src/test/java/org/apache/http/client/protocol/TestRequestTargetAuthentication.java
@@ -32,7 +32,7 @@ import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AUTH;
-import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.AuthProtocolState;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
@@ -66,14 +66,11 @@ public class TestRequestTargetAuthentication {
BasicScheme authscheme = new BasicScheme();
Credentials creds = new UsernamePasswordCredentials("user", "secret");
- AuthScope authscope = new AuthScope("localhost", 8080, "auth-realm", "http");
BasicHeader challenge = new BasicHeader(AUTH.WWW_AUTH, "BASIC realm=auth-realm");
authscheme.processChallenge(challenge);
AuthState authstate = new AuthState();
- authstate.setAuthScheme(authscheme);
- authstate.setAuthScope(authscope);
- authstate.setCredentials(creds);
+ authstate.update(authscheme, creds);
context.setAttribute(ClientContext.TARGET_AUTH_STATE, authstate);
@@ -91,14 +88,11 @@ public class TestRequestTargetAuthentication {
BasicScheme authscheme = new BasicScheme();
Credentials creds = new UsernamePasswordCredentials("user", "secret");
- AuthScope authscope = new AuthScope("localhost", 8080, "auth-realm", "http");
BasicHeader challenge = new BasicHeader(AUTH.WWW_AUTH, "BASIC realm=auth-realm");
authscheme.processChallenge(challenge);
AuthState authstate = new AuthState();
- authstate.setAuthScheme(authscheme);
- authstate.setAuthScope(authscope);
- authstate.setCredentials(creds);
+ authstate.update(authscheme, creds);
context.setAttribute(ClientContext.TARGET_AUTH_STATE, authstate);
@@ -116,15 +110,12 @@ public class TestRequestTargetAuthentication {
BasicScheme authscheme = new BasicScheme();
Credentials creds = new UsernamePasswordCredentials("user", "secret");
- AuthScope authscope = new AuthScope("localhost", 8080, "auth-realm", "http");
BasicHeader challenge = new BasicHeader(AUTH.WWW_AUTH, "BASIC realm=auth-realm");
authscheme.processChallenge(challenge);
AuthState authstate = new AuthState();
- authstate.setAuthScheme(authscheme);
- authstate.setAuthScope(authscope);
- authstate.setCredentials(creds);
+ authstate.update(authscheme, creds);
context.setAttribute(ClientContext.TARGET_AUTH_STATE, authstate);
@@ -164,27 +155,6 @@ public class TestRequestTargetAuthentication {
}
@Test
- public void testAuthCredentialsNotSet() throws Exception {
- HttpRequest request = new BasicHttpRequest("GET", "/");
- HttpContext context = new BasicHttpContext();
-
- AuthState authstate = new AuthState();
-
- BasicScheme authscheme = new BasicScheme();
- BasicHeader challenge = new BasicHeader(AUTH.WWW_AUTH, "BASIC realm=auth-realm");
- authscheme.processChallenge(challenge);
-
- authstate.setAuthScheme(authscheme);
-
- context.setAttribute(ClientContext.TARGET_AUTH_STATE, authstate);
-
- HttpRequestInterceptor interceptor = new RequestTargetAuthentication();
- interceptor.process(request, context);
- Header header = request.getFirstHeader(AUTH.WWW_AUTH_RESP);
- Assert.assertNull(header);
- }
-
- @Test
public void testConnectionBasedAuthOnlyIfChallenged() throws Exception {
HttpRequest request = new BasicHttpRequest("GET", "/");
HttpContext context = new BasicHttpContext();
@@ -205,10 +175,8 @@ public class TestRequestTargetAuthentication {
Credentials creds = new UsernamePasswordCredentials("user", "secret");
- authstate.setAuthScheme(authscheme);
- authstate.setCredentials(creds);
- // No challenge
- authstate.setAuthScope(null);
+ authstate.setState(AuthProtocolState.SUCCESS);
+ authstate.update(authscheme, creds);
context.setAttribute(ClientContext.TARGET_AUTH_STATE, authstate);
diff --git a/httpclient/src/test/java/org/apache/http/client/protocol/TestResponseAuthCache.java b/httpclient/src/test/java/org/apache/http/client/protocol/TestResponseAuthCache.java
index d8edf35..465d5bd 100644
--- a/httpclient/src/test/java/org/apache/http/client/protocol/TestResponseAuthCache.java
+++ b/httpclient/src/test/java/org/apache/http/client/protocol/TestResponseAuthCache.java
@@ -33,7 +33,7 @@ import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpVersion;
import org.apache.http.auth.AUTH;
-import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.AuthProtocolState;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
@@ -48,16 +48,14 @@ import org.apache.http.protocol.HttpContext;
import org.junit.Before;
import org.junit.Test;
+ at Deprecated
public class TestResponseAuthCache {
private HttpHost target;
private HttpHost proxy;
- private Credentials creds1;
- private Credentials creds2;
- private AuthScope authscope1;
- private AuthScope authscope2;
private BasicScheme authscheme1;
private BasicScheme authscheme2;
+ private Credentials credentials;
private AuthState targetState;
private AuthState proxyState;
@@ -66,12 +64,9 @@ public class TestResponseAuthCache {
this.target = new HttpHost("localhost", 80);
this.proxy = new HttpHost("localhost", 8080);
- this.creds1 = new UsernamePasswordCredentials("user1", "secret1");
- this.creds2 = new UsernamePasswordCredentials("user2", "secret2");
- this.authscope1 = new AuthScope(this.target.getHostName(), this.target.getPort());
- this.authscope2 = new AuthScope(this.proxy.getHostName(), this.proxy.getPort());
this.authscheme1 = new BasicScheme();
this.authscheme2 = new BasicScheme();
+ this.credentials = new UsernamePasswordCredentials("user", "pwd");
this.targetState = new AuthState();
this.proxyState = new AuthState();
@@ -100,13 +95,11 @@ public class TestResponseAuthCache {
this.authscheme2.processChallenge(
new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=auth-realm"));
- this.targetState.setAuthScheme(this.authscheme1);
- this.targetState.setCredentials(this.creds1);
- this.targetState.setAuthScope(this.authscope1);
+ this.targetState.setState(AuthProtocolState.CHALLENGED);
+ this.targetState.update(this.authscheme1, this.credentials);
- this.proxyState.setAuthScheme(this.authscheme2);
- this.proxyState.setCredentials(this.creds2);
- this.proxyState.setAuthScope(this.authscope2);
+ this.proxyState.setState(AuthProtocolState.CHALLENGED);
+ this.proxyState.update(this.authscheme2, this.credentials);
HttpContext context = new BasicHttpContext();
context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, this.target);
@@ -159,13 +152,11 @@ public class TestResponseAuthCache {
public void testAuthSchemeNotCompleted() throws Exception {
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK");
- this.targetState.setAuthScheme(this.authscheme1);
- this.targetState.setCredentials(this.creds1);
- this.targetState.setAuthScope(this.authscope1);
+ this.targetState.setState(AuthProtocolState.CHALLENGED);
+ this.targetState.update(this.authscheme1, this.credentials);
- this.proxyState.setAuthScheme(this.authscheme2);
- this.proxyState.setCredentials(this.creds2);
- this.proxyState.setAuthScope(this.authscope2);
+ this.proxyState.setState(AuthProtocolState.CHALLENGED);
+ this.proxyState.update(this.authscheme2, this.credentials);
HttpContext context = new BasicHttpContext();
context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, this.target);
@@ -189,13 +180,11 @@ public class TestResponseAuthCache {
this.authscheme2.processChallenge(
new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=auth-realm"));
- this.targetState.setAuthScheme(this.authscheme1);
- this.targetState.setCredentials(this.creds1);
- this.targetState.setAuthScope(null);
+ this.targetState.setState(AuthProtocolState.UNCHALLENGED);
+ this.targetState.update(this.authscheme1, this.credentials);
- this.proxyState.setAuthScheme(this.authscheme2);
- this.proxyState.setCredentials(this.creds2);
- this.proxyState.setAuthScope(null);
+ this.proxyState.setState(AuthProtocolState.UNCHALLENGED);
+ this.proxyState.update(this.authscheme2, this.credentials);
HttpContext context = new BasicHttpContext();
context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, this.target);
@@ -221,13 +210,11 @@ public class TestResponseAuthCache {
this.authscheme2.processChallenge(
new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=auth-realm"));
- this.targetState.setAuthScheme(this.authscheme1);
- this.targetState.setCredentials(null);
- this.targetState.setAuthScope(this.authscope1);
+ this.targetState.setState(AuthProtocolState.FAILURE);
+ this.targetState.update(this.authscheme1, this.credentials);
- this.proxyState.setAuthScheme(this.authscheme2);
- this.proxyState.setCredentials(null);
- this.proxyState.setAuthScope(this.authscope2);
+ this.proxyState.setState(AuthProtocolState.FAILURE);
+ this.proxyState.update(this.authscheme2, this.credentials);
HttpContext context = new BasicHttpContext();
context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, this.target);
diff --git a/httpclient/src/test/java/org/apache/http/client/protocol/TestUriEscapes.java b/httpclient/src/test/java/org/apache/http/client/protocol/TestUriEscapes.java
index 96032a8..e61910b 100644
--- a/httpclient/src/test/java/org/apache/http/client/protocol/TestUriEscapes.java
+++ b/httpclient/src/test/java/org/apache/http/client/protocol/TestUriEscapes.java
@@ -51,9 +51,10 @@ public class TestUriEscapes extends BasicServerTestBase {
@Before
public void setUp() throws Exception {
- localServer = new LocalTestServer(null, null);
- localServer.registerDefaultHandlers();
- localServer.start();
+ this.localServer = new LocalTestServer(null, null);
+ this.localServer.registerDefaultHandlers();
+ this.localServer.start();
+ this.httpclient = new DefaultHttpClient();
}
private static class UriListeningService implements HttpRequestHandler {
@@ -83,18 +84,17 @@ public class TestUriEscapes extends BasicServerTestBase {
UriListeningService listener = new UriListeningService();
this.localServer.register("*", listener);
- DefaultHttpClient client = new DefaultHttpClient();
HttpResponse response;
if(!relative) {
String request = "http://" + host + ":" + port + uri;
HttpGet httpget = new HttpGet(request);
- response = client.execute(httpget);
+ response = this.httpclient.execute(httpget);
EntityUtils.consume(response.getEntity());
} else {
HttpHost target = new HttpHost(host, port);
HttpGet httpget = new HttpGet(uri);
- response = client.execute(target, httpget);
+ response = this.httpclient.execute(target, httpget);
EntityUtils.consume(response.getEntity());
}
@@ -104,12 +104,12 @@ public class TestUriEscapes extends BasicServerTestBase {
@Test
public void testEscapedAmpersandInQueryAbsolute() throws Exception {
- doTest("/path/a=b&c=%26d", false);
+ doTest("/path?a=b&c=%26d", false);
}
@Test
public void testEscapedAmpersandInQueryRelative() throws Exception {
- doTest("/path/a=b&c=%26d", true);
+ doTest("/path?a=b&c=%26d", true);
}
@Test
@@ -134,12 +134,12 @@ public class TestUriEscapes extends BasicServerTestBase {
@Test
public void testEscapedAmpersandInPathAbsolute() throws Exception {
- doTest("/this%26that?a=b&c=d", false);
+ doTest("/this%26that?a=b&c=%26d", false);
}
@Test
public void testEscapedAmpersandInPathRelative() throws Exception {
- doTest("/this%26that?a=b&c=d", true);
+ doTest("/this%26that?a=b&c=%26d", true);
}
@Test
diff --git a/httpclient/src/test/java/org/apache/http/client/utils/TestHttpClientUtils.java b/httpclient/src/test/java/org/apache/http/client/utils/TestHttpClientUtils.java
new file mode 100644
index 0000000..f6adea3
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/client/utils/TestHttpClientUtils.java
@@ -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.utils;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
+import org.apache.http.localserver.BasicServerTestBase;
+import org.apache.http.localserver.LocalTestServer;
+import org.apache.http.protocol.BasicHttpProcessor;
+import org.apache.http.protocol.ResponseConnControl;
+import org.apache.http.protocol.ResponseContent;
+import org.apache.http.protocol.ResponseDate;
+import org.apache.http.protocol.ResponseServer;
+import org.apache.http.util.EntityUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestHttpClientUtils extends BasicServerTestBase {
+
+ @Before
+ public void setUp() throws Exception {
+ BasicHttpProcessor httpproc = new BasicHttpProcessor();
+ httpproc.addInterceptor(new ResponseDate());
+ httpproc.addInterceptor(new ResponseServer());
+ httpproc.addInterceptor(new ResponseContent());
+ httpproc.addInterceptor(new ResponseConnControl());
+ this.localServer = new LocalTestServer(httpproc, null);
+ }
+
+ @Test
+ public void testCloseQuietlyClient() throws Exception {
+ HttpClient httpClient = new DefaultHttpClient();
+ HttpClientUtils.closeQuietly(httpClient);
+ }
+
+ @Test
+ public void testCloseQuietlyNullClient() throws Exception {
+ HttpClient httpClient = null;
+ HttpClientUtils.closeQuietly(httpClient);
+ }
+
+ @Test
+ public void testCloseQuietlyClientTwice() {
+ PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager();
+ HttpClient httpClient = new DefaultHttpClient(connectionManager);
+ HttpClientUtils.closeQuietly(httpClient);
+ HttpClientUtils.closeQuietly(httpClient);
+ }
+
+ @Test
+ public void testCloseQuietlyResponse() throws Exception {
+ this.localServer.registerDefaultHandlers();
+ this.localServer.start();
+ PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager();
+ HttpClient httpClient = new DefaultHttpClient(connectionManager);
+ HttpHost target = getServerHttp();
+ HttpResponse response = httpClient.execute(target, new HttpGet("/"));
+ HttpClientUtils.closeQuietly(response);
+ HttpClientUtils.closeQuietly(httpClient);
+ }
+
+ @Test
+ public void testCloseQuietlyResponseNull() throws Exception {
+ PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager();
+ HttpClient httpClient = new DefaultHttpClient(connectionManager);
+ HttpResponse response = null;
+ HttpClientUtils.closeQuietly(response);
+ HttpClientUtils.closeQuietly(httpClient);
+ }
+
+ @Test
+ public void testCloseQuietlyResponseTwice() throws Exception {
+ this.localServer.registerDefaultHandlers();
+ this.localServer.start();
+ PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager();
+ HttpClient httpClient = new DefaultHttpClient(connectionManager);
+ HttpHost target = getServerHttp();
+ HttpResponse response = httpClient.execute(target, new HttpGet("/"));
+ HttpClientUtils.closeQuietly(response);
+ HttpClientUtils.closeQuietly(response);
+ HttpClientUtils.closeQuietly(httpClient);
+ }
+
+ @Test
+ public void testCloseQuietlyResponseAfterConsumeContent() throws Exception {
+ this.localServer.registerDefaultHandlers();
+ this.localServer.start();
+ PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager();
+ HttpClient httpClient = new DefaultHttpClient(connectionManager);
+ HttpHost target = getServerHttp();
+ HttpResponse response = httpClient.execute(target, new HttpGet("/"));
+ EntityUtils.consume(response.getEntity());
+ HttpClientUtils.closeQuietly(response);
+ HttpClientUtils.closeQuietly(httpClient);
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/client/utils/TestURIBuilder.java b/httpclient/src/test/java/org/apache/http/client/utils/TestURIBuilder.java
new file mode 100644
index 0000000..dd5c1a4
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/client/utils/TestURIBuilder.java
@@ -0,0 +1,228 @@
+/*
+ * ====================================================================
+ *
+ * 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.utils;
+
+import java.net.URI;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestURIBuilder {
+
+ @Test
+ public void testHierarchicalUri() throws Exception {
+ URI uri = new URI("http", "stuff", "localhost", 80, "/some stuff", "param=stuff", "fragment");
+ URIBuilder uribuilder = new URIBuilder(uri);
+ URI result = uribuilder.build();
+ Assert.assertEquals(new URI("http://stuff@localhost:80/some%20stuff?param=stuff#fragment"), result);
+ }
+
+ @Test
+ public void testMutationToRelativeUri() throws Exception {
+ URI uri = new URI("http://stuff@localhost:80/stuff?param=stuff#fragment");
+ URIBuilder uribuilder = new URIBuilder(uri).setHost(null);
+ URI result = uribuilder.build();
+ Assert.assertEquals(new URI("http:///stuff?param=stuff#fragment"), result);
+ }
+
+ @Test
+ public void testMutationRemoveFragment() throws Exception {
+ URI uri = new URI("http://stuff@localhost:80/stuff?param=stuff#fragment");
+ URI result = new URIBuilder(uri).setFragment(null).build();
+ Assert.assertEquals(new URI("http://stuff@localhost:80/stuff?param=stuff"), result);
+ }
+
+ @Test
+ public void testMutationRemoveUserInfo() throws Exception {
+ URI uri = new URI("http://stuff@localhost:80/stuff?param=stuff#fragment");
+ URI result = new URIBuilder(uri).setUserInfo(null).build();
+ Assert.assertEquals(new URI("http://localhost:80/stuff?param=stuff#fragment"), result);
+ }
+
+ @Test
+ public void testMutationRemovePort() throws Exception {
+ URI uri = new URI("http://stuff@localhost:80/stuff?param=stuff#fragment");
+ URI result = new URIBuilder(uri).setPort(-1).build();
+ Assert.assertEquals(new URI("http://stuff@localhost/stuff?param=stuff#fragment"), result);
+ }
+
+ @Test
+ public void testOpaqueUri() throws Exception {
+ URI uri = new URI("stuff", "some-stuff", "fragment");
+ URIBuilder uribuilder = new URIBuilder(uri);
+ URI result = uribuilder.build();
+ Assert.assertEquals(uri, result);
+ }
+
+ @Test
+ public void testOpaqueUriMutation() throws Exception {
+ URI uri = new URI("stuff", "some-stuff", "fragment");
+ URIBuilder uribuilder = new URIBuilder(uri).setQuery("param1¶m2=stuff").setFragment(null);
+ Assert.assertEquals(new URI("stuff:?param1¶m2=stuff"), uribuilder.build());
+ }
+
+ @Test
+ public void testHierarchicalUriMutation() throws Exception {
+ URIBuilder uribuilder = new URIBuilder("/").setScheme("http").setHost("localhost").setPort(80).setPath("/stuff");
+ Assert.assertEquals(new URI("http://localhost:80/stuff"), uribuilder.build());
+ }
+
+ @Test
+ public void testEmpty() throws Exception {
+ URIBuilder uribuilder = new URIBuilder();
+ URI result = uribuilder.build();
+ Assert.assertEquals(new URI(""), result);
+ }
+
+ @Test
+ public void testSetUserInfo() throws Exception {
+ URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff", null);
+ URIBuilder uribuilder = new URIBuilder(uri).setUserInfo("user", "password");
+ URI result = uribuilder.build();
+ Assert.assertEquals(new URI("http://user:password@localhost:80/?param=stuff"), result);
+ }
+
+ @Test
+ public void testRemoveParameters() throws Exception {
+ URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff", null);
+ URIBuilder uribuilder = new URIBuilder(uri).removeQuery();
+ URI result = uribuilder.build();
+ Assert.assertEquals(new URI("http://localhost:80/"), result);
+ }
+
+ @Test
+ public void testSetParameter() throws Exception {
+ URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff&blah&blah", null);
+ URIBuilder uribuilder = new URIBuilder(uri).setParameter("param", "some other stuff")
+ .setParameter("blah", "blah");
+ URI result = uribuilder.build();
+ Assert.assertEquals(new URI("http://localhost:80/?param=some+other+stuff&blah=blah"), result);
+ }
+
+ @Test
+ public void testParameterWithSpecialChar() throws Exception {
+ URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff", null);
+ URIBuilder uribuilder = new URIBuilder(uri).addParameter("param", "1 + 1 = 2")
+ .addParameter("param", "blah&blah");
+ URI result = uribuilder.build();
+ Assert.assertEquals(new URI("http://localhost:80/?param=stuff¶m=1+%2B+1+%3D+2&" +
+ "param=blah%26blah"), result);
+ }
+
+ @Test
+ public void testAddParameter() throws Exception {
+ URI uri = new URI("http", null, "localhost", 80, "/", "param=stuff&blah&blah", null);
+ URIBuilder uribuilder = new URIBuilder(uri).addParameter("param", "some other stuff")
+ .addParameter("blah", "blah");
+ URI result = uribuilder.build();
+ Assert.assertEquals(new URI("http://localhost:80/?param=stuff&blah&blah&" +
+ "param=some+other+stuff&blah=blah"), result);
+ }
+
+ @Test
+ public void testQueryEncoding() throws Exception {
+ URI uri1 = new URI("https://somehost.com/stuff?client_id=1234567890" +
+ "&redirect_uri=https%3A%2F%2Fsomehost.com%2Fblah+blah%2F");
+ URI uri2 = new URIBuilder("https://somehost.com/stuff")
+ .addParameter("client_id","1234567890")
+ .addParameter("redirect_uri","https://somehost.com/blah blah/").build();
+ Assert.assertEquals(uri1, uri2);
+ }
+
+ @Test
+ public void testPathEncoding() throws Exception {
+ URI uri1 = new URI("https://somehost.com/some%20path%20with%20blanks/");
+ URI uri2 = new URIBuilder()
+ .setScheme("https")
+ .setHost("somehost.com")
+ .setPath("/some path with blanks/")
+ .build();
+ Assert.assertEquals(uri1, uri2);
+ }
+
+ @Test
+ public void testAgainstURI() throws Exception {
+ // Check that the URI generated by URI builder agrees with that generated by using URI directly
+ final String scheme="https";
+ final String host="localhost";
+ final String specials="/abcd!$&*()_-+.,=:;'~@[]?<>|#^%\"{}\\£`¬¦xyz"; // N.B. excludes space
+ URI uri = new URI(scheme, specials, host, 80, specials, specials, specials);
+
+ URI bld = new URIBuilder()
+ .setScheme(scheme)
+ .setHost(host)
+ .setUserInfo(specials)
+ .setPath(specials)
+ .addParameter(specials, null) // hack to bypass parsing of query data
+ .setFragment(specials)
+ .build();
+
+ Assert.assertEquals(uri.getHost(), bld.getHost());
+
+ Assert.assertEquals(uri.getUserInfo(), bld.getUserInfo());
+
+ Assert.assertEquals(uri.getPath(), bld.getPath());
+
+ Assert.assertEquals(uri.getQuery(), bld.getQuery());
+
+ Assert.assertEquals(uri.getFragment(), bld.getFragment());
+
+ }
+
+ @Test
+ public void testAgainstURIEncoded() throws Exception {
+ // Check that the encoded URI generated by URI builder agrees with that generated by using URI directly
+ final String scheme="https";
+ final String host="localhost";
+ final String specials="/ abcd!$&*()_-+.,=:;'~<>/@[]|#^%\"{}\\`xyz"; // N.B. excludes £¬¦
+ final String formdatasafe = "abcd-_.*zyz";
+ URI uri = new URI(scheme, specials, host, 80, specials,
+ formdatasafe, // TODO replace with specials when supported
+ specials);
+
+ URI bld = new URIBuilder()
+ .setScheme(scheme)
+ .setHost(host)
+ .setUserInfo(specials)
+ .setPath(specials)
+ .addParameter(formdatasafe, null) // TODO replace with specials when supported
+ .setFragment(specials)
+ .build();
+
+ Assert.assertEquals(uri.getHost(), bld.getHost());
+
+ Assert.assertEquals(uri.getRawUserInfo(), bld.getRawUserInfo());
+
+ Assert.assertEquals(uri.getRawPath(), bld.getRawPath());
+
+ Assert.assertEquals(uri.getRawQuery(), bld.getRawQuery());
+
+ Assert.assertEquals(uri.getRawFragment(), bld.getRawFragment());
+
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/client/utils/TestURIUtils.java b/httpclient/src/test/java/org/apache/http/client/utils/TestURIUtils.java
index 29a3c24..73debe0 100644
--- a/httpclient/src/test/java/org/apache/http/client/utils/TestURIUtils.java
+++ b/httpclient/src/test/java/org/apache/http/client/utils/TestURIUtils.java
@@ -40,310 +40,156 @@ public class TestURIUtils {
private URI baseURI = URI.create("http://a/b/c/d;p?q");
@Test
- public void testRewite00() throws Exception {
- URI uri = URI.create("http://thishost/stuff");
+ public void testRewrite() throws Exception {
HttpHost target = new HttpHost("thathost", -1);
- Assert.assertEquals("http://thathost/stuff", URIUtils.rewriteURI(uri, target).toString());
- }
-
- @Test
- public void testRewite01() throws Exception {
- URI uri = URI.create("http://thishost/stuff");
- Assert.assertEquals("/stuff", URIUtils.rewriteURI(uri, null).toString());
- }
-
- @Test
- public void testRewite02() throws Exception {
- URI uri = URI.create("http://thishost//");
- Assert.assertEquals("/", URIUtils.rewriteURI(uri, null).toString());
- }
-
- @Test
- public void testRewite03() throws Exception {
- URI uri = URI.create("http://thishost//stuff///morestuff");
- Assert.assertEquals("/stuff///morestuff", URIUtils.rewriteURI(uri, null).toString());
- }
-
- @Test
- public void testRewite04() throws Exception {
- URI uri = URI.create("http://thishost/stuff#crap");
- HttpHost target = new HttpHost("thathost", -1);
- Assert.assertEquals("http://thathost/stuff", URIUtils.rewriteURI(uri, target, true).toString());
- }
-
- @Test
- public void testRewite05() throws Exception {
- URI uri = URI.create("http://thishost/stuff#crap");
- HttpHost target = new HttpHost("thathost", -1);
- Assert.assertEquals("http://thathost/stuff#crap", URIUtils.rewriteURI(uri, target, false).toString());
- }
-
- @Test
- public void testRewite06() throws Exception {
- URI uri = URI.create("http://thishost//////////////stuff/");
- Assert.assertEquals("/stuff/", URIUtils.rewriteURI(uri, null).toString());
- }
-
- @Test
- public void testResolve00() {
+ Assert.assertEquals("http://thathost/stuff", URIUtils.rewriteURI(
+ URI.create("http://thishost/stuff"), target).toString());
+ Assert.assertEquals("/stuff", URIUtils.rewriteURI(
+ URI.create("http://thishost/stuff"), null).toString());
+ Assert.assertEquals("/", URIUtils.rewriteURI(
+ URI.create("http://thishost//"), null).toString());
+ Assert.assertEquals("/stuff///morestuff", URIUtils.rewriteURI(
+ URI.create("http://thishost//stuff///morestuff"), null).toString());
+ Assert.assertEquals("http://thathost/stuff", URIUtils.rewriteURI(
+ URI.create("http://thishost/stuff#crap"), target, true).toString());
+ Assert.assertEquals("http://thathost/stuff#crap", URIUtils.rewriteURI(
+ URI.create("http://thishost/stuff#crap"), target, false).toString());
+ Assert.assertEquals("/stuff/", URIUtils.rewriteURI(
+ URI.create("http://thishost//////////////stuff/"), null).toString());
+ Assert.assertEquals("http://thathost/stuff", URIUtils.rewriteURI(
+ URI.create("http://thathost/stuff")).toString());
+ Assert.assertEquals("http://thathost/stuff", URIUtils.rewriteURI(
+ URI.create("http://thathost/stuff#fragment")).toString());
+ Assert.assertEquals("http://thathost/stuff", URIUtils.rewriteURI(
+ URI.create("http://userinfo@thathost/stuff#fragment")).toString());
+ }
+
+ @Test
+ public void testRewritePort() throws Exception {
+ HttpHost target = new HttpHost("thathost", 8080); // port should be copied
+ Assert.assertEquals("http://thathost:8080/stuff", URIUtils.rewriteURI(
+ URI.create("http://thishost:80/stuff#crap"), target, true).toString());
+ Assert.assertEquals("http://thathost:8080/stuff#crap", URIUtils.rewriteURI(
+ URI.create("http://thishost:80/stuff#crap"), target, false).toString());
+ target = new HttpHost("thathost", -1); // input port should be dropped
+ Assert.assertEquals("http://thathost/stuff", URIUtils.rewriteURI(
+ URI.create("http://thishost:80/stuff#crap"), target, true).toString());
+ Assert.assertEquals("http://thathost/stuff#crap", URIUtils.rewriteURI(
+ URI.create("http://thishost:80/stuff#crap"), target, false).toString());
+ }
+
+ @Test
+ public void testRewriteScheme() throws Exception {
+ HttpHost target = new HttpHost("thathost", -1, "file"); // scheme should be copied
+ Assert.assertEquals("file://thathost/stuff", URIUtils.rewriteURI(
+ URI.create("http://thishost:80/stuff#crap"), target, true).toString());
+ }
+
+ @Test
+ public void testResolve() {
Assert.assertEquals("g:h", URIUtils.resolve(this.baseURI, "g:h").toString());
- }
-
- @Test
- public void testResolve01() {
Assert.assertEquals("http://a/b/c/g", URIUtils.resolve(this.baseURI, "g").toString());
- }
-
- @Test
- public void testResolve02() {
Assert.assertEquals("http://a/b/c/g", URIUtils.resolve(this.baseURI, "./g").toString());
- }
-
- @Test
- public void testResolve03() {
Assert.assertEquals("http://a/b/c/g/", URIUtils.resolve(this.baseURI, "g/").toString());
- }
-
- @Test
- public void testResolve04() {
Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "/g").toString());
- }
-
- @Test
- public void testResolve05() {
Assert.assertEquals("http://g", URIUtils.resolve(this.baseURI, "//g").toString());
- }
-
- @Test
- public void testResolve06() {
Assert.assertEquals("http://a/b/c/d;p?y", URIUtils.resolve(this.baseURI, "?y").toString());
- }
-
- @Test
- public void testResolve06_() {
Assert.assertEquals("http://a/b/c/d;p?y#f", URIUtils.resolve(this.baseURI, "?y#f")
.toString());
- }
-
- @Test
- public void testResolve07() {
Assert.assertEquals("http://a/b/c/g?y", URIUtils.resolve(this.baseURI, "g?y").toString());
- }
-
- @Test
- public void testResolve08() {
Assert.assertEquals("http://a/b/c/d;p?q#s", URIUtils.resolve(this.baseURI, "#s")
.toString());
- }
-
- @Test
- public void testResolve09() {
Assert.assertEquals("http://a/b/c/g#s", URIUtils.resolve(this.baseURI, "g#s").toString());
- }
-
- @Test
- public void testResolve10() {
Assert.assertEquals("http://a/b/c/g?y#s", URIUtils.resolve(this.baseURI, "g?y#s")
.toString());
- }
-
- @Test
- public void testResolve11() {
Assert.assertEquals("http://a/b/c/;x", URIUtils.resolve(this.baseURI, ";x").toString());
- }
-
- @Test
- public void testResolve12() {
Assert.assertEquals("http://a/b/c/g;x", URIUtils.resolve(this.baseURI, "g;x").toString());
- }
-
- @Test
- public void testResolve13() {
Assert.assertEquals("http://a/b/c/g;x?y#s", URIUtils.resolve(this.baseURI, "g;x?y#s")
.toString());
- }
-
- @Test
- public void testResolve14() {
Assert.assertEquals("http://a/b/c/d;p?q", URIUtils.resolve(this.baseURI, "").toString());
- }
-
- @Test
- public void testResolve15() {
Assert.assertEquals("http://a/b/c/", URIUtils.resolve(this.baseURI, ".").toString());
- }
-
- @Test
- public void testResolve16() {
Assert.assertEquals("http://a/b/c/", URIUtils.resolve(this.baseURI, "./").toString());
- }
-
- @Test
- public void testResolve17() {
Assert.assertEquals("http://a/b/", URIUtils.resolve(this.baseURI, "..").toString());
- }
-
- @Test
- public void testResolve18() {
Assert.assertEquals("http://a/b/", URIUtils.resolve(this.baseURI, "../").toString());
- }
-
- @Test
- public void testResolve19() {
Assert.assertEquals("http://a/b/g", URIUtils.resolve(this.baseURI, "../g").toString());
- }
-
- @Test
- public void testResolve20() {
Assert.assertEquals("http://a/", URIUtils.resolve(this.baseURI, "../..").toString());
- }
-
- @Test
- public void testResolve21() {
Assert.assertEquals("http://a/", URIUtils.resolve(this.baseURI, "../../").toString());
- }
-
- @Test
- public void testResolve22() {
Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "../../g").toString());
- }
-
- @Test
- public void testResolveAbnormal23() {
Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "../../../g").toString());
- }
-
- @Test
- public void testResolveAbnormal24() {
Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "../../../../g")
.toString());
- }
-
- @Test
- public void testResolve25() {
Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "/./g").toString());
- }
-
- @Test
- public void testResolve26() {
Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "/../g").toString());
- }
-
- @Test
- public void testResolve27() {
Assert.assertEquals("http://a/b/c/g.", URIUtils.resolve(this.baseURI, "g.").toString());
- }
-
- @Test
- public void testResolve28() {
Assert.assertEquals("http://a/b/c/.g", URIUtils.resolve(this.baseURI, ".g").toString());
- }
-
- @Test
- public void testResolve29() {
Assert.assertEquals("http://a/b/c/g..", URIUtils.resolve(this.baseURI, "g..").toString());
- }
-
- @Test
- public void testResolve30() {
Assert.assertEquals("http://a/b/c/..g", URIUtils.resolve(this.baseURI, "..g").toString());
- }
-
- @Test
- public void testResolve31() {
Assert.assertEquals("http://a/b/g", URIUtils.resolve(this.baseURI, "./../g").toString());
- }
-
- @Test
- public void testResolve32() {
Assert.assertEquals("http://a/b/c/g/", URIUtils.resolve(this.baseURI, "./g/.").toString());
- }
-
- @Test
- public void testResolve33() {
Assert.assertEquals("http://a/b/c/g/h", URIUtils.resolve(this.baseURI, "g/./h").toString());
- }
-
- @Test
- public void testResolve34() {
Assert.assertEquals("http://a/b/c/h", URIUtils.resolve(this.baseURI, "g/../h").toString());
- }
-
- @Test
- public void testResolve35() {
Assert.assertEquals("http://a/b/c/g;x=1/y", URIUtils.resolve(this.baseURI, "g;x=1/./y")
.toString());
- }
-
- @Test
- public void testResolve36() {
Assert.assertEquals("http://a/b/c/y", URIUtils.resolve(this.baseURI, "g;x=1/../y")
.toString());
- }
-
- @Test
- public void testResolve37() {
Assert.assertEquals("http://a/b/c/g?y/./x", URIUtils.resolve(this.baseURI, "g?y/./x")
.toString());
- }
-
- @Test
- public void testResolve38() {
Assert.assertEquals("http://a/b/c/g?y/../x", URIUtils.resolve(this.baseURI, "g?y/../x")
.toString());
- }
-
- @Test
- public void testResolve39() {
Assert.assertEquals("http://a/b/c/g#s/./x", URIUtils.resolve(this.baseURI, "g#s/./x")
.toString());
- }
-
- @Test
- public void testResolve40() {
Assert.assertEquals("http://a/b/c/g#s/../x", URIUtils.resolve(this.baseURI, "g#s/../x")
.toString());
- }
-
- @Test
- public void testResolve41() {
Assert.assertEquals("http:g", URIUtils.resolve(this.baseURI, "http:g").toString());
- }
-
- // examples from section 5.2.4
- @Test
- public void testResolve42() {
+ // examples from section 5.2.4
Assert.assertEquals("http://s/a/g", URIUtils.resolve(this.baseURI,
"http://s/a/b/c/./../../g").toString());
- }
-
- @Test
- public void testResolve43() {
Assert.assertEquals("http://s/mid/6", URIUtils.resolve(this.baseURI,
"http://s/mid/content=5/../6").toString());
}
@Test
- public void testHTTPCLIENT_911() throws Exception{
- Assert.assertEquals(new HttpHost("localhost"),URIUtils.extractHost(new URI("http://localhost/abcd")));
- Assert.assertEquals(new HttpHost("localhost"),URIUtils.extractHost(new URI("http://localhost/abcd%3A")));
-
- Assert.assertEquals(new HttpHost("local_host"),URIUtils.extractHost(new URI("http://local_host/abcd")));
- Assert.assertEquals(new HttpHost("local_host"),URIUtils.extractHost(new URI("http://local_host/abcd%3A")));
-
- Assert.assertEquals(new HttpHost("localhost",8),URIUtils.extractHost(new URI("http://localhost:8/abcd")));
- Assert.assertEquals(new HttpHost("local_host",8),URIUtils.extractHost(new URI("http://local_host:8/abcd")));
+ public void testExtractHost() throws Exception {
+ Assert.assertEquals(new HttpHost("localhost"),
+ URIUtils.extractHost(new URI("http://localhost/abcd")));
+ Assert.assertEquals(new HttpHost("localhost"),
+ URIUtils.extractHost(new URI("http://localhost/abcd%3A")));
+
+ Assert.assertEquals(new HttpHost("local_host"),
+ URIUtils.extractHost(new URI("http://local_host/abcd")));
+ Assert.assertEquals(new HttpHost("local_host"),
+ URIUtils.extractHost(new URI("http://local_host/abcd%3A")));
+
+ Assert.assertEquals(new HttpHost("localhost",8),
+ URIUtils.extractHost(new URI("http://localhost:8/abcd")));
+ Assert.assertEquals(new HttpHost("local_host",8),
+ URIUtils.extractHost(new URI("http://local_host:8/abcd")));
// URI seems to OK with missing port number
- Assert.assertEquals(new HttpHost("localhost"),URIUtils.extractHost(new URI("http://localhost:/abcd")));
- Assert.assertEquals(new HttpHost("local_host"),URIUtils.extractHost(new URI("http://local_host:/abcd")));
+ Assert.assertEquals(new HttpHost("localhost",-1),URIUtils.extractHost(
+ new URI("http://localhost:/abcd")));
+ Assert.assertEquals(new HttpHost("local_host",-1),URIUtils.extractHost(
+ new URI("http://local_host:/abcd")));
- Assert.assertEquals(new HttpHost("localhost",8080),URIUtils.extractHost(new URI("http://user:pass@localhost:8080/abcd")));
- Assert.assertEquals(new HttpHost("local_host",8080),URIUtils.extractHost(new URI("http://user:pass@local_host:8080/abcd")));
+ Assert.assertEquals(new HttpHost("localhost",8080),
+ URIUtils.extractHost(new URI("http://user:pass@localhost:8080/abcd")));
+ Assert.assertEquals(new HttpHost("local_host",8080),
+ URIUtils.extractHost(new URI("http://user:pass@local_host:8080/abcd")));
- Assert.assertEquals(new HttpHost("localhost",8080),URIUtils.extractHost(new URI("http://@localhost:8080/abcd")));
- Assert.assertEquals(new HttpHost("local_host",8080),URIUtils.extractHost(new URI("http://@local_host:8080/abcd")));
+ Assert.assertEquals(new HttpHost("localhost",8080),URIUtils.extractHost(
+ new URI("http://@localhost:8080/abcd")));
+ Assert.assertEquals(new HttpHost("local_host",8080),URIUtils.extractHost(
+ new URI("http://@local_host:8080/abcd")));
+ Assert.assertEquals(new HttpHost("[2a00:1450:400c:c01::69]",8080),
+ URIUtils.extractHost(new URI("http://[2a00:1450:400c:c01::69]:8080/")));
+
+ Assert.assertEquals(new HttpHost("localhost",8080),
+ URIUtils.extractHost(new URI("http://localhost:8080/;sessionid=stuff/abcd")));
+ Assert.assertEquals(new HttpHost("localhost",8080),
+ URIUtils.extractHost(new URI("http://localhost:8080;sessionid=stuff/abcd")));
+ Assert.assertEquals(new HttpHost("localhost",-1),
+ URIUtils.extractHost(new URI("http://localhost:;sessionid=stuff/abcd")));
}
-
+
}
diff --git a/httpclient/src/test/java/org/apache/http/client/utils/TestURLEncodedUtils.java b/httpclient/src/test/java/org/apache/http/client/utils/TestURLEncodedUtils.java
index 00c035b..2934b5d 100644
--- a/httpclient/src/test/java/org/apache/http/client/utils/TestURLEncodedUtils.java
+++ b/httpclient/src/test/java/org/apache/http/client/utils/TestURLEncodedUtils.java
@@ -31,7 +31,9 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.List;
+import org.apache.http.Consts;
import org.apache.http.NameValuePair;
+import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
@@ -41,55 +43,135 @@ import org.junit.Test;
public class TestURLEncodedUtils {
@Test
- public void testParseURI () throws Exception {
+ public void testParseURLCodedContent () throws Exception {
List <NameValuePair> result;
- result = parse("", null);
+ result = parse("");
Assert.assertTrue(result.isEmpty());
- result = parse("Name1=Value1", null);
+ result = parse("Name0");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "Name0", null);
+
+ result = parse("Name1=Value1");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "Name1", "Value1");
+
+ result = parse("Name2=");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "Name2", "");
+
+ result = parse("Name3");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "Name3", null);
+
+ result = parse("Name4=Value%204%21");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "Name4", "Value 4!");
+
+ result = parse("Name4=Value%2B4%21");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "Name4", "Value+4!");
+
+ result = parse("Name4=Value%204%21%20%214");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "Name4", "Value 4! !4");
+
+ result = parse("Name5=aaa&Name6=bbb");
+ Assert.assertEquals(2, result.size());
+ assertNameValuePair(result.get(0), "Name5", "aaa");
+ assertNameValuePair(result.get(1), "Name6", "bbb");
+
+ result = parse("Name7=aaa&Name7=b%2Cb&Name7=ccc");
+ Assert.assertEquals(3, result.size());
+ assertNameValuePair(result.get(0), "Name7", "aaa");
+ assertNameValuePair(result.get(1), "Name7", "b,b");
+ assertNameValuePair(result.get(2), "Name7", "ccc");
+
+ result = parse("Name8=xx%2C%20%20yy%20%20%2Czz");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "Name8", "xx, yy ,zz");
+
+ result = parse("price=10%20%E2%82%AC");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "price", "10 \u20AC");
+ }
+
+ @Test
+ public void testParseURLCodedContentString () throws Exception {
+ List <NameValuePair> result;
+
+ result = parseString("");
+ Assert.assertTrue(result.isEmpty());
+
+ result = parseString("Name0");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "Name0", null);
+
+ result = parseString("Name1=Value1");
Assert.assertEquals(1, result.size());
assertNameValuePair(result.get(0), "Name1", "Value1");
- result = parse("Name2=", null);
+ result = parseString("Name2=");
Assert.assertEquals(1, result.size());
- assertNameValuePair(result.get(0), "Name2", null);
+ assertNameValuePair(result.get(0), "Name2", "");
- result = parse("Name3", null);
+ result = parseString("Name3");
Assert.assertEquals(1, result.size());
assertNameValuePair(result.get(0), "Name3", null);
- result = parse("Name4=Value+4%21", null);
+ result = parseString("Name4=Value%204%21");
Assert.assertEquals(1, result.size());
assertNameValuePair(result.get(0), "Name4", "Value 4!");
- result = parse("Name4=Value%2B4%21", null);
+ result = parseString("Name4=Value%2B4%21");
Assert.assertEquals(1, result.size());
assertNameValuePair(result.get(0), "Name4", "Value+4!");
- result = parse("Name4=Value+4%21+%214", null);
+ result = parseString("Name4=Value%204%21%20%214");
Assert.assertEquals(1, result.size());
assertNameValuePair(result.get(0), "Name4", "Value 4! !4");
- result = parse("Name5=aaa&Name6=bbb", null);
+ result = parseString("Name5=aaa&Name6=bbb");
Assert.assertEquals(2, result.size());
assertNameValuePair(result.get(0), "Name5", "aaa");
assertNameValuePair(result.get(1), "Name6", "bbb");
- result = parse("Name7=aaa&Name7=b%2Cb&Name7=ccc", null);
+ result = parseString("Name7=aaa&Name7=b%2Cb&Name7=ccc");
Assert.assertEquals(3, result.size());
assertNameValuePair(result.get(0), "Name7", "aaa");
assertNameValuePair(result.get(1), "Name7", "b,b");
assertNameValuePair(result.get(2), "Name7", "ccc");
- result = parse("Name8=xx%2C++yy++%2Czz", null);
+ result = parseString("Name8=xx%2C%20%20yy%20%20%2Czz");
Assert.assertEquals(1, result.size());
assertNameValuePair(result.get(0), "Name8", "xx, yy ,zz");
+
+ result = parseString("price=10%20%E2%82%AC");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "price", "10 \u20AC");
+ }
+
+ @Test
+ public void testParseInvalidURLCodedContent () throws Exception {
+ List <NameValuePair> result;
+
+ result = parse("name=%");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "name", "%");
+
+ result = parse("name=%a");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "name", "%a");
+
+ result = parse("name=%wa%20");
+ Assert.assertEquals(1, result.size());
+ assertNameValuePair(result.get(0), "name", "%wa ");
}
@Test
public void testParseEntity () throws Exception {
- final StringEntity entity = new StringEntity("Name1=Value1", null);
+ final StringEntity entity = new StringEntity("Name1=Value1");
entity.setContentType(URLEncodedUtils.CONTENT_TYPE);
final List <NameValuePair> result = URLEncodedUtils.parse(entity);
@@ -127,13 +209,13 @@ public class TestURLEncodedUtils {
parameters.add(new BasicNameValuePair("russian", ru_hello));
parameters.add(new BasicNameValuePair("swiss", ch_hello));
- String s = URLEncodedUtils.format(parameters, HTTP.UTF_8);
+ String s = URLEncodedUtils.format(parameters, Consts.UTF_8);
Assert.assertEquals("russian=%D0%92%D1%81%D0%B5%D0%BC_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82" +
"&swiss=Gr%C3%BCezi_z%C3%A4m%C3%A4", s);
- StringEntity entity = new StringEntity(s, HTTP.UTF_8);
- entity.setContentType(URLEncodedUtils.CONTENT_TYPE + HTTP.CHARSET_PARAM + HTTP.UTF_8);
+ StringEntity entity = new StringEntity(s, ContentType.create(
+ URLEncodedUtils.CONTENT_TYPE, Consts.UTF_8));
List <NameValuePair> result = URLEncodedUtils.parse(entity);
Assert.assertEquals(2, result.size());
assertNameValuePair(result.get(0), "russian", ru_hello);
@@ -141,8 +223,44 @@ public class TestURLEncodedUtils {
}
@Test
+ public void testParseUTF8String () throws Exception {
+ String ru_hello = constructString(RUSSIAN_HELLO);
+ String ch_hello = constructString(SWISS_GERMAN_HELLO);
+ List <NameValuePair> parameters = new ArrayList<NameValuePair>();
+ parameters.add(new BasicNameValuePair("russian", ru_hello));
+ parameters.add(new BasicNameValuePair("swiss", ch_hello));
+
+ String s = URLEncodedUtils.format(parameters, Consts.UTF_8);
+
+ List <NameValuePair> result = URLEncodedUtils.parse(s, Consts.UTF_8);
+ Assert.assertEquals(2, result.size());
+ assertNameValuePair(result.get(0), "russian", ru_hello);
+ assertNameValuePair(result.get(1), "swiss", ch_hello);
+ }
+
+ @Test
+ public void testParseEntityDefaultContentType () throws Exception {
+ String ch_hello = constructString(SWISS_GERMAN_HELLO);
+ String us_hello = "hi there";
+ List <NameValuePair> parameters = new ArrayList<NameValuePair>();
+ parameters.add(new BasicNameValuePair("english", us_hello));
+ parameters.add(new BasicNameValuePair("swiss", ch_hello));
+
+ String s = URLEncodedUtils.format(parameters, HTTP.DEF_CONTENT_CHARSET);
+
+ Assert.assertEquals("english=hi+there&swiss=Gr%FCezi_z%E4m%E4", s);
+
+ StringEntity entity = new StringEntity(s, ContentType.create(
+ URLEncodedUtils.CONTENT_TYPE, HTTP.DEF_CONTENT_CHARSET));
+ List <NameValuePair> result = URLEncodedUtils.parse(entity);
+ Assert.assertEquals(2, result.size());
+ assertNameValuePair(result.get(0), "english", us_hello);
+ assertNameValuePair(result.get(1), "swiss", ch_hello);
+ }
+
+ @Test
public void testIsEncoded () throws Exception {
- final StringEntity entity = new StringEntity("...", null);
+ final StringEntity entity = new StringEntity("...");
entity.setContentType(URLEncodedUtils.CONTENT_TYPE);
Assert.assertTrue(URLEncodedUtils.isEncoded(entity));
@@ -157,46 +275,99 @@ public class TestURLEncodedUtils {
@Test
public void testFormat () throws Exception {
final List <NameValuePair> params = new ArrayList <NameValuePair>();
- Assert.assertEquals(0, URLEncodedUtils.format(params, null).length());
+ Assert.assertEquals(0, URLEncodedUtils.format(params, Consts.ASCII).length());
+
+ params.clear();
+ params.add(new BasicNameValuePair("Name0", null));
+ Assert.assertEquals("Name0", URLEncodedUtils.format(params, Consts.ASCII));
params.clear();
params.add(new BasicNameValuePair("Name1", "Value1"));
- Assert.assertEquals("Name1=Value1", URLEncodedUtils.format(params, null));
+ Assert.assertEquals("Name1=Value1", URLEncodedUtils.format(params, Consts.ASCII));
params.clear();
- params.add(new BasicNameValuePair("Name2", null));
- Assert.assertEquals("Name2=", URLEncodedUtils.format(params, null));
+ params.add(new BasicNameValuePair("Name2", ""));
+ Assert.assertEquals("Name2=", URLEncodedUtils.format(params, Consts.ASCII));
params.clear();
- params.add(new BasicNameValuePair("Name4", "Value 4!"));
- Assert.assertEquals("Name4=Value+4%21", URLEncodedUtils.format(params, null));
+ params.add(new BasicNameValuePair("Name4", "Value 4&"));
+ Assert.assertEquals("Name4=Value+4%26", URLEncodedUtils.format(params, Consts.ASCII));
params.clear();
- params.add(new BasicNameValuePair("Name4", "Value+4!"));
- Assert.assertEquals("Name4=Value%2B4%21", URLEncodedUtils.format(params, null));
+ params.add(new BasicNameValuePair("Name4", "Value+4&"));
+ Assert.assertEquals("Name4=Value%2B4%26", URLEncodedUtils.format(params, Consts.ASCII));
params.clear();
- params.add(new BasicNameValuePair("Name4", "Value 4! !4"));
- Assert.assertEquals("Name4=Value+4%21+%214", URLEncodedUtils.format(params, null));
+ params.add(new BasicNameValuePair("Name4", "Value 4& =4"));
+ Assert.assertEquals("Name4=Value+4%26+%3D4", URLEncodedUtils.format(params, Consts.ASCII));
params.clear();
params.add(new BasicNameValuePair("Name5", "aaa"));
params.add(new BasicNameValuePair("Name6", "bbb"));
- Assert.assertEquals("Name5=aaa&Name6=bbb", URLEncodedUtils.format(params, null));
+ Assert.assertEquals("Name5=aaa&Name6=bbb", URLEncodedUtils.format(params, Consts.ASCII));
params.clear();
params.add(new BasicNameValuePair("Name7", "aaa"));
params.add(new BasicNameValuePair("Name7", "b,b"));
params.add(new BasicNameValuePair("Name7", "ccc"));
- Assert.assertEquals("Name7=aaa&Name7=b%2Cb&Name7=ccc", URLEncodedUtils.format(params, null));
+ Assert.assertEquals("Name7=aaa&Name7=b%2Cb&Name7=ccc", URLEncodedUtils.format(params, Consts.ASCII));
params.clear();
params.add(new BasicNameValuePair("Name8", "xx, yy ,zz"));
- Assert.assertEquals("Name8=xx%2C++yy++%2Czz", URLEncodedUtils.format(params, null));
+ Assert.assertEquals("Name8=xx%2C++yy++%2Czz", URLEncodedUtils.format(params, Consts.ASCII));
+ }
+
+ @Test
+ public void testFormatString() throws Exception { // as above, using String
+ final List <NameValuePair> params = new ArrayList <NameValuePair>();
+ Assert.assertEquals(0, URLEncodedUtils.format(params, "US-ASCII").length());
+
+ params.clear();
+ params.add(new BasicNameValuePair("Name0", null));
+ Assert.assertEquals("Name0", URLEncodedUtils.format(params, "US-ASCII"));
+
+ params.clear();
+ params.add(new BasicNameValuePair("Name1", "Value1"));
+ Assert.assertEquals("Name1=Value1", URLEncodedUtils.format(params, "US-ASCII"));
+
+ params.clear();
+ params.add(new BasicNameValuePair("Name2", ""));
+ Assert.assertEquals("Name2=", URLEncodedUtils.format(params, "US-ASCII"));
+
+ params.clear();
+ params.add(new BasicNameValuePair("Name4", "Value 4&"));
+ Assert.assertEquals("Name4=Value+4%26", URLEncodedUtils.format(params, "US-ASCII"));
+
+ params.clear();
+ params.add(new BasicNameValuePair("Name4", "Value+4&"));
+ Assert.assertEquals("Name4=Value%2B4%26", URLEncodedUtils.format(params, "US-ASCII"));
+
+ params.clear();
+ params.add(new BasicNameValuePair("Name4", "Value 4& =4"));
+ Assert.assertEquals("Name4=Value+4%26+%3D4", URLEncodedUtils.format(params, "US-ASCII"));
+
+ params.clear();
+ params.add(new BasicNameValuePair("Name5", "aaa"));
+ params.add(new BasicNameValuePair("Name6", "bbb"));
+ Assert.assertEquals("Name5=aaa&Name6=bbb", URLEncodedUtils.format(params, "US-ASCII"));
+
+ params.clear();
+ params.add(new BasicNameValuePair("Name7", "aaa"));
+ params.add(new BasicNameValuePair("Name7", "b,b"));
+ params.add(new BasicNameValuePair("Name7", "ccc"));
+ Assert.assertEquals("Name7=aaa&Name7=b%2Cb&Name7=ccc", URLEncodedUtils.format(params, "US-ASCII"));
+
+ params.clear();
+ params.add(new BasicNameValuePair("Name8", "xx, yy ,zz"));
+ Assert.assertEquals("Name8=xx%2C++yy++%2Czz", URLEncodedUtils.format(params, "US-ASCII"));
+ }
+
+ private List <NameValuePair> parse (final String params) {
+ return URLEncodedUtils.parse(params, Consts.UTF_8);
}
- private List <NameValuePair> parse (final String params, final String encoding) {
- return URLEncodedUtils.parse(URI.create("http://hc.apache.org/params?" + params), encoding);
+ private List <NameValuePair> parseString (final String uri) throws Exception {
+ return URLEncodedUtils.parse(new URI("?"+uri), "UTF-8");
}
private static void assertNameValuePair (
@@ -207,4 +378,4 @@ public class TestURLEncodedUtils {
Assert.assertEquals(parameter.getValue(), expectedValue);
}
-}
\ No newline at end of file
+}
diff --git a/httpclient/src/test/java/org/apache/http/conn/TestConnectionAutoRelease.java b/httpclient/src/test/java/org/apache/http/conn/TestConnectionAutoRelease.java
index c77b2f7..3f7c443 100644
--- a/httpclient/src/test/java/org/apache/http/conn/TestConnectionAutoRelease.java
+++ b/httpclient/src/test/java/org/apache/http/conn/TestConnectionAutoRelease.java
@@ -40,12 +40,12 @@ import org.apache.http.HttpResponse;
import org.apache.http.MalformedChunkCodingException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.routing.HttpRoute;
-import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.localserver.ServerTestBase;
+import org.apache.http.pool.PoolStats;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
@@ -55,20 +55,15 @@ import org.junit.Test;
public class TestConnectionAutoRelease extends ServerTestBase {
- private ThreadSafeClientConnManager createTSCCM(SchemeRegistry schreg) {
- if (schreg == null)
- schreg = supportedSchemes;
- return new ThreadSafeClientConnManager(schreg);
- }
-
@Test
public void testReleaseOnEntityConsumeContent() throws Exception {
- ThreadSafeClientConnManager mgr = createTSCCM(null);
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
mgr.setDefaultMaxPerRoute(1);
mgr.setMaxTotal(1);
// Zero connections in the pool
- Assert.assertEquals(0, mgr.getConnectionsInPool());
+ PoolStats stats = mgr.getTotalStats();
+ Assert.assertEquals(0, stats.getAvailable());
DefaultHttpClient client = new DefaultHttpClient(mgr);
@@ -90,7 +85,8 @@ public class TestConnectionAutoRelease extends ServerTestBase {
EntityUtils.consume(e);
// Expect one connection in the pool
- Assert.assertEquals(1, mgr.getConnectionsInPool());
+ stats = mgr.getTotalStats();
+ Assert.assertEquals(1, stats.getAvailable());
// Make sure one connection is available
connreq = mgr.requestConnection(new HttpRoute(target), null);
@@ -103,12 +99,13 @@ public class TestConnectionAutoRelease extends ServerTestBase {
@Test
public void testReleaseOnEntityWriteTo() throws Exception {
- ThreadSafeClientConnManager mgr = createTSCCM(null);
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
mgr.setDefaultMaxPerRoute(1);
mgr.setMaxTotal(1);
// Zero connections in the pool
- Assert.assertEquals(0, mgr.getConnectionsInPool());
+ PoolStats stats = mgr.getTotalStats();
+ Assert.assertEquals(0, stats.getAvailable());
DefaultHttpClient client = new DefaultHttpClient(mgr);
@@ -131,7 +128,8 @@ public class TestConnectionAutoRelease extends ServerTestBase {
e.writeTo(outsteam);
// Expect one connection in the pool
- Assert.assertEquals(1, mgr.getConnectionsInPool());
+ stats = mgr.getTotalStats();
+ Assert.assertEquals(1, stats.getAvailable());
// Make sure one connection is available
connreq = mgr.requestConnection(new HttpRoute(target), null);
@@ -144,12 +142,13 @@ public class TestConnectionAutoRelease extends ServerTestBase {
@Test
public void testReleaseOnAbort() throws Exception {
- ThreadSafeClientConnManager mgr = createTSCCM(null);
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
mgr.setDefaultMaxPerRoute(1);
mgr.setMaxTotal(1);
// Zero connections in the pool
- Assert.assertEquals(0, mgr.getConnectionsInPool());
+ PoolStats stats = mgr.getTotalStats();
+ Assert.assertEquals(0, stats.getAvailable());
DefaultHttpClient client = new DefaultHttpClient(mgr);
@@ -171,7 +170,7 @@ public class TestConnectionAutoRelease extends ServerTestBase {
httpget.abort();
// Expect zero connections in the pool
- Assert.assertEquals(0, mgr.getConnectionsInPool());
+ Assert.assertEquals(0, mgr.getTotalStats().getAvailable());
// Make sure one connection is available
connreq = mgr.requestConnection(new HttpRoute(target), null);
@@ -217,12 +216,12 @@ public class TestConnectionAutoRelease extends ServerTestBase {
});
- ThreadSafeClientConnManager mgr = createTSCCM(null);
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
mgr.setDefaultMaxPerRoute(1);
mgr.setMaxTotal(1);
// Zero connections in the pool
- Assert.assertEquals(0, mgr.getConnectionsInPool());
+ Assert.assertEquals(0, mgr.getTotalStats().getAvailable());
DefaultHttpClient client = new DefaultHttpClient(mgr);
@@ -250,7 +249,7 @@ public class TestConnectionAutoRelease extends ServerTestBase {
}
// Expect zero connections in the pool
- Assert.assertEquals(0, mgr.getConnectionsInPool());
+ Assert.assertEquals(0, mgr.getTotalStats().getAvailable());
// Make sure one connection is available
connreq = mgr.requestConnection(new HttpRoute(target), null);
diff --git a/httpclient/src/test/java/org/apache/http/conn/TestConnectionReuse.java b/httpclient/src/test/java/org/apache/http/conn/TestConnectionReuse.java
index d7e7522..525c5d2 100644
--- a/httpclient/src/test/java/org/apache/http/conn/TestConnectionReuse.java
+++ b/httpclient/src/test/java/org/apache/http/conn/TestConnectionReuse.java
@@ -44,7 +44,7 @@ import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SchemeSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.localserver.LocalTestServer;
import org.apache.http.localserver.RandomHandler;
import org.apache.http.params.BasicHttpParams;
@@ -99,7 +99,7 @@ public class TestConnectionReuse {
SchemeSocketFactory sf = PlainSocketFactory.getSocketFactory();
supportedSchemes.register(new Scheme("http", 80, sf));
- ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(supportedSchemes);
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager(supportedSchemes);
mgr.setMaxTotal(5);
mgr.setDefaultMaxPerRoute(5);
@@ -130,7 +130,7 @@ public class TestConnectionReuse {
}
// Expect some connection in the pool
- Assert.assertTrue(mgr.getConnectionsInPool() > 0);
+ Assert.assertTrue(mgr.getTotalStats().getAvailable() > 0);
mgr.shutdown();
}
@@ -170,7 +170,7 @@ public class TestConnectionReuse {
SchemeSocketFactory sf = PlainSocketFactory.getSocketFactory();
supportedSchemes.register(new Scheme("http", 80, sf));
- ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(supportedSchemes);
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager(supportedSchemes);
mgr.setMaxTotal(5);
mgr.setDefaultMaxPerRoute(5);
@@ -201,7 +201,7 @@ public class TestConnectionReuse {
}
// Expect zero connections in the pool
- Assert.assertEquals(0, mgr.getConnectionsInPool());
+ Assert.assertEquals(0, mgr.getTotalStats().getAvailable());
mgr.shutdown();
}
@@ -231,7 +231,7 @@ public class TestConnectionReuse {
SchemeSocketFactory sf = PlainSocketFactory.getSocketFactory();
supportedSchemes.register(new Scheme("http", 80, sf));
- ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(supportedSchemes);
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager(supportedSchemes);
mgr.setMaxTotal(5);
mgr.setDefaultMaxPerRoute(5);
@@ -262,7 +262,7 @@ public class TestConnectionReuse {
}
// Expect zero connections in the pool
- Assert.assertEquals(0, mgr.getConnectionsInPool());
+ Assert.assertEquals(0, mgr.getTotalStats().getAvailable());
mgr.shutdown();
}
@@ -293,7 +293,7 @@ public class TestConnectionReuse {
SchemeSocketFactory sf = PlainSocketFactory.getSocketFactory();
supportedSchemes.register(new Scheme("http", 80, sf));
- ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(supportedSchemes);
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager(supportedSchemes);
mgr.setMaxTotal(1);
mgr.setDefaultMaxPerRoute(1);
@@ -303,13 +303,13 @@ public class TestConnectionReuse {
HttpResponse response = client.execute(target, new HttpGet("/random/2000"));
EntityUtils.consume(response.getEntity());
- Assert.assertEquals(1, mgr.getConnectionsInPool());
+ Assert.assertEquals(1, mgr.getTotalStats().getAvailable());
Assert.assertEquals(1, localServer.getAcceptedConnectionCount());
response = client.execute(target, new HttpGet("/random/2000"));
EntityUtils.consume(response.getEntity());
- Assert.assertEquals(1, mgr.getConnectionsInPool());
+ Assert.assertEquals(1, mgr.getTotalStats().getAvailable());
Assert.assertEquals(1, localServer.getAcceptedConnectionCount());
// Now sleep for 1.1 seconds and let the timeout do its work
@@ -317,7 +317,7 @@ public class TestConnectionReuse {
response = client.execute(target, new HttpGet("/random/2000"));
EntityUtils.consume(response.getEntity());
- Assert.assertEquals(1, mgr.getConnectionsInPool());
+ Assert.assertEquals(1, mgr.getTotalStats().getAvailable());
Assert.assertEquals(2, localServer.getAcceptedConnectionCount());
// Do another request just under the 1 second limit & make
@@ -326,7 +326,7 @@ public class TestConnectionReuse {
response = client.execute(target, new HttpGet("/random/2000"));
EntityUtils.consume(response.getEntity());
- Assert.assertEquals(1, mgr.getConnectionsInPool());
+ Assert.assertEquals(1, mgr.getTotalStats().getAvailable());
Assert.assertEquals(2, localServer.getAcceptedConnectionCount());
diff --git a/httpclient/src/test/java/org/apache/http/conn/ssl/TestHostnameVerifier.java b/httpclient/src/test/java/org/apache/http/conn/ssl/TestHostnameVerifier.java
index 41e2026..db8daa7 100644
--- a/httpclient/src/test/java/org/apache/http/conn/ssl/TestHostnameVerifier.java
+++ b/httpclient/src/test/java/org/apache/http/conn/ssl/TestHostnameVerifier.java
@@ -31,6 +31,7 @@ import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.util.Arrays;
import javax.net.ssl.SSLException;
@@ -228,4 +229,109 @@ public class TestHostnameVerifier {
}
}
+ // Test helper method
+ private void checkMatching(X509HostnameVerifier hv, String host,
+ String[] cns, String[] alts, boolean shouldFail) {
+ try {
+ hv.verify(host, cns, alts);
+ if (shouldFail) {
+ Assert.fail("HostnameVerifier should not allow [" + host + "] to match "
+ +Arrays.toString(cns)
+ +" or "
+ +Arrays.toString(alts));
+ }
+ }
+ catch(SSLException e) {
+ if (!shouldFail) {
+ Assert.fail("HostnameVerifier should have allowed [" + host + "] to match "
+ +Arrays.toString(cns)
+ +" or "
+ +Arrays.toString(alts));
+ }
+ }
+ }
+
+ @Test
+ // Check standard wildcard matching
+ public void testMatching() {
+ String cns[] = {};
+ String alt[] = {};
+ X509HostnameVerifier bhv = new BrowserCompatHostnameVerifier();
+ X509HostnameVerifier shv = new StrictHostnameVerifier();
+ checkMatching(bhv, "a.b.c", cns, alt, true); // empty
+ checkMatching(shv, "a.b.c", cns, alt, true); // empty
+
+ cns = new String []{"*.b.c"};
+ checkMatching(bhv, "a.b.c", cns, alt, false); // OK
+ checkMatching(shv, "a.b.c", cns, alt, false); // OK
+
+ checkMatching(bhv, "s.a.b.c", cns, alt, false); // OK
+ checkMatching(shv, "s.a.b.c", cns, alt, true); // subdomain not OK
+
+ cns = new String []{};
+ alt = new String []{"dummy", "*.b.c"}; // check matches against all alts
+ checkMatching(bhv, "a.b.c", cns, alt, false); // OK
+ checkMatching(shv, "a.b.c", cns, alt, false); // OK
+
+ checkMatching(bhv, "s.a.b.c", cns, alt, false); // OK
+ checkMatching(shv, "s.a.b.c", cns, alt, true); // subdomain not OK
+
+ alt = new String []{"*.gov.uk"};
+ checkMatching(bhv, "a.gov.uk", cns, alt, true); // Bad 2TLD
+ checkMatching(shv, "a.gov.uk", cns, alt, true); // Bad 2TLD
+
+ checkMatching(bhv, "s.a.gov.uk", cns, alt, true); // Bad 2TLD
+ checkMatching(shv, "s.a.gov.uk", cns, alt, true); // Bad 2TLD/no subdomain allowed
+
+ alt = new String []{"*.gov.com"};
+ checkMatching(bhv, "a.gov.com", cns, alt, false); // OK, gov not 2TLD here
+ checkMatching(shv, "a.gov.com", cns, alt, false); // OK, gov not 2TLD here
+
+ checkMatching(bhv, "s.a.gov.com", cns, alt, false); // OK, gov not 2TLD here
+ checkMatching(shv, "s.a.gov.com", cns, alt, true); // no subdomain allowed
+
+ cns = new String []{"a*.gov.uk"}; // 2TLD check applies to wildcards
+ checkMatching(bhv, "a.gov.uk", cns, alt, true); // Bad 2TLD
+ checkMatching(shv, "a.gov.uk", cns, alt, true); // Bad 2TLD
+
+ checkMatching(bhv, "s.a.gov.uk", cns, alt, true); // Bad 2TLD
+ checkMatching(shv, "s.a.gov.uk", cns, alt, true); // Bad 2TLD/no subdomain allowed
+
+ }
+
+ @Test
+ public void HTTPCLIENT_1097() {
+ String cns[];
+ String alt[] = {};
+ X509HostnameVerifier bhv = new BrowserCompatHostnameVerifier();
+ X509HostnameVerifier shv = new StrictHostnameVerifier();
+
+ cns = new String []{"a*.b.c"}; // component part
+ checkMatching(bhv, "a.b.c", cns, alt, false); // OK
+ checkMatching(shv, "a.b.c", cns, alt, false); // OK
+
+ checkMatching(bhv, "a.a.b.c", cns, alt, false); // OK
+ checkMatching(shv, "a.a.b.c", cns, alt, true); // subdomain not OK
+
+ checkWildcard("s*.co.uk", false); // 2 character TLD, invalid 2TLD
+ checkWildcard("s*.gov.uk", false); // 2 character TLD, invalid 2TLD
+ checkWildcard("s*.gouv.uk", false); // 2 character TLD, invalid 2TLD
+ }
+
+ // Helper
+ private void checkWildcard(String host, boolean isOK) {
+ Assert.assertTrue(host+" should be "+isOK, isOK==AbstractVerifier.acceptableCountryWildcard(host));
+ }
+
+ @Test
+ // Various checks of 2TLDs
+ public void testacceptableCountryWildcards() {
+ checkWildcard("*.co.org", true); // Not a 2 character TLD
+ checkWildcard("s*.co.org", true); // Not a 2 character TLD
+ checkWildcard("*.co.uk", false); // 2 character TLD, invalid 2TLD
+ checkWildcard("*.gov.uk", false); // 2 character TLD, invalid 2TLD
+ checkWildcard("*.gouv.uk", false); // 2 character TLD, invalid 2TLD
+ checkWildcard("*.a.co.uk", true); // 2 character TLD, invalid 2TLD, but using subdomain
+ checkWildcard("s*.a.co.uk", true); // 2 character TLD, invalid 2TLD, but using subdomain
+ }
}
diff --git a/httpclient/src/test/java/org/apache/http/conn/ssl/TestSSLSocketFactory.java b/httpclient/src/test/java/org/apache/http/conn/ssl/TestSSLSocketFactory.java
index 31af6d9..3ffdf4a 100644
--- a/httpclient/src/test/java/org/apache/http/conn/ssl/TestSSLSocketFactory.java
+++ b/httpclient/src/test/java/org/apache/http/conn/ssl/TestSSLSocketFactory.java
@@ -108,6 +108,7 @@ public class TestSSLSocketFactory extends BasicServerTestBase {
this.localServer.registerDefaultHandlers();
this.localServer.start();
+ this.httpclient = new DefaultHttpClient();
}
@Override
@@ -150,12 +151,11 @@ public class TestSSLSocketFactory extends BasicServerTestBase {
SSLSocketFactory socketFactory = new SSLSocketFactory(this.clientSSLContext, hostVerifier);
Scheme https = new Scheme("https", 443, socketFactory);
- DefaultHttpClient httpclient = new DefaultHttpClient();
- httpclient.getConnectionManager().getSchemeRegistry().register(https);
+ this.httpclient.getConnectionManager().getSchemeRegistry().register(https);
HttpHost target = getServerHttp();
HttpGet httpget = new HttpGet("/random/100");
- HttpResponse response = httpclient.execute(target, httpget);
+ HttpResponse response = this.httpclient.execute(target, httpget);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
Assert.assertTrue(hostVerifier.isFired());
}
@@ -170,12 +170,11 @@ public class TestSSLSocketFactory extends BasicServerTestBase {
SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
Scheme https = new Scheme("https", 443, socketFactory);
- DefaultHttpClient httpclient = new DefaultHttpClient();
- httpclient.getConnectionManager().getSchemeRegistry().register(https);
+ this.httpclient.getConnectionManager().getSchemeRegistry().register(https);
HttpHost target = getServerHttp();
HttpGet httpget = new HttpGet("/random/100");
- httpclient.execute(target, httpget);
+ this.httpclient.execute(target, httpget);
}
@Test
@@ -194,12 +193,11 @@ public class TestSSLSocketFactory extends BasicServerTestBase {
}, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
Scheme https = new Scheme("https", 443, socketFactory);
- DefaultHttpClient httpclient = new DefaultHttpClient();
- httpclient.getConnectionManager().getSchemeRegistry().register(https);
+ this.httpclient.getConnectionManager().getSchemeRegistry().register(https);
HttpHost target = getServerHttp();
HttpGet httpget = new HttpGet("/random/100");
- HttpResponse response = httpclient.execute(target, httpget);
+ HttpResponse response = this.httpclient.execute(target, httpget);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
}
diff --git a/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java b/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java
index dbfe182..96f1029 100644
--- a/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java
+++ b/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java
@@ -35,6 +35,8 @@ import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EncodingUtils;
import org.junit.Assert;
import org.junit.Test;
@@ -76,7 +78,8 @@ public class TestBasicScheme {
authscheme.processChallenge(challenge);
HttpRequest request = new BasicHttpRequest("GET", "/");
- Header authResponse = authscheme.authenticate(creds, request);
+ HttpContext context = new BasicHttpContext();
+ Header authResponse = authscheme.authenticate(creds, request, context);
String expected = "Basic " + EncodingUtils.getAsciiString(
Base64.encodeBase64(EncodingUtils.getAsciiBytes("testuser:testpass")));
@@ -98,7 +101,8 @@ public class TestBasicScheme {
authscheme.processChallenge(challenge);
HttpRequest request = new BasicHttpRequest("GET", "/");
- Header authResponse = authscheme.authenticate(creds, request);
+ HttpContext context = new BasicHttpContext();
+ Header authResponse = authscheme.authenticate(creds, request, context);
String expected = "Basic " + EncodingUtils.getAsciiString(
Base64.encodeBase64(EncodingUtils.getAsciiBytes("testuser:testpass")));
diff --git a/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java b/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java
index f4dcf61..d1f581f 100644
--- a/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java
+++ b/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java
@@ -26,20 +26,31 @@
package org.apache.http.impl.auth;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
+import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.AUTH;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHeaderValueParser;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.protocol.HttpContext;
import org.junit.Assert;
import org.junit.Test;
@@ -49,14 +60,14 @@ import org.junit.Test;
public class TestDigestScheme {
@Test(expected=MalformedChallengeException.class)
- public void testDigestAuthenticationWithNoRealm() throws Exception {
+ public void testDigestAuthenticationEmptyChallenge1() throws Exception {
Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, "Digest");
AuthScheme authscheme = new DigestScheme();
authscheme.processChallenge(authChallenge);
}
@Test(expected=MalformedChallengeException.class)
- public void testDigestAuthenticationWithNoRealm2() throws Exception {
+ public void testDigestAuthenticationEmptyChallenge2() throws Exception {
Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, "Digest ");
AuthScheme authscheme = new DigestScheme();
authscheme.processChallenge(authChallenge);
@@ -69,8 +80,11 @@ public class TestDigestScheme {
HttpRequest request = new BasicHttpRequest("Simple", "/");
Credentials cred = new UsernamePasswordCredentials("username","password");
DigestScheme authscheme = new DigestScheme();
+ HttpContext context = new BasicHttpContext();
authscheme.processChallenge(authChallenge);
- Header authResponse = authscheme.authenticate(cred, request);
+ Header authResponse = authscheme.authenticate(cred, request, context);
+ Assert.assertTrue(authscheme.isComplete());
+ Assert.assertFalse(authscheme.isConnectionBased());
Map<String, String> table = parseAuthResponse(authResponse);
Assert.assertEquals("username", table.get("username"));
@@ -87,8 +101,9 @@ public class TestDigestScheme {
HttpRequest request = new BasicHttpRequest("Simple", "/");
Credentials cred = new UsernamePasswordCredentials("username","password");
DigestScheme authscheme = new DigestScheme();
+ HttpContext context = new BasicHttpContext();
authscheme.processChallenge(authChallenge);
- Header authResponse = authscheme.authenticate(cred, request);
+ Header authResponse = authscheme.authenticate(cred, request, context);
Map<String, String> table = parseAuthResponse(authResponse);
Assert.assertEquals("username", table.get("username"));
@@ -99,6 +114,47 @@ public class TestDigestScheme {
}
@Test
+ public void testDigestAuthenticationInvalidInput() throws Exception {
+ String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
+ Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+ HttpRequest request = new BasicHttpRequest("Simple", "/");
+ Credentials cred = new UsernamePasswordCredentials("username","password");
+ DigestScheme authscheme = new DigestScheme();
+ HttpContext context = new BasicHttpContext();
+ authscheme.processChallenge(authChallenge);
+ try {
+ authscheme.authenticate(null, request, context);
+ Assert.fail("IllegalArgumentException should have been thrown");
+ } catch (IllegalArgumentException ex) {
+ }
+ try {
+ authscheme.authenticate(cred, null, context);
+ Assert.fail("IllegalArgumentException should have been thrown");
+ } catch (IllegalArgumentException ex) {
+ }
+ }
+
+ @Test
+ public void testDigestAuthenticationOverrideParameter() throws Exception {
+ String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
+ Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+ HttpRequest request = new BasicHttpRequest("Simple", "/");
+ Credentials cred = new UsernamePasswordCredentials("username","password");
+ DigestScheme authscheme = new DigestScheme();
+ HttpContext context = new BasicHttpContext();
+ authscheme.processChallenge(authChallenge);
+ authscheme.overrideParamter("realm", "other realm");
+ Header authResponse = authscheme.authenticate(cred, request, context);
+
+ Map<String, String> table = parseAuthResponse(authResponse);
+ Assert.assertEquals("username", table.get("username"));
+ Assert.assertEquals("other realm", table.get("realm"));
+ Assert.assertEquals("/", table.get("uri"));
+ Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
+ Assert.assertEquals("3f211de10463cbd055ab4cd9c5158eac", table.get("response"));
+ }
+
+ @Test
public void testDigestAuthenticationWithSHA() throws Exception {
String challenge = "Digest realm=\"realm1\", " +
"nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
@@ -106,9 +162,10 @@ public class TestDigestScheme {
Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
HttpRequest request = new BasicHttpRequest("Simple", "/");
Credentials cred = new UsernamePasswordCredentials("username","password");
+ HttpContext context = new BasicHttpContext();
DigestScheme authscheme = new DigestScheme();
authscheme.processChallenge(authChallenge);
- Header authResponse = authscheme.authenticate(cred, request);
+ Header authResponse = authscheme.authenticate(cred, request, context);
Map<String, String> table = parseAuthResponse(authResponse);
Assert.assertEquals("username", table.get("username"));
@@ -124,9 +181,10 @@ public class TestDigestScheme {
Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
HttpRequest request = new BasicHttpRequest("Simple", "/?param=value");
Credentials cred = new UsernamePasswordCredentials("username","password");
+ HttpContext context = new BasicHttpContext();
DigestScheme authscheme = new DigestScheme();
authscheme.processChallenge(authChallenge);
- Header authResponse = authscheme.authenticate(cred, request);
+ Header authResponse = authscheme.authenticate(cred, request, context);
Map<String, String> table = parseAuthResponse(authResponse);
Assert.assertEquals("username", table.get("username"));
@@ -145,9 +203,10 @@ public class TestDigestScheme {
Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge1);
HttpRequest request = new BasicHttpRequest("Simple", "/");
+ HttpContext context = new BasicHttpContext();
DigestScheme authscheme = new DigestScheme();
authscheme.processChallenge(authChallenge);
- Header authResponse = authscheme.authenticate(cred, request);
+ Header authResponse = authscheme.authenticate(cred, request, context);
Map<String, String> table = parseAuthResponse(authResponse);
Assert.assertEquals("username", table.get("username"));
@@ -159,7 +218,7 @@ public class TestDigestScheme {
authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge2);
DigestScheme authscheme2 = new DigestScheme();
authscheme2.processChallenge(authChallenge);
- authResponse = authscheme2.authenticate(cred2, request);
+ authResponse = authscheme2.authenticate(cred2, request, context);
table = parseAuthResponse(authResponse);
Assert.assertEquals("uname2", table.get("username"));
@@ -169,6 +228,32 @@ public class TestDigestScheme {
Assert.assertEquals("0283edd9ef06a38b378b3b74661391e9", table.get("response"));
}
+ @Test(expected=AuthenticationException.class)
+ public void testDigestAuthenticationNoRealm() throws Exception {
+ String challenge = "Digest no-realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
+ Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+ HttpContext context = new BasicHttpContext();
+ DigestScheme authscheme = new DigestScheme();
+ authscheme.processChallenge(authChallenge);
+
+ Credentials cred = new UsernamePasswordCredentials("username","password");
+ HttpRequest request = new BasicHttpRequest("Simple", "/");
+ authscheme.authenticate(cred, request, context);
+ }
+
+ @Test(expected=AuthenticationException.class)
+ public void testDigestAuthenticationNoNonce() throws Exception {
+ String challenge = "Digest realm=\"realm1\", no-nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
+ Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+ HttpContext context = new BasicHttpContext();
+ DigestScheme authscheme = new DigestScheme();
+ authscheme.processChallenge(authChallenge);
+
+ Credentials cred = new UsernamePasswordCredentials("username","password");
+ HttpRequest request = new BasicHttpRequest("Simple", "/");
+ authscheme.authenticate(cred, request, context);
+ }
+
/**
* Test digest authentication using the MD5-sess algorithm.
*/
@@ -192,10 +277,11 @@ public class TestDigestScheme {
Credentials cred = new UsernamePasswordCredentials(username, password);
HttpRequest request = new BasicHttpRequest("Simple", "/");
+ HttpContext context = new BasicHttpContext();
DigestScheme authscheme = new DigestScheme();
authscheme.processChallenge(authChallenge);
- Header authResponse = authscheme.authenticate(cred, request);
+ Header authResponse = authscheme.authenticate(cred, request, context);
String response = authResponse.getValue();
Assert.assertTrue(response.indexOf("nc=00000001") > 0); // test for quotes
@@ -238,10 +324,11 @@ public class TestDigestScheme {
Credentials cred = new UsernamePasswordCredentials(username, password);
HttpRequest request = new BasicHttpRequest("Simple", "/");
+ HttpContext context = new BasicHttpContext();
DigestScheme authscheme = new DigestScheme();
authscheme.processChallenge(authChallenge);
- Header authResponse = authscheme.authenticate(cred, request);
+ Header authResponse = authscheme.authenticate(cred, request, context);
Map<String, String> table = parseAuthResponse(authResponse);
Assert.assertEquals(username, table.get("username"));
@@ -257,13 +344,15 @@ public class TestDigestScheme {
}
/**
- * Test digest authentication with invalud qop value
+ * Test digest authentication with unknown qop value
*/
- @Test(expected=MalformedChallengeException.class)
- public void testDigestAuthenticationMD5SessInvalidQop() throws Exception {
+ @Test(expected=AuthenticationException.class)
+ public void testDigestAuthenticationMD5SessUnknownQop() throws Exception {
// Example using Digest auth with MD5-sess
String realm="realm";
+ String username="username";
+ String password="password";
String nonce="e273f1776275974f1a120d8b92c5b3cb";
String challenge="Digest realm=\"" + realm + "\", "
@@ -271,12 +360,47 @@ public class TestDigestScheme {
+ "opaque=\"SomeString\", "
+ "stale=false, "
+ "algorithm=MD5-sess, "
- + "qop=\"jakarta\""; // jakarta is an invalid qop value
+ + "qop=\"stuff\"";
Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
- AuthScheme authscheme = new DigestScheme();
+ DigestScheme authscheme = new DigestScheme();
+ authscheme.processChallenge(authChallenge);
+
+ Credentials cred = new UsernamePasswordCredentials(username, password);
+ HttpRequest request = new BasicHttpRequest("Simple", "/");
+ HttpContext context = new BasicHttpContext();
+ authscheme.authenticate(cred, request, context);
+ }
+
+ /**
+ * Test digest authentication with unknown qop value
+ */
+ @Test(expected=AuthenticationException.class)
+ public void testDigestAuthenticationUnknownAlgo() throws Exception {
+ // Example using Digest auth with MD5-sess
+
+ String realm="realm";
+ String username="username";
+ String password="password";
+ String nonce="e273f1776275974f1a120d8b92c5b3cb";
+
+ String challenge="Digest realm=\"" + realm + "\", "
+ + "nonce=\"" + nonce + "\", "
+ + "opaque=\"SomeString\", "
+ + "stale=false, "
+ + "algorithm=stuff, "
+ + "qop=\"auth\"";
+
+ Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+
+ DigestScheme authscheme = new DigestScheme();
authscheme.processChallenge(authChallenge);
+
+ Credentials cred = new UsernamePasswordCredentials(username, password);
+ HttpRequest request = new BasicHttpRequest("Simple", "/");
+ HttpContext context = new BasicHttpContext();
+ authscheme.authenticate(cred, request, context);
}
@Test
@@ -308,28 +432,191 @@ public class TestDigestScheme {
public void testDigestNouceCount() throws Exception {
String challenge1 = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth";
Header authChallenge1 = new BasicHeader(AUTH.WWW_AUTH, challenge1);
- HttpRequest request = new BasicHttpRequest("Simple", "/");
+ HttpRequest request = new BasicHttpRequest("GET", "/");
Credentials cred = new UsernamePasswordCredentials("username","password");
+ HttpContext context = new BasicHttpContext();
DigestScheme authscheme = new DigestScheme();
authscheme.processChallenge(authChallenge1);
- Header authResponse1 = authscheme.authenticate(cred, request);
+ Header authResponse1 = authscheme.authenticate(cred, request, context);
Map<String, String> table1 = parseAuthResponse(authResponse1);
Assert.assertEquals("00000001", table1.get("nc"));
- Header authResponse2 = authscheme.authenticate(cred, request);
+ Header authResponse2 = authscheme.authenticate(cred, request, context);
Map<String, String> table2 = parseAuthResponse(authResponse2);
Assert.assertEquals("00000002", table2.get("nc"));
String challenge2 = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth";
Header authChallenge2 = new BasicHeader(AUTH.WWW_AUTH, challenge2);
authscheme.processChallenge(authChallenge2);
- Header authResponse3 = authscheme.authenticate(cred, request);
+ Header authResponse3 = authscheme.authenticate(cred, request, context);
Map<String, String> table3 = parseAuthResponse(authResponse3);
Assert.assertEquals("00000003", table3.get("nc"));
String challenge3 = "Digest realm=\"realm1\", nonce=\"e273f1776275974f1a120d8b92c5b3cb\", qop=auth";
Header authChallenge3 = new BasicHeader(AUTH.WWW_AUTH, challenge3);
authscheme.processChallenge(authChallenge3);
- Header authResponse4 = authscheme.authenticate(cred, request);
+ Header authResponse4 = authscheme.authenticate(cred, request, context);
+ Map<String, String> table4 = parseAuthResponse(authResponse4);
+ Assert.assertEquals("00000001", table4.get("nc"));
+ }
+
+ @Test
+ public void testDigestMD5SessA1AndCnonceConsistency() throws Exception {
+ String challenge1 = "Digest qop=\"auth\", algorithm=MD5-sess, nonce=\"1234567890abcdef\", " +
+ "charset=utf-8, realm=\"subnet.domain.com\"";
+ Header authChallenge1 = new BasicHeader(AUTH.WWW_AUTH, challenge1);
+ HttpRequest request = new BasicHttpRequest("GET", "/");
+ Credentials cred = new UsernamePasswordCredentials("username","password");
+ HttpContext context = new BasicHttpContext();
+ DigestScheme authscheme = new DigestScheme();
+ authscheme.processChallenge(authChallenge1);
+ Header authResponse1 = authscheme.authenticate(cred, request, context);
+ Map<String, String> table1 = parseAuthResponse(authResponse1);
+ Assert.assertEquals("00000001", table1.get("nc"));
+ String cnonce1 = authscheme.getCnonce();
+ String sessionKey1 = authscheme.getA1();
+
+ Header authResponse2 = authscheme.authenticate(cred, request, context);
+ Map<String, String> table2 = parseAuthResponse(authResponse2);
+ Assert.assertEquals("00000002", table2.get("nc"));
+ String cnonce2 = authscheme.getCnonce();
+ String sessionKey2 = authscheme.getA1();
+
+ Assert.assertEquals(cnonce1, cnonce2);
+ Assert.assertEquals(sessionKey1, sessionKey2);
+
+ String challenge2 = "Digest qop=\"auth\", algorithm=MD5-sess, nonce=\"1234567890abcdef\", " +
+ "charset=utf-8, realm=\"subnet.domain.com\"";
+ Header authChallenge2 = new BasicHeader(AUTH.WWW_AUTH, challenge2);
+ authscheme.processChallenge(authChallenge2);
+ Header authResponse3 = authscheme.authenticate(cred, request, context);
+ Map<String, String> table3 = parseAuthResponse(authResponse3);
+ Assert.assertEquals("00000003", table3.get("nc"));
+
+ String cnonce3 = authscheme.getCnonce();
+ String sessionKey3 = authscheme.getA1();
+
+ Assert.assertEquals(cnonce1, cnonce3);
+ Assert.assertEquals(sessionKey1, sessionKey3);
+
+ String challenge3 = "Digest qop=\"auth\", algorithm=MD5-sess, nonce=\"fedcba0987654321\", " +
+ "charset=utf-8, realm=\"subnet.domain.com\"";
+ Header authChallenge3 = new BasicHeader(AUTH.WWW_AUTH, challenge3);
+ authscheme.processChallenge(authChallenge3);
+ Header authResponse4 = authscheme.authenticate(cred, request, context);
Map<String, String> table4 = parseAuthResponse(authResponse4);
Assert.assertEquals("00000001", table4.get("nc"));
+
+ String cnonce4 = authscheme.getCnonce();
+ String sessionKey4 = authscheme.getA1();
+
+ Assert.assertFalse(cnonce1.equals(cnonce4));
+ Assert.assertFalse(sessionKey1.equals(sessionKey4));
+ }
+
+ @Test
+ public void testHttpEntityDigest() throws Exception {
+ HttpEntityDigester digester = new HttpEntityDigester(MessageDigest.getInstance("MD5"));
+ Assert.assertNull(digester.getDigest());
+ digester.write('a');
+ digester.write('b');
+ digester.write('c');
+ digester.write(0xe4);
+ digester.write(0xf6);
+ digester.write(0xfc);
+ digester.write(new byte[] { 'a', 'b', 'c'});
+ Assert.assertNull(digester.getDigest());
+ digester.close();
+ Assert.assertEquals("acd2b59cd01c7737d8069015584c6cac", DigestScheme.encode(digester.getDigest()));
+ try {
+ digester.write('a');
+ Assert.fail("IOException should have been thrown");
+ } catch (IOException ex) {
+ }
+ try {
+ digester.write(new byte[] { 'a', 'b', 'c'});
+ Assert.fail("IOException should have been thrown");
+ } catch (IOException ex) {
+ }
+ }
+
+ @Test
+ public void testDigestAuthenticationQopAuthInt() throws Exception {
+ String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
+ "qop=\"auth,auth-int\"";
+ Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+ HttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("Post", "/");
+ request.setEntity(new StringEntity("abc\u00e4\u00f6\u00fcabc", HTTP.DEF_CONTENT_CHARSET));
+ Credentials cred = new UsernamePasswordCredentials("username","password");
+ DigestScheme authscheme = new DigestScheme();
+ HttpContext context = new BasicHttpContext();
+ authscheme.processChallenge(authChallenge);
+ Header authResponse = authscheme.authenticate(cred, request, context);
+
+ Assert.assertEquals("Post:/:acd2b59cd01c7737d8069015584c6cac", authscheme.getA2());
+
+ Map<String, String> table = parseAuthResponse(authResponse);
+ Assert.assertEquals("username", table.get("username"));
+ Assert.assertEquals("realm1", table.get("realm"));
+ Assert.assertEquals("/", table.get("uri"));
+ Assert.assertEquals("auth-int", table.get("qop"));
+ Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
+ }
+
+ @Test
+ public void testDigestAuthenticationQopAuthIntNullEntity() throws Exception {
+ String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
+ "qop=\"auth,auth-int\"";
+ Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+ HttpRequest request = new BasicHttpEntityEnclosingRequest("Post", "/");
+ Credentials cred = new UsernamePasswordCredentials("username","password");
+ DigestScheme authscheme = new DigestScheme();
+ HttpContext context = new BasicHttpContext();
+ authscheme.processChallenge(authChallenge);
+ Header authResponse = authscheme.authenticate(cred, request, context);
+
+ Assert.assertEquals("Post:/:d41d8cd98f00b204e9800998ecf8427e", authscheme.getA2());
+
+ Map<String, String> table = parseAuthResponse(authResponse);
+ Assert.assertEquals("username", table.get("username"));
+ Assert.assertEquals("realm1", table.get("realm"));
+ Assert.assertEquals("/", table.get("uri"));
+ Assert.assertEquals("auth-int", table.get("qop"));
+ Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
+ }
+
+ @Test
+ public void testDigestAuthenticationQopAuthOrAuthIntNonRepeatableEntity() throws Exception {
+ String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
+ "qop=\"auth,auth-int\"";
+ Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+ HttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("Post", "/");
+ request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1));
+ Credentials cred = new UsernamePasswordCredentials("username","password");
+ DigestScheme authscheme = new DigestScheme();
+ HttpContext context = new BasicHttpContext();
+ authscheme.processChallenge(authChallenge);
+ Header authResponse = authscheme.authenticate(cred, request, context);
+
+ Assert.assertEquals("Post:/", authscheme.getA2());
+
+ Map<String, String> table = parseAuthResponse(authResponse);
+ Assert.assertEquals("username", table.get("username"));
+ Assert.assertEquals("realm1", table.get("realm"));
+ Assert.assertEquals("/", table.get("uri"));
+ Assert.assertEquals("auth", table.get("qop"));
+ Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
+ }
+
+ @Test(expected=AuthenticationException.class)
+ public void testDigestAuthenticationQopIntOnlyNonRepeatableEntity() throws Exception {
+ String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
+ "qop=\"auth-int\"";
+ Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+ HttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("Post", "/");
+ request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1));
+ Credentials cred = new UsernamePasswordCredentials("username","password");
+ DigestScheme authscheme = new DigestScheme();
+ HttpContext context = new BasicHttpContext();
+ authscheme.processChallenge(authChallenge);
+ authscheme.authenticate(cred, request, context);
}
}
diff --git a/httpclient/src/test/java/org/apache/http/impl/auth/TestNegotiateScheme.java b/httpclient/src/test/java/org/apache/http/impl/auth/TestNegotiateScheme.java
deleted file mode 100644
index 0102259..0000000
--- a/httpclient/src/test/java/org/apache/http/impl/auth/TestNegotiateScheme.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.http.impl.auth;
-
-import java.io.IOException;
-import java.security.Principal;
-
-import org.apache.http.HttpException;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.auth.AuthScheme;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.Credentials;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.params.AuthPolicy;
-import org.apache.http.client.params.ClientPNames;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.localserver.BasicServerTestBase;
-import org.apache.http.localserver.LocalTestServer;
-import org.apache.http.message.BasicHeader;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.protocol.HttpRequestHandler;
-import org.apache.http.util.EntityUtils;
-
-import org.ietf.jgss.GSSContext;
-import org.ietf.jgss.GSSCredential;
-import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.GSSName;
-import org.ietf.jgss.Oid;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-
-/**
- * Tests for {@link NegotiateScheme}.
- */
-public class TestNegotiateScheme extends BasicServerTestBase {
-
- @Before
- public void setUp() throws Exception {
- localServer = new LocalTestServer(null, null);
-
- localServer.registerDefaultHandlers();
- localServer.start();
- }
-
- /**
- * This service will continue to ask for authentication.
- */
- private static class PleaseNegotiateService implements HttpRequestHandler {
-
- public void handle(
- final HttpRequest request,
- final HttpResponse response,
- final HttpContext context) throws HttpException, IOException {
- response.setStatusCode(HttpStatus.SC_UNAUTHORIZED);
- response.addHeader(new BasicHeader("WWW-Authenticate", "Negotiate blablabla"));
- response.addHeader(new BasicHeader("Connection", "Keep-Alive"));
- response.setEntity(new StringEntity("auth required "));
- }
- }
-
- /**
- * NegotatieScheme with a custom GSSManager that does not require any Jaas or
- * Kerberos configuration.
- *
- */
- private static class NegotiateSchemeWithMockGssManager extends NegotiateScheme {
-
- GSSManager manager = Mockito.mock(GSSManager.class);
- GSSName name = Mockito.mock(GSSName.class);
- GSSContext context = Mockito.mock(GSSContext.class);
-
- NegotiateSchemeWithMockGssManager() throws Exception {
- super(null, true);
- Mockito.when(context.initSecContext(
- Matchers.any(byte[].class), Matchers.anyInt(), Matchers.anyInt()))
- .thenReturn("12345678".getBytes());
- Mockito.when(manager.createName(
- Matchers.any(String.class), Matchers.any(Oid.class)))
- .thenReturn(name);
- Mockito.when(manager.createContext(
- Matchers.any(GSSName.class), Matchers.any(Oid.class),
- Matchers.any(GSSCredential.class), Matchers.anyInt()))
- .thenReturn(context);
- }
-
- @Override
- protected GSSManager getManager() {
- return manager;
- }
-
- }
-
- private static class UseJaasCredentials implements Credentials {
-
- public String getPassword() {
- return null;
- }
-
- public Principal getUserPrincipal() {
- return null;
- }
-
- }
-
- private static class NegotiateSchemeFactoryWithMockGssManager extends NegotiateSchemeFactory {
-
- NegotiateSchemeWithMockGssManager scheme;
-
- NegotiateSchemeFactoryWithMockGssManager() throws Exception {
- scheme = new NegotiateSchemeWithMockGssManager();
- }
-
- @Override
- public AuthScheme newInstance(HttpParams params) {
- return scheme;
- }
-
- }
-
- /**
- * Tests that the client will stop connecting to the server if
- * the server still keep asking for a valid ticket.
- */
- @Test
- public void testDontTryToAuthenticateEndlessly() throws Exception {
- int port = this.localServer.getServiceAddress().getPort();
- this.localServer.register("*", new PleaseNegotiateService());
-
- HttpHost target = new HttpHost("localhost", port);
- DefaultHttpClient client = new DefaultHttpClient();
-
- NegotiateSchemeFactory nsf = new NegotiateSchemeFactoryWithMockGssManager();
-
- client.getAuthSchemes().register(AuthPolicy.SPNEGO, nsf);
-
- Credentials use_jaas_creds = new UseJaasCredentials();
- client.getCredentialsProvider().setCredentials(
- new AuthScope(null, -1, null), use_jaas_creds);
- client.getParams().setParameter(ClientPNames.DEFAULT_HOST, target);
-
- String s = "/path";
- HttpGet httpget = new HttpGet(s);
- HttpResponse response = client.execute(httpget);
- EntityUtils.consume(response.getEntity());
-
- Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusLine().getStatusCode());
- }
-
- /**
- * Javadoc specifies that {@link GSSContext#initSecContext(byte[], int, int)} can return null
- * if no token is generated. Client should be able to deal with this response.
- */
- @Test
- public void testNoTokenGeneratedError() throws Exception {
- int port = this.localServer.getServiceAddress().getPort();
- this.localServer.register("*", new PleaseNegotiateService());
-
- HttpHost target = new HttpHost("localhost", port);
- DefaultHttpClient client = new DefaultHttpClient();
-
- NegotiateSchemeFactoryWithMockGssManager nsf = new NegotiateSchemeFactoryWithMockGssManager();
-
- client.getAuthSchemes().register(AuthPolicy.SPNEGO, nsf);
-
- Credentials use_jaas_creds = new UseJaasCredentials();
- client.getCredentialsProvider().setCredentials(
- new AuthScope(null, -1, null), use_jaas_creds);
- client.getParams().setParameter(ClientPNames.DEFAULT_HOST, target);
-
- String s = "/path";
- HttpGet httpget = new HttpGet(s);
- HttpResponse response = client.execute(httpget);
- EntityUtils.consume(response.getEntity());
-
- Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusLine().getStatusCode());
- }
-
-}
diff --git a/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java b/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java
index 8953628..04c10d5 100644
--- a/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java
+++ b/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java
@@ -73,11 +73,13 @@ public class TestRFC2617Scheme {
authscheme.processChallenge(header);
Assert.assertEquals("test", authscheme.getSchemeName());
+ Assert.assertEquals("TEST", authscheme.toString());
Assert.assertEquals("realm1", authscheme.getParameter("realm"));
Assert.assertEquals(null, authscheme.getParameter("test"));
Assert.assertEquals("stuff", authscheme.getParameter("test1"));
Assert.assertEquals("stuff, stuff", authscheme.getParameter("test2"));
Assert.assertEquals("\"crap", authscheme.getParameter("test3"));
+ Assert.assertEquals(null, authscheme.getParameter(null));
}
@Test
@@ -87,12 +89,23 @@ public class TestRFC2617Scheme {
buffer.append(" WWW-Authenticate: Test realm=\"realm1\"");
Header header = new BufferedHeader(buffer);
+
authscheme.processChallenge(header);
Assert.assertEquals("test", authscheme.getSchemeName());
Assert.assertEquals("realm1", authscheme.getParameter("realm"));
}
+ @Test
+ public void testNullHeader() throws Exception {
+ TestAuthScheme authscheme = new TestAuthScheme();
+ try {
+ authscheme.processChallenge(null);
+ Assert.fail("IllegalArgumentException should have been thrown");
+ } catch (IllegalArgumentException ex) {
+ }
+ }
+
@Test(expected=MalformedChallengeException.class)
public void testInvalidHeader() throws Exception {
TestAuthScheme authscheme = new TestAuthScheme();
@@ -101,6 +114,13 @@ public class TestRFC2617Scheme {
}
@Test(expected=MalformedChallengeException.class)
+ public void testInvalidSchemeName() throws Exception {
+ TestAuthScheme authscheme = new TestAuthScheme();
+ Header header = new BasicHeader(AUTH.WWW_AUTH, "Not-a-Test realm=\"realm1\"");
+ authscheme.processChallenge(header);
+ }
+
+ @Test(expected=MalformedChallengeException.class)
public void testEmptyHeader() throws Exception {
TestAuthScheme authscheme = new TestAuthScheme();
Header header = new BasicHeader(AUTH.WWW_AUTH, "Test ");
diff --git a/httpclient/src/test/java/org/apache/http/impl/auth/TestSPNegoScheme.java b/httpclient/src/test/java/org/apache/http/impl/auth/TestSPNegoScheme.java
new file mode 100644
index 0000000..b3a80f5
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/auth/TestSPNegoScheme.java
@@ -0,0 +1,209 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.auth;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.client.params.ClientPNames;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.localserver.BasicServerTestBase;
+import org.apache.http.localserver.LocalTestServer;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.apache.http.util.EntityUtils;
+
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+
+/**
+ * Tests for {@link NegotiateScheme}.
+ */
+public class TestSPNegoScheme extends BasicServerTestBase {
+
+ @Before
+ public void setUp() throws Exception {
+ this.localServer = new LocalTestServer(null, null);
+
+ this.localServer.registerDefaultHandlers();
+ this.localServer.start();
+ this.httpclient = new DefaultHttpClient();
+ }
+
+ /**
+ * This service will continue to ask for authentication.
+ */
+ private static class PleaseNegotiateService implements HttpRequestHandler {
+
+ public void handle(
+ final HttpRequest request,
+ final HttpResponse response,
+ final HttpContext context) throws HttpException, IOException {
+ response.setStatusCode(HttpStatus.SC_UNAUTHORIZED);
+ response.addHeader(new BasicHeader("WWW-Authenticate", "Negotiate blablabla"));
+ response.addHeader(new BasicHeader("Connection", "Keep-Alive"));
+ response.setEntity(new StringEntity("auth required "));
+ }
+ }
+
+ /**
+ * NegotatieScheme with a custom GSSManager that does not require any Jaas or
+ * Kerberos configuration.
+ *
+ */
+ private static class NegotiateSchemeWithMockGssManager extends SPNegoScheme {
+
+ GSSManager manager = Mockito.mock(GSSManager.class);
+ GSSName name = Mockito.mock(GSSName.class);
+ GSSContext context = Mockito.mock(GSSContext.class);
+
+ NegotiateSchemeWithMockGssManager() throws Exception {
+ super(true);
+ Mockito.when(context.initSecContext(
+ Matchers.any(byte[].class), Matchers.anyInt(), Matchers.anyInt()))
+ .thenReturn("12345678".getBytes());
+ Mockito.when(manager.createName(
+ Matchers.any(String.class), Matchers.any(Oid.class)))
+ .thenReturn(name);
+ Mockito.when(manager.createContext(
+ Matchers.any(GSSName.class), Matchers.any(Oid.class),
+ Matchers.any(GSSCredential.class), Matchers.anyInt()))
+ .thenReturn(context);
+ }
+
+ @Override
+ protected GSSManager getManager() {
+ return manager;
+ }
+
+ }
+
+ private static class UseJaasCredentials implements Credentials {
+
+ public String getPassword() {
+ return null;
+ }
+
+ public Principal getUserPrincipal() {
+ return null;
+ }
+
+ }
+
+ private static class NegotiateSchemeFactoryWithMockGssManager extends SPNegoSchemeFactory {
+
+ NegotiateSchemeWithMockGssManager scheme;
+
+ NegotiateSchemeFactoryWithMockGssManager() throws Exception {
+ scheme = new NegotiateSchemeWithMockGssManager();
+ }
+
+ @Override
+ public AuthScheme newInstance(HttpParams params) {
+ return scheme;
+ }
+
+ }
+
+ /**
+ * Tests that the client will stop connecting to the server if
+ * the server still keep asking for a valid ticket.
+ */
+ @Test
+ public void testDontTryToAuthenticateEndlessly() throws Exception {
+ int port = this.localServer.getServiceAddress().getPort();
+ this.localServer.register("*", new PleaseNegotiateService());
+
+ HttpHost target = new HttpHost("localhost", port);
+
+ SPNegoSchemeFactory nsf = new NegotiateSchemeFactoryWithMockGssManager();
+
+ this.httpclient.getAuthSchemes().register(AuthPolicy.SPNEGO, nsf);
+
+ Credentials use_jaas_creds = new UseJaasCredentials();
+ this.httpclient.getCredentialsProvider().setCredentials(
+ new AuthScope(null, -1, null), use_jaas_creds);
+ this.httpclient.getParams().setParameter(ClientPNames.DEFAULT_HOST, target);
+
+ String s = "/path";
+ HttpGet httpget = new HttpGet(s);
+ HttpResponse response = this.httpclient.execute(httpget);
+ EntityUtils.consume(response.getEntity());
+
+ Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusLine().getStatusCode());
+ }
+
+ /**
+ * Javadoc specifies that {@link GSSContext#initSecContext(byte[], int, int)} can return null
+ * if no token is generated. Client should be able to deal with this response.
+ */
+ @Test
+ public void testNoTokenGeneratedError() throws Exception {
+ int port = this.localServer.getServiceAddress().getPort();
+ this.localServer.register("*", new PleaseNegotiateService());
+
+ HttpHost target = new HttpHost("localhost", port);
+
+ NegotiateSchemeFactoryWithMockGssManager nsf = new NegotiateSchemeFactoryWithMockGssManager();
+
+ this.httpclient.getAuthSchemes().register(AuthPolicy.SPNEGO, nsf);
+
+ Credentials use_jaas_creds = new UseJaasCredentials();
+ this.httpclient.getCredentialsProvider().setCredentials(
+ new AuthScope(null, -1, null), use_jaas_creds);
+ this.httpclient.getParams().setParameter(ClientPNames.DEFAULT_HOST, target);
+
+ String s = "/path";
+ HttpGet httpget = new HttpGet(s);
+ HttpResponse response = this.httpclient.execute(httpget);
+ EntityUtils.consume(response.getEntity());
+
+ Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusLine().getStatusCode());
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/DummyHttpClient.java b/httpclient/src/test/java/org/apache/http/impl/client/DummyHttpClient.java
new file mode 100644
index 0000000..6717a3f
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/DummyHttpClient.java
@@ -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.impl.client;
+
+import java.io.IOException;
+
+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.ResponseHandler;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.impl.conn.SingleClientConnManager;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+
+ at SuppressWarnings("deprecation")
+public class DummyHttpClient implements HttpClient {
+
+ private HttpParams params = new BasicHttpParams();
+ private ClientConnectionManager connManager = new SingleClientConnManager();
+ private HttpRequest request;
+ private HttpResponse response = new BasicHttpResponse(new ProtocolVersion("HTTP",1,1), HttpStatus.SC_OK, "OK");
+
+ public void setParams(HttpParams params) {
+ this.params = params;
+ }
+
+ public HttpParams getParams() {
+ return params;
+ }
+
+ public ClientConnectionManager getConnectionManager() {
+ return connManager;
+ }
+
+ public void setConnectionManager(ClientConnectionManager ccm) {
+ connManager = ccm;
+ }
+
+ public void setResponse(HttpResponse resp) {
+ response = resp;
+ }
+
+ public HttpRequest getCapturedRequest() {
+ return request;
+ }
+
+ public HttpResponse execute(HttpUriRequest request) throws IOException,
+ ClientProtocolException {
+ this.request = request;
+ return response;
+ }
+
+ public HttpResponse execute(HttpUriRequest request, HttpContext context)
+ throws IOException, ClientProtocolException {
+ this.request = request;
+ return response;
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request)
+ throws IOException, ClientProtocolException {
+ this.request = request;
+ return response;
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request,
+ HttpContext context) throws IOException, ClientProtocolException {
+ this.request = request;
+ return response;
+ }
+
+ public <T> T execute(HttpUriRequest request,
+ ResponseHandler<? extends T> responseHandler) throws IOException,
+ ClientProtocolException {
+ this.request = request;
+ return responseHandler.handleResponse(response);
+ }
+
+ public <T> T execute(HttpUriRequest request,
+ ResponseHandler<? extends T> responseHandler, HttpContext context)
+ throws IOException, ClientProtocolException {
+ this.request = request;
+ return responseHandler.handleResponse(response);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler) throws IOException,
+ ClientProtocolException {
+ this.request = request;
+ return responseHandler.handleResponse(response);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler, HttpContext context)
+ throws IOException, ClientProtocolException {
+ this.request = request;
+ return responseHandler.handleResponse(response);
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/MockClock.java b/httpclient/src/test/java/org/apache/http/impl/client/MockClock.java
new file mode 100644
index 0000000..ac639ef
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/MockClock.java
@@ -0,0 +1,40 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client;
+
+public class MockClock implements Clock {
+
+ private long t = System.currentTimeMillis();
+
+ public long getCurrentTime() {
+ return t;
+ }
+
+ public void setCurrentTime(long now) {
+ t = now;
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/MockConnPoolControl.java b/httpclient/src/test/java/org/apache/http/impl/client/MockConnPoolControl.java
new file mode 100644
index 0000000..ca4fe9a
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/MockConnPoolControl.java
@@ -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.impl.client;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.pool.ConnPoolControl;
+import org.apache.http.pool.PoolStats;
+
+public final class MockConnPoolControl implements ConnPoolControl<HttpRoute> {
+
+ private final ConcurrentHashMap<HttpRoute, Integer> maxPerHostMap;
+
+ private volatile int totalMax;
+ private volatile int defaultMax;
+
+ public MockConnPoolControl() {
+ super();
+ this.maxPerHostMap = new ConcurrentHashMap<HttpRoute, Integer>();
+ this.totalMax = 20;
+ this.defaultMax = 2;
+ }
+
+ public void setMaxTotal(int max) {
+ this.totalMax = max;
+ }
+
+ public int getMaxTotal() {
+ return this.totalMax;
+ }
+
+ public PoolStats getTotalStats() {
+ return new PoolStats(-1, -1, -1, this.totalMax);
+ }
+
+ public PoolStats getStats(final HttpRoute route) {
+ return new PoolStats(-1, -1, -1, getMaxPerRoute(route));
+ }
+
+ public int getDefaultMaxPerRoute() {
+ return this.defaultMax;
+ }
+
+ public void setDefaultMaxPerRoute(int max) {
+ if (max < 1) {
+ throw new IllegalArgumentException
+ ("The maximum must be greater than 0.");
+ }
+ this.defaultMax = max;
+ }
+
+ public void setMaxPerRoute(final HttpRoute route, int max) {
+ if (route == null) {
+ throw new IllegalArgumentException
+ ("HTTP route may not be null.");
+ }
+ if (max < 1) {
+ throw new IllegalArgumentException
+ ("The maximum must be greater than 0.");
+ }
+ this.maxPerHostMap.put(route, Integer.valueOf(max));
+ }
+
+ public int getMaxPerRoute(final HttpRoute route) {
+ if (route == null) {
+ throw new IllegalArgumentException
+ ("HTTP route may not be null.");
+ }
+ Integer max = this.maxPerHostMap.get(route);
+ if (max != null) {
+ return max.intValue();
+ } else {
+ return this.defaultMax;
+ }
+ }
+
+ public void setMaxForRoutes(final Map<HttpRoute, Integer> map) {
+ if (map == null) {
+ return;
+ }
+ this.maxPerHostMap.clear();
+ this.maxPerHostMap.putAll(map);
+ }
+
+ @Override
+ public String toString() {
+ return this.maxPerHostMap.toString();
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestAIMDBackoffManager.java b/httpclient/src/test/java/org/apache/http/impl/client/TestAIMDBackoffManager.java
new file mode 100644
index 0000000..18a5826
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestAIMDBackoffManager.java
@@ -0,0 +1,174 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client;
+
+import static org.junit.Assert.*;
+
+import java.util.Random;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.BackoffManager;
+import org.apache.http.conn.routing.HttpRoute;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestAIMDBackoffManager {
+
+ private AIMDBackoffManager impl;
+ private MockConnPoolControl connPerRoute;
+ private HttpRoute route;
+ private MockClock clock;
+
+ @Before
+ public void setUp() {
+ connPerRoute = new MockConnPoolControl();
+ route = new HttpRoute(new HttpHost("localhost:80"));
+ clock = new MockClock();
+ impl = new AIMDBackoffManager(connPerRoute, clock);
+ impl.setPerHostConnectionCap(10);
+ }
+
+ @Test
+ public void isABackoffManager() {
+ assertTrue(impl instanceof BackoffManager);
+ }
+
+ @Test
+ public void halvesConnectionsOnBackoff() {
+ connPerRoute.setMaxPerRoute(route, 4);
+ impl.backOff(route);
+ assertEquals(2, connPerRoute.getMaxPerRoute(route));
+ }
+
+ @Test
+ public void doesNotBackoffBelowOneConnection() {
+ connPerRoute.setMaxPerRoute(route, 1);
+ impl.backOff(route);
+ assertEquals(1, connPerRoute.getMaxPerRoute(route));
+ }
+
+ @Test
+ public void increasesByOneOnProbe() {
+ connPerRoute.setMaxPerRoute(route, 2);
+ impl.probe(route);
+ assertEquals(3, connPerRoute.getMaxPerRoute(route));
+ }
+
+ @Test
+ public void doesNotIncreaseBeyondPerHostMaxOnProbe() {
+ connPerRoute.setDefaultMaxPerRoute(5);
+ connPerRoute.setMaxPerRoute(route, 5);
+ impl.setPerHostConnectionCap(5);
+ impl.probe(route);
+ assertEquals(5, connPerRoute.getMaxPerRoute(route));
+ }
+
+ @Test
+ public void backoffDoesNotAdjustDuringCoolDownPeriod() {
+ connPerRoute.setMaxPerRoute(route, 4);
+ long now = System.currentTimeMillis();
+ clock.setCurrentTime(now);
+ impl.backOff(route);
+ long max = connPerRoute.getMaxPerRoute(route);
+ clock.setCurrentTime(now + 1);
+ impl.backOff(route);
+ assertEquals(max, connPerRoute.getMaxPerRoute(route));
+ }
+
+ @Test
+ public void backoffStillAdjustsAfterCoolDownPeriod() {
+ connPerRoute.setMaxPerRoute(route, 8);
+ long now = System.currentTimeMillis();
+ clock.setCurrentTime(now);
+ impl.backOff(route);
+ long max = connPerRoute.getMaxPerRoute(route);
+ clock.setCurrentTime(now + 10 * 1000L);
+ impl.backOff(route);
+ assertTrue(max == 1 || max > connPerRoute.getMaxPerRoute(route));
+ }
+
+ @Test
+ public void probeDoesNotAdjustDuringCooldownPeriod() {
+ connPerRoute.setMaxPerRoute(route, 4);
+ long now = System.currentTimeMillis();
+ clock.setCurrentTime(now);
+ impl.probe(route);
+ long max = connPerRoute.getMaxPerRoute(route);
+ clock.setCurrentTime(now + 1);
+ impl.probe(route);
+ assertEquals(max, connPerRoute.getMaxPerRoute(route));
+ }
+
+ @Test
+ public void probeStillAdjustsAfterCoolDownPeriod() {
+ connPerRoute.setMaxPerRoute(route, 8);
+ long now = System.currentTimeMillis();
+ clock.setCurrentTime(now);
+ impl.probe(route);
+ long max = connPerRoute.getMaxPerRoute(route);
+ clock.setCurrentTime(now + 10 * 1000L);
+ impl.probe(route);
+ assertTrue(max < connPerRoute.getMaxPerRoute(route));
+ }
+
+ @Test
+ public void willBackoffImmediatelyEvenAfterAProbe() {
+ connPerRoute.setMaxPerRoute(route, 8);
+ long now = System.currentTimeMillis();
+ clock.setCurrentTime(now);
+ impl.probe(route);
+ long max = connPerRoute.getMaxPerRoute(route);
+ clock.setCurrentTime(now + 1);
+ impl.backOff(route);
+ assertTrue(connPerRoute.getMaxPerRoute(route) < max);
+ }
+
+ @Test
+ public void backOffFactorIsConfigurable() {
+ connPerRoute.setMaxPerRoute(route, 10);
+ impl.setBackoffFactor(0.9);
+ impl.backOff(route);
+ assertEquals(9, connPerRoute.getMaxPerRoute(route));
+ }
+
+ @Test
+ public void coolDownPeriodIsConfigurable() {
+ long cd = new Random().nextLong() / 2;
+ if (cd < 0) cd *= -1;
+ if (cd < 1) cd++;
+ long now = System.currentTimeMillis();
+ impl.setCooldownMillis(cd);
+ clock.setCurrentTime(now);
+ impl.probe(route);
+ int max0 = connPerRoute.getMaxPerRoute(route);
+ clock.setCurrentTime(now);
+ impl.probe(route);
+ assertEquals(max0, connPerRoute.getMaxPerRoute(route));
+ clock.setCurrentTime(now + cd + 1);
+ impl.probe(route);
+ assertTrue(max0 < connPerRoute.getMaxPerRoute(route));
+ }
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestAuthenticationStrategy.java b/httpclient/src/test/java/org/apache/http/impl/client/TestAuthenticationStrategy.java
new file mode 100644
index 0000000..4500fd7
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestAuthenticationStrategy.java
@@ -0,0 +1,433 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.http.impl.client;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.auth.AUTH;
+import org.apache.http.auth.AuthOption;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthSchemeRegistry;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.auth.params.AuthPNames;
+import org.apache.http.client.AuthCache;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.auth.BasicSchemeFactory;
+import org.apache.http.impl.auth.DigestScheme;
+import org.apache.http.impl.auth.DigestSchemeFactory;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Simple tests for {@link AuthenticationStrategyImpl}.
+ */
+public class TestAuthenticationStrategy {
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testIsAuthenticationRequestedInvalidInput() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpHost host = new HttpHost("localhost", 80);
+ HttpContext context = new BasicHttpContext();
+ authStrategy.isAuthenticationRequested(host, null, context);
+ }
+
+ @Test
+ public void testTargetAuthRequested() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ HttpHost host = new HttpHost("localhost", 80);
+ HttpContext context = new BasicHttpContext();
+ Assert.assertTrue(authStrategy.isAuthenticationRequested(host, response, context));
+ }
+
+ @Test
+ public void testProxyAuthRequested() throws Exception {
+ ProxyAuthenticationStrategy authStrategy = new ProxyAuthenticationStrategy();
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED, "UNAUTHORIZED");
+ HttpHost host = new HttpHost("localhost", 80);
+ HttpContext context = new BasicHttpContext();
+ Assert.assertTrue(authStrategy.isAuthenticationRequested(host, response, context));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testGetChallengesInvalidInput() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpHost host = new HttpHost("localhost", 80);
+ HttpContext context = new BasicHttpContext();
+ authStrategy.getChallenges(host, null, context);
+ }
+
+ @Test
+ public void testGetChallenges() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpHost host = new HttpHost("localhost", 80);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ Header h1 = new BasicHeader(AUTH.WWW_AUTH, " Basic realm=\"test\"");
+ Header h2 = new BasicHeader(AUTH.WWW_AUTH, "\t\tDigest realm=\"realm1\", nonce=\"1234\"");
+ Header h3 = new BasicHeader(AUTH.WWW_AUTH, "WhatEver realm=\"realm1\", stuff=\"1234\"");
+ response.addHeader(h1);
+ response.addHeader(h2);
+ response.addHeader(h3);
+
+ Map<String, Header> challenges = authStrategy.getChallenges(host, response, context);
+
+ Assert.assertNotNull(challenges);
+ Assert.assertEquals(3, challenges.size());
+ Assert.assertSame(h1, challenges.get("basic"));
+ Assert.assertSame(h2, challenges.get("digest"));
+ Assert.assertSame(h3, challenges.get("whatever"));
+ }
+
+ @Test
+ public void testSelectInvalidInput() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ Map<String, Header> challenges = new HashMap<String, Header>();
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ HttpHost authhost = new HttpHost("locahost", 80);
+ HttpContext context = new BasicHttpContext();
+ try {
+ authStrategy.select(null, authhost, response, context);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ }
+ try {
+ authStrategy.select(challenges, null, response, context);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ }
+ try {
+ authStrategy.select(challenges, authhost, null, context);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ }
+ try {
+ authStrategy.select(challenges, authhost, response, null);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ }
+ }
+
+ @Test
+ public void testSelectNoSchemeRegistry() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ HttpHost authhost = new HttpHost("locahost", 80);
+ HttpContext context = new BasicHttpContext();
+
+ Map<String, Header> challenges = new HashMap<String, Header>();
+ challenges.put("basic", new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"test\""));
+ challenges.put("digest", new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm1\", nonce=\"1234\""));
+
+ Queue<AuthOption> options = authStrategy.select(challenges, authhost, response, context);
+ Assert.assertNotNull(options);
+ Assert.assertEquals(0, options.size());
+ }
+
+ @Test
+ public void testSelectNoCredentialsProvider() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ HttpHost authhost = new HttpHost("locahost", 80);
+ HttpContext context = new BasicHttpContext();
+
+ Map<String, Header> challenges = new HashMap<String, Header>();
+ challenges.put("basic", new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"test\""));
+ challenges.put("digest", new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm1\", nonce=\"1234\""));
+
+ AuthSchemeRegistry authSchemeRegistry = new AuthSchemeRegistry();
+ authSchemeRegistry.register("basic", new BasicSchemeFactory());
+ authSchemeRegistry.register("digest", new DigestSchemeFactory());
+ context.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, authSchemeRegistry);
+
+ Queue<AuthOption> options = authStrategy.select(challenges, authhost, response, context);
+ Assert.assertNotNull(options);
+ Assert.assertEquals(0, options.size());
+ }
+
+ @Test
+ public void testNoCredentials() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ HttpHost authhost = new HttpHost("locahost", 80);
+ HttpContext context = new BasicHttpContext();
+
+ Map<String, Header> challenges = new HashMap<String, Header>();
+ challenges.put("basic", new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"realm1\""));
+ challenges.put("digest", new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm2\", nonce=\"1234\""));
+
+ AuthSchemeRegistry authSchemeRegistry = new AuthSchemeRegistry();
+ authSchemeRegistry.register("basic", new BasicSchemeFactory());
+ authSchemeRegistry.register("digest", new DigestSchemeFactory());
+ context.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, authSchemeRegistry);
+
+ CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ context.setAttribute(ClientContext.CREDS_PROVIDER, credentialsProvider);
+
+ Queue<AuthOption> options = authStrategy.select(challenges, authhost, response, context);
+ Assert.assertNotNull(options);
+ Assert.assertEquals(0, options.size());
+ }
+
+ @Test
+ public void testCredentialsFound() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ HttpHost authhost = new HttpHost("somehost", 80);
+ HttpContext context = new BasicHttpContext();
+
+ Map<String, Header> challenges = new HashMap<String, Header>();
+ challenges.put("basic", new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"realm1\""));
+ challenges.put("digest", new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm2\", nonce=\"1234\""));
+
+ AuthSchemeRegistry authSchemeRegistry = new AuthSchemeRegistry();
+ authSchemeRegistry.register("basic", new BasicSchemeFactory());
+ authSchemeRegistry.register("digest", new DigestSchemeFactory());
+ context.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, authSchemeRegistry);
+
+ CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ credentialsProvider.setCredentials(new AuthScope("somehost", 80, "realm2"),
+ new UsernamePasswordCredentials("user", "pwd"));
+ context.setAttribute(ClientContext.CREDS_PROVIDER, credentialsProvider);
+
+ Queue<AuthOption> options = authStrategy.select(challenges, authhost, response, context);
+ Assert.assertNotNull(options);
+ Assert.assertEquals(1, options.size());
+ AuthOption option = options.remove();
+ Assert.assertTrue(option.getAuthScheme() instanceof DigestScheme);
+ }
+
+ @Test
+ public void testUnsupportedScheme() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ HttpHost authhost = new HttpHost("somehost", 80);
+ HttpContext context = new BasicHttpContext();
+
+ Map<String, Header> challenges = new HashMap<String, Header>();
+ challenges.put("basic", new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"realm1\""));
+ challenges.put("digest", new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm2\", nonce=\"1234\""));
+ challenges.put("whatever", new BasicHeader(AUTH.WWW_AUTH, "Whatever realm=\"realm3\""));
+
+ AuthSchemeRegistry authSchemeRegistry = new AuthSchemeRegistry();
+ authSchemeRegistry.register("basic", new BasicSchemeFactory());
+ authSchemeRegistry.register("digest", new DigestSchemeFactory());
+ context.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, authSchemeRegistry);
+
+ CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ credentialsProvider.setCredentials(new AuthScope("somehost", 80),
+ new UsernamePasswordCredentials("user", "pwd"));
+ context.setAttribute(ClientContext.CREDS_PROVIDER, credentialsProvider);
+
+ Queue<AuthOption> options = authStrategy.select(challenges, authhost, response, context);
+ Assert.assertNotNull(options);
+ Assert.assertEquals(2, options.size());
+ AuthOption option1 = options.remove();
+ Assert.assertTrue(option1.getAuthScheme() instanceof DigestScheme);
+ AuthOption option2 = options.remove();
+ Assert.assertTrue(option2.getAuthScheme() instanceof BasicScheme);
+ }
+
+ @Test
+ public void testCustomAuthPreference() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ response.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF,
+ Arrays.asList(new String[] {AuthPolicy.BASIC } ));
+ HttpHost authhost = new HttpHost("somehost", 80);
+ HttpContext context = new BasicHttpContext();
+
+ Map<String, Header> challenges = new HashMap<String, Header>();
+ challenges.put("basic", new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"realm1\""));
+ challenges.put("digest", new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm2\", nonce=\"1234\""));
+
+ AuthSchemeRegistry authSchemeRegistry = new AuthSchemeRegistry();
+ authSchemeRegistry.register("basic", new BasicSchemeFactory());
+ authSchemeRegistry.register("digest", new DigestSchemeFactory());
+ context.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, authSchemeRegistry);
+
+ CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ credentialsProvider.setCredentials(new AuthScope("somehost", 80),
+ new UsernamePasswordCredentials("user", "pwd"));
+ context.setAttribute(ClientContext.CREDS_PROVIDER, credentialsProvider);
+
+ Queue<AuthOption> options = authStrategy.select(challenges, authhost, response, context);
+ Assert.assertNotNull(options);
+ Assert.assertEquals(1, options.size());
+ AuthOption option1 = options.remove();
+ Assert.assertTrue(option1.getAuthScheme() instanceof BasicScheme);
+ }
+
+ @Test
+ public void testAuthSucceededInvalidInput() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpHost authhost = new HttpHost("locahost", 80);
+ BasicScheme authScheme = new BasicScheme();
+ HttpContext context = new BasicHttpContext();
+ try {
+ authStrategy.authSucceeded(null, authScheme, context);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ }
+ try {
+ authStrategy.authSucceeded(authhost, null, context);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ }
+ try {
+ authStrategy.authSucceeded(authhost, authScheme, null);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ }
+ }
+
+ @Test
+ public void testAuthSucceeded() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpHost authhost = new HttpHost("somehost", 80);
+ BasicScheme authScheme = new BasicScheme();
+ authScheme.processChallenge(new BasicHeader(AUTH.WWW_AUTH, "Basic realm=test"));
+
+ AuthCache authCache = Mockito.mock(AuthCache.class);
+
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ClientContext.AUTH_CACHE, authCache);
+
+ authStrategy.authSucceeded(authhost, authScheme, context);
+ Mockito.verify(authCache).put(authhost, authScheme);
+ }
+
+ @Test
+ public void testAuthSucceededNoCache() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpHost authhost = new HttpHost("somehost", 80);
+ BasicScheme authScheme = new BasicScheme();
+ authScheme.processChallenge(new BasicHeader(AUTH.WWW_AUTH, "Basic realm=test"));
+
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ClientContext.AUTH_CACHE, null);
+
+ authStrategy.authSucceeded(authhost, authScheme, context);
+ AuthCache authCache = (AuthCache) context.getAttribute(ClientContext.AUTH_CACHE);
+ Assert.assertNotNull(authCache);
+ }
+
+ @Test
+ public void testAuthScemeNotCompleted() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpHost authhost = new HttpHost("somehost", 80);
+ BasicScheme authScheme = new BasicScheme();
+
+ AuthCache authCache = Mockito.mock(AuthCache.class);
+
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ClientContext.AUTH_CACHE, authCache);
+
+ authStrategy.authSucceeded(authhost, authScheme, context);
+ Mockito.verify(authCache, Mockito.never()).put(authhost, authScheme);
+ }
+
+ @Test
+ public void testAuthScemeNonCacheable() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpHost authhost = new HttpHost("somehost", 80);
+ AuthScheme authScheme = Mockito.mock(AuthScheme.class);
+ Mockito.when(authScheme.isComplete()).thenReturn(true);
+ Mockito.when(authScheme.getSchemeName()).thenReturn("whatever");
+
+ AuthCache authCache = Mockito.mock(AuthCache.class);
+
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ClientContext.AUTH_CACHE, authCache);
+
+ authStrategy.authSucceeded(authhost, authScheme, context);
+ Mockito.verify(authCache, Mockito.never()).put(authhost, authScheme);
+ }
+
+ @Test
+ public void testAuthFailedInvalidInput() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpHost authhost = new HttpHost("locahost", 80);
+ BasicScheme authScheme = new BasicScheme();
+ HttpContext context = new BasicHttpContext();
+ try {
+ authStrategy.authFailed(null, authScheme, context);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ }
+ try {
+ authStrategy.authFailed(authhost, authScheme, null);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException ex) {
+ }
+ }
+
+ @Test
+ public void testAuthFailed() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpHost authhost = new HttpHost("somehost", 80);
+ BasicScheme authScheme = new BasicScheme();
+ authScheme.processChallenge(new BasicHeader(AUTH.WWW_AUTH, "Basic realm=test"));
+
+ AuthCache authCache = Mockito.mock(AuthCache.class);
+
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ClientContext.AUTH_CACHE, authCache);
+
+ authStrategy.authFailed(authhost, authScheme, context);
+ Mockito.verify(authCache).remove(authhost);
+ }
+
+ @Test
+ public void testAuthFailedNoCache() throws Exception {
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+ HttpHost authhost = new HttpHost("somehost", 80);
+ BasicScheme authScheme = new BasicScheme();
+
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ClientContext.AUTH_CACHE, null);
+
+ authStrategy.authFailed(authhost, authScheme, context);
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestAutoRetryHttpClient.java b/httpclient/src/test/java/org/apache/http/impl/client/TestAutoRetryHttpClient.java
new file mode 100644
index 0000000..5eada02
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestAutoRetryHttpClient.java
@@ -0,0 +1,122 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.HttpClient;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestAutoRetryHttpClient{
+
+ private AutoRetryHttpClient impl;
+
+ private HttpClient mockBackend;
+
+ private HttpHost host;
+
+ @Before
+ public void setUp() {
+ mockBackend = mock(HttpClient.class);
+ host = new HttpHost("foo.example.com");
+ }
+
+ @Test
+ public void testDefaultRetryConfig(){
+ DefaultServiceUnavailableRetryStrategy retryStrategy = new DefaultServiceUnavailableRetryStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpResponse response1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, 503, "Oppsie");
+ assertTrue(retryStrategy.retryRequest(response1, 1, context));
+ HttpResponse response2 = new BasicHttpResponse(HttpVersion.HTTP_1_1, 502, "Oppsie");
+ assertFalse(retryStrategy.retryRequest(response2, 1, context));
+ assertEquals(1000, retryStrategy.getRetryInterval());
+ }
+
+ @Test
+ public void testNoAutoRetry() throws java.io.IOException{
+ DefaultServiceUnavailableRetryStrategy retryStrategy = new DefaultServiceUnavailableRetryStrategy(2, 100);
+
+ impl = new AutoRetryHttpClient(mockBackend,retryStrategy);
+
+ HttpRequest req1 = new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1);
+ HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
+ HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_OK, "OK");
+
+ when(mockBackend.execute(host, req1,(HttpContext)null)).thenReturn(resp1).thenReturn(resp2);
+
+ HttpResponse result = impl.execute(host, req1);
+
+ verify(mockBackend,times(1)).execute(host, req1,(HttpContext)null);
+
+ assertEquals(resp1,result);
+ assertEquals(500,result.getStatusLine().getStatusCode());
+ }
+
+ @Test
+ public void testMultipleAutoRetry() throws java.io.IOException{
+ DefaultServiceUnavailableRetryStrategy retryStrategy = new DefaultServiceUnavailableRetryStrategy(5, 100);
+
+ impl = new AutoRetryHttpClient(mockBackend,retryStrategy);
+
+ HttpRequest req1 = new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1);
+ HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_SERVICE_UNAVAILABLE, "Service Unavailable");
+ HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_SERVICE_UNAVAILABLE, "Service Unavailable");
+ HttpResponse resp3 = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_OK, "OK");
+
+ when(mockBackend.execute(host, req1,(HttpContext)null)).thenReturn(resp1).thenReturn(resp2).thenReturn(resp3);
+
+ HttpResponse result = impl.execute(host, req1);
+
+ verify(mockBackend,times(3)).execute(host, req1,(HttpContext)null);
+
+ assertEquals(resp3,result);
+ assertEquals(200,result.getStatusLine().getStatusCode());
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestBasicResponseHandler.java b/httpclient/src/test/java/org/apache/http/impl/client/TestBasicResponseHandler.java
new file mode 100644
index 0000000..2595cb3
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestBasicResponseHandler.java
@@ -0,0 +1,85 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.client;
+
+import java.io.InputStream;
+
+import junit.framework.Assert;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpResponseException;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.message.BasicStatusLine;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Unit tests for {@link BasicResponseHandler}.
+ */
+public class TestBasicResponseHandler {
+
+ @Test
+ public void testSuccessfulResponse() throws Exception {
+ StatusLine sl = new BasicStatusLine(HttpVersion.HTTP_1_1, 200, "OK");
+ HttpResponse response = Mockito.mock(HttpResponse.class);
+ HttpEntity entity = new StringEntity("stuff");
+ Mockito.when(response.getStatusLine()).thenReturn(sl);
+ Mockito.when(response.getEntity()).thenReturn(entity);
+
+ BasicResponseHandler handler = new BasicResponseHandler();
+ String s = handler.handleResponse(response);
+ Assert.assertEquals("stuff", s);
+ }
+
+ @Test
+ public void testUnsuccessfulResponse() throws Exception {
+ InputStream instream = Mockito.mock(InputStream.class);
+ HttpEntity entity = Mockito.mock(HttpEntity.class);
+ Mockito.when(entity.isStreaming()).thenReturn(true);
+ Mockito.when(entity.getContent()).thenReturn(instream);
+ StatusLine sl = new BasicStatusLine(HttpVersion.HTTP_1_1, 404, "Not Found");
+ HttpResponse response = Mockito.mock(HttpResponse.class);
+ Mockito.when(response.getStatusLine()).thenReturn(sl);
+ Mockito.when(response.getEntity()).thenReturn(entity);
+
+ BasicResponseHandler handler = new BasicResponseHandler();
+ try {
+ handler.handleResponse(response);
+ Assert.fail("HttpResponseException expected");
+ } catch (HttpResponseException ex) {
+ Assert.assertEquals(404, ex.getStatusCode());
+ Assert.assertEquals("Not Found", ex.getMessage());
+ }
+ Mockito.verify(entity).getContent();
+ Mockito.verify(instream).close();
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestClientAuthentication.java b/httpclient/src/test/java/org/apache/http/impl/client/TestClientAuthentication.java
index 5caa4c4..39813d6 100644
--- a/httpclient/src/test/java/org/apache/http/impl/client/TestClientAuthentication.java
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestClientAuthentication.java
@@ -28,8 +28,10 @@ package org.apache.http.impl.client;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
@@ -52,7 +54,6 @@ import org.apache.http.localserver.ResponseBasicUnauthorized;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
-import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpExpectationVerifier;
import org.apache.http.protocol.HttpRequestHandler;
@@ -80,7 +81,8 @@ public class TestClientAuthentication extends BasicServerTestBase {
httpproc.addInterceptor(new RequestBasicAuth());
httpproc.addInterceptor(new ResponseBasicUnauthorized());
- localServer = new LocalTestServer(httpproc, null);
+ this.localServer = new LocalTestServer(httpproc, null);
+ this.httpclient = new DefaultHttpClient();
}
static class AuthHandler implements HttpRequestHandler {
@@ -94,7 +96,7 @@ public class TestClientAuthentication extends BasicServerTestBase {
response.setStatusCode(HttpStatus.SC_UNAUTHORIZED);
} else {
response.setStatusCode(HttpStatus.SC_OK);
- StringEntity entity = new StringEntity("success", HTTP.ASCII);
+ StringEntity entity = new StringEntity("success", Consts.ASCII);
response.setEntity(entity);
}
}
@@ -153,17 +155,17 @@ public class TestClientAuthentication extends BasicServerTestBase {
@Test
public void testBasicAuthenticationNoCreds() throws Exception {
- localServer.register("*", new AuthHandler());
- localServer.start();
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
TestCredentialsProvider credsProvider = new TestCredentialsProvider(null);
- DefaultHttpClient httpclient = new DefaultHttpClient();
- httpclient.setCredentialsProvider(credsProvider);
+
+ this.httpclient.setCredentialsProvider(credsProvider);
HttpGet httpget = new HttpGet("/");
- HttpResponse response = httpclient.execute(getServerHttp(), httpget);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget);
HttpEntity entity = response.getEntity();
Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusLine().getStatusCode());
Assert.assertNotNull(entity);
@@ -175,18 +177,18 @@ public class TestClientAuthentication extends BasicServerTestBase {
@Test
public void testBasicAuthenticationFailure() throws Exception {
- localServer.register("*", new AuthHandler());
- localServer.start();
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
TestCredentialsProvider credsProvider = new TestCredentialsProvider(
new UsernamePasswordCredentials("test", "all-wrong"));
- DefaultHttpClient httpclient = new DefaultHttpClient();
- httpclient.setCredentialsProvider(credsProvider);
+
+ this.httpclient.setCredentialsProvider(credsProvider);
HttpGet httpget = new HttpGet("/");
- HttpResponse response = httpclient.execute(getServerHttp(), httpget);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget);
HttpEntity entity = response.getEntity();
Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusLine().getStatusCode());
Assert.assertNotNull(entity);
@@ -198,18 +200,18 @@ public class TestClientAuthentication extends BasicServerTestBase {
@Test
public void testBasicAuthenticationSuccess() throws Exception {
- localServer.register("*", new AuthHandler());
- localServer.start();
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
TestCredentialsProvider credsProvider = new TestCredentialsProvider(
new UsernamePasswordCredentials("test", "test"));
- DefaultHttpClient httpclient = new DefaultHttpClient();
- httpclient.setCredentialsProvider(credsProvider);
+
+ this.httpclient.setCredentialsProvider(credsProvider);
HttpGet httpget = new HttpGet("/");
- HttpResponse response = httpclient.execute(getServerHttp(), httpget);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget);
HttpEntity entity = response.getEntity();
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
Assert.assertNotNull(entity);
@@ -228,16 +230,16 @@ public class TestClientAuthentication extends BasicServerTestBase {
httpproc.addInterceptor(new ResponseConnControl());
httpproc.addInterceptor(new RequestBasicAuth());
httpproc.addInterceptor(new ResponseBasicUnauthorized());
- localServer = new LocalTestServer(
+ this.localServer = new LocalTestServer(
httpproc, null, null, new AuthExpectationVerifier(), null, null);
- localServer.register("*", new AuthHandler());
- localServer.start();
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
TestCredentialsProvider credsProvider = new TestCredentialsProvider(
new UsernamePasswordCredentials("test", "test"));
- DefaultHttpClient httpclient = new DefaultHttpClient();
- httpclient.setCredentialsProvider(credsProvider);
+
+ this.httpclient.setCredentialsProvider(credsProvider);
HttpPut httpput = new HttpPut("/");
httpput.setEntity(new InputStreamEntity(
@@ -246,7 +248,7 @@ public class TestClientAuthentication extends BasicServerTestBase {
-1));
httpput.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, true);
- HttpResponse response = httpclient.execute(getServerHttp(), httpput);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpput);
HttpEntity entity = response.getEntity();
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
Assert.assertNotNull(entity);
@@ -254,14 +256,14 @@ public class TestClientAuthentication extends BasicServerTestBase {
@Test(expected=ClientProtocolException.class)
public void testBasicAuthenticationFailureOnNonRepeatablePutDontExpectContinue() throws Exception {
- localServer.register("*", new AuthHandler());
- localServer.start();
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
TestCredentialsProvider credsProvider = new TestCredentialsProvider(
new UsernamePasswordCredentials("test", "test"));
- DefaultHttpClient httpclient = new DefaultHttpClient();
- httpclient.setCredentialsProvider(credsProvider);
+
+ this.httpclient.setCredentialsProvider(credsProvider);
HttpPut httpput = new HttpPut("/");
httpput.setEntity(new InputStreamEntity(
@@ -271,7 +273,7 @@ public class TestClientAuthentication extends BasicServerTestBase {
httpput.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
try {
- httpclient.execute(getServerHttp(), httpput);
+ this.httpclient.execute(getServerHttp(), httpput);
Assert.fail("ClientProtocolException should have been thrown");
} catch (ClientProtocolException ex) {
Throwable cause = ex.getCause();
@@ -283,19 +285,19 @@ public class TestClientAuthentication extends BasicServerTestBase {
@Test
public void testBasicAuthenticationSuccessOnRepeatablePost() throws Exception {
- localServer.register("*", new AuthHandler());
- localServer.start();
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
TestCredentialsProvider credsProvider = new TestCredentialsProvider(
new UsernamePasswordCredentials("test", "test"));
- DefaultHttpClient httpclient = new DefaultHttpClient();
- httpclient.setCredentialsProvider(credsProvider);
+
+ this.httpclient.setCredentialsProvider(credsProvider);
HttpPost httppost = new HttpPost("/");
- httppost.setEntity(new StringEntity("some important stuff", HTTP.ISO_8859_1));
+ httppost.setEntity(new StringEntity("some important stuff", Consts.ASCII));
- HttpResponse response = httpclient.execute(getServerHttp(), httppost);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httppost);
HttpEntity entity = response.getEntity();
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
Assert.assertNotNull(entity);
@@ -307,14 +309,14 @@ public class TestClientAuthentication extends BasicServerTestBase {
@Test(expected=ClientProtocolException.class)
public void testBasicAuthenticationFailureOnNonRepeatablePost() throws Exception {
- localServer.register("*", new AuthHandler());
- localServer.start();
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
TestCredentialsProvider credsProvider = new TestCredentialsProvider(
new UsernamePasswordCredentials("test", "test"));
- DefaultHttpClient httpclient = new DefaultHttpClient();
- httpclient.setCredentialsProvider(credsProvider);
+
+ this.httpclient.setCredentialsProvider(credsProvider);
HttpPost httppost = new HttpPost("/");
httppost.setEntity(new InputStreamEntity(
@@ -322,7 +324,7 @@ public class TestClientAuthentication extends BasicServerTestBase {
new byte[] { 0,1,2,3,4,5,6,7,8,9 }), -1));
try {
- httpclient.execute(getServerHttp(), httppost);
+ this.httpclient.execute(getServerHttp(), httppost);
Assert.fail("ClientProtocolException should have been thrown");
} catch (ClientProtocolException ex) {
Throwable cause = ex.getCause();
@@ -332,20 +334,21 @@ public class TestClientAuthentication extends BasicServerTestBase {
}
}
- static class TestTargetAuthenticationHandler extends DefaultTargetAuthenticationHandler {
+ static class TestTargetAuthenticationStrategy extends TargetAuthenticationStrategy {
private int count;
- public TestTargetAuthenticationHandler() {
+ public TestTargetAuthenticationStrategy() {
super();
this.count = 0;
}
@Override
public boolean isAuthenticationRequested(
+ final HttpHost host,
final HttpResponse response,
final HttpContext context) {
- boolean res = super.isAuthenticationRequested(response, context);
+ boolean res = super.isAuthenticationRequested(host, response, context);
if (res == true) {
synchronized (this) {
this.count++;
@@ -364,36 +367,65 @@ public class TestClientAuthentication extends BasicServerTestBase {
@Test
public void testBasicAuthenticationCredentialsCaching() throws Exception {
- localServer.register("*", new AuthHandler());
- localServer.start();
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials("test", "test"));
- TestTargetAuthenticationHandler authHandler = new TestTargetAuthenticationHandler();
+ TestTargetAuthenticationStrategy authStrategy = new TestTargetAuthenticationStrategy();
- DefaultHttpClient httpclient = new DefaultHttpClient();
- httpclient.setCredentialsProvider(credsProvider);
- httpclient.setTargetAuthenticationHandler(authHandler);
+ this.httpclient.setCredentialsProvider(credsProvider);
+ this.httpclient.setTargetAuthenticationStrategy(authStrategy);
HttpContext context = new BasicHttpContext();
HttpGet httpget = new HttpGet("/");
- HttpResponse response1 = httpclient.execute(getServerHttp(), httpget, context);
+ HttpResponse response1 = this.httpclient.execute(getServerHttp(), httpget, context);
HttpEntity entity1 = response1.getEntity();
Assert.assertEquals(HttpStatus.SC_OK, response1.getStatusLine().getStatusCode());
Assert.assertNotNull(entity1);
EntityUtils.consume(entity1);
- HttpResponse response2 = httpclient.execute(getServerHttp(), httpget, context);
+ HttpResponse response2 = this.httpclient.execute(getServerHttp(), httpget, context);
HttpEntity entity2 = response1.getEntity();
Assert.assertEquals(HttpStatus.SC_OK, response2.getStatusLine().getStatusCode());
Assert.assertNotNull(entity2);
EntityUtils.consume(entity2);
- Assert.assertEquals(1, authHandler.getCount());
+ Assert.assertEquals(1, authStrategy.getCount());
+ }
+
+ @Test
+ public void testAuthenticationUserinfoInRequestSuccess() throws Exception {
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
+
+ HttpHost target = getServerHttp();
+ HttpGet httpget = new HttpGet("http://test:test@" + target.toHostString() + "/");
+
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget);
+ HttpEntity entity = response.getEntity();
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+ Assert.assertNotNull(entity);
+ EntityUtils.consume(entity);
+ }
+
+ @Test
+ public void testAuthenticationUserinfoInRequestFailure() throws Exception {
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
+
+ HttpHost target = getServerHttp();
+ HttpGet httpget = new HttpGet("http://test:all-wrong@" + target.toHostString() + "/");
+
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget);
+ HttpEntity entity = response.getEntity();
+ Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusLine().getStatusCode());
+ Assert.assertNotNull(entity);
+ EntityUtils.consume(entity);
}
}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestClientAuthenticationFallBack.java b/httpclient/src/test/java/org/apache/http/impl/client/TestClientAuthenticationFallBack.java
new file mode 100644
index 0000000..1dcbe2e
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestClientAuthenticationFallBack.java
@@ -0,0 +1,156 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.http.impl.client;
+
+import java.io.IOException;
+
+import org.apache.http.Consts;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpResponseInterceptor;
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.AUTH;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.localserver.BasicServerTestBase;
+import org.apache.http.localserver.LocalTestServer;
+import org.apache.http.localserver.RequestBasicAuth;
+import org.apache.http.protocol.BasicHttpProcessor;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.apache.http.protocol.ResponseConnControl;
+import org.apache.http.protocol.ResponseContent;
+import org.apache.http.protocol.ResponseDate;
+import org.apache.http.protocol.ResponseServer;
+import org.apache.http.util.EntityUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestClientAuthenticationFallBack extends BasicServerTestBase {
+
+ public class ResponseBasicUnauthorized implements HttpResponseInterceptor {
+
+ public void process(
+ final HttpResponse response,
+ final HttpContext context) throws HttpException, IOException {
+ if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
+ response.addHeader(AUTH.WWW_AUTH, "Digest realm=\"test realm\" invalid");
+ response.addHeader(AUTH.WWW_AUTH, "Basic realm=\"test realm\"");
+ }
+ }
+
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ BasicHttpProcessor httpproc = new BasicHttpProcessor();
+ httpproc.addInterceptor(new ResponseDate());
+ httpproc.addInterceptor(new ResponseServer());
+ httpproc.addInterceptor(new ResponseContent());
+ httpproc.addInterceptor(new ResponseConnControl());
+ httpproc.addInterceptor(new RequestBasicAuth());
+ httpproc.addInterceptor(new ResponseBasicUnauthorized());
+
+ this.localServer = new LocalTestServer(httpproc, null);
+ this.httpclient = new DefaultHttpClient();
+ }
+
+ static class AuthHandler implements HttpRequestHandler {
+
+ public void handle(
+ final HttpRequest request,
+ final HttpResponse response,
+ final HttpContext context) throws HttpException, IOException {
+ String creds = (String) context.getAttribute("creds");
+ if (creds == null || !creds.equals("test:test")) {
+ response.setStatusCode(HttpStatus.SC_UNAUTHORIZED);
+ } else {
+ response.setStatusCode(HttpStatus.SC_OK);
+ StringEntity entity = new StringEntity("success", Consts.ASCII);
+ response.setEntity(entity);
+ }
+ }
+
+ }
+
+ static class TestCredentialsProvider implements CredentialsProvider {
+
+ private final Credentials creds;
+ private AuthScope authscope;
+
+ TestCredentialsProvider(final Credentials creds) {
+ super();
+ this.creds = creds;
+ }
+
+ public void clear() {
+ }
+
+ public Credentials getCredentials(AuthScope authscope) {
+ this.authscope = authscope;
+ return this.creds;
+ }
+
+ public void setCredentials(AuthScope authscope, Credentials credentials) {
+ }
+
+ public AuthScope getAuthScope() {
+ return this.authscope;
+ }
+
+ }
+
+ @Test
+ public void testBasicAuthenticationSuccess() throws Exception {
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
+
+ TestCredentialsProvider credsProvider = new TestCredentialsProvider(
+ new UsernamePasswordCredentials("test", "test"));
+
+
+ this.httpclient.setCredentialsProvider(credsProvider);
+
+ HttpGet httpget = new HttpGet("/");
+
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget);
+ HttpEntity entity = response.getEntity();
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+ Assert.assertNotNull(entity);
+ EntityUtils.consume(entity);
+ AuthScope authscope = credsProvider.getAuthScope();
+ Assert.assertNotNull(authscope);
+ Assert.assertEquals("test realm", authscope.getRealm());
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestClientReauthentication.java b/httpclient/src/test/java/org/apache/http/impl/client/TestClientReauthentication.java
new file mode 100644
index 0000000..7cde9c4
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestClientReauthentication.java
@@ -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.impl.client;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.http.Consts;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpResponseInterceptor;
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.AUTH;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.localserver.BasicServerTestBase;
+import org.apache.http.localserver.LocalTestServer;
+import org.apache.http.localserver.RequestBasicAuth;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.BasicHttpProcessor;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.apache.http.protocol.ResponseConnControl;
+import org.apache.http.protocol.ResponseContent;
+import org.apache.http.protocol.ResponseDate;
+import org.apache.http.protocol.ResponseServer;
+import org.apache.http.util.EntityUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestClientReauthentication extends BasicServerTestBase {
+
+ public class ResponseBasicUnauthorized implements HttpResponseInterceptor {
+
+ public void process(
+ final HttpResponse response,
+ final HttpContext context) throws HttpException, IOException {
+ if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
+ response.addHeader(AUTH.WWW_AUTH, "Basic realm=\"test realm\"");
+ }
+ }
+
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ BasicHttpProcessor httpproc = new BasicHttpProcessor();
+ httpproc.addInterceptor(new ResponseDate());
+ httpproc.addInterceptor(new ResponseServer());
+ httpproc.addInterceptor(new ResponseContent());
+ httpproc.addInterceptor(new ResponseConnControl());
+ httpproc.addInterceptor(new RequestBasicAuth());
+ httpproc.addInterceptor(new ResponseBasicUnauthorized());
+
+ this.localServer = new LocalTestServer(httpproc, null);
+ this.httpclient = new DefaultHttpClient();
+ }
+
+ static class AuthHandler implements HttpRequestHandler {
+
+ private AtomicLong count = new AtomicLong(0);
+
+ public void handle(
+ final HttpRequest request,
+ final HttpResponse response,
+ final HttpContext context) throws HttpException, IOException {
+ String creds = (String) context.getAttribute("creds");
+ if (creds == null || !creds.equals("test:test")) {
+ response.setStatusCode(HttpStatus.SC_UNAUTHORIZED);
+ } else {
+ // Make client re-authenticate on each fourth request
+ if (this.count.incrementAndGet() % 4 == 0) {
+ response.setStatusCode(HttpStatus.SC_UNAUTHORIZED);
+ } else {
+ response.setStatusCode(HttpStatus.SC_OK);
+ StringEntity entity = new StringEntity("success", Consts.ASCII);
+ response.setEntity(entity);
+ }
+ }
+ }
+
+ }
+
+ static class TestCredentialsProvider implements CredentialsProvider {
+
+ private final Credentials creds;
+ private AuthScope authscope;
+
+ TestCredentialsProvider(final Credentials creds) {
+ super();
+ this.creds = creds;
+ }
+
+ public void clear() {
+ }
+
+ public Credentials getCredentials(AuthScope authscope) {
+ this.authscope = authscope;
+ return this.creds;
+ }
+
+ public void setCredentials(AuthScope authscope, Credentials credentials) {
+ }
+
+ public AuthScope getAuthScope() {
+ return this.authscope;
+ }
+
+ }
+
+ @Test
+ public void testBasicAuthenticationSuccess() throws Exception {
+ this.localServer.register("*", new AuthHandler());
+ this.localServer.start();
+
+ TestCredentialsProvider credsProvider = new TestCredentialsProvider(
+ new UsernamePasswordCredentials("test", "test"));
+
+ this.httpclient.setCredentialsProvider(credsProvider);
+
+ HttpContext context = new BasicHttpContext();
+ for (int i = 0; i < 10; i++) {
+ HttpGet httpget = new HttpGet("/");
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
+ HttpEntity entity = response.getEntity();
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+ Assert.assertNotNull(entity);
+ EntityUtils.consume(entity);
+ }
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestContentCodings.java b/httpclient/src/test/java/org/apache/http/impl/client/TestContentCodings.java
index dff256e..f04393d 100644
--- a/httpclient/src/test/java/org/apache/http/impl/client/TestContentCodings.java
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestContentCodings.java
@@ -49,13 +49,12 @@ import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.DeflateDecompressingEntity;
import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.protocol.RequestAcceptEncoding;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.localserver.ServerTestBase;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
@@ -92,7 +91,7 @@ public class TestContentCodings extends ServerTestBase {
}
});
- DefaultHttpClient client = createHttpClient();
+ HttpClient client = new DecompressingHttpClient(new DefaultHttpClient());
HttpGet request = new HttpGet("/some-resource");
HttpResponse response = client.execute(getServerHttp(), request);
@@ -115,7 +114,7 @@ public class TestContentCodings extends ServerTestBase {
this.localServer.register("*", createDeflateEncodingRequestHandler(entityText, false));
- DefaultHttpClient client = createHttpClient();
+ HttpClient client = new DecompressingHttpClient(new DefaultHttpClient());
HttpGet request = new HttpGet("/some-resource");
HttpResponse response = client.execute(getServerHttp(), request);
@@ -138,7 +137,8 @@ public class TestContentCodings extends ServerTestBase {
this.localServer.register("*", createDeflateEncodingRequestHandler(entityText, true));
- DefaultHttpClient client = createHttpClient();
+ HttpClient client = new DecompressingHttpClient(new DefaultHttpClient());
+
HttpGet request = new HttpGet("/some-resource");
HttpResponse response = client.execute(getServerHttp(), request);
Assert.assertEquals("The entity text is correctly transported", entityText,
@@ -158,7 +158,8 @@ public class TestContentCodings extends ServerTestBase {
this.localServer.register("*", createGzipEncodingRequestHandler(entityText));
- DefaultHttpClient client = createHttpClient();
+ HttpClient client = new DecompressingHttpClient(new DefaultHttpClient());
+
HttpGet request = new HttpGet("/some-resource");
HttpResponse response = client.execute(getServerHttp(), request);
Assert.assertEquals("The entity text is correctly transported", entityText,
@@ -188,7 +189,7 @@ public class TestContentCodings extends ServerTestBase {
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
- ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry);
+ PoolingClientConnectionManager cm = new PoolingClientConnectionManager(schemeRegistry);
cm.setMaxTotal(clients);
final HttpClient httpClient = new DefaultHttpClient(cm);
@@ -224,48 +225,6 @@ public class TestContentCodings extends ServerTestBase {
}
/**
- * Checks that we can turn off the new Content-Coding support. The default is that it's on, but that is a change
- * to existing behaviour and might not be desirable in some situations.
- *
- * @throws Exception
- */
- @Test
- public void testCanBeDisabledAtRequestTime() throws Exception {
- final String entityText = "Hello, this is some plain text coming back.";
-
- /* Assume that server will see an Accept-Encoding header. */
- final boolean [] sawAcceptEncodingHeader = { true };
-
- this.localServer.register("*", new HttpRequestHandler() {
-
- /**
- * {@inheritDoc}
- */
- public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
- response.setEntity(new StringEntity(entityText));
- response.addHeader("Content-Type", "text/plain");
- Header[] acceptEncodings = request.getHeaders("Accept-Encoding");
-
- sawAcceptEncodingHeader[0] = acceptEncodings.length > 0;
- }
-
- });
-
- AbstractHttpClient client = createHttpClient();
- HttpGet request = new HttpGet("/some-resource");
-
- client.removeRequestInterceptorByClass(RequestAcceptEncoding.class);
-
- HttpResponse response = client.execute(getServerHttp(), request);
-
- Assert.assertFalse("The Accept-Encoding header was not there", sawAcceptEncodingHeader[0]);
- Assert.assertEquals("The entity isn't treated as gzip or zip content", entityText,
- EntityUtils.toString(response.getEntity()));
-
- client.getConnectionManager().shutdown();
- }
-
- /**
* Test that the returned {@link HttpEntity} in the response correctly overrides
* {@link HttpEntity#writeTo(OutputStream)} for gzip-encoding.
*
@@ -277,7 +236,7 @@ public class TestContentCodings extends ServerTestBase {
this.localServer.register("*", createGzipEncodingRequestHandler(entityText));
- DefaultHttpClient client = createHttpClient();
+ HttpClient client = new DecompressingHttpClient(new DefaultHttpClient());
HttpGet request = new HttpGet("/some-resource");
HttpResponse response = client.execute(getServerHttp(), request);
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -301,7 +260,8 @@ public class TestContentCodings extends ServerTestBase {
this.localServer.register("*", createDeflateEncodingRequestHandler(entityText, true));
- DefaultHttpClient client = createHttpClient();
+ HttpClient client = new DecompressingHttpClient(new DefaultHttpClient());
+
HttpGet request = new HttpGet("/some-resource");
HttpResponse response = client.execute(getServerHttp(), request);
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -313,6 +273,36 @@ public class TestContentCodings extends ServerTestBase {
client.getConnectionManager().shutdown();
}
+ @Test
+ public void gzipResponsesWorkWithBasicResponseHandler() throws Exception {
+ final String entityText = "Hello, this is some plain text coming back.";
+
+ this.localServer.register("*", createGzipEncodingRequestHandler(entityText));
+
+ HttpClient client = new DecompressingHttpClient(new DefaultHttpClient());
+
+ HttpGet request = new HttpGet("/some-resource");
+ String response = client.execute(getServerHttp(), request, new BasicResponseHandler());
+ Assert.assertEquals("The entity text is correctly transported", entityText, response);
+
+ client.getConnectionManager().shutdown();
+ }
+
+ @Test
+ public void deflateResponsesWorkWithBasicResponseHandler() throws Exception {
+ final String entityText = "Hello, this is some plain text coming back.";
+
+ this.localServer.register("*", createDeflateEncodingRequestHandler(entityText, false));
+
+ HttpClient client = new DecompressingHttpClient(new DefaultHttpClient());
+
+ HttpGet request = new HttpGet("/some-resource");
+ String response = client.execute(getServerHttp(), request, new BasicResponseHandler());
+ Assert.assertEquals("The entity text is correctly transported", entityText, response);
+
+ client.getConnectionManager().shutdown();
+ }
+
/**
* Creates a new {@link HttpRequestHandler} that will attempt to provide a deflate stream
* Content-Coding.
@@ -429,10 +419,6 @@ public class TestContentCodings extends ServerTestBase {
};
}
- private DefaultHttpClient createHttpClient() {
- return new ContentEncodingHttpClient();
- }
-
/**
* Sub-ordinate task passed off to a different thread to be executed.
*
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestDecompressingHttpClient.java b/httpclient/src/test/java/org/apache/http/impl/client/TestDecompressingHttpClient.java
new file mode 100644
index 0000000..efb8334
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestDecompressingHttpClient.java
@@ -0,0 +1,364 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+package org.apache.http.impl.client;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+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.HttpVersion;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+ at RunWith(MockitoJUnitRunner.class)
+public class TestDecompressingHttpClient {
+
+ private DummyHttpClient backend;
+ @Mock private ClientConnectionManager mockConnManager;
+ @Mock private ResponseHandler<Object> mockHandler;
+ private DecompressingHttpClient impl;
+ private HttpUriRequest request;
+ private HttpContext ctx;
+ private HttpHost host;
+ @Mock private HttpResponse mockResponse;
+ @Mock private HttpEntity mockEntity;
+ private Object handled;
+
+ @Before
+ public void canCreate() {
+ handled = new Object();
+ backend = new DummyHttpClient();
+ impl = new DecompressingHttpClient(backend);
+ request = new HttpGet("http://localhost:8080");
+ ctx = new BasicHttpContext();
+ host = new HttpHost("www.example.com");
+ }
+
+ @Test
+ public void isAnHttpClient() {
+ assertTrue(impl instanceof HttpClient);
+ }
+
+ @Test
+ public void usesParamsFromBackend() {
+ HttpParams params = new BasicHttpParams();
+ backend.setParams(params);
+ assertSame(params, impl.getParams());
+ }
+
+ @Test
+ public void extractsHostNameFromUriRequest() {
+ assertEquals(new HttpHost("www.example.com"),
+ impl.getHttpHost(new HttpGet("http://www.example.com/")));
+ }
+
+ @Test
+ public void extractsHostNameAndPortFromUriRequest() {
+ assertEquals(new HttpHost("www.example.com", 8080),
+ impl.getHttpHost(new HttpGet("http://www.example.com:8080/")));
+ }
+
+ @Test
+ public void extractsIPAddressFromUriRequest() {
+ assertEquals(new HttpHost("10.0.0.1"),
+ impl.getHttpHost(new HttpGet("http://10.0.0.1/")));
+ }
+
+ @Test
+ public void extractsIPAddressAndPortFromUriRequest() {
+ assertEquals(new HttpHost("10.0.0.1", 8080),
+ impl.getHttpHost(new HttpGet("http://10.0.0.1:8080/")));
+ }
+
+ @Test
+ public void extractsLocalhostFromUriRequest() {
+ assertEquals(new HttpHost("localhost"),
+ impl.getHttpHost(new HttpGet("http://localhost/")));
+ }
+
+ @Test
+ public void extractsLocalhostAndPortFromUriRequest() {
+ assertEquals(new HttpHost("localhost", 8080),
+ impl.getHttpHost(new HttpGet("http://localhost:8080/")));
+ }
+
+ @Test
+ public void usesConnectionManagerFromBackend() {
+ backend.setConnectionManager(mockConnManager);
+ assertSame(mockConnManager, impl.getConnectionManager());
+ }
+
+ private void assertAcceptEncodingGzipAndDeflateWereAddedToRequest(HttpRequest captured) {
+ boolean foundGzip = false;
+ boolean foundDeflate = false;
+ for(Header h : captured.getHeaders("Accept-Encoding")) {
+ for(HeaderElement elt : h.getElements()) {
+ if ("gzip".equals(elt.getName())) foundGzip = true;
+ if ("deflate".equals(elt.getName())) foundDeflate = true;
+ }
+ }
+ assertTrue(foundGzip);
+ assertTrue(foundDeflate);
+ }
+
+ @Test
+ public void addsAcceptEncodingHeaderToHttpUriRequest() throws Exception {
+ impl.execute(request);
+ assertAcceptEncodingGzipAndDeflateWereAddedToRequest(backend.getCapturedRequest());
+ }
+
+ @Test
+ public void addsAcceptEncodingHeaderToHttpUriRequestWithContext() throws Exception {
+ impl.execute(request, ctx);
+ assertAcceptEncodingGzipAndDeflateWereAddedToRequest(backend.getCapturedRequest());
+ }
+
+ @Test
+ public void addsAcceptEncodingHeaderToHostAndHttpRequest() throws Exception {
+ impl.execute(host, request);
+ assertAcceptEncodingGzipAndDeflateWereAddedToRequest(backend.getCapturedRequest());
+ }
+
+ @Test
+ public void addsAcceptEncodingHeaderToHostAndHttpRequestWithContext() throws Exception {
+ impl.execute(host, request, ctx);
+ assertAcceptEncodingGzipAndDeflateWereAddedToRequest(backend.getCapturedRequest());
+ }
+
+ @Test
+ public void addsAcceptEncodingHeaderToUriRequestWithHandler() throws Exception {
+ when(mockHandler.handleResponse(isA(HttpResponse.class))).thenReturn(new Object());
+ impl.execute(request, mockHandler);
+ assertAcceptEncodingGzipAndDeflateWereAddedToRequest(backend.getCapturedRequest());
+ }
+
+ @Test
+ public void addsAcceptEncodingHeaderToUriRequestWithHandlerAndContext() throws Exception {
+ when(mockHandler.handleResponse(isA(HttpResponse.class))).thenReturn(new Object());
+ impl.execute(request, mockHandler, ctx);
+ assertAcceptEncodingGzipAndDeflateWereAddedToRequest(backend.getCapturedRequest());
+ }
+
+ @Test
+ public void addsAcceptEncodingHeaderToRequestWithHostAndHandler() throws Exception {
+ when(mockHandler.handleResponse(isA(HttpResponse.class))).thenReturn(new Object());
+ impl.execute(host, request, mockHandler);
+ assertAcceptEncodingGzipAndDeflateWereAddedToRequest(backend.getCapturedRequest());
+ }
+
+ @Test
+ public void addsAcceptEncodingHeaderToRequestWithHostAndContextAndHandler() throws Exception {
+ when(mockHandler.handleResponse(isA(HttpResponse.class))).thenReturn(new Object());
+ impl.execute(host, request, mockHandler, ctx);
+ assertAcceptEncodingGzipAndDeflateWereAddedToRequest(backend.getCapturedRequest());
+ }
+
+ private void mockResponseHasNoContentEncodingHeaders() {
+ backend.setResponse(mockResponse);
+ when(mockResponse.getAllHeaders()).thenReturn(new Header[]{});
+ when(mockResponse.getHeaders("Content-Encoding")).thenReturn(new Header[]{});
+ when(mockResponse.getFirstHeader("Content-Encoding")).thenReturn(null);
+ when(mockResponse.getLastHeader("Content-Encoding")).thenReturn(null);
+ when(mockResponse.getEntity()).thenReturn(mockEntity);
+ }
+
+ @Test
+ public void doesNotModifyResponseBodyIfNoContentEncoding() throws Exception {
+ mockResponseHasNoContentEncodingHeaders();
+ assertSame(mockResponse, impl.execute(request));
+ verify(mockResponse, never()).setEntity(any(HttpEntity.class));
+ }
+
+ @Test
+ public void doesNotModifyResponseBodyIfNoContentEncodingWithContext() throws Exception {
+ mockResponseHasNoContentEncodingHeaders();
+ assertSame(mockResponse, impl.execute(request, ctx));
+ verify(mockResponse, never()).setEntity(any(HttpEntity.class));
+ }
+
+ @Test
+ public void doesNotModifyResponseBodyIfNoContentEncodingForHostRequest() throws Exception {
+ mockResponseHasNoContentEncodingHeaders();
+ assertSame(mockResponse, impl.execute(host, request));
+ verify(mockResponse, never()).setEntity(any(HttpEntity.class));
+ }
+
+ @Test
+ public void doesNotModifyResponseBodyIfNoContentEncodingForHostRequestWithContext() throws Exception {
+ mockResponseHasNoContentEncodingHeaders();
+ assertSame(mockResponse, impl.execute(host, request, ctx));
+ verify(mockResponse, never()).setEntity(any(HttpEntity.class));
+ }
+
+ @Test
+ public void doesNotModifyResponseBodyWithHandlerIfNoContentEncoding() throws Exception {
+ mockResponseHasNoContentEncodingHeaders();
+ when(mockHandler.handleResponse(mockResponse)).thenReturn(handled);
+ assertSame(handled, impl.execute(request, mockHandler));
+ verify(mockResponse, never()).setEntity(any(HttpEntity.class));
+ }
+
+ @Test
+ public void doesNotModifyResponseBodyWithHandlerAndContextIfNoContentEncoding() throws Exception {
+ mockResponseHasNoContentEncodingHeaders();
+ when(mockHandler.handleResponse(mockResponse)).thenReturn(handled);
+ assertSame(handled, impl.execute(request, mockHandler, ctx));
+ verify(mockResponse, never()).setEntity(any(HttpEntity.class));
+ }
+
+ @Test
+ public void doesNotModifyResponseBodyWithHostAndHandlerIfNoContentEncoding() throws Exception {
+ mockResponseHasNoContentEncodingHeaders();
+ when(mockHandler.handleResponse(mockResponse)).thenReturn(handled);
+ assertSame(handled, impl.execute(host, request, mockHandler));
+ verify(mockResponse, never()).setEntity(any(HttpEntity.class));
+ }
+
+ @Test
+ public void doesNotModifyResponseBodyWithHostAndHandlerAndContextIfNoContentEncoding() throws Exception {
+ mockResponseHasNoContentEncodingHeaders();
+ when(mockHandler.handleResponse(mockResponse)).thenReturn(handled);
+ assertSame(handled, impl.execute(host, request, mockHandler, ctx));
+ verify(mockResponse, never()).setEntity(any(HttpEntity.class));
+ }
+
+ @Test
+ public void successfullyUncompressesContent() throws Exception {
+ final String plainText = "hello\n";
+ HttpResponse response = getGzippedResponse(plainText);
+ backend.setResponse(response);
+
+ HttpResponse result = impl.execute(request);
+ ByteArrayOutputStream resultBuf = new ByteArrayOutputStream();
+ InputStream is = result.getEntity().getContent();
+ int b;
+ while((b = is.read()) != -1) {
+ resultBuf.write(b);
+ }
+ is.close();
+ assertEquals(plainText, new String(resultBuf.toByteArray()));
+ }
+
+ @Test
+ public void uncompressedResponseHasUnknownLength() throws Exception {
+ final String plainText = "hello\n";
+ HttpResponse response = getGzippedResponse(plainText);
+ backend.setResponse(response);
+
+ HttpResponse result = impl.execute(request);
+ HttpEntity entity = result.getEntity();
+ assertEquals(-1, entity.getContentLength());
+ EntityUtils.consume(entity);
+ assertNull(result.getFirstHeader("Content-Length"));
+ }
+
+ @Test
+ public void uncompressedResponseIsNotEncoded() throws Exception {
+ final String plainText = "hello\n";
+ HttpResponse response = getGzippedResponse(plainText);
+ backend.setResponse(response);
+
+ HttpResponse result = impl.execute(request);
+ assertNull(result.getFirstHeader("Content-Encoding"));
+ }
+
+ @Test
+ public void uncompressedResponseHasContentMD5Removed() throws Exception {
+ final String plainText = "hello\n";
+ HttpResponse response = getGzippedResponse(plainText);
+ response.setHeader("Content-MD5","a checksum");
+ backend.setResponse(response);
+
+ HttpResponse result = impl.execute(request);
+ assertNull(result.getFirstHeader("Content-MD5"));
+ }
+
+ @Test
+ public void unencodedResponseRetainsContentMD5() throws Exception {
+ final String plainText = "hello\n";
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
+ response.setHeader("Content-MD5","a checksum");
+ response.setEntity(new ByteArrayEntity(plainText.getBytes()));
+ backend.setResponse(response);
+
+ HttpResponse result = impl.execute(request);
+ assertNotNull(result.getFirstHeader("Content-MD5"));
+ }
+
+ @Test
+ public void passesThroughTheBodyOfAPOST() throws Exception {
+ when(mockHandler.handleResponse(isA(HttpResponse.class))).thenReturn(new Object());
+ HttpPost post = new HttpPost("http://localhost:8080/");
+ post.setEntity(new ByteArrayEntity("hello".getBytes()));
+ impl.execute(host, post, mockHandler, ctx);
+ assertNotNull(((HttpEntityEnclosingRequest)backend.getCapturedRequest()).getEntity());
+ }
+
+ private HttpResponse getGzippedResponse(final String plainText)
+ throws IOException {
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
+ response.setHeader("Content-Encoding","gzip");
+ response.setHeader("Content-Type","text/plain");
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ GZIPOutputStream gos = new GZIPOutputStream(buf);
+ gos.write(plainText.getBytes());
+ gos.close();
+ ByteArrayEntity body = new ByteArrayEntity(buf.toByteArray());
+ body.setContentEncoding("gzip");
+ body.setContentType("text/plain");
+ response.setHeader("Content-Length", "" + (int)body.getContentLength());
+ response.setEntity(body);
+ return response;
+ }
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultBackoffStrategy.java b/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultBackoffStrategy.java
new file mode 100644
index 0000000..015e8e2
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultBackoffStrategy.java
@@ -0,0 +1,88 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client;
+
+import static org.junit.Assert.*;
+
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.ConnectionBackoffStrategy;
+import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.message.BasicHttpResponse;
+import org.junit.Before;
+import org.junit.Test;
+
+
+public class TestDefaultBackoffStrategy {
+
+ private DefaultBackoffStrategy impl;
+
+ @Before
+ public void setUp() {
+ impl = new DefaultBackoffStrategy();
+ }
+
+ @Test
+ public void isABackoffStrategy() {
+ assertTrue(impl instanceof ConnectionBackoffStrategy);
+ }
+
+ @Test
+ public void backsOffForSocketTimeouts() {
+ assertTrue(impl.shouldBackoff(new SocketTimeoutException()));
+ }
+
+ @Test
+ public void backsOffForConnectionTimeouts() {
+ assertTrue(impl.shouldBackoff(new ConnectException()));
+ }
+
+ @Test
+ public void doesNotBackOffForConnectionManagerTimeout() {
+ assertFalse(impl.shouldBackoff(new ConnectionPoolTimeoutException()));
+ }
+
+ @Test
+ public void backsOffForServiceUnavailable() {
+ HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_SERVICE_UNAVAILABLE, "Service Unavailable");
+ assertTrue(impl.shouldBackoff(resp));
+ }
+
+ @Test
+ public void doesNotBackOffForNon503StatusCodes() {
+ for(int i = 100; i <= 599; i++) {
+ if (i == HttpStatus.SC_SERVICE_UNAVAILABLE) continue;
+ HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ i, "Foo");
+ assertFalse(impl.shouldBackoff(resp));
+ }
+ }
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultClientRequestDirector.java b/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultClientRequestDirector.java
index 149a859..22340a9 100644
--- a/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultClientRequestDirector.java
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultClientRequestDirector.java
@@ -27,10 +27,6 @@ package org.apache.http.impl.client;
import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.net.ConnectException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
import org.apache.http.Header;
import org.apache.http.HttpClientConnection;
@@ -40,34 +36,16 @@ import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
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.HttpRequestRetryHandler;
import org.apache.http.client.NonRepeatableRequestException;
-import org.apache.http.client.methods.AbortableHttpRequest;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.ClientPNames;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.ClientConnectionRequest;
-import org.apache.http.conn.ConnectionPoolTimeoutException;
-import org.apache.http.conn.ConnectionReleaseTrigger;
-import org.apache.http.conn.ManagedClientConnection;
-import org.apache.http.conn.routing.HttpRoute;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.conn.ClientConnAdapterMockup;
-import org.apache.http.impl.conn.SingleClientConnManager;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.localserver.BasicServerTestBase;
import org.apache.http.localserver.LocalTestServer;
-import org.apache.http.message.BasicHeader;
-import org.apache.http.mockup.SocketFactoryMockup;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
@@ -85,530 +63,151 @@ public class TestDefaultClientRequestDirector extends BasicServerTestBase {
@Before
public void setUp() throws Exception {
- localServer = new LocalTestServer(null, null);
- localServer.registerDefaultHandlers();
- localServer.start();
+ this.localServer = new LocalTestServer(null, null);
+ this.localServer.registerDefaultHandlers();
+ this.localServer.start();
+ this.httpclient = new DefaultHttpClient();
}
- /**
- * Tests that if abort is called on an {@link AbortableHttpRequest} while
- * {@link DefaultRequestDirector} is allocating a connection, that the
- * connection is properly aborted.
- */
- @Test
- public void testAbortInAllocate() throws Exception {
- CountDownLatch connLatch = new CountDownLatch(1);
- CountDownLatch awaitLatch = new CountDownLatch(1);
- final ConMan conMan = new ConMan(connLatch, awaitLatch);
- final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
- final CountDownLatch getLatch = new CountDownLatch(1);
- final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams());
- final HttpContext context = new BasicHttpContext();
- final HttpGet httpget = new HttpGet("http://www.example.com/a");
-
- new Thread(new Runnable() {
- public void run() {
- try {
- client.execute(httpget, context);
- } catch(Throwable t) {
- throwableRef.set(t);
- } finally {
- getLatch.countDown();
- }
- }
- }).start();
-
- Assert.assertTrue("should have tried to get a connection", connLatch.await(1, TimeUnit.SECONDS));
-
- httpget.abort();
-
- Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
- Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
- throwableRef.get() instanceof IOException);
- Assert.assertTrue("cause should be InterruptedException, was: " + throwableRef.get().getCause(),
- throwableRef.get().getCause() instanceof InterruptedException);
- }
-
- /**
- * Tests that an abort called after the connection has been retrieved
- * but before a release trigger is set does still abort the request.
- */
- @Test
- public void testAbortAfterAllocateBeforeRequest() throws Exception {
- this.localServer.register("*", new BasicService());
-
- CountDownLatch releaseLatch = new CountDownLatch(1);
- SchemeRegistry registry = new SchemeRegistry();
- registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
-
- SingleClientConnManager conMan = new SingleClientConnManager(registry);
- final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
- final CountDownLatch getLatch = new CountDownLatch(1);
- final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams());
- final HttpContext context = new BasicHttpContext();
- final HttpGet httpget = new CustomGet("a", releaseLatch);
-
- new Thread(new Runnable() {
- public void run() {
- try {
- client.execute(getServerHttp(), httpget, context);
- } catch(Throwable t) {
- throwableRef.set(t);
- } finally {
- getLatch.countDown();
- }
- }
- }).start();
-
- Thread.sleep(100); // Give it a little time to proceed to release...
-
- httpget.abort();
-
- releaseLatch.countDown();
-
- Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
- Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
- throwableRef.get() instanceof IOException);
- }
-
- /**
- * Tests that an abort called completely before execute
- * still aborts the request.
- */
- @Test
- public void testAbortBeforeExecute() throws Exception {
- this.localServer.register("*", new BasicService());
-
- SchemeRegistry registry = new SchemeRegistry();
- registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
-
- SingleClientConnManager conMan = new SingleClientConnManager(registry);
- final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
- final CountDownLatch getLatch = new CountDownLatch(1);
- final CountDownLatch startLatch = new CountDownLatch(1);
- final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams());
- final HttpContext context = new BasicHttpContext();
- final HttpGet httpget = new HttpGet("a");
-
- new Thread(new Runnable() {
- public void run() {
- try {
- try {
- if(!startLatch.await(1, TimeUnit.SECONDS))
- throw new RuntimeException("Took too long to start!");
- } catch(InterruptedException interrupted) {
- throw new RuntimeException("Never started!", interrupted);
- }
- client.execute(getServerHttp(), httpget, context);
- } catch(Throwable t) {
- throwableRef.set(t);
- } finally {
- getLatch.countDown();
- }
- }
- }).start();
-
- httpget.abort();
- startLatch.countDown();
-
- Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
- Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
- throwableRef.get() instanceof IOException);
- }
-
- /**
- * Tests that an abort called after a redirect has found a new host
- * still aborts in the correct place (while trying to get the new
- * host's route, not while doing the subsequent request).
- */
- @Test
- public void testAbortAfterRedirectedRoute() throws Exception {
- final int port = this.localServer.getServiceAddress().getPort();
- this.localServer.register("*", new BasicRedirectService(port));
-
- SchemeRegistry registry = new SchemeRegistry();
- registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
-
- CountDownLatch connLatch = new CountDownLatch(1);
- CountDownLatch awaitLatch = new CountDownLatch(1);
- ConnMan4 conMan = new ConnMan4(registry, connLatch, awaitLatch);
- final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
- final CountDownLatch getLatch = new CountDownLatch(1);
- final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams());
- final HttpContext context = new BasicHttpContext();
- final HttpGet httpget = new HttpGet("a");
-
- new Thread(new Runnable() {
- public void run() {
- try {
- HttpHost host = new HttpHost("127.0.0.1", port);
- client.execute(host, httpget, context);
- } catch(Throwable t) {
- throwableRef.set(t);
- } finally {
- getLatch.countDown();
- }
- }
- }).start();
-
- Assert.assertTrue("should have tried to get a connection", connLatch.await(1, TimeUnit.SECONDS));
-
- httpget.abort();
-
- Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
- Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
- throwableRef.get() instanceof IOException);
- Assert.assertTrue("cause should be InterruptedException, was: " + throwableRef.get().getCause(),
- throwableRef.get().getCause() instanceof InterruptedException);
- }
-
-
- /**
- * Tests that if a socket fails to connect, the allocated connection is
- * properly released back to the connection manager.
- */
- @Test
- public void testSocketConnectFailureReleasesConnection() throws Exception {
- final ConnMan2 conMan = new ConnMan2();
- final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams());
- final HttpContext context = new BasicHttpContext();
- final HttpGet httpget = new HttpGet("http://www.example.com/a");
-
- try {
- client.execute(httpget, context);
- Assert.fail("expected IOException");
- } catch(IOException expected) {}
-
- Assert.assertNotNull(conMan.allocatedConnection);
- Assert.assertSame(conMan.allocatedConnection, conMan.releasedConnection);
- }
-
- @Test
- public void testRequestFailureReleasesConnection() throws Exception {
- this.localServer.register("*", new ThrowingService());
-
- SchemeRegistry registry = new SchemeRegistry();
- registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
-
- ConnMan3 conMan = new ConnMan3(registry);
- DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams());
- HttpGet httpget = new HttpGet("/a");
-
- try {
- client.execute(getServerHttp(), httpget);
- Assert.fail("expected IOException");
- } catch (IOException expected) {}
+ private static class SimpleService implements HttpRequestHandler {
- Assert.assertNotNull(conMan.allocatedConnection);
- Assert.assertSame(conMan.allocatedConnection, conMan.releasedConnection);
- }
+ public SimpleService() {
+ super();
+ }
- private static class ThrowingService implements HttpRequestHandler {
public void handle(
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws HttpException, IOException {
- throw new IOException();
- }
- }
-
- private static class BasicService implements HttpRequestHandler {
- public void handle(final HttpRequest request,
- final HttpResponse response,
- final HttpContext context) throws HttpException, IOException {
- response.setStatusCode(200);
- response.setEntity(new StringEntity("Hello World"));
- }
- }
-
- private static class BasicRedirectService implements HttpRequestHandler {
- private int statuscode = HttpStatus.SC_SEE_OTHER;
- private int port;
-
- public BasicRedirectService(int port) {
- this.port = port;
- }
-
- public void handle(final HttpRequest request,
- final HttpResponse response, final HttpContext context)
- throws HttpException, IOException {
- ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
- response.setStatusLine(ver, this.statuscode);
- response.addHeader(new BasicHeader("Location", "http://localhost:"
- + this.port + "/newlocation/"));
- response.addHeader(new BasicHeader("Connection", "close"));
- }
- }
-
- private static class ConnMan4 extends ThreadSafeClientConnManager {
- private final CountDownLatch connLatch;
- private final CountDownLatch awaitLatch;
-
- public ConnMan4(SchemeRegistry schreg,
- CountDownLatch connLatch, CountDownLatch awaitLatch) {
- super(schreg);
- this.connLatch = connLatch;
- this.awaitLatch = awaitLatch;
- }
-
- @Override
- public ClientConnectionRequest requestConnection(HttpRoute route, Object state) {
- // If this is the redirect route, stub the return value
- // so-as to pretend the host is waiting on a slot...
- if(route.getTargetHost().getHostName().equals("localhost")) {
- final Thread currentThread = Thread.currentThread();
-
- return new ClientConnectionRequest() {
-
- public void abortRequest() {
- currentThread.interrupt();
- }
-
- public ManagedClientConnection getConnection(
- long timeout, TimeUnit tunit)
- throws InterruptedException,
- ConnectionPoolTimeoutException {
- connLatch.countDown(); // notify waiter that we're getting a connection
-
- // zero usually means sleep forever, but CountDownLatch doesn't interpret it that way.
- if(timeout == 0)
- timeout = Integer.MAX_VALUE;
-
- if(!awaitLatch.await(timeout, tunit))
- throw new ConnectionPoolTimeoutException();
-
- return new ClientConnAdapterMockup(ConnMan4.this);
- }
- };
- } else {
- return super.requestConnection(route, state);
- }
+ response.setStatusCode(HttpStatus.SC_OK);
+ StringEntity entity = new StringEntity("Whatever");
+ response.setEntity(entity);
}
}
+ @Test
+ public void testDefaultHostAtClientLevel() throws Exception {
+ int port = this.localServer.getServiceAddress().getPort();
+ this.localServer.register("*", new SimpleService());
- private static class ConnMan3 extends SingleClientConnManager {
- private ManagedClientConnection allocatedConnection;
- private ManagedClientConnection releasedConnection;
-
- public ConnMan3(SchemeRegistry schreg) {
- super(schreg);
- }
-
- @Override
- public ManagedClientConnection getConnection(HttpRoute route, Object state) {
- allocatedConnection = super.getConnection(route, state);
- return allocatedConnection;
- }
+ HttpHost target = new HttpHost("localhost", port);
- @Override
- public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) {
- releasedConnection = conn;
- super.releaseConnection(conn, validDuration, timeUnit);
- }
+ this.httpclient.getParams().setParameter(ClientPNames.DEFAULT_HOST, target);
+ String s = "/path";
+ HttpGet httpget = new HttpGet(s);
+ HttpResponse response = this.httpclient.execute(httpget);
+ EntityUtils.consume(response.getEntity());
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
}
- static class ConnMan2 implements ClientConnectionManager {
-
- private ManagedClientConnection allocatedConnection;
- private ManagedClientConnection releasedConnection;
-
- public ConnMan2() {
- }
-
- public void closeIdleConnections(long idletime, TimeUnit tunit) {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public void closeExpiredConnections() {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public ManagedClientConnection getConnection(HttpRoute route) {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public ManagedClientConnection getConnection(HttpRoute route,
- long timeout, TimeUnit tunit) {
- throw new UnsupportedOperationException("just a mockup");
- }
+ @Test
+ public void testDefaultHostHeader() throws Exception {
+ int port = this.localServer.getServiceAddress().getPort();
+ String hostname = getServerHttp().getHostName();
+ this.localServer.register("*", new SimpleService());
- public ClientConnectionRequest requestConnection(
- final HttpRoute route,
- final Object state) {
-
- return new ClientConnectionRequest() {
-
- public void abortRequest() {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public ManagedClientConnection getConnection(
- long timeout, TimeUnit unit)
- throws InterruptedException,
- ConnectionPoolTimeoutException {
- allocatedConnection = new ClientConnAdapterMockup(ConnMan2.this) {
- @Override
- public void open(HttpRoute route, HttpContext context,
- HttpParams params) throws IOException {
- throw new ConnectException();
- }
- };
- return allocatedConnection;
- }
- };
- }
+ HttpContext context = new BasicHttpContext();
- public HttpParams getParams() {
- throw new UnsupportedOperationException("just a mockup");
- }
+ String s = "http://localhost:" + port;
+ HttpGet httpget = new HttpGet(s);
- public SchemeRegistry getSchemeRegistry() {
- SchemeRegistry registry = new SchemeRegistry();
- registry.register(new Scheme("http", 80, new SocketFactoryMockup(null)));
- return registry;
- }
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
+ EntityUtils.consume(response.getEntity());
- public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) {
- this.releasedConnection = conn;
- }
+ HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
+ ExecutionContext.HTTP_REQUEST);
- public void shutdown() {
- throw new UnsupportedOperationException("just a mockup");
- }
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+ // Check that Host header is generated as expected
+ Header[] headers = reqWrapper.getHeaders("host");
+ Assert.assertNotNull(headers);
+ Assert.assertEquals(1, headers.length);
+ Assert.assertEquals(hostname + ":" + port, headers[0].getValue());
}
- static class ConMan implements ClientConnectionManager {
- private final CountDownLatch connLatch;
- private final CountDownLatch awaitLatch;
-
- public ConMan(CountDownLatch connLatch, CountDownLatch awaitLatch) {
- this.connLatch = connLatch;
- this.awaitLatch = awaitLatch;
- }
-
- public void closeIdleConnections(long idletime, TimeUnit tunit) {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public void closeExpiredConnections() {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public ManagedClientConnection getConnection(HttpRoute route) {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public ManagedClientConnection getConnection(HttpRoute route,
- long timeout, TimeUnit tunit) {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public ClientConnectionRequest requestConnection(
- final HttpRoute route,
- final Object state) {
-
- final Thread currentThread = Thread.currentThread();
-
- return new ClientConnectionRequest() {
-
- public void abortRequest() {
- currentThread.interrupt();
- }
-
- public ManagedClientConnection getConnection(
- long timeout, TimeUnit tunit)
- throws InterruptedException,
- ConnectionPoolTimeoutException {
- connLatch.countDown(); // notify waiter that we're getting a connection
-
- // zero usually means sleep forever, but CountDownLatch doesn't interpret it that way.
- if(timeout == 0)
- timeout = Integer.MAX_VALUE;
-
- if(!awaitLatch.await(timeout, tunit))
- throw new ConnectionPoolTimeoutException();
+ @Test
+ // HTTPCLIENT-1092
+ public void testVirtualHostHeader() throws Exception {
+ int port = this.localServer.getServiceAddress().getPort();
+ this.localServer.register("*", new SimpleService());
- return new ClientConnAdapterMockup(ConMan.this);
- }
- };
- }
+ HttpContext context = new BasicHttpContext();
- public HttpParams getParams() {
- throw new UnsupportedOperationException("just a mockup");
- }
+ String s = "http://localhost:" + port;
+ HttpGet httpget = new HttpGet(s);
- public SchemeRegistry getSchemeRegistry() {
- SchemeRegistry registry = new SchemeRegistry();
- registry.register(new Scheme("http", 80, new SocketFactoryMockup(null)));
- return registry;
- }
+ String virtHost = "virtual";
+ httpget.getParams().setParameter(ClientPNames.VIRTUAL_HOST, new HttpHost(virtHost, port));
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
+ EntityUtils.consume(response.getEntity());
- public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) {
- throw new UnsupportedOperationException("just a mockup");
- }
+ HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
+ ExecutionContext.HTTP_REQUEST);
- public void shutdown() {
- throw new UnsupportedOperationException("just a mockup");
- }
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+ // Check that Host header is generated as expected
+ Header[] headers = reqWrapper.getHeaders("host");
+ Assert.assertNotNull(headers);
+ Assert.assertEquals(1, headers.length);
+ Assert.assertEquals(virtHost+":"+port,headers[0].getValue());
}
- private static class CustomGet extends HttpGet {
- private final CountDownLatch releaseTriggerLatch;
-
- public CustomGet(String uri, CountDownLatch releaseTriggerLatch) {
- super(uri);
- this.releaseTriggerLatch = releaseTriggerLatch;
- }
-
- @Override
- public void setReleaseTrigger(ConnectionReleaseTrigger releaseTrigger) throws IOException {
- try {
- if(!releaseTriggerLatch.await(1, TimeUnit.SECONDS))
- throw new RuntimeException("Waited too long...");
- } catch(InterruptedException ie) {
- throw new RuntimeException(ie);
- }
+ @Test
+ // Test that virtual port is propagated if provided
+ // This is not expected to be used much, if ever
+ // HTTPCLIENT-1092
+ public void testVirtualHostPortHeader() throws Exception {
+ int port = this.localServer.getServiceAddress().getPort();
+ this.localServer.register("*", new SimpleService());
- super.setReleaseTrigger(releaseTrigger);
- }
+ HttpContext context = new BasicHttpContext();
- }
+ String s = "http://localhost:" + port;
+ HttpGet httpget = new HttpGet(s);
- private static class SimpleService implements HttpRequestHandler {
+ String virtHost = "virtual";
+ int virtPort = 9876;
+ httpget.getParams().setParameter(ClientPNames.VIRTUAL_HOST, new HttpHost(virtHost, virtPort));
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
+ EntityUtils.consume(response.getEntity());
- public SimpleService() {
- super();
- }
+ HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
+ ExecutionContext.HTTP_REQUEST);
- public void handle(
- final HttpRequest request,
- final HttpResponse response,
- final HttpContext context) throws HttpException, IOException {
- response.setStatusCode(HttpStatus.SC_OK);
- StringEntity entity = new StringEntity("Whatever");
- response.setEntity(entity);
- }
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+ // Check that Host header is generated as expected
+ Header[] headers = reqWrapper.getHeaders("host");
+ Assert.assertNotNull(headers);
+ Assert.assertEquals(1, headers.length);
+ Assert.assertEquals(virtHost+":"+virtPort,headers[0].getValue());
}
@Test
- public void testDefaultHostAtClientLevel() throws Exception {
+ public void testClientLevelVirtualHostHeader() throws Exception {
int port = this.localServer.getServiceAddress().getPort();
this.localServer.register("*", new SimpleService());
- HttpHost target = new HttpHost("localhost", port);
-
- DefaultHttpClient client = new DefaultHttpClient();
- client.getParams().setParameter(ClientPNames.DEFAULT_HOST, target);
+ HttpContext context = new BasicHttpContext();
- String s = "/path";
+ String s = "http://localhost:" + port;
HttpGet httpget = new HttpGet(s);
- HttpResponse response = client.execute(httpget);
+ String virtHost = "virtual";
+ this.httpclient.getParams().setParameter(ClientPNames.VIRTUAL_HOST, new HttpHost(virtHost, port));
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
+
+ HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
+ ExecutionContext.HTTP_REQUEST);
+
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+ // Check that Host header is generated as expected
+ Header[] headers = reqWrapper.getHeaders("host");
+ Assert.assertNotNull(headers);
+ Assert.assertEquals(1, headers.length);
+ Assert.assertEquals(virtHost+":"+port,headers[0].getValue());
}
@Test
@@ -619,14 +218,13 @@ public class TestDefaultClientRequestDirector extends BasicServerTestBase {
HttpHost target1 = new HttpHost("whatever", 80);
HttpHost target2 = new HttpHost("localhost", port);
- DefaultHttpClient client = new DefaultHttpClient();
- client.getParams().setParameter(ClientPNames.DEFAULT_HOST, target1);
+ this.httpclient.getParams().setParameter(ClientPNames.DEFAULT_HOST, target1);
String s = "/path";
HttpGet httpget = new HttpGet(s);
httpget.getParams().setParameter(ClientPNames.DEFAULT_HOST, target2);
- HttpResponse response = client.execute(httpget);
+ HttpResponse response = this.httpclient.execute(httpget);
EntityUtils.consume(response.getEntity());
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultHttpRequestRetryHandler.java b/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultHttpRequestRetryHandler.java
new file mode 100644
index 0000000..9ad045e
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultHttpRequestRetryHandler.java
@@ -0,0 +1,113 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client;
+
+import org.apache.http.conn.ConnectTimeoutException;
+import java.io.IOException;
+import java.net.UnknownHostException;
+
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.ExecutionContext;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.mock;
+import org.junit.Assert;
+import org.junit.Test;
+
+
+ at SuppressWarnings("boxing") // test class
+public class TestDefaultHttpRequestRetryHandler {
+
+ @Test
+ public void noRetryOnConnectTimeout() throws Exception {
+ HttpContext context = mock(HttpContext.class);
+ HttpUriRequest request = mock(HttpUriRequest.class);
+
+ DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler();
+ Assert.assertEquals(3, retryHandler.getRetryCount());
+
+ when(request.isAborted()).thenReturn(Boolean.FALSE);
+ when(context.getAttribute(ExecutionContext.HTTP_REQUEST)).thenReturn(request);
+
+ Assert.assertFalse(retryHandler.retryRequest(new ConnectTimeoutException(), 1, context));
+ }
+
+ @Test
+ public void noRetryOnUnknownHost() throws Exception {
+ HttpContext context = mock(HttpContext.class);
+ HttpUriRequest request = mock(HttpUriRequest.class);
+
+ DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler();
+
+ when(request.isAborted()).thenReturn(Boolean.FALSE);
+ when(context.getAttribute(ExecutionContext.HTTP_REQUEST)).thenReturn(request);
+
+ Assert.assertFalse(retryHandler.retryRequest(new UnknownHostException(), 1, context));
+ }
+
+ @Test
+ public void noRetryOnAbortedRequests() throws Exception{
+ HttpContext context = mock(HttpContext.class);
+ HttpUriRequest request = mock(HttpUriRequest.class);
+
+ DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler();
+
+ when(request.isAborted()).thenReturn(Boolean.TRUE);
+ when(context.getAttribute(ExecutionContext.HTTP_REQUEST)).thenReturn(request);
+
+ Assert.assertFalse(retryHandler.retryRequest(new IOException(),3,context));
+ }
+
+ @Test
+ public void retryOnNonAbortedRequests() throws Exception{
+
+ HttpContext context = mock(HttpContext.class);
+ HttpUriRequest request = mock(HttpUriRequest.class);
+
+ DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler();
+
+ when(request.isAborted()).thenReturn(Boolean.FALSE);
+ when(context.getAttribute(ExecutionContext.HTTP_REQUEST)).thenReturn(request);
+
+ Assert.assertTrue(retryHandler.retryRequest(new IOException(),3,context));
+ }
+
+ @Test
+ public void noRetryOnConnectionTimeout() throws Exception{
+
+ HttpContext context = mock(HttpContext.class);
+ HttpUriRequest request = mock(HttpUriRequest.class);
+
+ DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler();
+
+ when(request.isAborted()).thenReturn(false);
+ when(context.getAttribute(ExecutionContext.HTTP_REQUEST)).thenReturn(request);
+
+ Assert.assertFalse(retryHandler.retryRequest(new ConnectTimeoutException(),3,context));
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultRedirectStrategy.java b/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultRedirectStrategy.java
new file mode 100644
index 0000000..63da9be
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestDefaultRedirectStrategy.java
@@ -0,0 +1,348 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+package org.apache.http.impl.client;
+
+import java.net.URI;
+import java.util.List;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.ProtocolException;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.params.ClientPNames;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestDefaultRedirectStrategy {
+
+ @Test
+ public void testIsRedirectable() {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ Assert.assertTrue(redirectStrategy.isRedirectable(HttpGet.METHOD_NAME));
+ Assert.assertTrue(redirectStrategy.isRedirectable(HttpHead.METHOD_NAME));
+ Assert.assertFalse(redirectStrategy.isRedirectable(HttpPut.METHOD_NAME));
+ Assert.assertFalse(redirectStrategy.isRedirectable(HttpPost.METHOD_NAME));
+ Assert.assertFalse(redirectStrategy.isRedirectable(HttpDelete.METHOD_NAME));
+ }
+
+ @Test
+ public void testIsRedirectedMovedTemporary() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "http://localhost/stuff");
+ Assert.assertTrue(redirectStrategy.isRedirected(httpget, response, context));
+ HttpPost httppost = new HttpPost("http://localhost/");
+ Assert.assertFalse(redirectStrategy.isRedirected(httppost, response, context));
+ }
+
+ @Test
+ public void testIsRedirectedMovedTemporaryNoLocation() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ Assert.assertFalse(redirectStrategy.isRedirected(httpget, response, context));
+ }
+
+ @Test
+ public void testIsRedirectedMovedPermanently() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_PERMANENTLY, "Redirect");
+ Assert.assertTrue(redirectStrategy.isRedirected(httpget, response, context));
+ HttpPost httppost = new HttpPost("http://localhost/");
+ Assert.assertFalse(redirectStrategy.isRedirected(httppost, response, context));
+ }
+
+ @Test
+ public void testIsRedirectedTemporaryRedirect() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_TEMPORARY_REDIRECT, "Redirect");
+ Assert.assertTrue(redirectStrategy.isRedirected(httpget, response, context));
+ HttpPost httppost = new HttpPost("http://localhost/");
+ Assert.assertFalse(redirectStrategy.isRedirected(httppost, response, context));
+ }
+
+ @Test
+ public void testIsRedirectedSeeOther() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_SEE_OTHER, "Redirect");
+ Assert.assertTrue(redirectStrategy.isRedirected(httpget, response, context));
+ HttpPost httppost = new HttpPost("http://localhost/");
+ Assert.assertTrue(redirectStrategy.isRedirected(httppost, response, context));
+ }
+
+ @Test
+ public void testIsRedirectedUnknownStatus() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 333, "Redirect");
+ Assert.assertFalse(redirectStrategy.isRedirected(httpget, response, context));
+ HttpPost httppost = new HttpPost("http://localhost/");
+ Assert.assertFalse(redirectStrategy.isRedirected(httppost, response, context));
+ }
+
+ @Test
+ public void testIsRedirectedInvalidInput() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_SEE_OTHER, "Redirect");
+ try {
+ redirectStrategy.isRedirected(null, response, context);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ redirectStrategy.isRedirected(httpget, null, context);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testGetLocationUri() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "http://localhost/stuff");
+ URI uri = redirectStrategy.getLocationURI(httpget, response, context);
+ Assert.assertEquals(URI.create("http://localhost/stuff"), uri);
+ }
+
+ @Test(expected=ProtocolException.class)
+ public void testGetLocationUriMissingHeader() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ redirectStrategy.getLocationURI(httpget, response, context);
+ }
+
+ @Test(expected=ProtocolException.class)
+ public void testGetLocationUriInvalidLocation() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "http://localhost/not valid");
+ redirectStrategy.getLocationURI(httpget, response, context);
+ }
+
+ @Test
+ public void testGetLocationUriRelative() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, new HttpHost("localhost"));
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "/stuff");
+ URI uri = redirectStrategy.getLocationURI(httpget, response, context);
+ Assert.assertEquals(URI.create("http://localhost/stuff"), uri);
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testGetLocationUriRelativeMissingTargetHost() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "/stuff");
+ URI uri = redirectStrategy.getLocationURI(httpget, response, context);
+ Assert.assertEquals(URI.create("http://localhost/stuff"), uri);
+ }
+
+ @Test
+ public void testGetLocationUriRelativeWithFragment() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, new HttpHost("localhost"));
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "/stuff#fragment");
+ URI uri = redirectStrategy.getLocationURI(httpget, response, context);
+ Assert.assertEquals(URI.create("http://localhost/stuff"), uri);
+ }
+
+ @Test
+ public void testGetLocationUriAbsoluteWithFragment() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, new HttpHost("localhost"));
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "http://localhost/stuff#fragment");
+ URI uri = redirectStrategy.getLocationURI(httpget, response, context);
+ Assert.assertEquals(URI.create("http://localhost/stuff"), uri);
+ }
+
+ @Test
+ public void testGetLocationUriNormalized() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, new HttpHost("localhost"));
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "http://localhost/././stuff/../morestuff");
+ URI uri = redirectStrategy.getLocationURI(httpget, response, context);
+ Assert.assertEquals(URI.create("http://localhost/morestuff"), uri);
+ }
+
+ @Test(expected=ProtocolException.class)
+ public void testGetLocationUriRelativeLocationNotAllowed() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, new HttpHost("localhost"));
+ HttpGet httpget = new HttpGet("http://localhost/");
+ httpget.getParams().setParameter(ClientPNames.REJECT_RELATIVE_REDIRECT, Boolean.TRUE);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "/stuff");
+ redirectStrategy.getLocationURI(httpget, response, context);
+ }
+
+ @Test
+ public void testGetLocationUriAllowCircularRedirects() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, new HttpHost("localhost"));
+ HttpGet httpget = new HttpGet("http://localhost/");
+ httpget.getParams().setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, Boolean.TRUE);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "http://localhost/stuff");
+ URI uri = URI.create("http://localhost/stuff");
+ Assert.assertEquals(uri, redirectStrategy.getLocationURI(httpget, response, context));
+ Assert.assertEquals(uri, redirectStrategy.getLocationURI(httpget, response, context));
+ Assert.assertEquals(uri, redirectStrategy.getLocationURI(httpget, response, context));
+
+ RedirectLocations redirectLocations = (RedirectLocations) context.getAttribute(
+ DefaultRedirectStrategy.REDIRECT_LOCATIONS);
+ Assert.assertNotNull(redirectLocations);
+ Assert.assertTrue(redirectLocations.contains(uri));
+ List<URI> uris = redirectLocations.getAll();
+ Assert.assertNotNull(uris);
+ Assert.assertEquals(3, uris.size());
+ Assert.assertEquals(uri, uris.get(0));
+ Assert.assertEquals(uri, uris.get(1));
+ Assert.assertEquals(uri, uris.get(2));
+ }
+
+ @Test(expected=ProtocolException.class)
+ public void testGetLocationUriDisallowCircularRedirects() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, new HttpHost("localhost"));
+ HttpGet httpget = new HttpGet("http://localhost/");
+ httpget.getParams().setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, Boolean.FALSE);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "http://localhost/stuff");
+ URI uri = URI.create("http://localhost/stuff");
+ Assert.assertEquals(uri, redirectStrategy.getLocationURI(httpget, response, context));
+ redirectStrategy.getLocationURI(httpget, response, context);
+ }
+
+ @Test
+ public void testGetLocationUriInvalidInput() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpContext context = new BasicHttpContext();
+ HttpGet httpget = new HttpGet("http://localhost/");
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_MOVED_TEMPORARILY, "Redirect");
+ response.addHeader("Location", "http://localhost/stuff");
+ try {
+ redirectStrategy.getLocationURI(null, response, context);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ redirectStrategy.getLocationURI(httpget, null, context);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ redirectStrategy.getLocationURI(httpget, response, null);
+ Assert.fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testGetRedirectRequest() throws Exception {
+ DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_SEE_OTHER, "Redirect");
+ response.addHeader("Location", "http://localhost/stuff");
+ HttpContext context1 = new BasicHttpContext();
+ HttpUriRequest redirect1 = redirectStrategy.getRedirect(
+ new HttpGet("http://localhost/"), response, context1);
+ Assert.assertEquals("GET", redirect1.getMethod());
+ HttpContext context2 = new BasicHttpContext();
+ HttpUriRequest redirect2 = redirectStrategy.getRedirect(
+ new HttpPost("http://localhost/"), response, context2);
+ Assert.assertEquals("GET", redirect2.getMethod());
+ HttpContext context3 = new BasicHttpContext();
+ HttpUriRequest redirect3 = redirectStrategy.getRedirect(
+ new HttpHead("http://localhost/"), response, context3);
+ Assert.assertEquals("HEAD", redirect3.getMethod());
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestHttpAuthenticator.java b/httpclient/src/test/java/org/apache/http/impl/client/TestHttpAuthenticator.java
new file mode 100644
index 0000000..91c5899
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestHttpAuthenticator.java
@@ -0,0 +1,353 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client;
+
+import java.util.HashMap;
+import java.util.Queue;
+
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.auth.AUTH;
+import org.apache.http.auth.AuthOption;
+import org.apache.http.auth.AuthProtocolState;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthSchemeRegistry;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.AuthState;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.MalformedChallengeException;
+import org.apache.http.client.AuthCache;
+import org.apache.http.client.AuthenticationStrategy;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.auth.BasicSchemeFactory;
+import org.apache.http.impl.auth.DigestScheme;
+import org.apache.http.impl.auth.DigestSchemeFactory;
+import org.apache.http.impl.auth.NTLMSchemeFactory;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class TestHttpAuthenticator {
+
+ private AuthenticationStrategy authStrategy;
+ private AuthState authState;
+ private AuthScheme authScheme;
+ private HttpContext context;
+ private HttpHost host;
+ private HttpHost proxy;
+ private Credentials credentials;
+ private BasicCredentialsProvider credentialsProvider;
+ private AuthSchemeRegistry authSchemeRegistry;
+ private AuthCache authCache;
+ private HttpAuthenticator httpAuthenticator;
+
+ @Before
+ public void setUp() throws Exception {
+ this.authStrategy = Mockito.mock(AuthenticationStrategy.class);
+ this.authState = new AuthState();
+ this.authScheme = new BasicScheme();
+ this.authScheme.processChallenge(new BasicHeader(AUTH.WWW_AUTH, "Basic realm=test"));
+ this.context = new BasicHttpContext();
+ this.host = new HttpHost("localhost", 80);
+ this.proxy = new HttpHost("localhost", 8888);
+ this.context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, this.host);
+ this.context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, this.proxy);
+ this.credentials = Mockito.mock(Credentials.class);
+ this.credentialsProvider = new BasicCredentialsProvider();
+ this.credentialsProvider.setCredentials(AuthScope.ANY, this.credentials);
+ this.context.setAttribute(ClientContext.CREDS_PROVIDER, this.credentialsProvider);
+ this.authSchemeRegistry = new AuthSchemeRegistry();
+ this.authSchemeRegistry.register("basic", new BasicSchemeFactory());
+ this.authSchemeRegistry.register("digest", new DigestSchemeFactory());
+ this.authSchemeRegistry.register("ntlm", new NTLMSchemeFactory());
+ this.context.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry);
+ this.authCache = Mockito.mock(AuthCache.class);
+ this.context.setAttribute(ClientContext.AUTH_CACHE, this.authCache);
+ this.httpAuthenticator = new HttpAuthenticator();
+ }
+
+ @Test
+ public void testAuthenticationRequested() throws Exception {
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ Mockito.when(this.authStrategy.isAuthenticationRequested(
+ Mockito.any(HttpHost.class),
+ Mockito.any(HttpResponse.class),
+ Mockito.any(HttpContext.class))).thenReturn(Boolean.TRUE);
+
+ Assert.assertTrue(this.httpAuthenticator.isAuthenticationRequested(
+ this.host, response, this.authStrategy, this.authState, this.context));
+
+ Mockito.verify(this.authStrategy).isAuthenticationRequested(this.host, response, this.context);
+ }
+
+ @Test
+ public void testAuthenticationNotRequestedUnchallenged() throws Exception {
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
+ Mockito.when(this.authStrategy.isAuthenticationRequested(
+ Mockito.any(HttpHost.class),
+ Mockito.any(HttpResponse.class),
+ Mockito.any(HttpContext.class))).thenReturn(Boolean.FALSE);
+
+ Assert.assertFalse(this.httpAuthenticator.isAuthenticationRequested(
+ this.host, response, this.authStrategy, this.authState, this.context));
+ Assert.assertEquals(AuthProtocolState.UNCHALLENGED, this.authState.getState());
+
+ Mockito.verify(this.authStrategy).isAuthenticationRequested(this.host, response, this.context);
+ }
+
+ @Test
+ public void testAuthenticationNotRequestedSuccess1() throws Exception {
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
+ Mockito.when(this.authStrategy.isAuthenticationRequested(
+ Mockito.any(HttpHost.class),
+ Mockito.any(HttpResponse.class),
+ Mockito.any(HttpContext.class))).thenReturn(Boolean.FALSE);
+ this.authState.update(this.authScheme, this.credentials);
+ this.authState.setState(AuthProtocolState.CHALLENGED);
+
+ Assert.assertFalse(this.httpAuthenticator.isAuthenticationRequested(
+ this.host, response, this.authStrategy, this.authState, this.context));
+ Assert.assertEquals(AuthProtocolState.SUCCESS, this.authState.getState());
+
+ Mockito.verify(this.authStrategy).isAuthenticationRequested(this.host, response, this.context);
+ Mockito.verify(this.authStrategy).authSucceeded(this.host, this.authScheme, this.context);
+ }
+
+ @Test
+ public void testAuthenticationNotRequestedSuccess2() throws Exception {
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
+ Mockito.when(this.authStrategy.isAuthenticationRequested(
+ Mockito.any(HttpHost.class),
+ Mockito.any(HttpResponse.class),
+ Mockito.any(HttpContext.class))).thenReturn(Boolean.FALSE);
+ this.authState.update(this.authScheme, this.credentials);
+ this.authState.setState(AuthProtocolState.HANDSHAKE);
+
+ Assert.assertFalse(this.httpAuthenticator.isAuthenticationRequested(
+ this.host, response, this.authStrategy, this.authState, this.context));
+ Assert.assertEquals(AuthProtocolState.SUCCESS, this.authState.getState());
+
+ Mockito.verify(this.authStrategy).isAuthenticationRequested(this.host, response, this.context);
+ Mockito.verify(this.authStrategy).authSucceeded(this.host, this.authScheme, this.context);
+ }
+
+ @Test
+ public void testAuthentication() throws Exception {
+ HttpHost host = new HttpHost("somehost", 80);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"test\""));
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm1\", nonce=\"1234\""));
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "whatever realm=\"realm1\", stuff=\"1234\""));
+
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+
+ Assert.assertTrue(this.httpAuthenticator.authenticate(host,
+ response, authStrategy, this.authState, this.context));
+ Assert.assertEquals(AuthProtocolState.CHALLENGED, this.authState.getState());
+
+ Queue<AuthOption> options = this.authState.getAuthOptions();
+ Assert.assertNotNull(options);
+ AuthOption option1 = options.poll();
+ Assert.assertNotNull(option1);
+ Assert.assertEquals("digest", option1.getAuthScheme().getSchemeName());
+ AuthOption option2 = options.poll();
+ Assert.assertNotNull(option2);
+ Assert.assertEquals("basic", option2.getAuthScheme().getSchemeName());
+ Assert.assertNull(options.poll());
+ }
+
+ @Test
+ public void testAuthenticationNoChallenges() throws Exception {
+ HttpHost host = new HttpHost("somehost", 80);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+
+ Mockito.when(this.authStrategy.getChallenges(
+ Mockito.any(HttpHost.class),
+ Mockito.any(HttpResponse.class),
+ Mockito.any(HttpContext.class))).thenReturn(new HashMap<String, Header>());
+
+ Assert.assertFalse(this.httpAuthenticator.authenticate(host,
+ response, this.authStrategy, this.authState, this.context));
+ }
+
+ @Test
+ public void testAuthenticationNoSupportedChallenges() throws Exception {
+ HttpHost host = new HttpHost("somehost", 80);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "This realm=\"test\""));
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "That realm=\"realm1\", nonce=\"1234\""));
+
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+
+ Assert.assertFalse(this.httpAuthenticator.authenticate(host,
+ response, authStrategy, this.authState, this.context));
+ }
+
+ @Test
+ public void testAuthenticationNoCredentials() throws Exception {
+ HttpHost host = new HttpHost("somehost", 80);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"test\""));
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm1\", nonce=\"1234\""));
+
+ this.credentialsProvider.clear();
+
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+
+ Assert.assertFalse(this.httpAuthenticator.authenticate(host,
+ response, authStrategy, this.authState, this.context));
+ }
+
+ @Test
+ public void testAuthenticationFailed() throws Exception {
+ HttpHost host = new HttpHost("somehost", 80);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"test\""));
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm1\", nonce=\"1234\""));
+
+ this.authState.setState(AuthProtocolState.FAILURE);
+
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+
+ Assert.assertFalse(this.httpAuthenticator.authenticate(host,
+ response, authStrategy, this.authState, this.context));
+
+ Assert.assertEquals(AuthProtocolState.FAILURE, this.authState.getState());
+ }
+
+ @Test
+ public void testAuthenticationNoAuthScheme() throws Exception {
+ HttpHost host = new HttpHost("somehost", 80);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"test\""));
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm1\", nonce=\"1234\""));
+
+ this.authState.setState(AuthProtocolState.CHALLENGED);
+ this.authState.update(this.authScheme, this.credentials);
+
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+
+ Assert.assertFalse(this.httpAuthenticator.authenticate(host,
+ response, authStrategy, this.authState, this.context));
+
+ Assert.assertEquals(AuthProtocolState.FAILURE, this.authState.getState());
+
+ Mockito.verify(this.authCache).remove(host);
+ }
+
+ @Test
+ public void testAuthenticationFailure() throws Exception {
+ HttpHost host = new HttpHost("somehost", 80);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"test\""));
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm1\", nonce=\"1234\""));
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "whatever realm=\"realm1\", stuff=\"1234\""));
+
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+
+ this.authState.setState(AuthProtocolState.CHALLENGED);
+ this.authState.update(new BasicScheme(), this.credentials);
+
+ Assert.assertFalse(this.httpAuthenticator.authenticate(host,
+ response, authStrategy, this.authState, this.context));
+ Assert.assertEquals(AuthProtocolState.FAILURE, this.authState.getState());
+ Assert.assertNull(this.authState.getCredentials());
+ }
+
+ @Test
+ public void testAuthenticationHandshaking() throws Exception {
+ HttpHost host = new HttpHost("somehost", 80);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Basic realm=\"test\""));
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm1\", stale=true, nonce=\"1234\""));
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "whatever realm=\"realm1\", stuff=\"1234\""));
+
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+
+ this.authState.setState(AuthProtocolState.CHALLENGED);
+ this.authState.update(new DigestScheme(), this.credentials);
+
+ Assert.assertTrue(this.httpAuthenticator.authenticate(host,
+ response, authStrategy, this.authState, this.context));
+
+ Assert.assertEquals(AuthProtocolState.HANDSHAKE, this.authState.getState());
+ }
+
+ @Test
+ public void testAuthenticationNoMatchingChallenge() throws Exception {
+ HttpHost host = new HttpHost("somehost", 80);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "Digest realm=\"realm1\", nonce=\"1234\""));
+ response.addHeader(new BasicHeader(AUTH.WWW_AUTH, "whatever realm=\"realm1\", stuff=\"1234\""));
+
+ TargetAuthenticationStrategy authStrategy = new TargetAuthenticationStrategy();
+
+ this.authState.setState(AuthProtocolState.CHALLENGED);
+ this.authState.update(new BasicScheme(), this.credentials);
+
+ Assert.assertTrue(this.httpAuthenticator.authenticate(host,
+ response, authStrategy, this.authState, this.context));
+ Assert.assertEquals(AuthProtocolState.CHALLENGED, this.authState.getState());
+
+ Queue<AuthOption> options = this.authState.getAuthOptions();
+ Assert.assertNotNull(options);
+ AuthOption option1 = options.poll();
+ Assert.assertNotNull(option1);
+ Assert.assertEquals("digest", option1.getAuthScheme().getSchemeName());
+ Assert.assertNull(options.poll());
+ }
+
+ @Test
+ public void testAuthenticationException() throws Exception {
+ HttpHost host = new HttpHost("somehost", 80);
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_UNAUTHORIZED, "UNAUTHORIZED");
+
+ this.authState.setState(AuthProtocolState.CHALLENGED);
+
+ Mockito.doThrow(new MalformedChallengeException()).when(this.authStrategy).getChallenges(
+ Mockito.any(HttpHost.class),
+ Mockito.any(HttpResponse.class),
+ Mockito.any(HttpContext.class));
+
+ Assert.assertFalse(this.httpAuthenticator.authenticate(host,
+ response, this.authStrategy, this.authState, this.context));
+
+ Assert.assertEquals(AuthProtocolState.UNCHALLENGED, this.authState.getState());
+ Assert.assertNull(this.authState.getAuthScheme());
+ Assert.assertNull(this.authState.getCredentials());
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestNullBackoffStrategy.java b/httpclient/src/test/java/org/apache/http/impl/client/TestNullBackoffStrategy.java
new file mode 100644
index 0000000..8b53022
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestNullBackoffStrategy.java
@@ -0,0 +1,58 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.client;
+
+import static org.junit.Assert.*;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.message.BasicHttpResponse;
+import org.junit.Before;
+import org.junit.Test;
+
+
+public class TestNullBackoffStrategy {
+
+ private NullBackoffStrategy impl;
+
+ @Before
+ public void setUp() {
+ impl = new NullBackoffStrategy();
+ }
+
+ @Test
+ public void doesNotBackoffForThrowables() {
+ assertFalse(impl.shouldBackoff(new Exception()));
+ }
+
+ @Test
+ public void doesNotBackoffForResponses() {
+ HttpResponse resp = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ HttpStatus.SC_SERVICE_UNAVAILABLE, "Service Unavailable");
+ assertFalse(impl.shouldBackoff(resp));
+ }
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestRequestRetryHandler.java b/httpclient/src/test/java/org/apache/http/impl/client/TestRequestRetryHandler.java
index 40aee5f..2225d50 100644
--- a/httpclient/src/test/java/org/apache/http/impl/client/TestRequestRetryHandler.java
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestRequestRetryHandler.java
@@ -38,8 +38,7 @@ import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SchemeSocketFactory;
-import org.apache.http.impl.conn.SingleClientConnManager;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
@@ -49,26 +48,16 @@ import org.junit.Test;
public class TestRequestRetryHandler {
@Test(expected=UnknownHostException.class)
- public void testUseRetryHandlerInConnectionTimeOutWithThreadSafeClientConnManager()
+ public void testUseRetryHandlerInConnectionTimeOut()
throws Exception {
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", 80, new OppsieSchemeSocketFactory()));
- ClientConnectionManager connManager = new ThreadSafeClientConnManager(schemeRegistry);
+ ClientConnectionManager connManager = new PoolingClientConnectionManager(schemeRegistry);
assertOnRetry(connManager);
}
- @Test(expected=UnknownHostException.class)
- public void testUseRetryHandlerInConnectionTimeOutWithSingleClientConnManager()
- throws Exception {
-
- SchemeRegistry schemeRegistry = new SchemeRegistry();
- schemeRegistry.register(new Scheme("http", 80, new OppsieSchemeSocketFactory()));
- ClientConnectionManager connManager = new SingleClientConnManager(schemeRegistry);
- assertOnRetry(connManager);
- }
-
protected void assertOnRetry(ClientConnectionManager connManager) throws Exception {
DefaultHttpClient client = new DefaultHttpClient(connManager);
TestHttpRequestRetryHandler testRetryHandler = new TestHttpRequestRetryHandler();
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestRequestWrapper.java b/httpclient/src/test/java/org/apache/http/impl/client/TestRequestWrapper.java
index 17bcaac..ab7205a 100644
--- a/httpclient/src/test/java/org/apache/http/impl/client/TestRequestWrapper.java
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestRequestWrapper.java
@@ -52,9 +52,10 @@ public class TestRequestWrapper extends BasicServerTestBase {
@Before
public void setUp() throws Exception {
- localServer = new LocalTestServer(null, null);
- localServer.registerDefaultHandlers();
- localServer.start();
+ this.localServer = new LocalTestServer(null, null);
+ this.localServer.registerDefaultHandlers();
+ this.localServer.start();
+ this.httpclient = new DefaultHttpClient();
}
private static class SimpleService implements HttpRequestHandler {
@@ -78,13 +79,13 @@ public class TestRequestWrapper extends BasicServerTestBase {
int port = this.localServer.getServiceAddress().getPort();
this.localServer.register("*", new SimpleService());
- DefaultHttpClient client = new DefaultHttpClient();
+
HttpContext context = new BasicHttpContext();
String s = "http://localhost:" + port + "/path";
HttpGet httpget = new HttpGet(s);
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
@@ -101,13 +102,12 @@ public class TestRequestWrapper extends BasicServerTestBase {
int port = this.localServer.getServiceAddress().getPort();
this.localServer.register("*", new SimpleService());
- DefaultHttpClient client = new DefaultHttpClient();
HttpContext context = new BasicHttpContext();
String s = "http://localhost:" + port;
HttpGet httpget = new HttpGet(s);
- HttpResponse response = client.execute(getServerHttp(), httpget, context);
+ HttpResponse response = this.httpclient.execute(getServerHttp(), httpget, context);
EntityUtils.consume(response.getEntity());
HttpRequest reqWrapper = (HttpRequest) context.getAttribute(
diff --git a/httpclient/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java b/httpclient/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java
index 24b8714..a847e73 100644
--- a/httpclient/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java
+++ b/httpclient/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java
@@ -37,7 +37,7 @@ import org.apache.http.client.UserTokenHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ManagedClientConnection;
import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.localserver.ServerTestBase;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
@@ -85,7 +85,7 @@ public class TestStatefulConnManagement extends ServerTestBase {
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 10);
- ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(supportedSchemes);
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager(supportedSchemes);
mgr.setMaxTotal(workerCount);
mgr.setDefaultMaxPerRoute(workerCount);
@@ -207,7 +207,7 @@ public class TestStatefulConnManagement extends ServerTestBase {
this.localServer.register("*", new SimpleService());
// We build a client with 2 max active // connections, and 2 max per route.
- ThreadSafeClientConnManager connMngr = new ThreadSafeClientConnManager(supportedSchemes);
+ PoolingClientConnectionManager connMngr = new PoolingClientConnectionManager(supportedSchemes);
connMngr.setMaxTotal(maxConn);
connMngr.setDefaultMaxPerRoute(maxConn);
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/ClientConnAdapterMockup.java b/httpclient/src/test/java/org/apache/http/impl/conn/ClientConnAdapterMockup.java
deleted file mode 100644
index 5f77e3a..0000000
--- a/httpclient/src/test/java/org/apache/http/impl/conn/ClientConnAdapterMockup.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.impl.conn;
-
-import java.io.IOException;
-
-import org.apache.http.HttpHost;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.routing.HttpRoute;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.HttpContext;
-
-
-/**
- * Mockup connection adapter.
- */
-public class ClientConnAdapterMockup extends AbstractClientConnAdapter {
-
- public ClientConnAdapterMockup(ClientConnectionManager mgr) {
- super(mgr, null);
- }
-
- public void close() {
- }
-
- public HttpRoute getRoute() {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public void layerProtocol(HttpContext context, HttpParams params) {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public void open(HttpRoute route, HttpContext context, HttpParams params) throws IOException {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public void shutdown() {
- }
-
- public void tunnelTarget(boolean secure, HttpParams params) {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public void tunnelProxy(HttpHost next, boolean secure, HttpParams params) {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public Object getState() {
- throw new UnsupportedOperationException("just a mockup");
- }
-
- public void setState(Object state) {
- throw new UnsupportedOperationException("just a mockup");
- }
-}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/ConnPoolBench.java b/httpclient/src/test/java/org/apache/http/impl/conn/ConnPoolBench.java
new file mode 100644
index 0000000..60a1a46
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/ConnPoolBench.java
@@ -0,0 +1,173 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.conn;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.HttpHost;
+import org.apache.http.conn.ClientConnectionOperator;
+import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.conn.params.ConnPerRoute;
+import org.apache.http.conn.params.ConnPerRouteBean;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.impl.conn.tsccm.BasicPoolEntry;
+import org.apache.http.impl.conn.tsccm.ConnPoolByRoute;
+import org.apache.http.impl.conn.tsccm.PoolEntryRequest;
+
+ at SuppressWarnings("deprecation")
+public class ConnPoolBench {
+
+ private final static HttpRoute ROUTE = new HttpRoute(new HttpHost("localhost"));
+
+ public static void main(String[] args) throws Exception {
+ int c = 200;
+ long reps = 100000;
+ oldPool(c, reps);
+ newPool(c, reps);
+ }
+
+ public static void newPool(int c, long reps) throws Exception {
+ Log log = LogFactory.getLog(ConnPoolBench.class);
+
+ HttpConnPool pool = new HttpConnPool(log, c, c * 10, -1, TimeUnit.MILLISECONDS);
+
+ WorkerThread1[] workers = new WorkerThread1[c];
+ for (int i = 0; i < workers.length; i++) {
+ workers[i] = new WorkerThread1(pool, reps);
+ }
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < workers.length; i++) {
+ workers[i].start();
+ }
+ for (int i = 0; i < workers.length; i++) {
+ workers[i].join();
+ }
+ long finish = System.currentTimeMillis();
+ float totalTimeSec = (float) (finish - start) / 1000;
+ System.out.print("Concurrency level:\t");
+ System.out.println(c);
+ System.out.print("Total operations:\t");
+ System.out.println(c * reps);
+ System.out.print("Time taken for tests:\t");
+ System.out.print(totalTimeSec);
+ System.out.println(" seconds");
+ }
+
+ static void oldPool(int c, long reps) throws Exception {
+ ClientConnectionOperator operator = new DefaultClientConnectionOperator(
+ SchemeRegistryFactory.createDefault());
+ ConnPerRoute connPerRoute = new ConnPerRouteBean(c);
+ ConnPoolByRoute pool = new ConnPoolByRoute(operator, connPerRoute, c * 10);
+
+ WorkerThread2[] workers = new WorkerThread2[c];
+ for (int i = 0; i < workers.length; i++) {
+ workers[i] = new WorkerThread2(pool, reps);
+ }
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < workers.length; i++) {
+ workers[i].start();
+ }
+ for (int i = 0; i < workers.length; i++) {
+ workers[i].join();
+ }
+ long finish = System.currentTimeMillis();
+ float totalTimeSec = (float) (finish - start) / 1000;
+ System.out.print("Concurrency level:\t");
+ System.out.println(c);
+ System.out.print("Total operations:\t");
+ System.out.println(c * reps);
+ System.out.print("Time taken for tests:\t");
+ System.out.print(totalTimeSec);
+ System.out.println(" seconds");
+ }
+
+ static class WorkerThread1 extends Thread {
+
+ private final HttpConnPool pool;
+ private final long reps;
+
+ WorkerThread1(final HttpConnPool pool, final long reps) {
+ super();
+ this.pool = pool;
+ this.reps = reps;
+ }
+
+ @Override
+ public void run() {
+ for (long c = 0; c < this.reps; c++) {
+ Future<HttpPoolEntry> future = this.pool.lease(ROUTE, null);
+ try {
+ HttpPoolEntry entry = future.get(-1, TimeUnit.MILLISECONDS);
+ this.pool.release(entry, true);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ } catch (TimeoutException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ }
+
+ static class WorkerThread2 extends Thread {
+
+ private final ConnPoolByRoute pool;
+ private final long reps;
+
+ WorkerThread2(final ConnPoolByRoute pool, final long reps) {
+ super();
+ this.pool = pool;
+ this.reps = reps;
+ }
+
+ @Override
+ public void run() {
+ for (long c = 0; c < this.reps; c++) {
+ PoolEntryRequest request = this.pool.requestPoolEntry(ROUTE, null);
+ BasicPoolEntry entry;
+ try {
+ entry = request.getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ this.pool.freeEntry(entry, true, -1, TimeUnit.MILLISECONDS);
+ } catch (ConnectionPoolTimeoutException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ }
+
+}
+
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/ExecReqThread.java b/httpclient/src/test/java/org/apache/http/impl/conn/ExecReqThread.java
index 3beafcd..0c3574d 100644
--- a/httpclient/src/test/java/org/apache/http/impl/conn/ExecReqThread.java
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/ExecReqThread.java
@@ -111,10 +111,9 @@ public class ExecReqThread extends GetConnThread {
doConsumeResponse();
- } catch (Throwable dart) {
- dart.printStackTrace(System.out);
+ } catch (Exception ex) {
if (exception != null)
- exception = dart;
+ exception = ex;
} finally {
conn_manager.releaseConnection(connection, -1, null);
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/GetConnThread.java b/httpclient/src/test/java/org/apache/http/impl/conn/GetConnThread.java
index e9d3f33..b474f59 100644
--- a/httpclient/src/test/java/org/apache/http/impl/conn/GetConnThread.java
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/GetConnThread.java
@@ -47,7 +47,7 @@ public class GetConnThread extends Thread {
protected final ClientConnectionRequest conn_request;
protected volatile ManagedClientConnection connection;
- protected volatile Throwable exception;
+ protected volatile Exception exception;
/**
* Creates a new thread for requesting a connection from the given manager.
@@ -81,8 +81,8 @@ public class GetConnThread extends Thread {
try {
connection = conn_request.getConnection
(conn_timeout, TimeUnit.MILLISECONDS);
- } catch (Throwable dart) {
- exception = dart;
+ } catch (Exception ex) {
+ exception = ex;
}
// terminate
}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestAbortHandling.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestAbortHandling.java
new file mode 100644
index 0000000..14c2424
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/TestAbortHandling.java
@@ -0,0 +1,510 @@
+/*
+ * ====================================================================
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.conn;
+
+import java.io.IOException;
+import java.net.ConnectException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.http.HttpException;
+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.methods.AbortableHttpRequest;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.ClientConnectionRequest;
+import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.conn.ConnectionReleaseTrigger;
+import org.apache.http.conn.ManagedClientConnection;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.DefaultRequestDirector;
+import org.apache.http.impl.client.RequestWrapper;
+import org.apache.http.localserver.BasicServerTestBase;
+import org.apache.http.localserver.LocalTestServer;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.mockup.SocketFactoryMockup;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Tests for Abort handling.
+ */
+public class TestAbortHandling extends BasicServerTestBase {
+
+ @Before
+ public void setUp() throws Exception {
+ this.localServer = new LocalTestServer(null, null);
+ this.localServer.registerDefaultHandlers();
+ this.localServer.start();
+ this.httpclient = new DefaultHttpClient();
+ }
+
+ @Test
+ public void testAbortRetry_HTTPCLIENT_1120() throws Exception {
+ int port = this.localServer.getServiceAddress().getPort();
+ final CountDownLatch wait = new CountDownLatch(1);
+
+ this.localServer.register("*", new HttpRequestHandler(){
+ public void handle(HttpRequest request, HttpResponse response,
+ HttpContext context) throws HttpException, IOException {
+ try {
+ wait.countDown(); // trigger abort
+ Thread.sleep(2000); // allow time for abort to happen
+ response.setStatusCode(HttpStatus.SC_OK);
+ StringEntity entity = new StringEntity("Whatever");
+ response.setEntity(entity);
+ } catch (Exception e) {
+ response.setStatusCode(HttpStatus.SC_REQUEST_TIMEOUT);
+ }
+ }});
+
+ String s = "http://localhost:" + port + "/path";
+ final HttpGet httpget = new HttpGet(s);
+
+ Thread t = new Thread() {
+ @Override
+ public void run(){
+ try {
+ wait.await();
+ } catch (InterruptedException e) {
+ }
+ httpget.abort();
+ }
+ };
+
+ t.start();
+
+ HttpContext context = new BasicHttpContext();
+ try {
+ this.httpclient.execute(getServerHttp(), httpget, context);
+ } catch (IllegalStateException e) {
+ } catch (IOException e) {
+ }
+
+ HttpRequest reqWrapper = (HttpRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
+ Assert.assertNotNull("Request should exist",reqWrapper);
+ Assert.assertEquals(1,((RequestWrapper) reqWrapper).getExecCount());
+ }
+
+ /**
+ * Tests that if abort is called on an {@link AbortableHttpRequest} while
+ * {@link DefaultRequestDirector} is allocating a connection, that the
+ * connection is properly aborted.
+ */
+ @Test
+ public void testAbortInAllocate() throws Exception {
+ CountDownLatch connLatch = new CountDownLatch(1);
+ CountDownLatch awaitLatch = new CountDownLatch(1);
+ final ConMan conMan = new ConMan(connLatch, awaitLatch);
+ final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
+ final CountDownLatch getLatch = new CountDownLatch(1);
+ final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams());
+ final HttpContext context = new BasicHttpContext();
+ final HttpGet httpget = new HttpGet("http://www.example.com/a");
+
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ client.execute(httpget, context);
+ } catch(Throwable t) {
+ throwableRef.set(t);
+ } finally {
+ getLatch.countDown();
+ }
+ }
+ }).start();
+
+ Assert.assertTrue("should have tried to get a connection", connLatch.await(1, TimeUnit.SECONDS));
+
+ httpget.abort();
+
+ Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
+ Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
+ throwableRef.get() instanceof IOException);
+ Assert.assertTrue("cause should be InterruptedException, was: " + throwableRef.get().getCause(),
+ throwableRef.get().getCause() instanceof InterruptedException);
+ }
+
+ /**
+ * Tests that an abort called after the connection has been retrieved
+ * but before a release trigger is set does still abort the request.
+ */
+ @Test
+ public void testAbortAfterAllocateBeforeRequest() throws Exception {
+ this.localServer.register("*", new BasicService());
+
+ CountDownLatch releaseLatch = new CountDownLatch(1);
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
+
+ PoolingClientConnectionManager conMan = new PoolingClientConnectionManager(registry);
+ final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
+ final CountDownLatch getLatch = new CountDownLatch(1);
+ final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams());
+ final HttpContext context = new BasicHttpContext();
+ final HttpGet httpget = new CustomGet("a", releaseLatch);
+
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ client.execute(getServerHttp(), httpget, context);
+ } catch(Throwable t) {
+ throwableRef.set(t);
+ } finally {
+ getLatch.countDown();
+ }
+ }
+ }).start();
+
+ Thread.sleep(100); // Give it a little time to proceed to release...
+
+ httpget.abort();
+
+ releaseLatch.countDown();
+
+ Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
+ Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
+ throwableRef.get() instanceof IOException);
+ }
+
+ /**
+ * Tests that an abort called completely before execute
+ * still aborts the request.
+ */
+ @Test
+ public void testAbortBeforeExecute() throws Exception {
+ this.localServer.register("*", new BasicService());
+
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
+
+ PoolingClientConnectionManager conMan = new PoolingClientConnectionManager(registry);
+ final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
+ final CountDownLatch getLatch = new CountDownLatch(1);
+ final CountDownLatch startLatch = new CountDownLatch(1);
+ final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams());
+ final HttpContext context = new BasicHttpContext();
+ final HttpGet httpget = new HttpGet("a");
+
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ try {
+ if(!startLatch.await(1, TimeUnit.SECONDS))
+ throw new RuntimeException("Took too long to start!");
+ } catch(InterruptedException interrupted) {
+ throw new RuntimeException("Never started!", interrupted);
+ }
+ client.execute(getServerHttp(), httpget, context);
+ } catch(Throwable t) {
+ throwableRef.set(t);
+ } finally {
+ getLatch.countDown();
+ }
+ }
+ }).start();
+
+ httpget.abort();
+ startLatch.countDown();
+
+ Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
+ Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
+ throwableRef.get() instanceof IOException);
+ }
+
+ /**
+ * Tests that an abort called after a redirect has found a new host
+ * still aborts in the correct place (while trying to get the new
+ * host's route, not while doing the subsequent request).
+ */
+ @Test
+ public void testAbortAfterRedirectedRoute() throws Exception {
+ final int port = this.localServer.getServiceAddress().getPort();
+ this.localServer.register("*", new BasicRedirectService(port));
+
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
+
+ CountDownLatch connLatch = new CountDownLatch(1);
+ CountDownLatch awaitLatch = new CountDownLatch(1);
+ ConnMan4 conMan = new ConnMan4(registry, connLatch, awaitLatch);
+ final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
+ final CountDownLatch getLatch = new CountDownLatch(1);
+ final DefaultHttpClient client = new DefaultHttpClient(conMan, new BasicHttpParams());
+ final HttpContext context = new BasicHttpContext();
+ final HttpGet httpget = new HttpGet("a");
+
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ HttpHost host = new HttpHost("127.0.0.1", port);
+ client.execute(host, httpget, context);
+ } catch(Throwable t) {
+ throwableRef.set(t);
+ } finally {
+ getLatch.countDown();
+ }
+ }
+ }).start();
+
+ Assert.assertTrue("should have tried to get a connection", connLatch.await(1, TimeUnit.SECONDS));
+
+ httpget.abort();
+
+ Assert.assertTrue("should have finished get request", getLatch.await(1, TimeUnit.SECONDS));
+ Assert.assertTrue("should be instanceof IOException, was: " + throwableRef.get(),
+ throwableRef.get() instanceof IOException);
+ Assert.assertTrue("cause should be InterruptedException, was: " + throwableRef.get().getCause(),
+ throwableRef.get().getCause() instanceof InterruptedException);
+ }
+
+
+ /**
+ * Tests that if a socket fails to connect, the allocated connection is
+ * properly released back to the connection manager.
+ */
+ @Test
+ public void testSocketConnectFailureReleasesConnection() throws Exception {
+ ManagedClientConnection conn = Mockito.mock(ManagedClientConnection.class);
+ Mockito.doThrow(new ConnectException()).when(conn).open(
+ Mockito.any(HttpRoute.class),
+ Mockito.any(HttpContext.class),
+ Mockito.any(HttpParams.class));
+ ClientConnectionRequest connrequest = Mockito.mock(ClientConnectionRequest.class);
+ Mockito.when(connrequest.getConnection(
+ Mockito.anyInt(), Mockito.any(TimeUnit.class))).thenReturn(conn);
+ ClientConnectionManager connmgr = Mockito.mock(ClientConnectionManager.class);
+
+ SchemeRegistry schemeRegistry = SchemeRegistryFactory.createDefault();
+
+ Mockito.when(connmgr.requestConnection(
+ Mockito.any(HttpRoute.class), Mockito.any())).thenReturn(connrequest);
+ Mockito.when(connmgr.getSchemeRegistry()).thenReturn(schemeRegistry);
+
+ final DefaultHttpClient client = new DefaultHttpClient(connmgr, new BasicHttpParams());
+ final HttpContext context = new BasicHttpContext();
+ final HttpGet httpget = new HttpGet("http://www.example.com/a");
+
+ try {
+ client.execute(httpget, context);
+ Assert.fail("expected IOException");
+ } catch(IOException expected) {}
+
+ Mockito.verify(conn).abortConnection();
+ }
+
+ private static class BasicService implements HttpRequestHandler {
+ public void handle(final HttpRequest request,
+ final HttpResponse response,
+ final HttpContext context) throws HttpException, IOException {
+ response.setStatusCode(200);
+ response.setEntity(new StringEntity("Hello World"));
+ }
+ }
+
+ private static class BasicRedirectService implements HttpRequestHandler {
+ private int statuscode = HttpStatus.SC_SEE_OTHER;
+ private int port;
+
+ public BasicRedirectService(int port) {
+ this.port = port;
+ }
+
+ public void handle(final HttpRequest request,
+ final HttpResponse response, final HttpContext context)
+ throws HttpException, IOException {
+ ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
+ response.setStatusLine(ver, this.statuscode);
+ response.addHeader(new BasicHeader("Location", "http://localhost:"
+ + this.port + "/newlocation/"));
+ response.addHeader(new BasicHeader("Connection", "close"));
+ }
+ }
+
+ private static class ConnMan4 extends PoolingClientConnectionManager {
+ private final CountDownLatch connLatch;
+ private final CountDownLatch awaitLatch;
+
+ public ConnMan4(SchemeRegistry schreg,
+ CountDownLatch connLatch, CountDownLatch awaitLatch) {
+ super(schreg);
+ this.connLatch = connLatch;
+ this.awaitLatch = awaitLatch;
+ }
+
+ @Override
+ public ClientConnectionRequest requestConnection(HttpRoute route, Object state) {
+ // If this is the redirect route, stub the return value
+ // so-as to pretend the host is waiting on a slot...
+ if(route.getTargetHost().getHostName().equals("localhost")) {
+ final Thread currentThread = Thread.currentThread();
+
+ return new ClientConnectionRequest() {
+
+ public void abortRequest() {
+ currentThread.interrupt();
+ }
+
+ public ManagedClientConnection getConnection(
+ long timeout, TimeUnit tunit)
+ throws InterruptedException,
+ ConnectionPoolTimeoutException {
+ connLatch.countDown(); // notify waiter that we're getting a connection
+
+ // zero usually means sleep forever, but CountDownLatch doesn't interpret it that way.
+ if(timeout == 0)
+ timeout = Integer.MAX_VALUE;
+
+ if(!awaitLatch.await(timeout, tunit))
+ throw new ConnectionPoolTimeoutException();
+
+ return Mockito.mock(ManagedClientConnection.class);
+ }
+ };
+ } else {
+ return super.requestConnection(route, state);
+ }
+ }
+ }
+
+
+ static class ConMan implements ClientConnectionManager {
+ private final CountDownLatch connLatch;
+ private final CountDownLatch awaitLatch;
+
+ public ConMan(CountDownLatch connLatch, CountDownLatch awaitLatch) {
+ this.connLatch = connLatch;
+ this.awaitLatch = awaitLatch;
+ }
+
+ public void closeIdleConnections(long idletime, TimeUnit tunit) {
+ throw new UnsupportedOperationException("just a mockup");
+ }
+
+ public void closeExpiredConnections() {
+ throw new UnsupportedOperationException("just a mockup");
+ }
+
+ public ManagedClientConnection getConnection(HttpRoute route) {
+ throw new UnsupportedOperationException("just a mockup");
+ }
+
+ public ManagedClientConnection getConnection(HttpRoute route,
+ long timeout, TimeUnit tunit) {
+ throw new UnsupportedOperationException("just a mockup");
+ }
+
+ public ClientConnectionRequest requestConnection(
+ final HttpRoute route,
+ final Object state) {
+
+ final Thread currentThread = Thread.currentThread();
+
+ return new ClientConnectionRequest() {
+
+ public void abortRequest() {
+ currentThread.interrupt();
+ }
+
+ public ManagedClientConnection getConnection(
+ long timeout, TimeUnit tunit)
+ throws InterruptedException,
+ ConnectionPoolTimeoutException {
+ connLatch.countDown(); // notify waiter that we're getting a connection
+
+ // zero usually means sleep forever, but CountDownLatch doesn't interpret it that way.
+ if(timeout == 0)
+ timeout = Integer.MAX_VALUE;
+
+ if(!awaitLatch.await(timeout, tunit))
+ throw new ConnectionPoolTimeoutException();
+
+ return Mockito.mock(ManagedClientConnection.class);
+ }
+ };
+ }
+
+ public HttpParams getParams() {
+ throw new UnsupportedOperationException("just a mockup");
+ }
+
+ public SchemeRegistry getSchemeRegistry() {
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(new Scheme("http", 80, new SocketFactoryMockup(null)));
+ return registry;
+ }
+
+ public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) {
+ throw new UnsupportedOperationException("just a mockup");
+ }
+
+ public void shutdown() {
+ throw new UnsupportedOperationException("just a mockup");
+ }
+ }
+
+ private static class CustomGet extends HttpGet {
+ private final CountDownLatch releaseTriggerLatch;
+
+ public CustomGet(String uri, CountDownLatch releaseTriggerLatch) {
+ super(uri);
+ this.releaseTriggerLatch = releaseTriggerLatch;
+ }
+
+ @Override
+ public void setReleaseTrigger(ConnectionReleaseTrigger releaseTrigger) throws IOException {
+ try {
+ if(!releaseTriggerLatch.await(1, TimeUnit.SECONDS))
+ throw new RuntimeException("Waited too long...");
+ } catch(InterruptedException ie) {
+ throw new RuntimeException(ie);
+ }
+
+ super.setReleaseTrigger(releaseTrigger);
+ }
+
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestBasicConnManager.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestBasicConnManager.java
new file mode 100644
index 0000000..6bba101
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/TestBasicConnManager.java
@@ -0,0 +1,214 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.conn;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.conn.ManagedClientConnection;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.localserver.ServerTestBase;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.util.EntityUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestBasicConnManager extends ServerTestBase {
+
+ public BasicClientConnectionManager createConnManager(SchemeRegistry schreg) {
+ if (schreg == null)
+ schreg = supportedSchemes;
+ return new BasicClientConnectionManager(schreg);
+ }
+
+ /**
+ * Tests that SCM can still connect to the same host after
+ * a connection was aborted.
+ */
+ @Test
+ public void testOpenAfterAbort() throws Exception {
+ BasicClientConnectionManager mgr = createConnManager(null);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+
+ ManagedClientConnection conn = mgr.getConnection(route, null);
+ conn.abortConnection();
+
+ conn = mgr.getConnection(route, null);
+ Assert.assertFalse("connection should have been closed", conn.isOpen());
+ conn.open(route, httpContext, defaultParams);
+
+ mgr.releaseConnection(conn, -1, null);
+ mgr.shutdown();
+ }
+
+ /**
+ * Tests releasing with time limits.
+ */
+ @Test
+ public void testReleaseConnectionWithTimeLimits() throws Exception {
+
+ BasicClientConnectionManager mgr = createConnManager(null);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+ final int rsplen = 8;
+ final String uri = "/random/" + rsplen;
+
+ HttpRequest request =
+ new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1);
+
+ ManagedClientConnection conn = mgr.getConnection(route, null);
+ conn.open(route, httpContext, defaultParams);
+
+ // a new context is created for each testcase, no need to reset
+ HttpResponse response = Helper.execute(
+ request, conn, target,
+ httpExecutor, httpProcessor, defaultParams, httpContext);
+
+ Assert.assertEquals("wrong status in first response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+ byte[] data = EntityUtils.toByteArray(response.getEntity());
+ Assert.assertEquals("wrong length of first response entity",
+ rsplen, data.length);
+ // ignore data, but it must be read
+
+ // release connection without marking for re-use
+ // expect the next connection obtained to be closed
+ mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
+ conn = mgr.getConnection(route, null);
+ Assert.assertFalse("connection should have been closed", conn.isOpen());
+
+ // repeat the communication, no need to prepare the request again
+ conn.open(route, httpContext, defaultParams);
+ httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
+ response = httpExecutor.execute(request, conn, httpContext);
+ httpExecutor.postProcess(response, httpProcessor, httpContext);
+
+ Assert.assertEquals("wrong status in second response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+ data = EntityUtils.toByteArray(response.getEntity());
+ Assert.assertEquals("wrong length of second response entity",
+ rsplen, data.length);
+ // ignore data, but it must be read
+
+ // release connection after marking it for re-use
+ // expect the next connection obtained to be open
+ conn.markReusable();
+ mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
+ conn = mgr.getConnection(route, null);
+ Assert.assertTrue("connection should have been open", conn.isOpen());
+
+ // repeat the communication, no need to prepare the request again
+ httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
+ response = httpExecutor.execute(request, conn, httpContext);
+ httpExecutor.postProcess(response, httpProcessor, httpContext);
+
+ Assert.assertEquals("wrong status in third response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+ data = EntityUtils.toByteArray(response.getEntity());
+ Assert.assertEquals("wrong length of third response entity",
+ rsplen, data.length);
+ // ignore data, but it must be read
+
+ conn.markReusable();
+ mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
+ Thread.sleep(150);
+ conn = mgr.getConnection(route, null);
+ Assert.assertTrue("connection should have been closed", !conn.isOpen());
+
+ // repeat the communication, no need to prepare the request again
+ conn.open(route, httpContext, defaultParams);
+ httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
+ response = httpExecutor.execute(request, conn, httpContext);
+ httpExecutor.postProcess(response, httpProcessor, httpContext);
+
+ Assert.assertEquals("wrong status in third response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+ data = EntityUtils.toByteArray(response.getEntity());
+ Assert.assertEquals("wrong length of fourth response entity",
+ rsplen, data.length);
+ // ignore data, but it must be read
+
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testCloseExpiredConnections() throws Exception {
+
+ BasicClientConnectionManager mgr = createConnManager(null);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+
+ ManagedClientConnection conn = mgr.getConnection(route, null);
+ conn.open(route, httpContext, defaultParams);
+ conn.markReusable();
+ mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
+
+ mgr.closeExpiredConnections();
+
+ conn = mgr.getConnection(route, null);
+ Assert.assertTrue(conn.isOpen());
+ mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
+
+ Thread.sleep(150);
+ mgr.closeExpiredConnections();
+ conn = mgr.getConnection(route, null);
+ Assert.assertFalse(conn.isOpen());
+
+ mgr.shutdown();
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testAlreadyLeased() throws Exception {
+
+ BasicClientConnectionManager mgr = createConnManager(null);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+
+ ManagedClientConnection conn = mgr.getConnection(route, null);
+ mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
+
+ mgr.getConnection(route, null);
+ mgr.getConnection(route, null);
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestDefaultClientConnectOperator.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestDefaultClientConnectOperator.java
new file mode 100644
index 0000000..26c0763
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/TestDefaultClientConnectOperator.java
@@ -0,0 +1,85 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.impl.conn;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.apache.http.conn.DnsResolver;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestDefaultClientConnectOperator {
+
+ @Test
+ public void testCustomDnsResolver() throws Exception {
+ DnsResolver dnsResolver = mock(DnsResolver.class);
+ InetAddress[] firstAddress = translateIp("192.168.1.1");
+ when(dnsResolver.resolve("somehost.example.com")).thenReturn(firstAddress);
+
+ InetAddress[] secondAddress = translateIp("192.168.12.16");
+ when(dnsResolver.resolve("otherhost.example.com")).thenReturn(secondAddress);
+
+ DefaultClientConnectionOperator operator = new DefaultClientConnectionOperator(
+ SchemeRegistryFactory.createDefault(), dnsResolver);
+
+ Assert.assertArrayEquals(firstAddress, operator.resolveHostname("somehost.example.com"));
+ Assert.assertArrayEquals(secondAddress, operator.resolveHostname("otherhost.example.com"));
+ }
+
+ @Test(expected=UnknownHostException.class)
+ public void testDnsResolverUnknownHost() throws Exception {
+ DnsResolver dnsResolver = mock(DnsResolver.class);
+ when(dnsResolver.resolve("unknown.example.com")).thenThrow(new UnknownHostException());
+
+ DefaultClientConnectionOperator operator = new DefaultClientConnectionOperator(
+ SchemeRegistryFactory.createDefault(), dnsResolver);
+ operator.resolveHostname("unknown.example.com");
+ }
+
+ @Test
+ public void testDefaultLocalHost() throws Exception {
+ DefaultClientConnectionOperator operator = new DefaultClientConnectionOperator(
+ SchemeRegistryFactory.createDefault());
+ operator.resolveHostname("localhost");
+ }
+
+ private InetAddress[] translateIp(String ip) throws UnknownHostException {
+ String[] ipParts = ip.split("\\.");
+
+ byte[] byteIpAddress = new byte[4];
+ for (int i = 0; i < 4; i++) {
+ byteIpAddress[i] = Integer.decode(ipParts[i]).byteValue();
+ }
+ return new InetAddress[] { InetAddress.getByAddress(byteIpAddress) };
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestDefaultHttpResponseParser.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestDefaultHttpResponseParser.java
new file mode 100644
index 0000000..4286a56
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/TestDefaultHttpResponseParser.java
@@ -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.impl.conn;
+
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.NoHttpResponseException;
+import org.apache.http.ProtocolException;
+import org.apache.http.impl.DefaultHttpResponseFactory;
+import org.apache.http.io.HttpMessageParser;
+import org.apache.http.io.SessionInputBuffer;
+import org.apache.http.message.BasicLineParser;
+import org.apache.http.mockup.SessionInputBufferMockup;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.util.CharArrayBuffer;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for <code>DefaultResponseParser</code>.
+ */
+public class TestDefaultHttpResponseParser {
+
+ @Test
+ public void testResponseParsingWithSomeGarbage() throws Exception {
+ String s =
+ "garbage\r\n" +
+ "garbage\r\n" +
+ "more garbage\r\n" +
+ "HTTP/1.1 200 OK\r\n" +
+ "header1: value1\r\n" +
+ "header2: value2\r\n" +
+ "\r\n";
+
+ HttpParams params = new BasicHttpParams();
+ SessionInputBuffer inbuffer = new SessionInputBufferMockup(s, "US-ASCII", params);
+ HttpMessageParser<HttpResponse> parser = new DefaultHttpResponseParser(
+ inbuffer,
+ BasicLineParser.DEFAULT,
+ new DefaultHttpResponseFactory(),
+ params);
+
+ HttpResponse response = (HttpResponse) parser.parse();
+ Assert.assertNotNull(response);
+ Assert.assertEquals(HttpVersion.HTTP_1_1, response.getProtocolVersion());
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+
+ Header[] headers = response.getAllHeaders();
+ Assert.assertNotNull(headers);
+ Assert.assertEquals(2, headers.length);
+ Assert.assertEquals("header1", headers[0].getName());
+ Assert.assertEquals("header2", headers[1].getName());
+ }
+
+ @Test(expected=ProtocolException.class)
+ public void testResponseParsingWithTooMuchGarbage() throws Exception {
+ String s =
+ "garbage\r\n" +
+ "garbage\r\n" +
+ "more garbage\r\n" +
+ "HTTP/1.1 200 OK\r\n" +
+ "header1: value1\r\n" +
+ "header2: value2\r\n" +
+ "\r\n";
+
+ HttpParams params = new BasicHttpParams();
+ SessionInputBuffer inbuffer = new SessionInputBufferMockup(s, "US-ASCII", params);
+ HttpMessageParser<HttpResponse> parser = new DefaultHttpResponseParser(
+ inbuffer,
+ BasicLineParser.DEFAULT,
+ new DefaultHttpResponseFactory(),
+ params) {
+
+ @Override
+ protected boolean reject(final CharArrayBuffer line, int count) {
+ return count >= 2;
+ }
+
+ };
+ parser.parse();
+ }
+
+ @Test(expected=NoHttpResponseException.class)
+ public void testResponseParsingNoResponse() throws Exception {
+ HttpParams params = new BasicHttpParams();
+ SessionInputBuffer inbuffer = new SessionInputBufferMockup("", "US-ASCII", params);
+ HttpMessageParser<HttpResponse> parser = new DefaultHttpResponseParser(
+ inbuffer,
+ BasicLineParser.DEFAULT,
+ new DefaultHttpResponseFactory(),
+ params);
+ parser.parse();
+ }
+
+ @Test(expected=ProtocolException.class)
+ public void testResponseParsingOnlyGarbage() throws Exception {
+ String s =
+ "garbage\r\n" +
+ "garbage\r\n" +
+ "more garbage\r\n" +
+ "a lot more garbage\r\n";
+ HttpParams params = new BasicHttpParams();
+ SessionInputBuffer inbuffer = new SessionInputBufferMockup(s, "US-ASCII", params);
+ HttpMessageParser<HttpResponse> parser = new DefaultHttpResponseParser(
+ inbuffer,
+ BasicLineParser.DEFAULT,
+ new DefaultHttpResponseFactory(),
+ params);
+ parser.parse();
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestDefaultResponseParser.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestDefaultResponseParser.java
deleted file mode 100644
index 2bd9e9b..0000000
--- a/httpclient/src/test/java/org/apache/http/impl/conn/TestDefaultResponseParser.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.impl.conn;
-
-import org.apache.http.Header;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpVersion;
-import org.apache.http.NoHttpResponseException;
-import org.apache.http.ProtocolException;
-import org.apache.http.conn.params.ConnConnectionPNames;
-import org.apache.http.impl.DefaultHttpResponseFactory;
-import org.apache.http.io.HttpMessageParser;
-import org.apache.http.io.SessionInputBuffer;
-import org.apache.http.message.BasicLineParser;
-import org.apache.http.mockup.SessionInputBufferMockup;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpParams;
-import org.junit.Assert;
-import org.junit.Test;
-
-/**
- * Tests for <code>DefaultResponseParser</code>.
- */
-public class TestDefaultResponseParser {
-
- @Test
- public void testResponseParsingWithSomeGarbage() throws Exception {
- String s =
- "garbage\r\n" +
- "garbage\r\n" +
- "more garbage\r\n" +
- "HTTP/1.1 200 OK\r\n" +
- "header1: value1\r\n" +
- "header2: value2\r\n" +
- "\r\n";
-
- HttpParams params = new BasicHttpParams();
- SessionInputBuffer inbuffer = new SessionInputBufferMockup(s, "US-ASCII", params);
- HttpMessageParser parser = new DefaultResponseParser(
- inbuffer,
- BasicLineParser.DEFAULT,
- new DefaultHttpResponseFactory(),
- params);
-
- HttpResponse response = (HttpResponse) parser.parse();
- Assert.assertNotNull(response);
- Assert.assertEquals(HttpVersion.HTTP_1_1, response.getProtocolVersion());
- Assert.assertEquals(200, response.getStatusLine().getStatusCode());
-
- Header[] headers = response.getAllHeaders();
- Assert.assertNotNull(headers);
- Assert.assertEquals(2, headers.length);
- Assert.assertEquals("header1", headers[0].getName());
- Assert.assertEquals("header2", headers[1].getName());
- }
-
- @Test(expected=ProtocolException.class)
- public void testResponseParsingWithTooMuchGarbage() throws Exception {
- String s =
- "garbage\r\n" +
- "garbage\r\n" +
- "more garbage\r\n" +
- "HTTP/1.1 200 OK\r\n" +
- "header1: value1\r\n" +
- "header2: value2\r\n" +
- "\r\n";
-
- HttpParams params = new BasicHttpParams();
- params.setParameter(ConnConnectionPNames.MAX_STATUS_LINE_GARBAGE, Integer.valueOf(2));
- SessionInputBuffer inbuffer = new SessionInputBufferMockup(s, "US-ASCII", params);
- HttpMessageParser parser = new DefaultResponseParser(
- inbuffer,
- BasicLineParser.DEFAULT,
- new DefaultHttpResponseFactory(),
- params);
- parser.parse();
- }
-
- @Test(expected=NoHttpResponseException.class)
- public void testResponseParsingNoResponse() throws Exception {
- HttpParams params = new BasicHttpParams();
- SessionInputBuffer inbuffer = new SessionInputBufferMockup("", "US-ASCII", params);
- HttpMessageParser parser = new DefaultResponseParser(
- inbuffer,
- BasicLineParser.DEFAULT,
- new DefaultHttpResponseFactory(),
- params);
- parser.parse();
- }
-
- @Test(expected=ProtocolException.class)
- public void testResponseParsingOnlyGarbage() throws Exception {
- String s =
- "garbage\r\n" +
- "garbage\r\n" +
- "more garbage\r\n" +
- "a lot more garbage\r\n";
- HttpParams params = new BasicHttpParams();
- SessionInputBuffer inbuffer = new SessionInputBufferMockup(s, "US-ASCII", params);
- HttpMessageParser parser = new DefaultResponseParser(
- inbuffer,
- BasicLineParser.DEFAULT,
- new DefaultHttpResponseFactory(),
- params);
- parser.parse();
- }
-
-}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestIdleConnectionEviction.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestIdleConnectionEviction.java
index 80d83e3..62dd2bc 100644
--- a/httpclient/src/test/java/org/apache/http/impl/conn/TestIdleConnectionEviction.java
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/TestIdleConnectionEviction.java
@@ -41,7 +41,6 @@ import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.localserver.LocalTestServer;
import org.apache.http.localserver.ServerTestBase;
import org.apache.http.params.BasicHttpParams;
@@ -69,7 +68,7 @@ public class TestIdleConnectionEviction extends ServerTestBase {
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
- ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry);
+ PoolingClientConnectionManager cm = new PoolingClientConnectionManager(schemeRegistry);
cm.setDefaultMaxPerRoute(10);
cm.setMaxTotal(50);
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestPoolingConnManager.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestPoolingConnManager.java
new file mode 100644
index 0000000..52fa9ac
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/TestPoolingConnManager.java
@@ -0,0 +1,790 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.conn;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.ClientConnectionOperator;
+import org.apache.http.conn.ClientConnectionRequest;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.conn.ManagedClientConnection;
+import org.apache.http.conn.OperatedClientConnection;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.scheme.SchemeSocketFactory;
+import org.apache.http.localserver.ServerTestBase;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for <code>PoolingClientConnectionManager</code> that do require a server
+ * to communicate with.
+ */
+public class TestPoolingConnManager extends ServerTestBase {
+
+ /**
+ * Tests executing several requests in parallel.
+ */
+ @Test
+ public void testParallelRequests() throws Exception {
+ // 3.x: TestHttpConnectionManager.testGetFromMultipleThreads
+
+ final int COUNT = 8; // adjust to execute more requests
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(COUNT/2);
+ mgr.setDefaultMaxPerRoute(COUNT/2);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+ final int rsplen = 8;
+ final String uri = "/random/" + rsplen;
+
+ ExecReqThread[] threads = new ExecReqThread [COUNT];
+ for (int i=0; i<COUNT; i++) {
+
+ HttpRequest request = new BasicHttpRequest
+ ("GET", uri, HttpVersion.HTTP_1_1);
+
+ ExecReqThread.RequestSpec ertrs = new ExecReqThread.RequestSpec();
+ ertrs.executor = httpExecutor;
+ ertrs.processor = httpProcessor;
+ ertrs.context = new BasicHttpContext(null);
+ ertrs.params = defaultParams;
+
+ ertrs.context.setAttribute
+ (ExecutionContext.HTTP_TARGET_HOST, target);
+ ertrs.context.setAttribute
+ (ExecutionContext.HTTP_REQUEST, request);
+
+ threads[i] = new ExecReqThread(mgr, route, 5000L, ertrs);
+ }
+
+ for (int i=0; i<threads.length; i++) {
+ threads[i].start();
+ }
+
+ for (int i=0; i<threads.length; i++) {
+ threads[i].join(10000);
+ Assert.assertNull("exception in thread " + i,
+ threads[i].getException());
+ Assert.assertNotNull("no response in thread " + i,
+ threads[i].getResponse());
+ Assert.assertEquals("wrong status code in thread " + i, 200,
+ threads[i].getResponse()
+ .getStatusLine().getStatusCode());
+ Assert.assertNotNull("no response data in thread " + i,
+ threads[i].getResponseData());
+ Assert.assertEquals("wrong length of data in thread" + i, rsplen,
+ threads[i].getResponseData().length);
+ }
+
+ mgr.shutdown();
+ }
+
+ private static ManagedClientConnection getConnection(
+ final ClientConnectionManager mgr,
+ final HttpRoute route,
+ long timeout,
+ TimeUnit unit) throws ConnectionPoolTimeoutException, InterruptedException {
+ ClientConnectionRequest connRequest = mgr.requestConnection(route, null);
+ return connRequest.getConnection(timeout, unit);
+ }
+
+ private static ManagedClientConnection getConnection(
+ final ClientConnectionManager mgr,
+ final HttpRoute route) throws ConnectionPoolTimeoutException, InterruptedException {
+ ClientConnectionRequest connRequest = mgr.requestConnection(route, null);
+ return connRequest.getConnection(0, null);
+ }
+
+ /**
+ * Tests releasing and re-using a connection after a response is read.
+ */
+ @Test
+ public void testReleaseConnection() throws Exception {
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(1);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+ final int rsplen = 8;
+ final String uri = "/random/" + rsplen;
+
+ HttpRequest request =
+ new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1);
+
+ ManagedClientConnection conn = getConnection(mgr, route);
+ conn.open(route, httpContext, defaultParams);
+
+ // a new context is created for each testcase, no need to reset
+ HttpResponse response = Helper.execute(
+ request, conn, target,
+ httpExecutor, httpProcessor, defaultParams, httpContext);
+
+ Assert.assertEquals("wrong status in first response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+ byte[] data = EntityUtils.toByteArray(response.getEntity());
+ Assert.assertEquals("wrong length of first response entity",
+ rsplen, data.length);
+ // ignore data, but it must be read
+
+ // check that there is no auto-release by default
+ try {
+ // this should fail quickly, connection has not been released
+ getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+ // release connection without marking for re-use
+ // expect the next connection obtained to be closed
+ mgr.releaseConnection(conn, -1, null);
+ conn = getConnection(mgr, route);
+ Assert.assertFalse("connection should have been closed", conn.isOpen());
+
+ // repeat the communication, no need to prepare the request again
+ conn.open(route, httpContext, defaultParams);
+ httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
+ response = httpExecutor.execute(request, conn, httpContext);
+ httpExecutor.postProcess(response, httpProcessor, httpContext);
+
+ Assert.assertEquals("wrong status in second response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+ data = EntityUtils.toByteArray(response.getEntity());
+ Assert.assertEquals("wrong length of second response entity",
+ rsplen, data.length);
+ // ignore data, but it must be read
+
+ // release connection after marking it for re-use
+ // expect the next connection obtained to be open
+ conn.markReusable();
+ mgr.releaseConnection(conn, -1, null);
+ conn = getConnection(mgr, route);
+ Assert.assertTrue("connection should have been open", conn.isOpen());
+
+ // repeat the communication, no need to prepare the request again
+ httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
+ response = httpExecutor.execute(request, conn, httpContext);
+ httpExecutor.postProcess(response, httpProcessor, httpContext);
+
+ Assert.assertEquals("wrong status in third response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+ data = EntityUtils.toByteArray(response.getEntity());
+ Assert.assertEquals("wrong length of third response entity",
+ rsplen, data.length);
+ // ignore data, but it must be read
+
+ mgr.releaseConnection(conn, -1, null);
+ mgr.shutdown();
+ }
+
+ /**
+ * Tests releasing with time limits.
+ */
+ @Test
+ public void testReleaseConnectionWithTimeLimits() throws Exception {
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(1);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+ final int rsplen = 8;
+ final String uri = "/random/" + rsplen;
+
+ HttpRequest request =
+ new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1);
+
+ ManagedClientConnection conn = getConnection(mgr, route);
+ conn.open(route, httpContext, defaultParams);
+
+ // a new context is created for each testcase, no need to reset
+ HttpResponse response = Helper.execute(
+ request, conn, target,
+ httpExecutor, httpProcessor, defaultParams, httpContext);
+
+ Assert.assertEquals("wrong status in first response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+ byte[] data = EntityUtils.toByteArray(response.getEntity());
+ Assert.assertEquals("wrong length of first response entity",
+ rsplen, data.length);
+ // ignore data, but it must be read
+
+ // check that there is no auto-release by default
+ try {
+ // this should fail quickly, connection has not been released
+ getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+ // release connection without marking for re-use
+ // expect the next connection obtained to be closed
+ mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
+ conn = getConnection(mgr, route);
+ Assert.assertFalse("connection should have been closed", conn.isOpen());
+
+ // repeat the communication, no need to prepare the request again
+ conn.open(route, httpContext, defaultParams);
+ httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
+ response = httpExecutor.execute(request, conn, httpContext);
+ httpExecutor.postProcess(response, httpProcessor, httpContext);
+
+ Assert.assertEquals("wrong status in second response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+ data = EntityUtils.toByteArray(response.getEntity());
+ Assert.assertEquals("wrong length of second response entity",
+ rsplen, data.length);
+ // ignore data, but it must be read
+
+ // release connection after marking it for re-use
+ // expect the next connection obtained to be open
+ conn.markReusable();
+ mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
+ conn = getConnection(mgr, route);
+ Assert.assertTrue("connection should have been open", conn.isOpen());
+
+ // repeat the communication, no need to prepare the request again
+ httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
+ response = httpExecutor.execute(request, conn, httpContext);
+ httpExecutor.postProcess(response, httpProcessor, httpContext);
+
+ Assert.assertEquals("wrong status in third response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+ data = EntityUtils.toByteArray(response.getEntity());
+ Assert.assertEquals("wrong length of third response entity",
+ rsplen, data.length);
+ // ignore data, but it must be read
+
+ conn.markReusable();
+ mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
+ Thread.sleep(150);
+ conn = getConnection(mgr, route);
+ Assert.assertTrue("connection should have been closed", !conn.isOpen());
+
+ // repeat the communication, no need to prepare the request again
+ conn.open(route, httpContext, defaultParams);
+ httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
+ response = httpExecutor.execute(request, conn, httpContext);
+ httpExecutor.postProcess(response, httpProcessor, httpContext);
+
+ Assert.assertEquals("wrong status in third response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+ data = EntityUtils.toByteArray(response.getEntity());
+ Assert.assertEquals("wrong length of fourth response entity",
+ rsplen, data.length);
+ // ignore data, but it must be read
+
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testCloseExpiredIdleConnections() throws Exception {
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(1);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+
+ ManagedClientConnection conn = getConnection(mgr, route);
+ conn.open(route, httpContext, defaultParams);
+
+ Assert.assertEquals(1, mgr.getTotalStats().getLeased());
+ Assert.assertEquals(1, mgr.getStats(route).getLeased());
+ conn.markReusable();
+ mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
+
+ // Released, still active.
+ Assert.assertEquals(1, mgr.getTotalStats().getAvailable());
+ Assert.assertEquals(1, mgr.getStats(route).getAvailable());
+
+ mgr.closeExpiredConnections();
+
+ // Time has not expired yet.
+ Assert.assertEquals(1, mgr.getTotalStats().getAvailable());
+ Assert.assertEquals(1, mgr.getStats(route).getAvailable());
+
+ Thread.sleep(150);
+
+ mgr.closeExpiredConnections();
+
+ // Time expired now, connections are destroyed.
+ Assert.assertEquals(0, mgr.getTotalStats().getAvailable());
+ Assert.assertEquals(0, mgr.getStats(route).getAvailable());
+
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testCloseExpiredTTLConnections() throws Exception {
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager(
+ SchemeRegistryFactory.createDefault(), 100, TimeUnit.MILLISECONDS);
+ mgr.setMaxTotal(1);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+
+ ManagedClientConnection conn = getConnection(mgr, route);
+ conn.open(route, httpContext, defaultParams);
+
+ Assert.assertEquals(1, mgr.getTotalStats().getLeased());
+ Assert.assertEquals(1, mgr.getStats(route).getLeased());
+ // Release, let remain idle for forever
+ conn.markReusable();
+ mgr.releaseConnection(conn, -1, TimeUnit.MILLISECONDS);
+
+ // Released, still active.
+ Assert.assertEquals(1, mgr.getTotalStats().getAvailable());
+ Assert.assertEquals(1, mgr.getStats(route).getAvailable());
+
+ mgr.closeExpiredConnections();
+
+ // Time has not expired yet.
+ Assert.assertEquals(1, mgr.getTotalStats().getAvailable());
+ Assert.assertEquals(1, mgr.getStats(route).getAvailable());
+
+ Thread.sleep(150);
+
+ mgr.closeExpiredConnections();
+
+ // TTL expired now, connections are destroyed.
+ Assert.assertEquals(0, mgr.getTotalStats().getAvailable());
+ Assert.assertEquals(0, mgr.getStats(route).getAvailable());
+
+ mgr.shutdown();
+ }
+
+ /**
+ * Tests releasing connection from #abort method called from the
+ * main execution thread while there is no blocking I/O operation.
+ */
+ @Test
+ public void testReleaseConnectionOnAbort() throws Exception {
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(1);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+ final int rsplen = 8;
+ final String uri = "/random/" + rsplen;
+
+ HttpRequest request =
+ new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1);
+
+ ManagedClientConnection conn = getConnection(mgr, route);
+ conn.open(route, httpContext, defaultParams);
+
+ // a new context is created for each testcase, no need to reset
+ HttpResponse response = Helper.execute(
+ request, conn, target,
+ httpExecutor, httpProcessor, defaultParams, httpContext);
+
+ Assert.assertEquals("wrong status in first response",
+ HttpStatus.SC_OK,
+ response.getStatusLine().getStatusCode());
+
+ // check that there are no connections available
+ try {
+ // this should fail quickly, connection has not been released
+ getConnection(mgr, route, 100L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+ // abort the connection
+ Assert.assertTrue(conn instanceof ManagedClientConnection);
+ ((ManagedClientConnection) conn).abortConnection();
+
+ // the connection is expected to be released back to the manager
+ conn = getConnection(mgr, route, 5L, TimeUnit.SECONDS);
+ Assert.assertFalse("connection should have been closed", conn.isOpen());
+
+ mgr.releaseConnection(conn, -1, null);
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testAbortDuringConnecting() throws Exception {
+ final CountDownLatch connectLatch = new CountDownLatch(1);
+ final StallingSocketFactory stallingSocketFactory = new StallingSocketFactory(
+ connectLatch, WaitPolicy.BEFORE_CONNECT, PlainSocketFactory.getSocketFactory());
+ Scheme scheme = new Scheme("http", 80, stallingSocketFactory);
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(scheme);
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager(registry);
+ mgr.setMaxTotal(1);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+
+ final ManagedClientConnection conn = getConnection(mgr, route);
+
+ final AtomicReference<Throwable> throwRef = new AtomicReference<Throwable>();
+ Thread abortingThread = new Thread(new Runnable() {
+ public void run() {
+ try {
+ stallingSocketFactory.waitForState();
+ conn.abortConnection();
+ connectLatch.countDown();
+ } catch (Throwable e) {
+ throwRef.set(e);
+ }
+ }
+ });
+ abortingThread.start();
+
+ try {
+ conn.open(route, httpContext, defaultParams);
+ Assert.fail("expected SocketException");
+ } catch(SocketException expected) {}
+
+ abortingThread.join(5000);
+ if(throwRef.get() != null)
+ throw new RuntimeException(throwRef.get());
+
+ Assert.assertFalse(conn.isOpen());
+ Assert.assertEquals(0, localServer.getAcceptedConnectionCount());
+
+ // the connection is expected to be released back to the manager
+ ManagedClientConnection conn2 = getConnection(mgr, route, 5L, TimeUnit.SECONDS);
+ Assert.assertFalse("connection should have been closed", conn2.isOpen());
+
+ mgr.releaseConnection(conn2, -1, null);
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testAbortBeforeSocketCreate() throws Exception {
+ final CountDownLatch connectLatch = new CountDownLatch(1);
+ final StallingSocketFactory stallingSocketFactory = new StallingSocketFactory(
+ connectLatch, WaitPolicy.BEFORE_CREATE, PlainSocketFactory.getSocketFactory());
+ Scheme scheme = new Scheme("http", 80, stallingSocketFactory);
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(scheme);
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager(registry);
+ mgr.setMaxTotal(1);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+
+ final ManagedClientConnection conn = getConnection(mgr, route);
+
+ final AtomicReference<Throwable> throwRef = new AtomicReference<Throwable>();
+ Thread abortingThread = new Thread(new Runnable() {
+ public void run() {
+ try {
+ stallingSocketFactory.waitForState();
+ conn.abortConnection();
+ connectLatch.countDown();
+ } catch (Throwable e) {
+ throwRef.set(e);
+ }
+ }
+ });
+ abortingThread.start();
+
+ try {
+ conn.open(route, httpContext, defaultParams);
+ Assert.fail("expected exception");
+ } catch(IOException expected) {
+ Assert.assertEquals("Connection already shutdown", expected.getMessage());
+ }
+
+ abortingThread.join(5000);
+ if(throwRef.get() != null)
+ throw new RuntimeException(throwRef.get());
+
+ Assert.assertFalse(conn.isOpen());
+ Assert.assertEquals(0, localServer.getAcceptedConnectionCount());
+
+ // the connection is expected to be released back to the manager
+ ManagedClientConnection conn2 = getConnection(mgr, route, 5L, TimeUnit.SECONDS);
+ Assert.assertFalse("connection should have been closed", conn2.isOpen());
+
+ mgr.releaseConnection(conn2, -1, null);
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testAbortAfterSocketConnect() throws Exception {
+ final CountDownLatch connectLatch = new CountDownLatch(1);
+ final StallingSocketFactory stallingSocketFactory = new StallingSocketFactory(
+ connectLatch, WaitPolicy.AFTER_CONNECT, PlainSocketFactory.getSocketFactory());
+ Scheme scheme = new Scheme("http", 80, stallingSocketFactory);
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(scheme);
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager(registry);
+ mgr.setMaxTotal(1);
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+
+ final ManagedClientConnection conn = getConnection(mgr, route);
+
+ final AtomicReference<Throwable> throwRef = new AtomicReference<Throwable>();
+ Thread abortingThread = new Thread(new Runnable() {
+ public void run() {
+ try {
+ stallingSocketFactory.waitForState();
+ conn.abortConnection();
+ connectLatch.countDown();
+ } catch (Throwable e) {
+ throwRef.set(e);
+ }
+ }
+ });
+ abortingThread.start();
+
+ try {
+ conn.open(route, httpContext, defaultParams);
+ Assert.fail("expected SocketException");
+ } catch(SocketException expected) {}
+
+ abortingThread.join(5000);
+ if(throwRef.get() != null)
+ throw new RuntimeException(throwRef.get());
+
+ Assert.assertFalse(conn.isOpen());
+ // Give the server a bit of time to accept the connection, but
+ // ensure that it can accept it.
+ for(int i = 0; i < 10; i++) {
+ if(localServer.getAcceptedConnectionCount() == 1)
+ break;
+ Thread.sleep(100);
+ }
+ Assert.assertEquals(1, localServer.getAcceptedConnectionCount());
+
+ // the connection is expected to be released back to the manager
+ ManagedClientConnection conn2 = getConnection(mgr, route, 5L, TimeUnit.SECONDS);
+ Assert.assertFalse("connection should have been closed", conn2.isOpen());
+
+ mgr.releaseConnection(conn2, -1, null);
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testAbortAfterOperatorOpen() throws Exception {
+ final CountDownLatch connectLatch = new CountDownLatch(1);
+ final AtomicReference<StallingOperator> operatorRef = new AtomicReference<StallingOperator>();
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager() {
+ @Override
+ protected ClientConnectionOperator createConnectionOperator(
+ SchemeRegistry schreg) {
+ operatorRef.set(new StallingOperator(connectLatch, WaitPolicy.AFTER_OPEN, super.createConnectionOperator(schreg)));
+ return operatorRef.get();
+ }
+ };
+ mgr.setMaxTotal(1);
+ Assert.assertNotNull(operatorRef.get());
+
+ final HttpHost target = getServerHttp();
+ final HttpRoute route = new HttpRoute(target, null, false);
+
+ final ManagedClientConnection conn = getConnection(mgr, route);
+
+ final AtomicReference<Throwable> throwRef = new AtomicReference<Throwable>();
+ Thread abortingThread = new Thread(new Runnable() {
+ public void run() {
+ try {
+ operatorRef.get().waitForState();
+ conn.abortConnection();
+ connectLatch.countDown();
+ } catch (Throwable e) {
+ throwRef.set(e);
+ }
+ }
+ });
+ abortingThread.start();
+
+ try {
+ conn.open(route, httpContext, defaultParams);
+ Assert.fail("expected exception");
+ } catch(IOException iox) {
+ }
+
+ abortingThread.join(5000);
+ if(throwRef.get() != null)
+ throw new RuntimeException(throwRef.get());
+
+ Assert.assertFalse(conn.isOpen());
+ // Give the server a bit of time to accept the connection, but
+ // ensure that it can accept it.
+ for(int i = 0; i < 10; i++) {
+ if(localServer.getAcceptedConnectionCount() == 1)
+ break;
+ Thread.sleep(100);
+ }
+ Assert.assertEquals(1, localServer.getAcceptedConnectionCount());
+
+ // the connection is expected to be released back to the manager
+ ManagedClientConnection conn2 = getConnection(mgr, route, 5L, TimeUnit.SECONDS);
+ Assert.assertFalse("connection should have been closed", conn2.isOpen());
+
+ mgr.releaseConnection(conn2, -1, null);
+ mgr.shutdown();
+ }
+
+ private static class LatchSupport {
+ private final CountDownLatch continueLatch;
+ private final CountDownLatch waitLatch = new CountDownLatch(1);
+ protected final WaitPolicy waitPolicy;
+
+ LatchSupport(CountDownLatch continueLatch, WaitPolicy waitPolicy) {
+ this.continueLatch = continueLatch;
+ this.waitPolicy = waitPolicy;
+ }
+
+ void waitForState() throws InterruptedException {
+ if(!waitLatch.await(1, TimeUnit.SECONDS))
+ throw new RuntimeException("waited too long");
+ }
+
+ void latch() {
+ waitLatch.countDown();
+ try {
+ if (!continueLatch.await(60, TimeUnit.SECONDS))
+ throw new RuntimeException("waited too long!");
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private static class StallingOperator extends LatchSupport implements ClientConnectionOperator {
+ private final ClientConnectionOperator delegate;
+
+ public StallingOperator(CountDownLatch continueLatch,
+ WaitPolicy waitPolicy, ClientConnectionOperator delegate) {
+ super(continueLatch, waitPolicy);
+ this.delegate = delegate;
+ }
+
+ public OperatedClientConnection createConnection() {
+ return delegate.createConnection();
+ }
+
+ public void openConnection(OperatedClientConnection conn,
+ HttpHost target, InetAddress local, HttpContext context,
+ HttpParams params) throws IOException {
+ delegate.openConnection(conn, target, local, context, params);
+ if(waitPolicy == WaitPolicy.AFTER_OPEN)
+ latch();
+ }
+
+ public void updateSecureConnection(OperatedClientConnection conn,
+ HttpHost target, HttpContext context, HttpParams params)
+ throws IOException {
+ delegate.updateSecureConnection(conn, target, context, params);
+ }
+ }
+
+ private static class StallingSocketFactory extends LatchSupport implements SchemeSocketFactory {
+
+ private final SchemeSocketFactory delegate;
+
+ public StallingSocketFactory(
+ final CountDownLatch continueLatch,
+ final WaitPolicy waitPolicy,
+ final SchemeSocketFactory delegate) {
+ super(continueLatch, waitPolicy);
+ this.delegate = delegate;
+ }
+
+ public Socket connectSocket(
+ final Socket sock,
+ final InetSocketAddress remoteAddress,
+ final InetSocketAddress localAddress,
+ final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
+ if(waitPolicy == WaitPolicy.BEFORE_CONNECT)
+ latch();
+
+ Socket socket = delegate.connectSocket(sock, remoteAddress, localAddress, params);
+
+ if(waitPolicy == WaitPolicy.AFTER_CONNECT)
+ latch();
+
+ return socket;
+ }
+
+ public Socket createSocket(final HttpParams params) throws IOException {
+ if(waitPolicy == WaitPolicy.BEFORE_CREATE)
+ latch();
+
+ return delegate.createSocket(params);
+ }
+
+ public boolean isSecure(Socket sock) throws IllegalArgumentException {
+ return delegate.isSecure(sock);
+ }
+ }
+
+ private enum WaitPolicy { BEFORE_CREATE, BEFORE_CONNECT, AFTER_CONNECT, AFTER_OPEN }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestPoolingConnManagerNoServer.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestPoolingConnManagerNoServer.java
new file mode 100644
index 0000000..a51f6e7
--- /dev/null
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/TestPoolingConnManagerNoServer.java
@@ -0,0 +1,533 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.conn;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpVersion;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.ClientConnectionRequest;
+import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.conn.ManagedClientConnection;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Tests for <code>PoolingClientConnectionManager</code> that do not require a server to
+ * communicate with.
+ */
+public class TestPoolingConnManagerNoServer {
+
+ private static ManagedClientConnection getConnection(
+ final ClientConnectionManager mgr,
+ final HttpRoute route,
+ long timeout,
+ TimeUnit unit) throws ConnectionPoolTimeoutException, InterruptedException {
+ ClientConnectionRequest connRequest = mgr.requestConnection(route, null);
+ return connRequest.getConnection(timeout, unit);
+ }
+
+ private static ManagedClientConnection getConnection(
+ final ClientConnectionManager mgr,
+ final HttpRoute route) throws ConnectionPoolTimeoutException, InterruptedException {
+ ClientConnectionRequest connRequest = mgr.requestConnection(route, null);
+ return connRequest.getConnection(0, null);
+ }
+
+ /**
+ * Instantiates default parameters.
+ *
+ * @return the default parameters
+ */
+ public HttpParams createDefaultParams() {
+
+ HttpParams params = new BasicHttpParams();
+ HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
+ HttpProtocolParams.setUseExpectContinue(params, false);
+
+ return params;
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testIllegalConstructor() {
+ new PoolingClientConnectionManager(null);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testGetConnection()
+ throws InterruptedException, ConnectionPoolTimeoutException {
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+
+ HttpHost target = new HttpHost("www.test.invalid", 80, "http");
+ HttpRoute route = new HttpRoute(target, null, false);
+
+ ManagedClientConnection conn = getConnection(mgr, route);
+ Assert.assertNotNull(conn);
+ Assert.assertNull(conn.getRoute());
+ Assert.assertFalse(conn.isOpen());
+
+ mgr.releaseConnection(conn, -1, null);
+
+ try {
+ getConnection(mgr, null);
+ } finally {
+ mgr.shutdown();
+ }
+ }
+
+ // testTimeout in 3.x TestHttpConnectionManager is redundant
+ // several other tests here rely on timeout behavior
+ @Test
+ public void testMaxConnTotal()
+ throws InterruptedException, ConnectionPoolTimeoutException {
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(2);
+ mgr.setDefaultMaxPerRoute(1);
+
+ HttpHost target1 = new HttpHost("www.test1.invalid", 80, "http");
+ HttpRoute route1 = new HttpRoute(target1, null, false);
+ HttpHost target2 = new HttpHost("www.test2.invalid", 80, "http");
+ HttpRoute route2 = new HttpRoute(target2, null, false);
+
+ ManagedClientConnection conn1 = getConnection(mgr, route1);
+ Assert.assertNotNull(conn1);
+ ManagedClientConnection conn2 = getConnection(mgr, route2);
+ Assert.assertNotNull(conn2);
+
+ try {
+ // this should fail quickly, connection has not been released
+ getConnection(mgr, route2, 100L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+ // release one of the connections
+ mgr.releaseConnection(conn2, -1, null);
+ conn2 = null;
+
+ // there should be a connection available now
+ try {
+ getConnection(mgr, route2, 100L, TimeUnit.MILLISECONDS);
+ } catch (ConnectionPoolTimeoutException cptx) {
+ Assert.fail("connection should have been available: " + cptx);
+ }
+
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testMaxConnPerHost() throws Exception {
+
+ HttpHost target1 = new HttpHost("www.test1.invalid", 80, "http");
+ HttpRoute route1 = new HttpRoute(target1, null, false);
+ HttpHost target2 = new HttpHost("www.test2.invalid", 80, "http");
+ HttpRoute route2 = new HttpRoute(target2, null, false);
+ HttpHost target3 = new HttpHost("www.test3.invalid", 80, "http");
+ HttpRoute route3 = new HttpRoute(target3, null, false);
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(100);
+ mgr.setDefaultMaxPerRoute(1);
+ mgr.setMaxPerRoute(route2, 2);
+ mgr.setMaxPerRoute(route3, 3);
+
+ // route 3, limit 3
+ ManagedClientConnection conn1 =
+ getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
+ Assert.assertNotNull(conn1);
+ ManagedClientConnection conn2 =
+ getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
+ Assert.assertNotNull(conn2);
+ ManagedClientConnection conn3 =
+ getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
+ Assert.assertNotNull(conn3);
+ try {
+ // should fail quickly, connection has not been released
+ getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+ // route 2, limit 2
+ conn1 = getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
+ conn2 = getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
+ try {
+ // should fail quickly, connection has not been released
+ getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+ // route 1, should use default limit of 1
+ conn1 = getConnection(mgr, route1, 10L, TimeUnit.MILLISECONDS);
+ try {
+ // should fail quickly, connection has not been released
+ getConnection(mgr, route1, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+
+ // check releaseConnection with invalid arguments
+ try {
+ mgr.releaseConnection(null, -1, null);
+ Assert.fail("null connection adapter not detected");
+ } catch (IllegalArgumentException iax) {
+ // expected
+ }
+ try {
+ ManagedClientConnection conn = Mockito.mock(ManagedClientConnection.class);
+ mgr.releaseConnection(conn, -1, null);
+ Assert.fail("foreign connection adapter not detected");
+ } catch (IllegalArgumentException iax) {
+ // expected
+ }
+
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testReleaseConnection() throws Exception {
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(3);
+ mgr.setDefaultMaxPerRoute(1);
+
+ HttpHost target1 = new HttpHost("www.test1.invalid", 80, "http");
+ HttpRoute route1 = new HttpRoute(target1, null, false);
+ HttpHost target2 = new HttpHost("www.test2.invalid", 80, "http");
+ HttpRoute route2 = new HttpRoute(target2, null, false);
+ HttpHost target3 = new HttpHost("www.test3.invalid", 80, "http");
+ HttpRoute route3 = new HttpRoute(target3, null, false);
+
+ // the first three allocations should pass
+ ManagedClientConnection conn1 =
+ getConnection(mgr, route1, 10L, TimeUnit.MILLISECONDS);
+ ManagedClientConnection conn2 =
+ getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
+ ManagedClientConnection conn3 =
+ getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
+ Assert.assertNotNull(conn1);
+ Assert.assertNotNull(conn2);
+ Assert.assertNotNull(conn3);
+
+ // obtaining another connection for either of the three should fail
+ // this is somehow redundant with testMaxConnPerHost
+ try {
+ getConnection(mgr, route1, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+ try {
+ getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+ try {
+ getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+ // now release one and check that exactly that one can be obtained then
+ mgr.releaseConnection(conn2, -1, null);
+ conn2 = null;
+ try {
+ getConnection(mgr, route1, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+ // this one succeeds
+ conn2 = getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
+ Assert.assertNotNull(conn2);
+ try {
+ getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("ConnectionPoolTimeoutException should have been thrown");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testDeleteClosedConnections()
+ throws InterruptedException, ConnectionPoolTimeoutException {
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+
+ HttpHost target = new HttpHost("www.test.invalid", 80, "http");
+ HttpRoute route = new HttpRoute(target, null, false);
+
+ ManagedClientConnection conn = getConnection(mgr, route);
+
+ Assert.assertEquals(1, mgr.getTotalStats().getLeased());
+ Assert.assertEquals(1, mgr.getStats(route).getLeased());
+ conn.markReusable();
+ mgr.releaseConnection(conn, -1, null);
+
+ Assert.assertEquals(1, mgr.getTotalStats().getAvailable());
+ Assert.assertEquals(1, mgr.getStats(route).getAvailable());
+
+ // this implicitly deletes them
+ mgr.closeIdleConnections(0L, TimeUnit.MILLISECONDS);
+
+ Assert.assertEquals(0, mgr.getTotalStats().getAvailable());
+ Assert.assertEquals(0, mgr.getStats(route).getAvailable());
+
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testShutdown() throws Exception {
+ // 3.x: TestHttpConnectionManager.testShutdown
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(1);
+ mgr.setDefaultMaxPerRoute(1);
+
+ HttpHost target = new HttpHost("www.test.invalid", 80, "http");
+ HttpRoute route = new HttpRoute(target, null, false);
+
+ // get the only connection, then start an extra thread
+ // on shutdown, the extra thread should get an exception
+
+ ManagedClientConnection conn =
+ getConnection(mgr, route, 1L, TimeUnit.MILLISECONDS);
+ GetConnThread gct = new GetConnThread(mgr, route, 0L); // no timeout
+ gct.start();
+ Thread.sleep(100); // give extra thread time to block
+
+
+ mgr.shutdown();
+
+ // First release the connection. If the manager keeps working
+ // despite the shutdown, this will deblock the extra thread.
+ // The release itself should turn into a no-op, without exception.
+ mgr.releaseConnection(conn, -1, null);
+
+
+ gct.join(10000);
+ Assert.assertNull("thread should not have obtained connection",
+ gct.getConnection());
+ Assert.assertNotNull("thread should have gotten an exception",
+ gct.getException());
+ Assert.assertSame("thread got wrong exception",
+ InterruptedException.class, gct.getException().getClass());
+
+ // the manager is down, we should not be able to get a connection
+ try {
+ getConnection(mgr, route, 1L, TimeUnit.MILLISECONDS);
+ Assert.fail("shut-down manager does not raise exception");
+ } catch (IllegalStateException isx) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testInterruptThread() throws Exception {
+ // 3.x: TestHttpConnectionManager.testWaitingThreadInterrupted
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(1);
+
+ HttpHost target = new HttpHost("www.test.invalid", 80, "http");
+ HttpRoute route = new HttpRoute(target, null, false);
+
+ // get the only connection, then start an extra thread
+ ManagedClientConnection conn =
+ getConnection(mgr, route, 1L, TimeUnit.MILLISECONDS);
+ GetConnThread gct = new GetConnThread(mgr, route, 0L); // no timeout
+ gct.start();
+ Thread.sleep(100); // give extra thread time to block
+
+
+ // interrupt the thread, it should cancel waiting with an exception
+ gct.interrupt();
+
+
+ gct.join(10000);
+ Assert.assertNotNull("thread should have gotten an exception",
+ gct.getException());
+ Assert.assertSame("thread got wrong exception",
+ InterruptedException.class,
+ gct.getException().getClass());
+
+ // make sure the manager is still working
+ try {
+ getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("should have gotten a timeout");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+ mgr.releaseConnection(conn, -1, null);
+ // this time: no exception
+ conn = getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
+ Assert.assertNotNull("should have gotten a connection", conn);
+
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testReusePreference() throws Exception {
+ // 3.x: TestHttpConnectionManager.testHostReusePreference
+
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(1);
+
+ HttpHost target1 = new HttpHost("www.test1.invalid", 80, "http");
+ HttpRoute route1 = new HttpRoute(target1, null, false);
+ HttpHost target2 = new HttpHost("www.test2.invalid", 80, "http");
+ HttpRoute route2 = new HttpRoute(target2, null, false);
+
+ // get the only connection, then start two extra threads
+ ManagedClientConnection conn =
+ getConnection(mgr, route1, 1L, TimeUnit.MILLISECONDS);
+ GetConnThread gct1 = new GetConnThread(mgr, route1, 1000L);
+ GetConnThread gct2 = new GetConnThread(mgr, route2, 1000L);
+
+ // the second thread is started first, to distinguish the
+ // route-based reuse preference from first-come, first-served
+ gct2.start();
+ Thread.sleep(100); // give the thread time to block
+ gct1.start();
+ Thread.sleep(100); // give the thread time to block
+
+
+ // releasing the connection for route1 should deblock thread1
+ // the other thread gets a timeout
+ mgr.releaseConnection(conn, -1, null);
+
+ gct1.join(10000);
+ gct2.join(10000);
+
+ Assert.assertNotNull("thread 1 should have gotten a connection",
+ gct1.getConnection());
+ Assert.assertNull ("thread 2 should NOT have gotten a connection",
+ gct2.getConnection());
+
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testAbortAfterRequestStarts() throws Exception {
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(1);
+
+ HttpHost target = new HttpHost("www.test.invalid", 80, "http");
+ HttpRoute route = new HttpRoute(target, null, false);
+
+ // get the only connection, then start an extra thread
+ ManagedClientConnection conn = getConnection(mgr, route, 1L, TimeUnit.MILLISECONDS);
+ ClientConnectionRequest request = mgr.requestConnection(route, null);
+ GetConnThread gct = new GetConnThread(request, route, 0L); // no timeout
+ gct.start();
+ Thread.sleep(100); // give extra thread time to block
+
+ request.abortRequest();
+
+ gct.join(10000);
+ Assert.assertNotNull("thread should have gotten an exception",
+ gct.getException());
+ Assert.assertSame("thread got wrong exception",
+ InterruptedException.class,
+ gct.getException().getClass());
+
+ // make sure the manager is still working
+ try {
+ getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("should have gotten a timeout");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+ mgr.releaseConnection(conn, -1, null);
+ // this time: no exception
+ conn = getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
+ Assert.assertNotNull("should have gotten a connection", conn);
+
+ mgr.shutdown();
+ }
+
+ @Test
+ public void testAbortBeforeRequestStarts() throws Exception {
+ PoolingClientConnectionManager mgr = new PoolingClientConnectionManager();
+ mgr.setMaxTotal(1);
+
+ HttpHost target = new HttpHost("www.test.invalid", 80, "http");
+ HttpRoute route = new HttpRoute(target, null, false);
+
+ // get the only connection, then start an extra thread
+ ManagedClientConnection conn = getConnection(mgr, route, 1L, TimeUnit.MILLISECONDS);
+ ClientConnectionRequest request = mgr.requestConnection(route, null);
+ request.abortRequest();
+
+ GetConnThread gct = new GetConnThread(request, route, 0L); // no timeout
+ gct.start();
+ Thread.sleep(100); // give extra thread time to block
+
+ gct.join(10000);
+ Assert.assertNotNull("thread should have gotten an exception",
+ gct.getException());
+ Assert.assertSame("thread got wrong exception",
+ InterruptedException.class,
+ gct.getException().getClass());
+
+ // make sure the manager is still working
+ try {
+ getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
+ Assert.fail("should have gotten a timeout");
+ } catch (ConnectionPoolTimeoutException e) {
+ // expected
+ }
+
+ mgr.releaseConnection(conn, -1, null);
+ // this time: no exception
+ conn = getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
+ Assert.assertNotNull("should have gotten a connection", conn);
+
+ mgr.shutdown();
+ }
+
+}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestSCMWithServer.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestSCMWithServer.java
deleted file mode 100644
index 572802d..0000000
--- a/httpclient/src/test/java/org/apache/http/impl/conn/TestSCMWithServer.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.impl.conn;
-
-import java.util.concurrent.TimeUnit;
-
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.HttpVersion;
-import org.apache.http.conn.ManagedClientConnection;
-import org.apache.http.conn.routing.HttpRoute;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.localserver.ServerTestBase;
-import org.apache.http.message.BasicHttpRequest;
-import org.apache.http.protocol.ExecutionContext;
-import org.apache.http.util.EntityUtils;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class TestSCMWithServer extends ServerTestBase {
-
- /**
- * Helper to instantiate a <code>SingleClientConnManager</code>.
- *
- * @param schreg the scheme registry, or
- * <code>null</code> to use defaults
- *
- * @return a connection manager to test
- */
- public SingleClientConnManager createSCCM(SchemeRegistry schreg) {
- if (schreg == null)
- schreg = supportedSchemes;
- return new SingleClientConnManager(schreg);
- }
-
- /**
- * Tests that SCM can still connect to the same host after
- * a connection was aborted.
- */
- @Test
- public void testOpenAfterAbort() throws Exception {
- SingleClientConnManager mgr = createSCCM(null);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
-
- ManagedClientConnection conn = mgr.getConnection(route, null);
- Assert.assertTrue(conn instanceof AbstractClientConnAdapter);
- ((AbstractClientConnAdapter) conn).abortConnection();
-
- conn = mgr.getConnection(route, null);
- Assert.assertFalse("connection should have been closed", conn.isOpen());
- conn.open(route, httpContext, defaultParams);
-
- mgr.releaseConnection(conn, -1, null);
- mgr.shutdown();
- }
-
- /**
- * Tests releasing with time limits.
- */
- @Test
- public void testReleaseConnectionWithTimeLimits() throws Exception {
-
- SingleClientConnManager mgr = createSCCM(null);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
- final int rsplen = 8;
- final String uri = "/random/" + rsplen;
-
- HttpRequest request =
- new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1);
-
- ManagedClientConnection conn = mgr.getConnection(route, null);
- conn.open(route, httpContext, defaultParams);
-
- // a new context is created for each testcase, no need to reset
- HttpResponse response = Helper.execute(
- request, conn, target,
- httpExecutor, httpProcessor, defaultParams, httpContext);
-
- Assert.assertEquals("wrong status in first response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
- byte[] data = EntityUtils.toByteArray(response.getEntity());
- Assert.assertEquals("wrong length of first response entity",
- rsplen, data.length);
- // ignore data, but it must be read
-
- // release connection without marking for re-use
- // expect the next connection obtained to be closed
- mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
- conn = mgr.getConnection(route, null);
- Assert.assertFalse("connection should have been closed", conn.isOpen());
-
- // repeat the communication, no need to prepare the request again
- conn.open(route, httpContext, defaultParams);
- httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
- response = httpExecutor.execute(request, conn, httpContext);
- httpExecutor.postProcess(response, httpProcessor, httpContext);
-
- Assert.assertEquals("wrong status in second response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
- data = EntityUtils.toByteArray(response.getEntity());
- Assert.assertEquals("wrong length of second response entity",
- rsplen, data.length);
- // ignore data, but it must be read
-
- // release connection after marking it for re-use
- // expect the next connection obtained to be open
- conn.markReusable();
- mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
- conn = mgr.getConnection(route, null);
- Assert.assertTrue("connection should have been open", conn.isOpen());
-
- // repeat the communication, no need to prepare the request again
- httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
- response = httpExecutor.execute(request, conn, httpContext);
- httpExecutor.postProcess(response, httpProcessor, httpContext);
-
- Assert.assertEquals("wrong status in third response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
- data = EntityUtils.toByteArray(response.getEntity());
- Assert.assertEquals("wrong length of third response entity",
- rsplen, data.length);
- // ignore data, but it must be read
-
- conn.markReusable();
- mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
- Thread.sleep(150);
- conn = mgr.getConnection(route, null);
- Assert.assertTrue("connection should have been closed", !conn.isOpen());
-
- // repeat the communication, no need to prepare the request again
- conn.open(route, httpContext, defaultParams);
- httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
- response = httpExecutor.execute(request, conn, httpContext);
- httpExecutor.postProcess(response, httpProcessor, httpContext);
-
- Assert.assertEquals("wrong status in third response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
- data = EntityUtils.toByteArray(response.getEntity());
- Assert.assertEquals("wrong length of fourth response entity",
- rsplen, data.length);
- // ignore data, but it must be read
-
- mgr.shutdown();
- }
-
- @Test
- public void testCloseExpiredConnections() throws Exception {
-
- SingleClientConnManager mgr = createSCCM(null);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
-
- ManagedClientConnection conn = mgr.getConnection(route, null);
- conn.open(route, httpContext, defaultParams);
- mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
-
- mgr.closeExpiredConnections();
-
- conn = mgr.getConnection(route, null);
- Assert.assertTrue(conn.isOpen());
- mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
-
- Thread.sleep(150);
- mgr.closeExpiredConnections();
- conn = mgr.getConnection(route, null);
- Assert.assertFalse(conn.isOpen());
-
- mgr.shutdown();
- }
-
- @Test(expected=IllegalStateException.class)
- public void testAlreadyLeased() throws Exception {
-
- SingleClientConnManager mgr = createSCCM(null);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
-
- ManagedClientConnection conn = mgr.getConnection(route, null);
- mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
-
- mgr.getConnection(route, null);
- mgr.getConnection(route, null);
- }
-
-}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMNoServer.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMNoServer.java
deleted file mode 100644
index 79a1563..0000000
--- a/httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMNoServer.java
+++ /dev/null
@@ -1,577 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.impl.conn;
-
-import java.util.concurrent.TimeUnit;
-
-import org.apache.http.HttpHost;
-import org.apache.http.HttpVersion;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.ClientConnectionRequest;
-import org.apache.http.conn.ConnectionPoolTimeoutException;
-import org.apache.http.conn.ManagedClientConnection;
-import org.apache.http.conn.routing.HttpRoute;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpParams;
-import org.apache.http.params.HttpProtocolParams;
-import org.junit.Assert;
-import org.junit.Test;
-
-/**
- * Tests for <code>ThreadSafeClientConnManager</code> that do not require
- * a server to communicate with.
- */
-public class TestTSCCMNoServer {
-
- private static ManagedClientConnection getConnection(
- final ClientConnectionManager mgr,
- final HttpRoute route,
- long timeout,
- TimeUnit unit) throws ConnectionPoolTimeoutException, InterruptedException {
- ClientConnectionRequest connRequest = mgr.requestConnection(route, null);
- return connRequest.getConnection(timeout, unit);
- }
-
- private static ManagedClientConnection getConnection(
- final ClientConnectionManager mgr,
- final HttpRoute route) throws ConnectionPoolTimeoutException, InterruptedException {
- ClientConnectionRequest connRequest = mgr.requestConnection(route, null);
- return connRequest.getConnection(0, null);
- }
-
- /**
- * Helper to instantiate a <code>ThreadSafeClientConnManager</code>.
- *
- * @param schreg the scheme registry, or
- * <code>null</code> to use defaults
- *
- * @return a connection manager to test
- */
- public ThreadSafeClientConnManager createTSCCM(SchemeRegistry schreg) {
- if (schreg == null)
- schreg = createSchemeRegistry();
- return new ThreadSafeClientConnManager(schreg);
- }
-
-
- /**
- * Instantiates default parameters.
- *
- * @return the default parameters
- */
- public HttpParams createDefaultParams() {
-
- HttpParams params = new BasicHttpParams();
- HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
- HttpProtocolParams.setUseExpectContinue(params, false);
-
- return params;
- }
-
- /**
- * Instantiates a default scheme registry.
- *
- * @return the default scheme registry
- */
- public SchemeRegistry createSchemeRegistry() {
-
- SchemeRegistry schreg = new SchemeRegistry();
- schreg.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
-
- return schreg;
- }
-
- @Test
- public void testConstructor() {
- SchemeRegistry schreg = createSchemeRegistry();
-
- ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(schreg);
- Assert.assertNotNull(mgr);
- mgr.shutdown();
- }
-
- @Test(expected=IllegalArgumentException.class)
- public void testIllegalConstructor() {
- new ThreadSafeClientConnManager(null);
- }
-
- @Test(expected=IllegalArgumentException.class)
- public void testGetConnection()
- throws InterruptedException, ConnectionPoolTimeoutException {
- ThreadSafeClientConnManager mgr = createTSCCM(null);
-
- HttpHost target = new HttpHost("www.test.invalid", 80, "http");
- HttpRoute route = new HttpRoute(target, null, false);
-
- ManagedClientConnection conn = getConnection(mgr, route);
- Assert.assertNotNull(conn);
- Assert.assertNull(conn.getRoute());
- Assert.assertFalse(conn.isOpen());
-
- mgr.releaseConnection(conn, -1, null);
-
- try {
- getConnection(mgr, null);
- } finally {
- mgr.shutdown();
- }
- }
-
- // testTimeout in 3.x TestHttpConnectionManager is redundant
- // several other tests here rely on timeout behavior
- @Test
- public void testMaxConnTotal()
- throws InterruptedException, ConnectionPoolTimeoutException {
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(2);
- mgr.setDefaultMaxPerRoute(1);
-
- HttpHost target1 = new HttpHost("www.test1.invalid", 80, "http");
- HttpRoute route1 = new HttpRoute(target1, null, false);
- HttpHost target2 = new HttpHost("www.test2.invalid", 80, "http");
- HttpRoute route2 = new HttpRoute(target2, null, false);
-
- ManagedClientConnection conn1 = getConnection(mgr, route1);
- Assert.assertNotNull(conn1);
- ManagedClientConnection conn2 = getConnection(mgr, route2);
- Assert.assertNotNull(conn2);
-
- try {
- // this should fail quickly, connection has not been released
- getConnection(mgr, route2, 100L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
- // release one of the connections
- mgr.releaseConnection(conn2, -1, null);
- conn2 = null;
-
- // there should be a connection available now
- try {
- getConnection(mgr, route2, 100L, TimeUnit.MILLISECONDS);
- } catch (ConnectionPoolTimeoutException cptx) {
- Assert.fail("connection should have been available: " + cptx);
- }
-
- mgr.shutdown();
- }
-
- @Test
- public void testMaxConnPerHost() throws Exception {
-
- HttpHost target1 = new HttpHost("www.test1.invalid", 80, "http");
- HttpRoute route1 = new HttpRoute(target1, null, false);
- HttpHost target2 = new HttpHost("www.test2.invalid", 80, "http");
- HttpRoute route2 = new HttpRoute(target2, null, false);
- HttpHost target3 = new HttpHost("www.test3.invalid", 80, "http");
- HttpRoute route3 = new HttpRoute(target3, null, false);
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(100);
- mgr.setDefaultMaxPerRoute(1);
- mgr.setMaxForRoute(route2, 2);
- mgr.setMaxForRoute(route3, 3);
-
- // route 3, limit 3
- ManagedClientConnection conn1 =
- getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
- Assert.assertNotNull(conn1);
- ManagedClientConnection conn2 =
- getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
- Assert.assertNotNull(conn2);
- ManagedClientConnection conn3 =
- getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
- Assert.assertNotNull(conn3);
- try {
- // should fail quickly, connection has not been released
- getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
- // route 2, limit 2
- conn1 = getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
- conn2 = getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
- try {
- // should fail quickly, connection has not been released
- getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
- // route 1, should use default limit of 1
- conn1 = getConnection(mgr, route1, 10L, TimeUnit.MILLISECONDS);
- try {
- // should fail quickly, connection has not been released
- getConnection(mgr, route1, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
-
- // check releaseConnection with invalid arguments
- try {
- mgr.releaseConnection(null, -1, null);
- Assert.fail("null connection adapter not detected");
- } catch (IllegalArgumentException iax) {
- // expected
- }
- try {
- mgr.releaseConnection(new ClientConnAdapterMockup(null), -1, null);
- Assert.fail("foreign connection adapter not detected");
- } catch (IllegalArgumentException iax) {
- // expected
- }
-
- mgr.shutdown();
- }
-
- @Test
- public void testReleaseConnection() throws Exception {
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(3);
- mgr.setDefaultMaxPerRoute(1);
-
- HttpHost target1 = new HttpHost("www.test1.invalid", 80, "http");
- HttpRoute route1 = new HttpRoute(target1, null, false);
- HttpHost target2 = new HttpHost("www.test2.invalid", 80, "http");
- HttpRoute route2 = new HttpRoute(target2, null, false);
- HttpHost target3 = new HttpHost("www.test3.invalid", 80, "http");
- HttpRoute route3 = new HttpRoute(target3, null, false);
-
- // the first three allocations should pass
- ManagedClientConnection conn1 =
- getConnection(mgr, route1, 10L, TimeUnit.MILLISECONDS);
- ManagedClientConnection conn2 =
- getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
- ManagedClientConnection conn3 =
- getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
- Assert.assertNotNull(conn1);
- Assert.assertNotNull(conn2);
- Assert.assertNotNull(conn3);
-
- // obtaining another connection for either of the three should fail
- // this is somehow redundant with testMaxConnPerHost
- try {
- getConnection(mgr, route1, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
- try {
- getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
- try {
- getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
- // now release one and check that exactly that one can be obtained then
- mgr.releaseConnection(conn2, -1, null);
- conn2 = null;
- try {
- getConnection(mgr, route1, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
- // this one succeeds
- conn2 = getConnection(mgr, route2, 10L, TimeUnit.MILLISECONDS);
- Assert.assertNotNull(conn2);
- try {
- getConnection(mgr, route3, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
- mgr.shutdown();
- }
-
- @Test
- public void testDeleteClosedConnections()
- throws InterruptedException, ConnectionPoolTimeoutException {
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
-
- HttpHost target = new HttpHost("www.test.invalid", 80, "http");
- HttpRoute route = new HttpRoute(target, null, false);
-
- ManagedClientConnection conn = getConnection(mgr, route);
-
- Assert.assertEquals("connectionsInPool",
- mgr.getConnectionsInPool(), 1);
- Assert.assertEquals("connectionsInPool(host)",
- mgr.getConnectionsInPool(route), 1);
- mgr.releaseConnection(conn, -1, null);
-
- Assert.assertEquals("connectionsInPool",
- mgr.getConnectionsInPool(), 1);
- Assert.assertEquals("connectionsInPool(host)",
- mgr.getConnectionsInPool(route), 1);
-
- // this implicitly deletes them
- mgr.closeIdleConnections(0L, TimeUnit.MILLISECONDS);
-
- Assert.assertEquals("connectionsInPool",
- mgr.getConnectionsInPool(), 0);
- Assert.assertEquals("connectionsInPool(host)",
- mgr.getConnectionsInPool(route), 0);
-
- mgr.shutdown();
- }
-
- @Test
- public void testShutdown() throws Exception {
- // 3.x: TestHttpConnectionManager.testShutdown
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(1);
- mgr.setDefaultMaxPerRoute(1);
-
- HttpHost target = new HttpHost("www.test.invalid", 80, "http");
- HttpRoute route = new HttpRoute(target, null, false);
-
- // get the only connection, then start an extra thread
- // on shutdown, the extra thread should get an exception
-
- ManagedClientConnection conn =
- getConnection(mgr, route, 1L, TimeUnit.MILLISECONDS);
- GetConnThread gct = new GetConnThread(mgr, route, 0L); // no timeout
- gct.start();
- Thread.sleep(100); // give extra thread time to block
-
-
- mgr.shutdown();
-
- // First release the connection. If the manager keeps working
- // despite the shutdown, this will deblock the extra thread.
- // The release itself should turn into a no-op, without exception.
- mgr.releaseConnection(conn, -1, null);
-
-
- gct.join(10000);
- Assert.assertNull("thread should not have obtained connection",
- gct.getConnection());
- Assert.assertNotNull("thread should have gotten an exception",
- gct.getException());
- Assert.assertSame("thread got wrong exception",
- IllegalStateException.class, gct.getException().getClass());
-
- // the manager is down, we should not be able to get a connection
- try {
- getConnection(mgr, route, 1L, TimeUnit.MILLISECONDS);
- Assert.fail("shut-down manager does not raise exception");
- } catch (IllegalStateException isx) {
- // expected
- }
- }
-
- @Test
- public void testInterruptThread() throws Exception {
- // 3.x: TestHttpConnectionManager.testWaitingThreadInterrupted
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(1);
-
- HttpHost target = new HttpHost("www.test.invalid", 80, "http");
- HttpRoute route = new HttpRoute(target, null, false);
-
- // get the only connection, then start an extra thread
- ManagedClientConnection conn =
- getConnection(mgr, route, 1L, TimeUnit.MILLISECONDS);
- GetConnThread gct = new GetConnThread(mgr, route, 0L); // no timeout
- gct.start();
- Thread.sleep(100); // give extra thread time to block
-
-
- // interrupt the thread, it should cancel waiting with an exception
- gct.interrupt();
-
-
- gct.join(10000);
- Assert.assertNotNull("thread should have gotten an exception",
- gct.getException());
- Assert.assertSame("thread got wrong exception",
- InterruptedException.class,
- gct.getException().getClass());
-
- // make sure the manager is still working
- try {
- getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("should have gotten a timeout");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
- mgr.releaseConnection(conn, -1, null);
- // this time: no exception
- conn = getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
- Assert.assertNotNull("should have gotten a connection", conn);
-
- mgr.shutdown();
- }
-
- @Test
- public void testReusePreference() throws Exception {
- // 3.x: TestHttpConnectionManager.testHostReusePreference
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(1);
-
- HttpHost target1 = new HttpHost("www.test1.invalid", 80, "http");
- HttpRoute route1 = new HttpRoute(target1, null, false);
- HttpHost target2 = new HttpHost("www.test2.invalid", 80, "http");
- HttpRoute route2 = new HttpRoute(target2, null, false);
-
- // get the only connection, then start two extra threads
- ManagedClientConnection conn =
- getConnection(mgr, route1, 1L, TimeUnit.MILLISECONDS);
- GetConnThread gct1 = new GetConnThread(mgr, route1, 1000L);
- GetConnThread gct2 = new GetConnThread(mgr, route2, 1000L);
-
- // the second thread is started first, to distinguish the
- // route-based reuse preference from first-come, first-served
- gct2.start();
- Thread.sleep(100); // give the thread time to block
- gct1.start();
- Thread.sleep(100); // give the thread time to block
-
-
- // releasing the connection for route1 should deblock thread1
- // the other thread gets a timeout
- mgr.releaseConnection(conn, -1, null);
-
- gct1.join(10000);
- gct2.join(10000);
-
- Assert.assertNotNull("thread 1 should have gotten a connection",
- gct1.getConnection());
- Assert.assertNull ("thread 2 should NOT have gotten a connection",
- gct2.getConnection());
-
- mgr.shutdown();
- }
-
- @Test
- public void testAbortAfterRequestStarts() throws Exception {
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(1);
-
- HttpHost target = new HttpHost("www.test.invalid", 80, "http");
- HttpRoute route = new HttpRoute(target, null, false);
-
- // get the only connection, then start an extra thread
- ManagedClientConnection conn = getConnection(mgr, route, 1L, TimeUnit.MILLISECONDS);
- ClientConnectionRequest request = mgr.requestConnection(route, null);
- GetConnThread gct = new GetConnThread(request, route, 0L); // no timeout
- gct.start();
- Thread.sleep(100); // give extra thread time to block
-
- request.abortRequest();
-
- gct.join(10000);
- Assert.assertNotNull("thread should have gotten an exception",
- gct.getException());
- Assert.assertSame("thread got wrong exception",
- InterruptedException.class,
- gct.getException().getClass());
-
- // make sure the manager is still working
- try {
- getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("should have gotten a timeout");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
- mgr.releaseConnection(conn, -1, null);
- // this time: no exception
- conn = getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
- Assert.assertNotNull("should have gotten a connection", conn);
-
- mgr.shutdown();
- }
-
- @Test
- public void testAbortBeforeRequestStarts() throws Exception {
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(1);
-
- HttpHost target = new HttpHost("www.test.invalid", 80, "http");
- HttpRoute route = new HttpRoute(target, null, false);
-
- // get the only connection, then start an extra thread
- ManagedClientConnection conn = getConnection(mgr, route, 1L, TimeUnit.MILLISECONDS);
- ClientConnectionRequest request = mgr.requestConnection(route, null);
- request.abortRequest();
-
- GetConnThread gct = new GetConnThread(request, route, 0L); // no timeout
- gct.start();
- Thread.sleep(100); // give extra thread time to block
-
- gct.join(10000);
- Assert.assertNotNull("thread should have gotten an exception",
- gct.getException());
- Assert.assertSame("thread got wrong exception",
- InterruptedException.class,
- gct.getException().getClass());
-
- // make sure the manager is still working
- try {
- getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("should have gotten a timeout");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
- mgr.releaseConnection(conn, -1, null);
- // this time: no exception
- conn = getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
- Assert.assertNotNull("should have gotten a connection", conn);
-
- mgr.shutdown();
- }
-
-}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMWithServer.java b/httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMWithServer.java
deleted file mode 100644
index c3dc6ef..0000000
--- a/httpclient/src/test/java/org/apache/http/impl/conn/TestTSCCMWithServer.java
+++ /dev/null
@@ -1,863 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.http.impl.conn;
-
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.HttpVersion;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.ClientConnectionOperator;
-import org.apache.http.conn.ClientConnectionRequest;
-import org.apache.http.conn.ConnectTimeoutException;
-import org.apache.http.conn.ConnectionPoolTimeoutException;
-import org.apache.http.conn.ManagedClientConnection;
-import org.apache.http.conn.OperatedClientConnection;
-import org.apache.http.conn.routing.HttpRoute;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.conn.scheme.SchemeSocketFactory;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.apache.http.localserver.ServerTestBase;
-import org.apache.http.message.BasicHttpRequest;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.BasicHttpContext;
-import org.apache.http.protocol.ExecutionContext;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.util.EntityUtils;
-import org.junit.Assert;
-import org.junit.Test;
-
-/**
- * Tests for <code>ThreadSafeClientConnManager</code> that do require
- * a server to communicate with.
- */
-public class TestTSCCMWithServer extends ServerTestBase {
-
- /**
- * Helper to instantiate a <code>ThreadSafeClientConnManager</code>.
- *
- * @param schreg the scheme registry, or
- * <code>null</code> to use defaults
- *
- * @return a connection manager to test
- */
- public ThreadSafeClientConnManager createTSCCM(SchemeRegistry schreg) {
- return createTSCCM(schreg, -1, TimeUnit.MILLISECONDS);
- }
-
- public ThreadSafeClientConnManager createTSCCM(SchemeRegistry schreg,
- long connTTL, TimeUnit connTTLTimeUnit) {
- if (schreg == null)
- schreg = supportedSchemes;
- return new ThreadSafeClientConnManager(schreg, connTTL, connTTLTimeUnit);
- }
-
- /**
- * Tests executing several requests in parallel.
- */
- @Test
- public void testParallelRequests() throws Exception {
- // 3.x: TestHttpConnectionManager.testGetFromMultipleThreads
-
- final int COUNT = 8; // adjust to execute more requests
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(COUNT/2);
- mgr.setDefaultMaxPerRoute(COUNT/2);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
- final int rsplen = 8;
- final String uri = "/random/" + rsplen;
-
- ExecReqThread[] threads = new ExecReqThread [COUNT];
- for (int i=0; i<COUNT; i++) {
-
- HttpRequest request = new BasicHttpRequest
- ("GET", uri, HttpVersion.HTTP_1_1);
-
- ExecReqThread.RequestSpec ertrs = new ExecReqThread.RequestSpec();
- ertrs.executor = httpExecutor;
- ertrs.processor = httpProcessor;
- ertrs.context = new BasicHttpContext(null);
- ertrs.params = defaultParams;
-
- ertrs.context.setAttribute
- (ExecutionContext.HTTP_TARGET_HOST, target);
- ertrs.context.setAttribute
- (ExecutionContext.HTTP_REQUEST, request);
-
- threads[i] = new ExecReqThread(mgr, route, 5000L, ertrs);
- }
-
- for (int i=0; i<threads.length; i++) {
- threads[i].start();
- }
-
- for (int i=0; i<threads.length; i++) {
- threads[i].join(10000);
- Assert.assertNull("exception in thread " + i,
- threads[i].getException());
- Assert.assertNotNull("no response in thread " + i,
- threads[i].getResponse());
- Assert.assertEquals("wrong status code in thread " + i, 200,
- threads[i].getResponse()
- .getStatusLine().getStatusCode());
- Assert.assertNotNull("no response data in thread " + i,
- threads[i].getResponseData());
- Assert.assertEquals("wrong length of data in thread" + i, rsplen,
- threads[i].getResponseData().length);
- }
-
- mgr.shutdown();
- }
-
- private static ManagedClientConnection getConnection(
- final ClientConnectionManager mgr,
- final HttpRoute route,
- long timeout,
- TimeUnit unit) throws ConnectionPoolTimeoutException, InterruptedException {
- ClientConnectionRequest connRequest = mgr.requestConnection(route, null);
- return connRequest.getConnection(timeout, unit);
- }
-
- private static ManagedClientConnection getConnection(
- final ClientConnectionManager mgr,
- final HttpRoute route) throws ConnectionPoolTimeoutException, InterruptedException {
- ClientConnectionRequest connRequest = mgr.requestConnection(route, null);
- return connRequest.getConnection(0, null);
- }
-
- /**
- * Tests releasing and re-using a connection after a response is read.
- */
- @Test
- public void testReleaseConnection() throws Exception {
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(1);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
- final int rsplen = 8;
- final String uri = "/random/" + rsplen;
-
- HttpRequest request =
- new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1);
-
- ManagedClientConnection conn = getConnection(mgr, route);
- conn.open(route, httpContext, defaultParams);
-
- // a new context is created for each testcase, no need to reset
- HttpResponse response = Helper.execute(
- request, conn, target,
- httpExecutor, httpProcessor, defaultParams, httpContext);
-
- Assert.assertEquals("wrong status in first response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
- byte[] data = EntityUtils.toByteArray(response.getEntity());
- Assert.assertEquals("wrong length of first response entity",
- rsplen, data.length);
- // ignore data, but it must be read
-
- // check that there is no auto-release by default
- try {
- // this should fail quickly, connection has not been released
- getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
- // release connection without marking for re-use
- // expect the next connection obtained to be closed
- mgr.releaseConnection(conn, -1, null);
- conn = getConnection(mgr, route);
- Assert.assertFalse("connection should have been closed", conn.isOpen());
-
- // repeat the communication, no need to prepare the request again
- conn.open(route, httpContext, defaultParams);
- httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
- response = httpExecutor.execute(request, conn, httpContext);
- httpExecutor.postProcess(response, httpProcessor, httpContext);
-
- Assert.assertEquals("wrong status in second response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
- data = EntityUtils.toByteArray(response.getEntity());
- Assert.assertEquals("wrong length of second response entity",
- rsplen, data.length);
- // ignore data, but it must be read
-
- // release connection after marking it for re-use
- // expect the next connection obtained to be open
- conn.markReusable();
- mgr.releaseConnection(conn, -1, null);
- conn = getConnection(mgr, route);
- Assert.assertTrue("connection should have been open", conn.isOpen());
-
- // repeat the communication, no need to prepare the request again
- httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
- response = httpExecutor.execute(request, conn, httpContext);
- httpExecutor.postProcess(response, httpProcessor, httpContext);
-
- Assert.assertEquals("wrong status in third response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
- data = EntityUtils.toByteArray(response.getEntity());
- Assert.assertEquals("wrong length of third response entity",
- rsplen, data.length);
- // ignore data, but it must be read
-
- mgr.releaseConnection(conn, -1, null);
- mgr.shutdown();
- }
-
- /**
- * Tests releasing with time limits.
- */
- @Test
- public void testReleaseConnectionWithTimeLimits() throws Exception {
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(1);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
- final int rsplen = 8;
- final String uri = "/random/" + rsplen;
-
- HttpRequest request =
- new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1);
-
- ManagedClientConnection conn = getConnection(mgr, route);
- conn.open(route, httpContext, defaultParams);
-
- // a new context is created for each testcase, no need to reset
- HttpResponse response = Helper.execute(
- request, conn, target,
- httpExecutor, httpProcessor, defaultParams, httpContext);
-
- Assert.assertEquals("wrong status in first response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
- byte[] data = EntityUtils.toByteArray(response.getEntity());
- Assert.assertEquals("wrong length of first response entity",
- rsplen, data.length);
- // ignore data, but it must be read
-
- // check that there is no auto-release by default
- try {
- // this should fail quickly, connection has not been released
- getConnection(mgr, route, 10L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
- // release connection without marking for re-use
- // expect the next connection obtained to be closed
- mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
- conn = getConnection(mgr, route);
- Assert.assertFalse("connection should have been closed", conn.isOpen());
-
- // repeat the communication, no need to prepare the request again
- conn.open(route, httpContext, defaultParams);
- httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
- response = httpExecutor.execute(request, conn, httpContext);
- httpExecutor.postProcess(response, httpProcessor, httpContext);
-
- Assert.assertEquals("wrong status in second response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
- data = EntityUtils.toByteArray(response.getEntity());
- Assert.assertEquals("wrong length of second response entity",
- rsplen, data.length);
- // ignore data, but it must be read
-
- // release connection after marking it for re-use
- // expect the next connection obtained to be open
- conn.markReusable();
- mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
- conn = getConnection(mgr, route);
- Assert.assertTrue("connection should have been open", conn.isOpen());
-
- // repeat the communication, no need to prepare the request again
- httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
- response = httpExecutor.execute(request, conn, httpContext);
- httpExecutor.postProcess(response, httpProcessor, httpContext);
-
- Assert.assertEquals("wrong status in third response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
- data = EntityUtils.toByteArray(response.getEntity());
- Assert.assertEquals("wrong length of third response entity",
- rsplen, data.length);
- // ignore data, but it must be read
-
- conn.markReusable();
- mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
- Thread.sleep(150);
- conn = getConnection(mgr, route);
- Assert.assertTrue("connection should have been closed", !conn.isOpen());
-
- // repeat the communication, no need to prepare the request again
- conn.open(route, httpContext, defaultParams);
- httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
- response = httpExecutor.execute(request, conn, httpContext);
- httpExecutor.postProcess(response, httpProcessor, httpContext);
-
- Assert.assertEquals("wrong status in third response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
- data = EntityUtils.toByteArray(response.getEntity());
- Assert.assertEquals("wrong length of fourth response entity",
- rsplen, data.length);
- // ignore data, but it must be read
-
- mgr.shutdown();
- }
-
- @Test
- public void testCloseExpiredIdleConnections() throws Exception {
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(1);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
-
- ManagedClientConnection conn = getConnection(mgr, route);
- conn.open(route, httpContext, defaultParams);
-
- Assert.assertEquals("connectionsInPool", 1, mgr.getConnectionsInPool());
- Assert.assertEquals("connectionsInPool(host)", 1, mgr.getConnectionsInPool(route));
- mgr.releaseConnection(conn, 100, TimeUnit.MILLISECONDS);
-
- // Released, still active.
- Assert.assertEquals("connectionsInPool", 1, mgr.getConnectionsInPool());
- Assert.assertEquals("connectionsInPool(host)", 1, mgr.getConnectionsInPool(route));
-
- mgr.closeExpiredConnections();
-
- // Time has not expired yet.
- Assert.assertEquals("connectionsInPool", 1, mgr.getConnectionsInPool());
- Assert.assertEquals("connectionsInPool(host)", 1, mgr.getConnectionsInPool(route));
-
- Thread.sleep(150);
-
- mgr.closeExpiredConnections();
-
- // Time expired now, connections are destroyed.
- Assert.assertEquals("connectionsInPool", 0, mgr.getConnectionsInPool());
- Assert.assertEquals("connectionsInPool(host)", 0, mgr.getConnectionsInPool(route));
-
- mgr.shutdown();
- }
-
- @Test
- public void testCloseExpiredTTLConnections() throws Exception {
-
- ThreadSafeClientConnManager mgr = createTSCCM(null, 100, TimeUnit.MILLISECONDS);
- mgr.setMaxTotal(1);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
-
- ManagedClientConnection conn = getConnection(mgr, route);
- conn.open(route, httpContext, defaultParams);
-
- Assert.assertEquals("connectionsInPool", 1, mgr.getConnectionsInPool());
- Assert.assertEquals("connectionsInPool(host)", 1, mgr.getConnectionsInPool(route));
- // Release, let remain idle for forever
- mgr.releaseConnection(conn, -1, TimeUnit.MILLISECONDS);
-
- // Released, still active.
- Assert.assertEquals("connectionsInPool", 1, mgr.getConnectionsInPool());
- Assert.assertEquals("connectionsInPool(host)", 1, mgr.getConnectionsInPool(route));
-
- mgr.closeExpiredConnections();
-
- // Time has not expired yet.
- Assert.assertEquals("connectionsInPool", 1, mgr.getConnectionsInPool());
- Assert.assertEquals("connectionsInPool(host)", 1, mgr.getConnectionsInPool(route));
-
- Thread.sleep(150);
-
- mgr.closeExpiredConnections();
-
- // TTL expired now, connections are destroyed.
- Assert.assertEquals("connectionsInPool", 0, mgr.getConnectionsInPool());
- Assert.assertEquals("connectionsInPool(host)", 0, mgr.getConnectionsInPool(route));
-
- mgr.shutdown();
- }
-
- /**
- * Tests releasing connection from #abort method called from the
- * main execution thread while there is no blocking I/O operation.
- */
- @Test
- public void testReleaseConnectionOnAbort() throws Exception {
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
- mgr.setMaxTotal(1);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
- final int rsplen = 8;
- final String uri = "/random/" + rsplen;
-
- HttpRequest request =
- new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1);
-
- ManagedClientConnection conn = getConnection(mgr, route);
- conn.open(route, httpContext, defaultParams);
-
- // a new context is created for each testcase, no need to reset
- HttpResponse response = Helper.execute(
- request, conn, target,
- httpExecutor, httpProcessor, defaultParams, httpContext);
-
- Assert.assertEquals("wrong status in first response",
- HttpStatus.SC_OK,
- response.getStatusLine().getStatusCode());
-
- // check that there are no connections available
- try {
- // this should fail quickly, connection has not been released
- getConnection(mgr, route, 100L, TimeUnit.MILLISECONDS);
- Assert.fail("ConnectionPoolTimeoutException should have been thrown");
- } catch (ConnectionPoolTimeoutException e) {
- // expected
- }
-
- // abort the connection
- Assert.assertTrue(conn instanceof AbstractClientConnAdapter);
- ((AbstractClientConnAdapter) conn).abortConnection();
-
- // the connection is expected to be released back to the manager
- conn = getConnection(mgr, route, 5L, TimeUnit.SECONDS);
- Assert.assertFalse("connection should have been closed", conn.isOpen());
-
- mgr.releaseConnection(conn, -1, null);
- mgr.shutdown();
- }
-
- /**
- * Tests GC of an unreferenced connection manager.
- */
- @Test
- public void testConnectionManagerGC() throws Exception {
- // 3.x: TestHttpConnectionManager.testDroppedThread
-
- ThreadSafeClientConnManager mgr = createTSCCM(null);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
- final int rsplen = 8;
- final String uri = "/random/" + rsplen;
-
- HttpRequest request =
- new BasicHttpRequest("GET", uri, HttpVersion.HTTP_1_1);
-
- ManagedClientConnection conn = getConnection(mgr, route);
- conn.open(route, httpContext, defaultParams);
-
- // a new context is created for each testcase, no need to reset
- HttpResponse response = Helper.execute(request, conn, target,
- httpExecutor, httpProcessor, defaultParams, httpContext);
- EntityUtils.toByteArray(response.getEntity());
-
- // release connection after marking it for re-use
- conn.markReusable();
- mgr.releaseConnection(conn, -1, null);
-
- // We now have a manager with an open connection in its pool.
- // We drop all potential hard reference to the manager and check
- // whether it is GCed. Internal references might prevent that
- // if set up incorrectly.
- // Note that we still keep references to the connection wrapper
- // we got from the manager, directly as well as in the request
- // and in the context. The manager will be GCed only if the
- // connection wrapper is truly detached.
- WeakReference<ThreadSafeClientConnManager> wref =
- new WeakReference<ThreadSafeClientConnManager>(mgr);
- mgr = null;
-
- // Java does not guarantee that this will trigger the GC, but
- // it does in the test environment. GC is asynchronous, so we
- // need to give the garbage collector some time afterwards.
- System.gc();
- Thread.sleep(1000);
-
- Assert.assertNull("TSCCM not garbage collected", wref.get());
- }
-
- @Test
- public void testAbortDuringConnecting() throws Exception {
- final CountDownLatch connectLatch = new CountDownLatch(1);
- final StallingSocketFactory stallingSocketFactory = new StallingSocketFactory(
- connectLatch, WaitPolicy.BEFORE_CONNECT, PlainSocketFactory.getSocketFactory());
- Scheme scheme = new Scheme("http", 80, stallingSocketFactory);
- SchemeRegistry registry = new SchemeRegistry();
- registry.register(scheme);
-
- ThreadSafeClientConnManager mgr = createTSCCM(registry);
- mgr.setMaxTotal(1);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
-
- final ManagedClientConnection conn = getConnection(mgr, route);
- Assert.assertTrue(conn instanceof AbstractClientConnAdapter);
-
- final AtomicReference<Throwable> throwRef = new AtomicReference<Throwable>();
- Thread abortingThread = new Thread(new Runnable() {
- public void run() {
- try {
- stallingSocketFactory.waitForState();
- conn.abortConnection();
- connectLatch.countDown();
- } catch (Throwable e) {
- throwRef.set(e);
- }
- }
- });
- abortingThread.start();
-
- try {
- conn.open(route, httpContext, defaultParams);
- Assert.fail("expected SocketException");
- } catch(SocketException expected) {}
-
- abortingThread.join(5000);
- if(throwRef.get() != null)
- throw new RuntimeException(throwRef.get());
-
- Assert.assertFalse(conn.isOpen());
- Assert.assertEquals(0, localServer.getAcceptedConnectionCount());
-
- // the connection is expected to be released back to the manager
- ManagedClientConnection conn2 = getConnection(mgr, route, 5L, TimeUnit.SECONDS);
- Assert.assertFalse("connection should have been closed", conn2.isOpen());
-
- mgr.releaseConnection(conn2, -1, null);
- mgr.shutdown();
- }
-
- @Test
- public void testAbortBeforeSocketCreate() throws Exception {
- final CountDownLatch connectLatch = new CountDownLatch(1);
- final StallingSocketFactory stallingSocketFactory = new StallingSocketFactory(
- connectLatch, WaitPolicy.BEFORE_CREATE, PlainSocketFactory.getSocketFactory());
- Scheme scheme = new Scheme("http", 80, stallingSocketFactory);
- SchemeRegistry registry = new SchemeRegistry();
- registry.register(scheme);
-
- ThreadSafeClientConnManager mgr = createTSCCM(registry);
- mgr.setMaxTotal(1);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
-
- final ManagedClientConnection conn = getConnection(mgr, route);
- Assert.assertTrue(conn instanceof AbstractClientConnAdapter);
-
- final AtomicReference<Throwable> throwRef = new AtomicReference<Throwable>();
- Thread abortingThread = new Thread(new Runnable() {
- public void run() {
- try {
- stallingSocketFactory.waitForState();
- conn.abortConnection();
- connectLatch.countDown();
- } catch (Throwable e) {
- throwRef.set(e);
- }
- }
- });
- abortingThread.start();
-
- try {
- conn.open(route, httpContext, defaultParams);
- Assert.fail("expected exception");
- } catch(IOException expected) {
- Assert.assertEquals("Connection already shutdown", expected.getMessage());
- }
-
- abortingThread.join(5000);
- if(throwRef.get() != null)
- throw new RuntimeException(throwRef.get());
-
- Assert.assertFalse(conn.isOpen());
- Assert.assertEquals(0, localServer.getAcceptedConnectionCount());
-
- // the connection is expected to be released back to the manager
- ManagedClientConnection conn2 = getConnection(mgr, route, 5L, TimeUnit.SECONDS);
- Assert.assertFalse("connection should have been closed", conn2.isOpen());
-
- mgr.releaseConnection(conn2, -1, null);
- mgr.shutdown();
- }
-
- @Test
- public void testAbortAfterSocketConnect() throws Exception {
- final CountDownLatch connectLatch = new CountDownLatch(1);
- final StallingSocketFactory stallingSocketFactory = new StallingSocketFactory(
- connectLatch, WaitPolicy.AFTER_CONNECT, PlainSocketFactory.getSocketFactory());
- Scheme scheme = new Scheme("http", 80, stallingSocketFactory);
- SchemeRegistry registry = new SchemeRegistry();
- registry.register(scheme);
-
- ThreadSafeClientConnManager mgr = createTSCCM(registry);
- mgr.setMaxTotal(1);
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
-
- final ManagedClientConnection conn = getConnection(mgr, route);
- Assert.assertTrue(conn instanceof AbstractClientConnAdapter);
-
- final AtomicReference<Throwable> throwRef = new AtomicReference<Throwable>();
- Thread abortingThread = new Thread(new Runnable() {
- public void run() {
- try {
- stallingSocketFactory.waitForState();
- conn.abortConnection();
- connectLatch.countDown();
- } catch (Throwable e) {
- throwRef.set(e);
- }
- }
- });
- abortingThread.start();
-
- try {
- conn.open(route, httpContext, defaultParams);
- Assert.fail("expected SocketException");
- } catch(SocketException expected) {}
-
- abortingThread.join(5000);
- if(throwRef.get() != null)
- throw new RuntimeException(throwRef.get());
-
- Assert.assertFalse(conn.isOpen());
- // Give the server a bit of time to accept the connection, but
- // ensure that it can accept it.
- for(int i = 0; i < 10; i++) {
- if(localServer.getAcceptedConnectionCount() == 1)
- break;
- Thread.sleep(100);
- }
- Assert.assertEquals(1, localServer.getAcceptedConnectionCount());
-
- // the connection is expected to be released back to the manager
- ManagedClientConnection conn2 = getConnection(mgr, route, 5L, TimeUnit.SECONDS);
- Assert.assertFalse("connection should have been closed", conn2.isOpen());
-
- mgr.releaseConnection(conn2, -1, null);
- mgr.shutdown();
- }
-
- @Test
- public void testAbortAfterOperatorOpen() throws Exception {
- final CountDownLatch connectLatch = new CountDownLatch(1);
- final AtomicReference<StallingOperator> operatorRef = new AtomicReference<StallingOperator>();
-
- ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(supportedSchemes) {
- @Override
- protected ClientConnectionOperator createConnectionOperator(
- SchemeRegistry schreg) {
- operatorRef.set(new StallingOperator(connectLatch, WaitPolicy.AFTER_OPEN, super.createConnectionOperator(schreg)));
- return operatorRef.get();
- }
- };
- mgr.setMaxTotal(1);
- Assert.assertNotNull(operatorRef.get());
-
- final HttpHost target = getServerHttp();
- final HttpRoute route = new HttpRoute(target, null, false);
-
- final ManagedClientConnection conn = getConnection(mgr, route);
- Assert.assertTrue(conn instanceof AbstractClientConnAdapter);
-
- final AtomicReference<Throwable> throwRef = new AtomicReference<Throwable>();
- Thread abortingThread = new Thread(new Runnable() {
- public void run() {
- try {
- operatorRef.get().waitForState();
- conn.abortConnection();
- connectLatch.countDown();
- } catch (Throwable e) {
- throwRef.set(e);
- }
- }
- });
- abortingThread.start();
-
- try {
- conn.open(route, httpContext, defaultParams);
- Assert.fail("expected exception");
- } catch(IOException iox) {
- Assert.assertEquals("Request aborted", iox.getMessage());
- }
-
- abortingThread.join(5000);
- if(throwRef.get() != null)
- throw new RuntimeException(throwRef.get());
-
- Assert.assertFalse(conn.isOpen());
- // Give the server a bit of time to accept the connection, but
- // ensure that it can accept it.
- for(int i = 0; i < 10; i++) {
- if(localServer.getAcceptedConnectionCount() == 1)
- break;
- Thread.sleep(100);
- }
- Assert.assertEquals(1, localServer.getAcceptedConnectionCount());
-
- // the connection is expected to be released back to the manager
- ManagedClientConnection conn2 = getConnection(mgr, route, 5L, TimeUnit.SECONDS);
- Assert.assertFalse("connection should have been closed", conn2.isOpen());
-
- mgr.releaseConnection(conn2, -1, null);
- mgr.shutdown();
- }
-
- private static class LatchSupport {
- private final CountDownLatch continueLatch;
- private final CountDownLatch waitLatch = new CountDownLatch(1);
- protected final WaitPolicy waitPolicy;
-
- LatchSupport(CountDownLatch continueLatch, WaitPolicy waitPolicy) {
- this.continueLatch = continueLatch;
- this.waitPolicy = waitPolicy;
- }
-
- void waitForState() throws InterruptedException {
- if(!waitLatch.await(1, TimeUnit.SECONDS))
- throw new RuntimeException("waited too long");
- }
-
- void latch() {
- waitLatch.countDown();
- try {
- if (!continueLatch.await(1, TimeUnit.SECONDS))
- throw new RuntimeException("waited too long!");
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- private static class StallingOperator extends LatchSupport implements ClientConnectionOperator {
- private final ClientConnectionOperator delegate;
-
- public StallingOperator(CountDownLatch continueLatch,
- WaitPolicy waitPolicy, ClientConnectionOperator delegate) {
- super(continueLatch, waitPolicy);
- this.delegate = delegate;
- }
-
- public OperatedClientConnection createConnection() {
- return delegate.createConnection();
- }
-
- public void openConnection(OperatedClientConnection conn,
- HttpHost target, InetAddress local, HttpContext context,
- HttpParams params) throws IOException {
- delegate.openConnection(conn, target, local, context, params);
- if(waitPolicy == WaitPolicy.AFTER_OPEN)
- latch();
- }
-
- public void updateSecureConnection(OperatedClientConnection conn,
- HttpHost target, HttpContext context, HttpParams params)
- throws IOException {
- delegate.updateSecureConnection(conn, target, context, params);
- }
- }
-
- private static class StallingSocketFactory extends LatchSupport implements SchemeSocketFactory {
-
- private final SchemeSocketFactory delegate;
-
- public StallingSocketFactory(
- final CountDownLatch continueLatch,
- final WaitPolicy waitPolicy,
- final SchemeSocketFactory delegate) {
- super(continueLatch, waitPolicy);
- this.delegate = delegate;
- }
-
- public Socket connectSocket(
- final Socket sock,
- final InetSocketAddress remoteAddress,
- final InetSocketAddress localAddress,
- final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
- if(waitPolicy == WaitPolicy.BEFORE_CONNECT)
- latch();
-
- Socket socket = delegate.connectSocket(sock, remoteAddress, localAddress, params);
-
- if(waitPolicy == WaitPolicy.AFTER_CONNECT)
- latch();
-
- return socket;
- }
-
- public Socket createSocket(final HttpParams params) throws IOException {
- if(waitPolicy == WaitPolicy.BEFORE_CREATE)
- latch();
-
- return delegate.createSocket(params);
- }
-
- public boolean isSecure(Socket sock) throws IllegalArgumentException {
- return delegate.isSecure(sock);
- }
- }
-
- private enum WaitPolicy { BEFORE_CREATE, BEFORE_CONNECT, AFTER_CONNECT, AFTER_OPEN }
-
-}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/AwaitThread.java b/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/AwaitThread.java
index 02ca6a7..a6dd17d 100644
--- a/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/AwaitThread.java
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/AwaitThread.java
@@ -35,6 +35,7 @@ import java.util.concurrent.locks.Lock;
/**
* Thread to await something.
*/
+ at Deprecated
public class AwaitThread extends Thread {
protected final WaitingThread wait_object;
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestConnPoolByRoute.java b/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestConnPoolByRoute.java
index 1dee2ab..133038c 100644
--- a/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestConnPoolByRoute.java
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestConnPoolByRoute.java
@@ -27,20 +27,57 @@
package org.apache.http.impl.conn.tsccm;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpHost;
import org.apache.http.conn.ClientConnectionOperator;
import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.conn.OperatedClientConnection;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.conn.DefaultClientConnectionOperator;
import org.apache.http.localserver.ServerTestBase;
+import org.apache.http.params.BasicHttpParams;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+ at SuppressWarnings("deprecation")
+ at RunWith(MockitoJUnitRunner.class)
+ at Deprecated
public class TestConnPoolByRoute extends ServerTestBase {
+ private ConnPoolByRoute impl;
+ private HttpRoute route = new HttpRoute(new HttpHost("localhost"));
+ private HttpRoute route2 = new HttpRoute(new HttpHost("localhost:8080"));
+
+ @Mock private OperatedClientConnection mockConnection;
+ @Mock private OperatedClientConnection mockConnection2;
+ @Mock private ClientConnectionOperator mockOperator;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ impl = new ConnPoolByRoute(
+ new DefaultClientConnectionOperator(supportedSchemes),
+ new ConnPerRouteBean(), 1, -1, TimeUnit.MILLISECONDS);
+ }
+
+ private void useMockOperator() {
+ reset(mockOperator);
+ impl = new ConnPoolByRoute(
+ mockOperator, new ConnPerRouteBean(), 1, -1, TimeUnit.MILLISECONDS);
+ when(mockOperator.createConnection()).thenReturn(mockConnection);
+ }
+
@Test
public void testStatelessConnections() throws Exception {
final HttpHost target = getServerHttp();
@@ -154,4 +191,265 @@ public class TestConnPoolByRoute extends ServerTestBase {
}
}
+ @Test(expected=IllegalArgumentException.class)
+ public void nullOperatorIsNotAllowed() {
+ new ConnPoolByRoute(null, new ConnPerRouteBean(), 1, -1, TimeUnit.MILLISECONDS);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void nullConnPerRouteIsNotAllowed() {
+ new ConnPoolByRoute(new DefaultClientConnectionOperator(supportedSchemes),
+ null, 1, -1, TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void deprecatedConstructorIsStillSupported() {
+ new ConnPoolByRoute(new DefaultClientConnectionOperator(supportedSchemes),
+ new BasicHttpParams());
+ }
+
+ @Test
+ public void emptyPoolHasNoConnections() {
+ assertEquals(0, impl.getConnectionsInPool());
+ }
+
+ @Test
+ public void poolHasOneConnectionAfterRequestingOne() throws Exception {
+ useMockOperator();
+ impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ assertEquals(1, impl.getConnectionsInPool());
+ }
+
+ @Test
+ public void emptyPoolHasNoRouteSpecificConnections() {
+ assertEquals(0, impl.getConnectionsInPool(route));
+ }
+
+ @Test
+ public void routeSpecificPoolHasOneConnectionAfterRequestingOne() throws Exception {
+ useMockOperator();
+ impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ assertEquals(1, impl.getConnectionsInPool(route));
+ }
+
+ @Test
+ public void abortingPoolEntryRequestEarlyDoesNotCreateConnection() {
+ PoolEntryRequest req = impl.requestPoolEntry(route, new Object());
+ req.abortRequest();
+ assertEquals(0, impl.getConnectionsInPool(route));
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void cannotAcquireConnectionIfPoolShutdown() throws Exception {
+ impl.shutdown();
+ impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void multipleShutdownsAreOk() {
+ impl.shutdown();
+ impl.shutdown();
+ }
+
+ @Test
+ public void canAcquirePoolEntry() throws Exception {
+ impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void canRetrieveMaxTotalConnections() {
+ int max = (new Random()).nextInt(10) + 2;
+ impl.setMaxTotalConnections(max);
+ assertEquals(max, impl.getMaxTotalConnections());
+ }
+
+ @Test
+ public void closesFreedConnectionsWhenShutdown() throws Exception {
+ useMockOperator();
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ impl.shutdown();
+ impl.freeEntry(entry, true, Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+ verify(mockConnection, atLeastOnce()).close();
+ }
+
+ @Test
+ public void deleteClosedConnectionsReclaimsPoolSpace() throws Exception {
+ useMockOperator();
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ impl.freeEntry(entry, true, Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+ assertFalse(impl.freeConnections.isEmpty());
+ when(mockConnection.isOpen()).thenReturn(false);
+ impl.deleteClosedConnections();
+ assertTrue(impl.freeConnections.isEmpty());
+ assertEquals(0, impl.numConnections);
+ }
+
+ @Test
+ public void deleteClosedConnectionsDoesNotReclaimOpenConnections() throws Exception {
+ useMockOperator();
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ impl.freeEntry(entry, true, Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+ assertFalse(impl.freeConnections.isEmpty());
+ when(mockConnection.isOpen()).thenReturn(true);
+ impl.deleteClosedConnections();
+ assertFalse(impl.freeConnections.isEmpty());
+ assertEquals(1, impl.numConnections);
+ }
+
+ @Test
+ public void closeIdleConnectionsClosesThoseThatHaveTimedOut() throws Exception {
+ useMockOperator();
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ impl.freeEntry(entry, true, Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+ Thread.sleep(200L);
+ impl.closeIdleConnections(1, TimeUnit.MILLISECONDS);
+ verify(mockConnection, atLeastOnce()).close();
+ }
+
+ @Test
+ public void closeIdleConnectionsDoesNotCloseThoseThatHaveNotTimedOut() throws Exception {
+ useMockOperator();
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ impl.freeEntry(entry, true, Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+ impl.closeIdleConnections(3, TimeUnit.SECONDS);
+ verify(mockConnection, never()).close();
+ }
+
+ @Test
+ public void closeExpiredConnectionsClosesExpiredOnes() throws Exception {
+ useMockOperator();
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ impl.freeEntry(entry, true, 1, TimeUnit.MILLISECONDS);
+ Thread.sleep(200L);
+ impl.closeExpiredConnections();
+ verify(mockConnection, atLeastOnce()).close();
+ }
+
+ @Test
+ public void closeExpiredConnectionsDoesNotCloseUnexpiredOnes() throws Exception {
+ useMockOperator();
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ impl.freeEntry(entry, true, 10, TimeUnit.SECONDS);
+ Thread.sleep(200L);
+ impl.closeExpiredConnections();
+ verify(mockConnection, never()).close();
+ }
+
+ @Test
+ public void closesNonReusableConnections() throws Exception {
+ useMockOperator();
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ impl.freeEntry(entry, false, 0, TimeUnit.MILLISECONDS);
+ verify(mockConnection, atLeastOnce()).close();
+ }
+
+ @Test
+ public void handlesExceptionsWhenClosingConnections() throws Exception {
+ useMockOperator();
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ doThrow(new IOException()).when(mockConnection).close();
+ impl.freeEntry(entry, false, 0, TimeUnit.MILLISECONDS);
+ }
+
+ @Test
+ public void wakesUpWaitingThreadsWhenEntryAvailable() throws Exception {
+ useMockOperator();
+ when(mockOperator.createConnection()).thenReturn(mockConnection);
+ impl.setMaxTotalConnections(1);
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ final Flag f = new Flag(false);
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ f.flag = true;
+ } catch (ConnectionPoolTimeoutException e) {
+ } catch (InterruptedException e) {
+ }
+ }
+ });
+ t.start();
+ Thread.sleep(100);
+ impl.freeEntry(entry, true, 1000, TimeUnit.MILLISECONDS);
+ Thread.sleep(100);
+ assertTrue(f.flag);
+ }
+
+ @Test
+ public void wakesUpWaitingThreadsOnOtherRoutesWhenEntryAvailable() throws Exception {
+ useMockOperator();
+ when(mockOperator.createConnection()).thenReturn(mockConnection);
+ impl.setMaxTotalConnections(1);
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ final Flag f = new Flag(false);
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ impl.requestPoolEntry(route2, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ f.flag = true;
+ } catch (ConnectionPoolTimeoutException e) {
+ } catch (InterruptedException e) {
+ }
+ }
+ });
+ t.start();
+ Thread.sleep(100);
+ impl.freeEntry(entry, true, 1000, TimeUnit.MILLISECONDS);
+ Thread.sleep(100);
+ assertTrue(f.flag);
+ }
+
+ @Test
+ public void doesNotRecycleExpiredConnections() throws Exception {
+ useMockOperator();
+ when(mockOperator.createConnection()).thenReturn(mockConnection, mockConnection2);
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ impl.freeEntry(entry, true, 1, TimeUnit.MILLISECONDS);
+ Thread.sleep(200L);
+ BasicPoolEntry entry2 = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ assertNotSame(mockConnection, entry2.getConnection());
+ }
+
+ @Test
+ public void closesExpiredConnectionsWhenNotReusingThem() throws Exception {
+ useMockOperator();
+ when(mockOperator.createConnection()).thenReturn(mockConnection, mockConnection2);
+ BasicPoolEntry entry = impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ impl.freeEntry(entry, true, 1, TimeUnit.MILLISECONDS);
+ Thread.sleep(200L);
+ impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ verify(mockConnection, atLeastOnce()).close();
+ }
+
+
+ @Test
+ public void wakesUpWaitingThreadsOnShutdown() throws Exception {
+ useMockOperator();
+ when(mockOperator.createConnection()).thenReturn(mockConnection);
+ when(mockOperator.createConnection()).thenReturn(mockConnection);
+ impl.setMaxTotalConnections(1);
+ impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ final Flag f = new Flag(false);
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ impl.requestPoolEntry(route, new Object()).getPoolEntry(-1, TimeUnit.MILLISECONDS);
+ } catch (IllegalStateException expected) {
+ f.flag = true;
+ } catch (ConnectionPoolTimeoutException e) {
+ } catch (InterruptedException e) {
+ }
+ }
+ });
+ t.start();
+ Thread.sleep(1);
+ impl.shutdown();
+ Thread.sleep(1);
+ assertTrue(f.flag);
+ }
+
+ private static class Flag {
+ public boolean flag;
+ public Flag(boolean init) { flag = init; }
+ }
}
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestSpuriousWakeup.java b/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestSpuriousWakeup.java
index 5b4644a..c744fc6 100644
--- a/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestSpuriousWakeup.java
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestSpuriousWakeup.java
@@ -52,6 +52,7 @@ import org.junit.Test;
* satisfying the condition.
*
*/
+ at Deprecated
public class TestSpuriousWakeup {
public final static
diff --git a/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestWaitingThread.java b/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestWaitingThread.java
index 79fefe1..8d03172 100644
--- a/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestWaitingThread.java
+++ b/httpclient/src/test/java/org/apache/http/impl/conn/tsccm/TestWaitingThread.java
@@ -41,6 +41,7 @@ import org.junit.Test;
/**
* Tests for <code>WaitingThread</code>.
*/
+ at Deprecated
public class TestWaitingThread {
public final static
diff --git a/httpclient/src/test/java/org/apache/http/localserver/BasicServerTestBase.java b/httpclient/src/test/java/org/apache/http/localserver/BasicServerTestBase.java
index d4b60df..333af92 100644
--- a/httpclient/src/test/java/org/apache/http/localserver/BasicServerTestBase.java
+++ b/httpclient/src/test/java/org/apache/http/localserver/BasicServerTestBase.java
@@ -30,19 +30,29 @@ package org.apache.http.localserver;
import java.net.InetSocketAddress;
import org.apache.http.HttpHost;
+import org.apache.http.impl.client.DefaultHttpClient;
import org.junit.After;
+import org.mockito.Mockito;
/**
* Base class for tests using {@link LocalTestServer}. The server will not be started
* per default.
*/
-public abstract class BasicServerTestBase {
+public abstract class BasicServerTestBase extends Mockito {
/** The local server for testing. */
protected LocalTestServer localServer;
+ protected DefaultHttpClient httpclient;
@After
- public void tearDown() throws Exception {
+ public void shutDownClient() throws Exception {
+ if (httpclient != null) {
+ httpclient.getConnectionManager().shutdown();
+ }
+ }
+
+ @After
+ public void shutDownServer() throws Exception {
if (localServer != null) {
localServer.stop();
}
diff --git a/httpclient/src/test/java/org/apache/http/mockup/SecureSocketFactoryMockup.java b/httpclient/src/test/java/org/apache/http/mockup/SecureSocketFactoryMockup.java
index 1756865..85d01e4 100644
--- a/httpclient/src/test/java/org/apache/http/mockup/SecureSocketFactoryMockup.java
+++ b/httpclient/src/test/java/org/apache/http/mockup/SecureSocketFactoryMockup.java
@@ -29,16 +29,17 @@ package org.apache.http.mockup;
import java.net.Socket;
-import org.apache.http.conn.scheme.LayeredSchemeSocketFactory;
+import org.apache.http.conn.scheme.SchemeLayeredSocketFactory;
+import org.apache.http.params.HttpParams;
/**
* {@link LayeredSchemeSocketFactory} mockup implementation.
*/
public class SecureSocketFactoryMockup extends SocketFactoryMockup
- implements LayeredSchemeSocketFactory {
+ implements SchemeLayeredSocketFactory {
/* A default instance of this mockup. */
- public final static LayeredSchemeSocketFactory INSTANCE = new SecureSocketFactoryMockup("INSTANCE");
+ public final static SchemeLayeredSocketFactory INSTANCE = new SecureSocketFactoryMockup("INSTANCE");
public SecureSocketFactoryMockup(String name) {
super(name);
@@ -53,7 +54,7 @@ public class SecureSocketFactoryMockup extends SocketFactoryMockup
public Socket createLayeredSocket(Socket socket, String host, int port,
- boolean autoClose) {
+ HttpParams params) {
throw new UnsupportedOperationException("I'm a mockup!");
}
diff --git a/httpclient-cache/src/test/resources/commons-logging.properties b/httpclient/src/test/resources/commons-logging.properties
similarity index 100%
copy from httpclient-cache/src/test/resources/commons-logging.properties
copy to httpclient/src/test/resources/commons-logging.properties
diff --git a/httpclient/src/test/resources/test.keystore b/httpclient/src/test/resources/test.keystore
index 97a862b..917f83e 100644
Binary files a/httpclient/src/test/resources/test.keystore and b/httpclient/src/test/resources/test.keystore differ
diff --git a/httpmime/pom.xml b/httpmime/pom.xml
index 8853cc2..ab5998a 100644
--- a/httpmime/pom.xml
+++ b/httpmime/pom.xml
@@ -30,7 +30,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
- <version>4.1.1</version>
+ <version>4.2.1</version>
</parent>
<artifactId>httpmime</artifactId>
<name>HttpMime</name>
@@ -44,19 +44,18 @@
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
- <version>${httpcore.version}</version>
<scope>compile</scope>
</dependency>
+ <!-- Not currently used
<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>
</dependencies>
@@ -120,6 +119,7 @@
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
+ <version>${hc.javadoc.version}</version>
<configuration>
<!-- reduce console output. Can override with -Dquiet=false -->
<quiet>true</quiet>
@@ -149,10 +149,12 @@
<plugin>
<artifactId>maven-jxr-plugin</artifactId>
+ <version>${hc.jxr.version}</version>
</plugin>
<plugin>
<artifactId>maven-surefire-report-plugin</artifactId>
+ <version>${hc.surefire-report.version}</version>
</plugin>
</plugins>
diff --git a/httpmime/src/main/java/org/apache/http/entity/mime/HttpMultipart.java b/httpmime/src/main/java/org/apache/http/entity/mime/HttpMultipart.java
index c42a261..5d9301b 100644
--- a/httpmime/src/main/java/org/apache/http/entity/mime/HttpMultipart.java
+++ b/httpmime/src/main/java/org/apache/http/entity/mime/HttpMultipart.java
@@ -103,7 +103,7 @@ public class HttpMultipart {
/**
* Creates an instance with the specified settings.
- *
+ *
* @param subType mime subtype - must not be {@code null}
* @param charset the character set to use. May be {@code null}, in which case {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
* @param boundary to use - must not be {@code null}
@@ -128,7 +128,7 @@ public class HttpMultipart {
/**
* Creates an instance with the specified settings.
* Mode is set to {@link HttpMultipartMode#STRICT}
- *
+ *
* @param subType mime subtype - must not be {@code null}
* @param charset the character set to use. May be {@code null}, in which case {@link MIME#DEFAULT_CHARSET} - i.e. US-ASCII - is used.
* @param boundary to use - must not be {@code null}
diff --git a/httpmime/src/main/java/org/apache/http/entity/mime/content/FileBody.java b/httpmime/src/main/java/org/apache/http/entity/mime/content/FileBody.java
index 9623554..93ea6a2 100644
--- a/httpmime/src/main/java/org/apache/http/entity/mime/content/FileBody.java
+++ b/httpmime/src/main/java/org/apache/http/entity/mime/content/FileBody.java
@@ -85,14 +85,6 @@ public class FileBody extends AbstractContentBody {
return new FileInputStream(this.file);
}
- /**
- * @deprecated use {@link #writeTo(OutputStream)}
- */
- @Deprecated
- public void writeTo(final OutputStream out, int mode) throws IOException {
- writeTo(out);
- }
-
public void writeTo(final OutputStream out) throws IOException {
if (out == null) {
throw new IllegalArgumentException("Output stream may not be null");
diff --git a/httpmime/src/main/java/org/apache/http/entity/mime/content/InputStreamBody.java b/httpmime/src/main/java/org/apache/http/entity/mime/content/InputStreamBody.java
index 9766212..1e0f9ea 100644
--- a/httpmime/src/main/java/org/apache/http/entity/mime/content/InputStreamBody.java
+++ b/httpmime/src/main/java/org/apache/http/entity/mime/content/InputStreamBody.java
@@ -59,14 +59,6 @@ public class InputStreamBody extends AbstractContentBody {
return this.in;
}
- /**
- * @deprecated use {@link #writeTo(OutputStream)}
- */
- @Deprecated
- public void writeTo(final OutputStream out, int mode) throws IOException {
- writeTo(out);
- }
-
public void writeTo(final OutputStream out) throws IOException {
if (out == null) {
throw new IllegalArgumentException("Output stream may not be null");
diff --git a/httpmime/src/main/java/org/apache/http/entity/mime/content/StringBody.java b/httpmime/src/main/java/org/apache/http/entity/mime/content/StringBody.java
index f406c58..d3bdfb4 100644
--- a/httpmime/src/main/java/org/apache/http/entity/mime/content/StringBody.java
+++ b/httpmime/src/main/java/org/apache/http/entity/mime/content/StringBody.java
@@ -78,7 +78,7 @@ public class StringBody extends AbstractContentBody {
/**
* Create a StringBody from the specified text, mime type and character set.
- *
+ *
* @param text to be used for the body, not {@code null}
* @param mimeType the mime type, not {@code null}
* @param charset the character set, may be {@code null}, in which case the US-ASCII charset is used
@@ -103,7 +103,7 @@ public class StringBody extends AbstractContentBody {
/**
* Create a StringBody from the specified text and character set.
* The mime type is set to "text/plain".
- *
+ *
* @param text to be used for the body, not {@code null}
* @param charset the character set, may be {@code null}, in which case the US-ASCII charset is used
* @throws UnsupportedEncodingException
@@ -117,7 +117,7 @@ public class StringBody extends AbstractContentBody {
* Create a StringBody from the specified text.
* The mime type is set to "text/plain".
* The hosts default charset is used.
- *
+ *
* @param text to be used for the body, not {@code null}
* @throws UnsupportedEncodingException
* @throws IllegalArgumentException if the {@code text} parameter is null
@@ -132,14 +132,6 @@ public class StringBody extends AbstractContentBody {
this.charset);
}
- /**
- * @deprecated use {@link #writeTo(OutputStream)}
- */
- @Deprecated
- public void writeTo(final OutputStream out, int mode) throws IOException {
- writeTo(out);
- }
-
public void writeTo(final OutputStream out) throws IOException {
if (out == null) {
throw new IllegalArgumentException("Output stream may not be null");
diff --git a/httpmime/src/site/site.xml b/httpmime/src/site/site.xml
index 463453f..02ea590 100644
--- a/httpmime/src/site/site.xml
+++ b/httpmime/src/site/site.xml
@@ -30,7 +30,7 @@
<body>
<menu name="HttpClient Overview">
<item name="Description" href="../index.html"/>
- <item name="Examples" href="../examples.html"/>
+ <item name="Quick Start" href="../quickstart.html"/>
</menu>
<menu ref="modules" />
<menu ref="reports"/>
diff --git a/pom.xml b/pom.xml
index e3ba289..c2f2497 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,15 +28,14 @@
<parent>
<artifactId>project</artifactId>
<groupId>org.apache.httpcomponents</groupId>
- <version>4.1.1</version>
+ <version>6</version>
<relativePath>../project/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
- <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-client</artifactId>
<name>HttpComponents Client</name>
- <version>4.1.1</version>
- <description>Components to build client side HTTP services</description>
+ <version>4.2.1</version>
+ <description>Apache HttpComponents Client is a library of components for building client side HTTP services</description>
<url>http://hc.apache.org/httpcomponents-client</url>
<inceptionYear>1999</inceptionYear>
<packaging>pom</packaging>
@@ -60,29 +59,102 @@
</issueManagement>
<scm>
- <connection>scm:svn:https://svn.apache.org/repos/asf/httpcomponents/httpclient/tags/4.1.1</connection>
- <developerConnection>scm:svn:https://svn.apache.org/repos/asf/httpcomponents/httpclient/tags/4.1.1</developerConnection>
- <url>https://svn.apache.org/repos/asf/httpcomponents/httpclient/tags/4.1.1</url>
+ <connection>scm:svn:https://svn.apache.org/repos/asf/httpcomponents/httpclient/tags/4.2.1</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/httpcomponents/httpclient/tags/4.2.1</developerConnection>
+ <url>https://svn.apache.org/repos/asf/httpcomponents/httpclient/tags/4.2.1</url>
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <httpcore.version>4.1</httpcore.version>
+ <httpcore.version>4.2.1</httpcore.version>
<commons-logging.version>1.1.1</commons-logging.version>
- <commons-codec.version>1.4</commons-codec.version>
+ <commons-codec.version>1.6</commons-codec.version>
<ehcache.version>2.2.0</ehcache.version>
+ <memcached.version>2.6</memcached.version>
<slf4j.version>1.5.11</slf4j.version>
- <junit.version>4.8.2</junit.version>
+ <junit.version>4.9</junit.version>
<easymock.version>2.5.2</easymock.version>
<mockito.version>1.8.5</mockito.version>
- <comparisonVersion>4.1</comparisonVersion>
+ <api.comparison.version>4.1</api.comparison.version>
</properties>
+ <repositories>
+ <repository>
+ <id>spy</id>
+ <name>Spy Repository</name>
+ <layout>default</layout>
+ <url>http://files.couchbase.com/maven2/</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ <version>${httpcore.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>${commons-logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>${commons-codec.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache-core</artifactId>
+ <version>${ehcache.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-jcl</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>spy</groupId>
+ <artifactId>spymemcached</artifactId>
+ <version>${memcached.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.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>
+ </dependencyManagement>
+
<modules>
<module>httpclient</module>
<module>httpmime</module>
<module>httpclient-cache</module>
+ <module>fluent-hc</module>
<module>httpclient-osgi</module>
</modules>
@@ -147,7 +219,18 @@
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>javadoc-aggregate</id>
+ <goals>
+ <goal>aggregate</goal>
+ </goals>
+ <phase>pre-site</phase>
+ </execution>
+ </executions>
<configuration>
+ <!-- reduce console output. Can override with -Dquiet=false -->
+ <quiet>true</quiet>
<source>1.5</source>
<links>
<link>http://download.oracle.com/javase/1.5.0/docs/api/</link>
@@ -200,6 +283,16 @@
<scope>runtime</scope>
</dependency>
</dependencies>
+ <executions>
+ <execution>
+ <id>tutorial-site</id>
+ <goals>
+ <goal>generate-html</goal>
+ <goal>generate-pdf</goal>
+ </goals>
+ <phase>pre-site</phase>
+ </execution>
+ </executions>
<configuration>
<includes>index.xml</includes>
<chunkedOutput>true</chunkedOutput>
@@ -214,13 +307,12 @@
</entity>
</entities>
<postProcess>
- <copy todir="target/site/tutorial">
- <fileset dir="target/docbkx">
+ <copy todir="target/site/tutorial/html" failonerror="false">
+ <fileset dir="target/docbkx/html/index">
<include name="**/*.html" />
- <include name="**/*.pdf" />
</fileset>
</copy>
- <copy todir="target/site/tutorial/html">
+ <copy todir="target/site/tutorial/html" failonerror="false">
<fileset dir="src/docbkx/resources">
<include name="**/*.css" />
<include name="**/*.png" />
@@ -228,7 +320,7 @@
<include name="**/*.jpg" />
</fileset>
</copy>
- <move file="target/site/tutorial/pdf/index.pdf" tofile="target/site/tutorial/pdf/httpclient-tutorial.pdf" failonerror="false" />
+ <copy file="target/docbkx/pdf/index.pdf" tofile="target/site/tutorial/pdf/httpclient-tutorial.pdf" failonerror="false" />
</postProcess>
</configuration>
</plugin>
@@ -257,7 +349,14 @@
<groupId>org.codehaus.mojo</groupId>
<artifactId>clirr-maven-plugin</artifactId>
<configuration>
- <comparisonVersion>${comparisonVersion}</comparisonVersion>
+ <comparisonVersion>${api.comparison.version}</comparisonVersion>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <configuration>
+ <autoVersionSubmodules>true</autoVersionSubmodules>
</configuration>
</plugin>
</plugins>
@@ -268,12 +367,12 @@
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
+ <version>${hc.project-info.version}</version>
<reportSets>
<reportSet>
<reports>
<report>dependencies</report>
<report>project-team</report>
- <report>mailing-list</report>
<report>issue-tracking</report>
<report>scm</report>
</reports>
@@ -284,8 +383,9 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>clirr-maven-plugin</artifactId>
+ <version>${hc.clirr.version}</version>
<configuration>
- <comparisonVersion>${comparisonVersion}</comparisonVersion>
+ <comparisonVersion>${api.comparison.version}</comparisonVersion>
</configuration>
</plugin>
diff --git a/src/docbkx/authentication.xml b/src/docbkx/authentication.xml
index 7a59f1a..29e8f23 100644
--- a/src/docbkx/authentication.xml
+++ b/src/docbkx/authentication.xml
@@ -113,7 +113,7 @@ pwd
</listitem>
<listitem>
<formalpara>
- <title>SPNEGO/Kerberos:</title>
+ <title>SPNEGO:</title>
<para><literal>SPNEGO</literal> (<emphasis>S</emphasis>imple and
<emphasis>P</emphasis>rotected <literal>GSSAPI</literal>
<emphasis>Nego</emphasis>tiation Mechanism) is a <literal>GSSAPI</literal>
@@ -124,6 +124,12 @@ pwd
At present HttpClient only supports the Kerberos sub-mechanism. </para>
</formalpara>
</listitem>
+ <listitem>
+ <formalpara>
+ <title>Kerberos:</title>
+ <para>Kerberos authentication implementation. </para>
+ </formalpara>
+ </listitem>
</itemizedlist>
</section>
<section>
@@ -209,7 +215,13 @@ httpclient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authpref);
<listitem>
<formalpara>
<title>AuthPolicy.SPNEGO:</title>
- <para>SPNEGO/Kerberos authentication</para>
+ <para>SPNEGO authentication</para>
+ </formalpara>
+ </listitem>
+ <listitem>
+ <formalpara>
+ <title>AuthPolicy.KERBEROS:</title>
+ <para>Kerberos authentication</para>
</formalpara>
</listitem>
</itemizedlist>
@@ -327,12 +339,12 @@ HttpResponse response = httpclient.execute(httpget, localContext);
AuthState proxyAuthState = (AuthState) localContext.getAttribute(
ClientContext.PROXY_AUTH_STATE);
-System.out.println("Proxy auth scope: " + proxyAuthState.getAuthScope());
+System.out.println("Proxy auth state: " + proxyAuthState.getState());
System.out.println("Proxy auth scheme: " + proxyAuthState.getAuthScheme());
System.out.println("Proxy auth credentials: " + proxyAuthState.getCredentials());
AuthState targetAuthState = (AuthState) localContext.getAttribute(
ClientContext.TARGET_AUTH_STATE);
-System.out.println("Target auth scope: " + targetAuthState.getAuthScope());
+System.out.println("Target auth state: " + targetAuthState.getState());
System.out.println("Target auth scheme: " + targetAuthState.getAuthScheme());
System.out.println("Target auth credentials: " + targetAuthState.getCredentials());
]]></programlisting>
@@ -490,7 +502,7 @@ EntityUtils.consume(entity2);
supports <literal>SPNEGO</literal> authentication more completely.</para>
<para>The Sun JRE provides the supporting classes to do nearly all the Kerberos and
<literal>SPNEGO</literal> token handling. This means that a lot of the setup is
- for the GSS classes. The <classname>NegotiateScheme</classname> is a simple class to
+ for the GSS classes. The <classname>SPNegoScheme</classname> is a simple class to
handle marshalling the tokens and reading and writing the correct headers.</para>
<para>The best way to start is to grab the <literal>KerberosHttpClient.java</literal>
file in examples and try and get it to work. There are a lot of issues that can
@@ -583,40 +595,6 @@ Value: 0x01
]]>
</programlisting>
</section>
- <section>
- <title>Customizing <literal>SPNEGO</literal> authentication scheme</title>
- <para>In order to customize <literal>SPNEGO</literal> support a new instance of
- the <classname>NegotiateSchemeFactory</classname> class must be created and
- registered with the authentication scheme registry of HttpClient. </para>
- <programlisting><![CDATA[
-DefaultHttpClient httpclient = new DefaultHttpClient();
-NegotiateSchemeFactory nsf = new NegotiateSchemeFactory();
-httpclient.getAuthSchemes().register(AuthPolicy.SPNEGO, nsf);
-]]>
- </programlisting>
- <para>There are several options that can be used to customize the behaviour of
- <classname>NegotiateSchemeFactory</classname>. </para>
- <section>
- <title>Strip port</title>
- <para>Strips the port off service names e.g.
- <literal>HTTP/webserver.ad.example.net:8080</literal> ->
- <literal>HTTP/webserver.ad.example.net</literal></para>
- <para>Found it useful when authenticating against JBoss Negotiation.</para>
- </section>
- <section>
- <title>Custom <literal>SPNEGO</literal> token generator</title>
- <para>Use this method to inject a custom
- <interfacename>SpnegoTokenGenerator</interfacename> class to do the Kerberos
- to <literal>SPNEGO</literal> token wrapping.
- The <classname>BouncySpnegoTokenGenerator</classname> implementation is provided
- as an unsupported contribution from the contrib package. This requires the
- BouncyCastle libraries <ulink url="http://www.bouncycastle.org/java.html"
- >"http://www.bouncycastle.org/java.html"</ulink>. Found especially useful
- when using Java 1.5, which is known to provide only a limited support for
- <literal>SPNEGO</literal> authentication.
- </para>
- </section>
- </section>
</section>
</chapter>
diff --git a/src/docbkx/connmgmt.xml b/src/docbkx/connmgmt.xml
index c596307..8e8f70c 100644
--- a/src/docbkx/connmgmt.xml
+++ b/src/docbkx/connmgmt.xml
@@ -253,7 +253,7 @@ sf.connectSocket(socket, address, null, params);
]]></programlisting>
<section>
<title>Secure socket layering</title>
- <para><interfacename>LayeredSchemeSocketFactory</interfacename> is an extension of
+ <para><interfacename>SchemeLayeredSocketFactory</interfacename> is an extension of
the <interfacename>SchemeSocketFactory</interfacename> interface. Layered socket
factories are capable of creating sockets layered over an existing plain socket.
Socket layering is used primarily for creating secure sockets through proxies.
@@ -270,37 +270,14 @@ sf.connectSocket(socket, address, null, params);
<interfacename>javax.net.ssl.SSLContext</interfacename> as a parameter and use
it to create custom configured SSL connections.</para>
<programlisting><![CDATA[
-TrustManager easyTrustManager = new X509TrustManager() {
-
- @Override
- public void checkClientTrusted(
- X509Certificate[] chain,
- String authType) throws CertificateException {
- // Oh, I am easy!
- }
-
- @Override
- public void checkServerTrusted(
- X509Certificate[] chain,
- String authType) throws CertificateException {
- // Oh, I am easy!
- }
-
- @Override
- public X509Certificate[] getAcceptedIssuers() {
- return null;
- }
-
-};
-
+HttpParams params = new BasicHttpParams();
SSLContext sslcontext = SSLContext.getInstance("TLS");
-sslcontext.init(null, new TrustManager[] { easyTrustManager }, null);
+sslcontext.init(null, null, null);
SSLSocketFactory sf = new SSLSocketFactory(sslcontext);
-SSLSocket socket = (SSLSocket) sf.createSocket();
+SSLSocket socket = (SSLSocket) sf.createSocket(params);
socket.setEnabledCipherSuites(new String[] { "SSL_RSA_WITH_RC4_128_MD5" });
-HttpParams params = new BasicHttpParams();
params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);
InetSocketAddress address = new InetSocketAddress("locahost", 443);
sf.connectSocket(socket, address, null, params);
@@ -485,7 +462,7 @@ httpclient.setRoutePlanner(new HttpRoutePlanner() {
Scheme http = new Scheme("http", 80, PlainSocketFactory.getSocketFactory());
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
-ClientConnectionManager connMrg = new SingleClientConnManager(sr);
+ClientConnectionManager connMrg = new BasicClientConnectionManager(sr);
// Request new connection. This can be a long process
ClientConnectionRequest connRequest = connMrg.requestConnection(
@@ -539,27 +516,27 @@ try {
</section>
<section>
<title>Simple connection manager</title>
- <para><classname>SingleClientConnManager</classname> is a simple connection manager that
- maintains only one connection at a time. Even though this class is thread-safe it
- ought to be used by one execution thread only.
- <classname>SingleClientConnManager</classname> will make an effort to reuse the
- connection for subsequent requests with the same route. It will, however, close the
- existing connection and re-open it for the given route, if the route of the persistent
- connection does not match that of the connection request. If the connection has been
- already been allocated, then
- <exceptionname>java.lang.IllegalStateException</exceptionname> is thrown.</para>
- <para><classname>SingleClientConnManager</classname> is used by HttpClient per
+ <para><classname>BasicClientConnectionManager</classname> is a simple connection manager
+ that maintains only one connection at a time. Even though this class is thread-safe
+ it ought to be used by one execution thread only.
+ <classname>BasicClientConnectionManager</classname> will make an effort to reuse
+ the connection for subsequent requests with the same route. It will, however, close
+ the existing connection and re-open it for the given route, if the route of the
+ persistent connection does not match that of the connection request.
+ If the connection has been already been allocated, then <exceptionname>
+ java.lang.IllegalStateException</exceptionname> is thrown.</para>
+ <para><classname>BasicClientConnectionManager</classname> is used by HttpClient per
default.</para>
</section>
<section>
<title>Pooling connection manager</title>
- <para><classname>ThreadSafeClientConnManager</classname> is a more complex
+ <para><classname>PoolingClientConnectionManager</classname> is a more complex
implementation that manages a pool of client connections and is able to service
connection requests from multiple execution threads. Connections are pooled on a per
route basis. A request for a route for which the manager already has a persistent
connection available in the pool will be serviced by leasing a connection from
the pool rather than creating a brand new connection.</para>
- <para><classname>ThreadSafeClientConnManager</classname> maintains a maximum limit of
+ <para><classname>PoolingClientConnectionManager</classname> maintains a maximum limit of
connections on a per route basis and in total. Per default this implementation will
create no more than 2 concurrent connections per given route and no more 20
connections in total. For many real-world applications these limits may prove too
@@ -573,14 +550,14 @@ schemeRegistry.register(
schemeRegistry.register(
new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
-ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry);
+PoolingClientConnectionManager cm = new PoolingClientConnectionManager(schemeRegistry);
// Increase max total connection to 200
-cm.setMaxTotalConnections(200);
+cm.setMaxTotal(200);
// Increase default max connection per route to 20
cm.setDefaultMaxPerRoute(20);
// Increase max connections for localhost:80 to 50
HttpHost localhost = new HttpHost("locahost", 80);
-cm.setMaxForRoute(new HttpRoute(localhost), 50);
+cm.setMaxPerRoute(new HttpRoute(localhost), 50);
HttpClient httpClient = new DefaultHttpClient(cm);
]]></programlisting>
@@ -604,22 +581,23 @@ httpclient.getConnectionManager().shutdown();
</section>
<section>
<title>Multithreaded request execution</title>
- <para>When equipped with a pooling connection manager such as ThreadSafeClientConnManager,
- HttpClient can be used to execute multiple requests simultaneously using multiple
- threads of execution.</para>
- <para>The <classname>ThreadSafeClientConnManager</classname> will allocate connections based on
- its configuration. If all connections for a given route have already been leased, a
- request for a connection will block until a connection is released back to the pool. One
- can ensure the connection manager does not block indefinitely in the connection request
- operation by setting <literal>'http.conn-manager.timeout'</literal> to a positive value.
- If the connection request cannot be serviced within the given time period
- <exceptionname>ConnectionPoolTimeoutException</exceptionname> will be thrown.</para>
+ <para>When equipped with a pooling connection manager such as <classname>
+ PoolingClientConnectionManager</classname>, HttpClient can be used to execute multiple
+ requests simultaneously using multiple threads of execution.</para>
+ <para>The <classname>PoolingClientConnectionManager</classname> will allocate connections
+ based on its configuration. If all connections for a given route have already been
+ leased, a request for a connection will block until a connection is released back to
+ the pool. One can ensure the connection manager does not block indefinitely in the
+ connection request operation by setting <literal>'http.conn-manager.timeout'</literal>
+ to a positive value. If the connection request cannot be serviced within the given time
+ period <exceptionname>ConnectionPoolTimeoutException</exceptionname> will be thrown.
+ </para>
<programlisting><![CDATA[
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
-ClientConnectionManager cm = new ThreadSafeClientConnManager(schemeRegistry);
+ClientConnectionManager cm = new PoolingClientConnectionManager(schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm);
// URIs to perform GETs on
@@ -648,6 +626,10 @@ for (int j = 0; j < threads.length; j++) {
}
]]></programlisting>
+ <para>While <interfacename>HttpClient</interfacename> instances are thread safe and can be
+ shared between multiple threads of execution, it is highly recommended that each
+ thread maintains its own dedicated instance of <interfacename>HttpContext
+ </interfacename>.</para>
<programlisting><![CDATA[
static class GetThread extends Thread {
diff --git a/src/docbkx/fluent.xml b/src/docbkx/fluent.xml
new file mode 100644
index 0000000..1fb38ce
--- /dev/null
+++ b/src/docbkx/fluent.xml
@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE preface PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+ "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<!--
+ ====================================================================
+ 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.
+ ====================================================================
+
+-->
+<chapter id="fluent">
+ <title>Fluent API</title>
+ <section>
+ <title>Easy to use facade API</title>
+ <para>
+ As of version of 4.2 HttpClient comes with an easy to use facade API based on the concept
+ of a fluent interface. Fluent facade API exposes only the most fundamental functions of
+ HttpClient and is intended for simple use cases that do not require the full flexibility of
+ HttpClient. For instance, fluent facade API relieves the users from having to deal with
+ connection management and resource deallocation.
+ </para>
+ <para>Here are several examples of HTTP requests executed through the HC fluent API</para>
+ <programlisting><![CDATA[
+// Execute a GET with timeout settings and return response content as String.
+Request.Get("http://somehost/")
+ .connectTimeout(1000)
+ .socketTimeout(1000)
+ .execute().returnContent().asString();
+]]>
+ </programlisting>
+ <programlisting><![CDATA[
+// Execute a POST with the 'expect-continue' handshake, using HTTP/1.1,
+// containing a request body as String and return response content as byte array.
+Request.Post("http://somehost/do-stuff")
+ .useExpectContinue()
+ .version(HttpVersion.HTTP_1_1)
+ .bodyString("Important stuff", ContentType.DEFAULT_TEXT)
+ .execute().returnContent().asBytes();
+]]>
+ </programlisting>
+ <programlisting><![CDATA[
+// Execute a POST with a custom header through the proxy containing a request body
+// as an HTML form and save the result to the file
+Request.Post("http://somehost/some-form")
+ .addHeader("X-Custom-header", "stuff")
+ .viaProxy(new HttpHost("myproxy", 8080))
+ .bodyForm(Form.form().add("username", "vip").add("password", "secret").build())
+ .execute().saveContent(new File("result.dump"));
+]]>
+ </programlisting>
+ <para>One can also use <classname>Executor</classname> directly in order to execute requests in
+ a specific security context whereby authentication details are cached and re-used for
+ subsequent requests.
+ </para>
+ <programlisting><![CDATA[
+Executor executor = Executor.newInstance()
+ .auth(new HttpHost("somehost"), "username", "password")
+ .auth(new HttpHost("myproxy", 8080), "username", "password")
+ .authPreemptive(new HttpHost("myproxy", 8080));
+
+executor.execute(Request.Get("http://somehost/"))
+ .returnContent().asString();
+
+executor.execute(Request.Post("http://somehost/do-stuff")
+ .useExpectContinue()
+ .bodyString("Important stuff", ContentType.DEFAULT_TEXT))
+ .returnContent().asString();
+]]>
+ </programlisting>
+ <section>
+ <title>Response handling</title>
+ <para>The fluent facade API generally relieves the users from having to deal with
+ connection management and resource deallocation. In most cases, though, this comes at
+ a price of having to buffer content of response messages in memory. It is highly
+ recommended to use <interfacename>ResponseHandler</interfacename> for HTTP response
+ processing in order to avoid having to buffer content in memory.</para>
+ <programlisting><![CDATA[
+Document result = Request.Get("http://somehost/content")
+ .execute().handleResponse(new ResponseHandler<Document>() {
+
+ public Document handleResponse(final HttpResponse response) throws IOException {
+ StatusLine statusLine = response.getStatusLine();
+ HttpEntity entity = response.getEntity();
+ if (statusLine.getStatusCode() >= 300) {
+ throw new HttpResponseException(
+ statusLine.getStatusCode(),
+ statusLine.getReasonPhrase());
+ }
+ if (entity == null) {
+ throw new ClientProtocolException("Response contains no content");
+ }
+ DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
+ try {
+ DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
+ ContentType contentType = ContentType.getOrDefault(entity);
+ if (!contentType.equals(ContentType.APPLICATION_XML)) {
+ throw new ClientProtocolException("Unexpected content type:" + contentType);
+ }
+ String charset = contentType.getCharset();
+ if (charset == null) {
+ charset = HTTP.DEFAULT_CONTENT_CHARSET;
+ }
+ return docBuilder.parse(entity.getContent(), charset);
+ } catch (ParserConfigurationException ex) {
+ throw new IllegalStateException(ex);
+ } catch (SAXException ex) {
+ throw new ClientProtocolException("Malformed XML document", ex);
+ }
+ }
+
+ });
+]]>
+ </programlisting>
+ </section>
+ <section>
+ <title>Asynchronous execution</title>
+ <para>The fluent facade API can be used to execute multiple requests asynchronously using
+ background threads.
+ </para>
+ <programlisting><![CDATA[
+ExecutorService threadpool = Executors.newFixedThreadPool(2);
+Async async = Async.newInstance().use(threadpool);
+
+Request[] requests = new Request[] {
+ Request.Get("http://www.google.com/"),
+ Request.Get("http://www.yahoo.com/"),
+ Request.Get("http://www.apache.com/"),
+ Request.Get("http://www.apple.com/")
+};
+
+Queue<Future<Content>> queue = new LinkedList<Future<Content>>();
+for (final Request request: requests) {
+ Future<Content> future = async.execute(request, new FutureCallback<Content>() {
+
+ public void failed(final Exception ex) {
+ System.out.println(ex.getMessage() + ": " + request);
+ }
+
+ public void completed(final Content content) {
+ System.out.println("Request completed: " + request);
+ }
+
+ public void cancelled() {
+ }
+
+ });
+ queue.add(future);
+}
+
+// Process the queue
+]]>
+ </programlisting>
+ </section>
+ </section>
+</chapter>
diff --git a/src/docbkx/fundamentals.xml b/src/docbkx/fundamentals.xml
index 15df4c4..9c550dc 100644
--- a/src/docbkx/fundamentals.xml
+++ b/src/docbkx/fundamentals.xml
@@ -40,9 +40,10 @@ HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
- int l;
- byte[] tmp = new byte[2048];
- while ((l = instream.read(tmp)) != -1) {
+ try {
+ // do something useful
+ } finally {
+ instream.close();
}
}
]]></programlisting>
@@ -65,28 +66,16 @@ if (entity != null) {
HttpGet httpget = new HttpGet(
"http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
]]></programlisting>
- <para>HttpClient provides a number of utility methods to simplify creation and
- modification of request URIs.</para>
- <para>URI can be assembled programmatically:</para>
- <programlisting><![CDATA[
-URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",
- "q=httpclient&btnG=Google+Search&aq=f&oq=", null);
-HttpGet httpget = new HttpGet(uri);
-System.out.println(httpget.getURI());
-]]></programlisting>
- <para>stdout ></para>
- <programlisting><![CDATA[
-http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=
-]]></programlisting>
- <para>Query string can also be generated from individual parameters:</para>
+ <para>HttpClient provides <classname>URIBuilder</classname> utility class to simplify
+ creation and modification of request URIs.</para>
<programlisting><![CDATA[
-List<NameValuePair> qparams = new ArrayList<NameValuePair>();
-qparams.add(new BasicNameValuePair("q", "httpclient"));
-qparams.add(new BasicNameValuePair("btnG", "Google Search"));
-qparams.add(new BasicNameValuePair("aq", "f"));
-qparams.add(new BasicNameValuePair("oq", null));
-URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",
- URLEncodedUtils.format(qparams, "UTF-8"), null);
+URIBuilder builder = new URIBuilder();
+builder.setScheme("http").setHost("www.google.com").setPath("/search")
+ .setParameter("q", "httpclient")
+ .setParameter("btnG", "Google Search")
+ .setParameter("aq", "f")
+ .setParameter("oq", "");
+URI uri = builder.build();
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());
]]></programlisting>
@@ -276,19 +265,16 @@ domain=localhost
supplied by the creator of the entity.</para>
<programlisting><![CDATA[
StringEntity myEntity = new StringEntity("important message",
- "UTF-8");
+ ContentType.create("text/plain", "UTF-8"));
System.out.println(myEntity.getContentType());
System.out.println(myEntity.getContentLength());
-System.out.println(EntityUtils.getContentCharSet(myEntity));
System.out.println(EntityUtils.toString(myEntity));
-System.out.println(EntityUtils.toByteArray(myEntity).length);
-]]></programlisting>
+System.out.println(EntityUtils.toByteArray(myEntity).length);]]></programlisting>
<para>stdout ></para>
<programlisting><![CDATA[
-Content-Type: text/plain; charset=UTF-8
+Content-Type: text/plain; charset=utf-8
17
-UTF-8
important message
17
]]></programlisting>
@@ -394,7 +380,7 @@ if (entity != null) {
<classname>FileEntity</classname>.</para>
<programlisting><![CDATA[
File file = new File("somefile.txt");
-FileEntity entity = new FileEntity(file, "text/plain; charset=\"UTF-8\"");
+FileEntity entity = new FileEntity(file, ContentType.create("text/plain", "UTF-8"));
HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);
@@ -405,34 +391,6 @@ httppost.setEntity(entity);
self-contained instead of using the generic <classname>InputStreamEntity</classname>.
<classname>FileEntity</classname> can be a good starting point.</para>
<section>
- <title>Dynamic content entities</title>
- <para>Often HTTP entities need to be generated dynamically based a particular
- execution context. HttpClient provides support for dynamic entities by using
- the <classname>EntityTemplate</classname> entity class and
- <interfacename>ContentProducer</interfacename> interface. Content producers
- are objects which produce their content on demand, by writing it out to an
- output stream. They are expected to be able produce their content every time
- they are requested to do so. So entities created with
- <classname>EntityTemplate</classname> are generally self-contained and
- repeatable.</para>
- <programlisting><![CDATA[
-ContentProducer cp = new ContentProducer() {
- public void writeTo(OutputStream outstream) throws IOException {
- Writer writer = new OutputStreamWriter(outstream, "UTF-8");
- writer.write("<response>");
- writer.write(" <content>");
- writer.write(" important stuff");
- writer.write(" </content>");
- writer.write("</response>");
- writer.flush();
- }
-};
-HttpEntity entity = new EntityTemplate(cp);
-HttpPost httppost = new HttpPost("http://localhost/handler.do");
-httppost.setEntity(entity);
-]]></programlisting>
- </section>
- <section>
<title>HTML forms</title>
<para>Many applications need to simulate the process of submitting an
HTML form, for instance, in order to log in to a web application or submit input
@@ -515,6 +473,9 @@ byte[] response = httpclient.execute(httpget, handler);
simply a collection of arbitrary named values. An application can populate context
attributes prior to request execution or examine the context after the execution has
been completed.</para>
+ <para><interfacename>HttpContext</interfacename> can contain arbitrary objects and
+ therefore may be unsafe to share between multiple threads. It is recommended that
+ each thread of execution maintains its own context.</para>
<para>In the course of HTTP request execution HttpClient adds the following attributes to
the execution context:</para>
<itemizedlist>
@@ -659,15 +620,6 @@ Final target: http://www.google.ch
target server (i.e. the request has not been fully transmitted to the
server).</para>
</listitem>
- <listitem>
- <para>HttpClient will automatically retry those methods that have been fully
- transmitted to the server, but the server failed to respond with an HTTP
- status code (the server simply drops the connection without sending anything
- back). In this case it is assumed that the request has not been processed by
- the server and the application state has not changed. If this assumption may
- not hold true for the web server your application is targeting it is highly
- recommended to provide a custom exception handler.</para>
- </listitem>
</itemizedlist>
</section>
<section>
@@ -688,12 +640,20 @@ HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
// Do not retry if over max retry count
return false;
}
- if (exception instanceof NoHttpResponseException) {
- // Retry if the server dropped connection on us
- return true;
+ if (exception instanceof InterruptedIOException) {
+ // Timeout
+ return false;
+ }
+ if (exception instanceof UnknownHostException) {
+ // Unknown host
+ return false;
+ }
+ if (exception instanceof ConnectException) {
+ // Connection refused
+ return false;
}
- if (exception instanceof SSLHandshakeException) {
- // Do not retry on SSL handshake exception
+ if (exception instanceof SSLException) {
+ // SSL handshake exception
return false;
}
HttpRequest request = (HttpRequest) context.getAttribute(
diff --git a/src/docbkx/httpagent.xml b/src/docbkx/httpagent.xml
index e66be82..94ab8c5 100644
--- a/src/docbkx/httpagent.xml
+++ b/src/docbkx/httpagent.xml
@@ -144,10 +144,12 @@ httpclient.getConnectionManager().shutdown();
<listitem>
<formalpara>
<title><constant>ClientPNames.VIRTUAL_HOST</constant>='http.virtual-host':</title>
- <para>defines the virtual host name to be used in the <literal>Host</literal>
- header instead of the physical host name. This parameter expects a value of
- type <classname>HttpHost</classname>. If this parameter is not set name or
- IP address of the target host will be used.</para>
+ <para>defines the virtual host settings to be used in the <literal>Host</literal>
+ header instead of the physical host. This parameter expects a value of
+ type <classname>HttpHost</classname>. The HttpHost port does not have to
+ be specified as it will be derived from the target.
+ If this parameter is not set, the name or
+ IP address (and port if required) of the target host will be used.</para>
</formalpara>
</listitem>
<listitem>
diff --git a/src/docbkx/index.xml b/src/docbkx/index.xml
index 7759da1..677f7bc 100644
--- a/src/docbkx/index.xml
+++ b/src/docbkx/index.xml
@@ -66,6 +66,7 @@
<xi:include href="statemgmt.xml"/>
<xi:include href="authentication.xml"/>
<xi:include href="httpagent.xml"/>
+ <xi:include href="fluent.xml"/>
<xi:include href="caching.xml"/>
<xi:include href="advanced.xml"/>
diff --git a/src/docbkx/statemgmt.xml b/src/docbkx/statemgmt.xml
index c8c8651..73fabf9 100644
--- a/src/docbkx/statemgmt.xml
+++ b/src/docbkx/statemgmt.xml
@@ -160,6 +160,12 @@ stdCookie.setAttribute(ClientCookie.PORT_ATTR, "80,8080");
above implementations into one class.</para>
</formalpara>
</listitem>
+ <listitem>
+ <formalpara>
+ <title>Ignore cookies:</title>
+ <para>All cookies are ignored.</para>
+ </formalpara>
+ </listitem>
</itemizedlist>
<para>It is strongly recommended to use the <literal>Best Match</literal> policy and let
HttpClient pick up an appropriate compliance level at runtime based on the execution
@@ -247,6 +253,12 @@ stdCookie.setAttribute(ClientCookie.PORT_ATTR, "80,8080");
<para>Best match meta-policy.</para>
</formalpara>
</listitem>
+ <listitem>
+ <formalpara>
+ <title>ignoreCookies:</title>
+ <para>All cookies are ignored.</para>
+ </formalpara>
+ </listitem>
</itemizedlist>
</section>
<section>
diff --git a/src/main/assembly/bin.xml b/src/main/assembly/bin.xml
index e500a5f..e97771c 100644
--- a/src/main/assembly/bin.xml
+++ b/src/main/assembly/bin.xml
@@ -69,18 +69,10 @@
<directory>target/site/apidocs</directory>
<outputDirectory>javadoc</outputDirectory>
</fileSet>
- <!-- Tutorial (HTML) -->
+ <!-- Tutorial -->
<fileSet>
- <directory>target/docbkx/html</directory>
- <outputDirectory>tutorial/html</outputDirectory>
- </fileSet>
- <fileSet>
- <directory>src/docbkx/resources/css</directory>
- <outputDirectory>tutorial/html/css</outputDirectory>
- </fileSet>
- <fileSet>
- <directory>src/docbkx/resources/images</directory>
- <outputDirectory>tutorial/html/images</outputDirectory>
+ <directory>target/site/tutorial</directory>
+ <outputDirectory>tutorial</outputDirectory>
</fileSet>
<!-- Base module -->
<fileSet>
@@ -99,12 +91,4 @@
</includes>
</fileSet>
</fileSets>
- <files>
- <!-- Tutorial (PDF) -->
- <file>
- <source>target/docbkx/pdf/index.pdf</source>
- <outputDirectory>tutorial/pdf</outputDirectory>
- <destName>httpclient-tutorial.pdf</destName>
- </file>
- </files>
</assembly>
diff --git a/src/main/assembly/osgi-bin.xml b/src/main/assembly/osgi-bin.xml
index 9ff0f8c..36c0cea 100644
--- a/src/main/assembly/osgi-bin.xml
+++ b/src/main/assembly/osgi-bin.xml
@@ -56,18 +56,10 @@
<directory>target/site/apidocs</directory>
<outputDirectory>javadoc</outputDirectory>
</fileSet>
- <!-- Tutorial (HTML) -->
+ <!-- Tutorial -->
<fileSet>
- <directory>target/docbkx/html</directory>
- <outputDirectory>tutorial/html</outputDirectory>
- </fileSet>
- <fileSet>
- <directory>src/docbkx/resources/css</directory>
- <outputDirectory>tutorial/html/css</outputDirectory>
- </fileSet>
- <fileSet>
- <directory>src/docbkx/resources/images</directory>
- <outputDirectory>tutorial/html/images</outputDirectory>
+ <directory>target/site/tutorial</directory>
+ <outputDirectory>tutorial</outputDirectory>
</fileSet>
<!-- Base module -->
<fileSet>
@@ -86,12 +78,4 @@
</includes>
</fileSet>
</fileSets>
- <files>
- <!-- Tutorial (PDF) -->
- <file>
- <source>target/docbkx/pdf/index.pdf</source>
- <outputDirectory>tutorial/pdf</outputDirectory>
- <destName>httpclient-tutorial.pdf</destName>
- </file>
- </files>
</assembly>
diff --git a/src/site/apt/download.apt b/src/site/apt/download.apt
index e7334a2..a063244 100644
--- a/src/site/apt/download.apt
+++ b/src/site/apt/download.apt
@@ -42,35 +42,47 @@ HttpClient Downloads
in your {{{http://maven.apache.org/guides/introduction/introduction-to-the-pom.html}pom.xml}}
by adding the following block to the dependency descriptor:
-* {HttpComponents Client 4.1.1}
+* {HttpComponents Client 4.2.1}
-------------------------
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
- <version>4.1.1</version>
+ <version>4.2.1</version>
<scope>compile</scope>
</dependency>
-------------------------
-* {HttpComponents HttpMime 4.1.1}
+* {HttpComponents HttpMime 4.2.1}
-------------------------
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
- <version>4.1.1</version>
+ <version>4.2.1</version>
<scope>compile</scope>
</dependency>
-------------------------
-* {HttpComponents HttpClient Cache 4.1.1}
+* {HttpComponents HttpClient Cache 4.2.1}
-------------------------
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient-cache</artifactId>
- <version>4.1.1</version>
+ <version>4.2.1</version>
<scope>compile</scope>
</dependency>
-------------------------
+
+* {HttpComponents HttpClient Fluent 4.2.1}
+
+-------------------------
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>hc-fluent</artifactId>
+ <version>4.2.1</version>
+ <scope>compile</scope>
+ </dependency>
+-------------------------
+
diff --git a/src/site/apt/index.apt b/src/site/apt/index.apt
index 92ba78d..b0dde66 100644
--- a/src/site/apt/index.apt
+++ b/src/site/apt/index.apt
@@ -47,10 +47,21 @@ HttpClient Overview
{Documentation}
- * HttpClient Tutorial ( {{{./tutorial/html/index.html}HTML}} / {{{./tutorial/pdf/httpclient-tutorial.pdf}PDF}} )
-
- * Some examples of HttpClient in action can be found {{{./examples.html}here}}
+ [[1]] {{{./quickstart.html}Quick Start}} - contains a simple, complete example of an HTTP GET
+ and POST with parameters.
+
+ [[1]] {{{./tutorial/html/index.html}HttpClient Tutorial}} - gives a detailed examination of the
+ HttpClient API, which was written in close accordance with the (sometimes not very intuitive)
+ HTTP specification/standard. A copy is also shipped with the release.
+ {{{./tutorial/pdf/httpclient-tutorial.pdf}A PDF version}} is also available
+
+ [[1]] {{{./examples.html}HttpClient Examples}} - a set of examples demonstrating some of
+ the more complex behavior.
+ [[1]] {{{./primer.html}HttpClient Primer}} - explains the scope of HttpClient.
+ Note that HttpClient is not a browser. It lacks the UI, HTML renderer and a JavaScript engine
+ that a browser will possess.
+
{Features}
* Standards based, pure Java, implementation of HTTP versions 1.0 and 1.1
@@ -64,7 +75,7 @@ HttpClient Overview
* Tunneled HTTPS connections through HTTP proxies, via the CONNECT method.
- * Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO/Kerberos authentication schemes.
+ * Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO, Kerberos authentication schemes.
* Plug-in mechanism for custom authentication schemes.
@@ -91,7 +102,7 @@ HttpClient Overview
* The ability to set connection timeouts.
- * Experimental support for HTTP/1.1 response caching.
+ * Support for HTTP/1.1 response caching.
* Source code is freely available under the Apache License.
diff --git a/src/site/apt/ntlm.apt b/src/site/apt/ntlm.apt
new file mode 100644
index 0000000..3fd54aa
--- /dev/null
+++ b/src/site/apt/ntlm.apt
@@ -0,0 +1,155 @@
+~~ ====================================================================
+~~ 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/>.
+
+ ----------
+ NTLM support in HttpClient
+ ----------
+ ----------
+ ----------
+
+NTLM support in HttpClient
+
+* {Background}
+
+ NTLM is a proprietary authentication scheme developed by Microsoft and optimized for
+ Windows operating system.
+
+ Until year 2008 there was no official, publicly available, complete documentation of
+ the protocol. {{{http://davenport.sourceforge.net/ntlm.html}Unofficial}} 3rd party
+ protocol descriptions existed as a result of reverse-engineering efforts. It was not
+ really known whether the protocol based on the reverse-engineering were complete or
+ even correct.
+
+ Microsoft published {{{http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NLMP%5D.pdf}MS-NLMP}}
+ and {{{http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-NTHT%5D.pdf}MS-NTHT}}
+ specifications in February 2008 as a part of its
+ {{{http://www.microsoft.com/interop/principles/default.mspx}Interoperability
+ Principles initiative}}.
+
+ HttpClient as of version 4.1 supports NTLMv1 and NTLMv2 authentication protocols out
+ of the box using a custom authentication engine. However, there are still known compatibility
+ issues with newer Microsoft products as the default NTLM engine implementation is still
+ relatively new. One can also use {{{http://jcifs.samba.org/}JCIFS}} as an alternative, more
+ established and mature NTLM engine developed by Samba project.
+
+* {Using Samba JCIFS as an alternative NTLM engine}
+
+ Follow these instructions to build an NTLMEngine implementation using JCIFS library
+
+ <<!!!!DISCLAIMER !!!! HttpComponents project DOES _NOT_ SUPPORT the code provided below.
+ Use it as is at your own discretion>>.
+
+ * Download version 1.3.14 or newer of the JCIFS library from the
+ {{{http://jcifs.samba.org/}Samba}} web site
+
+ * Implement NTLMEngine interface
+
+----------------------------------------
+import java.io.IOException;
+
+import jcifs.ntlmssp.NtlmFlags;
+import jcifs.ntlmssp.Type1Message;
+import jcifs.ntlmssp.Type2Message;
+import jcifs.ntlmssp.Type3Message;
+import jcifs.util.Base64;
+
+import org.apache.http.impl.auth.NTLMEngine;
+import org.apache.http.impl.auth.NTLMEngineException;
+
+public final class JCIFSEngine implements NTLMEngine {
+
+ private static final int TYPE_1_FLAGS =
+ NtlmFlags.NTLMSSP_NEGOTIATE_56 |
+ NtlmFlags.NTLMSSP_NEGOTIATE_128 |
+ NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2 |
+ NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
+ NtlmFlags.NTLMSSP_REQUEST_TARGET;
+
+ public String generateType1Msg(final String domain, final String workstation)
+ throws NTLMEngineException {
+ final Type1Message type1Message = new Type1Message(TYPE_1_FLAGS, domain, workstation);
+ return Base64.encode(type1Message.toByteArray());
+ }
+
+ public String generateType3Msg(final String username, final String password,
+ final String domain, final String workstation, final String challenge)
+ throws NTLMEngineException {
+ Type2Message type2Message;
+ try {
+ type2Message = new Type2Message(Base64.decode(challenge));
+ } catch (final IOException exception) {
+ throw new NTLMEngineException("Invalid NTLM type 2 message", exception);
+ }
+ final int type2Flags = type2Message.getFlags();
+ final int type3Flags = type2Flags
+ & (0xffffffff ^ (NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));
+ final Type3Message type3Message = new Type3Message(type2Message, password, domain,
+ username, workstation, type3Flags);
+ return Base64.encode(type3Message.toByteArray());
+ }
+
+}
+----------------------------------------
+
+ * Implement AuthSchemeFactory interface
+
+----------------------------------------
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthSchemeFactory;
+import org.apache.http.impl.auth.NTLMScheme;
+import org.apache.http.params.HttpParams;
+
+public class NTLMSchemeFactory implements AuthSchemeFactory {
+
+ public AuthScheme newInstance(final HttpParams params) {
+ return new NTLMScheme(new JCIFSEngine());
+ }
+
+}
+----------------------------------------
+
+ * Register NTLMSchemeFactory with the HttpClient instance you want to NTLM
+ enable.
+
+----------------------------------------
+httpclient.getAuthSchemes().register("ntlm", new NTLMSchemeFactory());
+----------------------------------------
+
+ * Set NTCredentials for the web server you are going to access.
+
+----------------------------------------
+httpclient.getCredentialsProvider().setCredentials(
+ new AuthScope("myserver", -1),
+ new NTCredentials("username", "password", "MYSERVER", "MYDOMAIN"));
+-----------------------------------------------------------
+
+ * You are done.
+
+
+* {Why this code is not distributed with HttpClient}
+
+ JCIFS is licensed under the Lesser General Public License (LGPL). This license
+ is not compatible with the Apache Licenses under which all Apache Software is
+ released. Lawyers of the Apache Software Foundation are currently investigating
+ under which conditions Apache software is allowed to make use of LGPL software.
diff --git a/src/site/apt/quickstart.apt b/src/site/apt/quickstart.apt
index 688a8e3..c003f50 100644
--- a/src/site/apt/quickstart.apt
+++ b/src/site/apt/quickstart.apt
@@ -33,8 +33,8 @@ HttpClient Quick Start
[[1]] Download 'Binary' package of the latest official release from
the {{{http://hc.apache.org/downloads.cgi} project download page}}.
- There should be 5 jars in total (components marked with (*) are optional if MIME multipart
- support is not required)
+ There should be 7 jars in total (components marked with (*) include additional features and
+ are optional) on the classpath.
* commons-logging-<x.x.x>.jar
@@ -46,14 +46,85 @@ HttpClient Quick Start
* httpmime-<x.x.x>.jar (*)
+ * httpclient-cache-<x.x.x>.jar (*)
+
+ * fluent-hc-<x.x.x>.jar (*)
+
[]
+
+ [[2]] The below code fragment illustrates the execution of HTTP GET and POST requests using the HttpClient native API.
+
+-------------
+
+DefaultHttpClient httpclient = new DefaultHttpClient();
+HttpGet httpGet = new HttpGet("http://targethost/homepage");
+
+ HttpResponse response1 = httpclient.execute(httpGet);
+
+// The underlying HTTP connection is still held by the response object
+// to allow the response content to be streamed directly from the network socket.
+// In order to ensure correct deallocation of system resources
+// the user MUST either fully consume the response content or abort request
+// execution by calling HttpGet#releaseConnection().
+
+try {
+ System.out.println(response1.getStatusLine());
+ HttpEntity entity1 = response1.getEntity();
+ // do something useful with the response body
+ // and ensure it is fully consumed
+ EntityUtils.consume(entity1);
+} finally {
+ httpGet.releaseConnection();
+}
+
+HttpPost httpPost = new HttpPost("http://targethost/login");
+List <NameValuePair> nvps = new ArrayList <NameValuePair>();
+nvps.add(new BasicNameValuePair("username", "vip"));
+nvps.add(new BasicNameValuePair("password", "secret"));
+httpPost.setEntity(new UrlEncodedFormEntity(nvps));
+HttpResponse response2 = httpclient.execute(httpPost);
+
+try {
+ System.out.println(response2.getStatusLine());
+ HttpEntity entity2 = response2.getEntity();
+ // do something useful with the response body
+ // and ensure it is fully consumed
+ EntityUtils.consume(entity2);
+} finally {
+ httpPost.releaseConnection();
+}
+
+-------------
+
+ Source can be downloaded
+ {{{./httpclient/examples/org/apache/http/examples/client/QuickStart.java}here}}
+
+ [[3]] The same requests can be executed using a simpler, albeit less flexible, fluent API.
+
+-------------
+
+// The fluent API relieves the user from having to deal with manual deallocation of system
+// resources at the cost of having to buffer response content in memory in some cases.
+
+Request.Get("http://targethost/homepage")
+ .execute().returnContent();
+Request.Post("http://targethost/login")
+ .bodyForm(Form.form().add("username", "vip").add("password", "secret").build())
+ .execute().returnContent();
+
+-------------
- [[2]] Take a look at the HttpClient tutorial shipped with the release package or available
- {{{./tutorial/html/index.html}online}} to learn the HttpClient API.
-
- [[3]] Another good way of getting started with HttpClient is by seeing it in action. Take
- a look at the samples shipped with the release package or available {{{./examples.html}online}}.
+ Source can be downloaded
+ {{{./httpclient/examples/org/apache/http/examples/client/fuent/FluentQuickStart.java}here}}
- [[4]] Please note that HttpClient is not a browser. Importantly it lacks UI, cache, HTML
- renderer and a JavaScript engine. To learn more about the scope of HttpClient please refer to
- {{{./primer.html}HttpClient Primer}}
\ No newline at end of file
+ [[4]] {{{./examples.html}HttpClient Examples}} - a set of examples demonstrating some of
+ the more complex behavior.
+
+ [[5]] {{{./tutorial/html/index.html}HttpClient Tutorial}} - gives a detailed examination of the
+ HttpClient API, which was written in close accordance with the (sometimes not very intuitive)
+ HTTP specification/standard. A copy is also shipped with the release.
+ {{{./tutorial/pdf/httpclient-tutorial.pdf}A PDF version}} is also available
+
+ [[6]] {{{./primer.html}HttpClient Primer}} - explains the scope of HttpClient.
+ Note that HttpClient is not a browser. It lacks the UI, HTML renderer and a JavaScript engine
+ that a browser will possess.
diff --git a/src/site/site.xml b/src/site/site.xml
index 902cd66..153f70a 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -36,12 +36,14 @@
<item name="Tutorial" href="tutorial/html/index.html"/>
<item name="Examples" href="examples.html"/>
<item name="Client HTTP Programming Primer" href="primer.html"/>
+ <item name="NTLM Guide" href="ntlm.html"/>
<item name="Logging" href="logging.html"/>
</menu>
<menu name="Modules">
<item name="HttpClient" href="httpclient/index.html"/>
<item name="HttpMime" href="httpmime/index.html"/>
<item name="HttpClient Cache" href="httpclient-cache/index.html"/>
+ <item name="Fluent HC" href="fluent-hc/index.html"/>
</menu>
<!-- Reports don't really apply at this level; in particular the dependecy report is misleading -->
<!-- menu ref="reports"/-->
--
httpcomponents-client: HTTP/1.1 compliant HTTP agent Java implementation
More information about the pkg-java-commits
mailing list