Index: httpclient-osgi/src/main/java/org/apache/http/osgi/Activator.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- httpclient-osgi/src/main/java/org/apache/http/osgi/Activator.java (revision ) +++ httpclient-osgi/src/main/java/org/apache/http/osgi/Activator.java (revision ) @@ -0,0 +1,67 @@ +/* + * ==================================================================== + * 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 + * . + * + */ +package org.apache.http.osgi; + +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.impl.conn.ClientConnectionManagerTracker; +import org.apache.http.osgi.proxy.ConfigurableProxySelector; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +/** + * This is the bundle activator. It registers the activator as a service, so it can receive configuration properties. + * Once a configuration is updated, it registers a proxy selector based on the configuration properties. + *

+ * The activator also is responsible for unregistering the service upon bundle shutdown. Additionally the shutdown + * ensures that all open connections of all http-clients are terminated, so the bundle properly stops. Finally, upon + * shutdown, the default proxy selector is reverted to java's default. + */ +public class Activator implements BundleActivator { + + private ServiceRegistration configurator; + + public void start(BundleContext context) throws Exception { + configurator = context + .registerService(ConfigurableProxySelector.class.getName(), new ConfigurableProxySelector(), null); + } + + public void stop(BundleContext context) throws Exception { + + // stop receiving configuration + if (configurator != null) { + configurator.unregister(); + } + + // ensure all clients are terminated + for (final ClientConnectionManager manager : ClientConnectionManagerTracker.getInstance().getAll()) { + if (null != manager) { + manager.shutdown(); + } + } + } +} Index: httpclient/src/main/java/org/apache/http/impl/conn/BasicClientConnectionManager.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- httpclient/src/main/java/org/apache/http/impl/conn/BasicClientConnectionManager.java (revision 1395548) +++ httpclient/src/main/java/org/apache/http/impl/conn/BasicClientConnectionManager.java (revision ) @@ -101,6 +101,9 @@ } this.schemeRegistry = schreg; this.connOperator = createConnectionOperator(schreg); + + // have this connection manager centrally tracked via weak reference + ClientConnectionManagerTracker.getInstance().add(this); } public BasicClientConnectionManager() { Index: httpclient-osgi/src/main/java/org/apache/http/osgi/proxy/ConfigurableProxySelector.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- httpclient-osgi/src/main/java/org/apache/http/osgi/proxy/ConfigurableProxySelector.java (revision ) +++ httpclient-osgi/src/main/java/org/apache/http/osgi/proxy/ConfigurableProxySelector.java (revision ) @@ -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 + * . + * + */ +package org.apache.http.osgi.proxy; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.ComponentContext; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +import java.io.IOException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.osgi.framework.Constants.SERVICE_PID; + +@Component(metatype = false) +public class ConfigurableProxySelector extends ProxySelector { + + private final Log log = LogFactory.getLog(getClass()); + + private static int MAX_FAILURES = 3; + + /** + * This service tracks proxy configurations. + */ + private ServiceTracker serviceTracker; + + /** + * The bundle context is used to access tracked service references. + */ + private BundleContext bundleContext; + + /** + * The default proxy selector present before this service was started and registered itself as the new default + * selector. The previous selector serves as a fallback and will be put back into place upon shutdown of this + * service. + */ + private ProxySelector previousSelector; + + /** + * The map holding valid and enabled proxy configurations + */ + private final Map proxyConfigurations = new HashMap(); + + // ------- API + + @Override + public List select(final URI uri) { + + if (null == uri) { + throw new IllegalArgumentException("URI must not be null"); + } + + final List proxies = new ArrayList(); + + for (final ProxyConfiguration config : proxyConfigurations.values()) { + if (config.accepts(uri)) { + proxies.add(config.getProxy()); + } + } + + if (proxies.size() == 0 && null != previousSelector) { + return previousSelector.select(uri); + } else if (proxies.size() == 0) { + proxies.add(Proxy.NO_PROXY); + } + + return proxies; + } + + @Override + public void connectFailed(final URI uri, final SocketAddress address, final IOException ioe) { + + if (null == uri || null == address || null == ioe) { + throw new IllegalArgumentException("arguments may not be null."); + } + + String toBeRemoved = null; + + for (final Map.Entry entry : proxyConfigurations.entrySet()) { + final ProxyConfiguration config = entry.getValue(); + if (address.equals(config.getAddress())) { + final int failures = config.failed(); + log.error("Connection to proxy [" + config + "] failed (" + failures + " time(s))."); + if (failures >= MAX_FAILURES) { + toBeRemoved = entry.getKey(); + log.error("Proxy [" + config + "] failed more than " + MAX_FAILURES + " times, removed."); + } + break; + } + } + + if (null != toBeRemoved) { + synchronized (proxyConfigurations) { + proxyConfigurations.remove(toBeRemoved); + } + } + } + + // ------- OSGI HANDLING + + @Activate + @SuppressWarnings("unused") + protected void activate(final ComponentContext context) { + + previousSelector = ProxySelector.getDefault(); + ProxySelector.setDefault(this); + + bundleContext = context.getBundleContext(); + serviceTracker = new ServiceTracker(bundleContext, + ProxyConfiguration.class.getName(), + new ProxyTracker()); + serviceTracker.open(); + } + + @Deactivate + @SuppressWarnings("unused") + protected void deactivate() { + + ProxySelector.setDefault(previousSelector); + + if (serviceTracker != null) { + serviceTracker.close(); + serviceTracker = null; + } + + proxyConfigurations.clear(); + + bundleContext = null; + } + + // ------- PRIVATE + + private class ProxyTracker implements ServiceTrackerCustomizer { + + public Object addingService(final ServiceReference reference) { + + final Object service = bundleContext.getService(reference); + if (serviceIsValid(service)) { + addProxy(reference, service); + return service; + } + + return null; + } + + public void modifiedService(final ServiceReference reference, final Object service) { + removeProxy(reference, service); + addingService(reference); + } + + public void removedService(final ServiceReference reference, final Object service) { + removeProxy(reference, service); + } + + private boolean serviceIsValid(final Object service) { + return service instanceof ProxyConfiguration && ((ProxyConfiguration) service).isValid(); + } + + private void addProxy(final ServiceReference reference, final Object service) { + final ProxyConfiguration config = (ProxyConfiguration) service; + synchronized (proxyConfigurations) { + proxyConfigurations.put((String) reference.getProperty(SERVICE_PID), config); + } + if (log.isInfoEnabled()) { + log.info("Added new proxy [" + config + "]"); + } + } + + private void removeProxy(final ServiceReference reference, final Object service) { + if (log.isInfoEnabled()) { + final ProxyConfiguration config = (ProxyConfiguration) service; + log.info("Removed proxy [" + config + "]"); + } + synchronized (proxyConfigurations) { + proxyConfigurations.remove((String) reference.getProperty(SERVICE_PID)); + } + } + } +} Index: httpclient/src/main/java/org/apache/http/conn/routing/HttpRoute.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- httpclient/src/main/java/org/apache/http/conn/routing/HttpRoute.java (revision 1395548) +++ httpclient/src/main/java/org/apache/http/conn/routing/HttpRoute.java (revision ) @@ -27,7 +27,15 @@ package org.apache.http.conn.routing; +import java.lang.reflect.UndeclaredThrowableException; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; import org.apache.http.annotation.Immutable; import org.apache.http.util.LangUtils; @@ -100,6 +108,32 @@ throw new IllegalArgumentException ("Proxies may not be null."); } + + // select a proxy from the default proxy selector. in an OSGi-environment, + // the proxy selector is set to org.apache.http.osgi.proxy.ConfigurableProxySelector + // and thus provides centrally configured proxies without the knowledge of the API user + if (proxies.length == 0) { + try { + // get list of proxies from the default proxy selector + final List defaultProxyList = ProxySelector.getDefault().select(new URI(target.toURI())); + final ArrayList defaultProxyHosts = new ArrayList(); + for (final Proxy proxy : defaultProxyList) { + // downcast to InetSocketAddress for host and port accessors + final InetSocketAddress address = (InetSocketAddress) proxy.address(); + + // only HTTP proxies are supported, the address of which is not null + if (Proxy.Type.HTTP == proxy.type() && null != address) { + defaultProxyHosts.add(new HttpHost(address.getHostName(), address.getPort())); + } + } + + proxies = defaultProxyHosts.toArray(new HttpHost[defaultProxyHosts.size()]); + + } catch (URISyntaxException e) { + throw new UndeclaredThrowableException(e); + } + } + if ((tunnelled == TunnelType.TUNNELLED) && (proxies.length == 0)) { throw new IllegalArgumentException ("Proxy required if tunnelled."); Index: httpclient-osgi/src/main/java/org/apache/http/osgi/util/PropertiesUtil.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- httpclient-osgi/src/main/java/org/apache/http/osgi/util/PropertiesUtil.java (revision ) +++ httpclient-osgi/src/main/java/org/apache/http/osgi/util/PropertiesUtil.java (revision ) @@ -0,0 +1,219 @@ +/* + * 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. + */ +package org.apache.http.osgi.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * The PropertiesUtil is a utility class providing some + * usefull utility methods for converting property types. + * + * @since 2.1 + */ +public class PropertiesUtil { + + /** + * Returns the boolean value of the parameter or the + * defaultValue if the parameter is null. + * If the parameter is not a Boolean it is converted + * by calling Boolean.valueOf on the string value of the + * object. + * @param propValue the property value or null + * @param defaultValue the default boolean value + */ + public static boolean toBoolean(Object propValue, boolean defaultValue) { + propValue = toObject(propValue); + if (propValue instanceof Boolean) { + return (Boolean) propValue; + } else if (propValue != null) { + return Boolean.valueOf(String.valueOf(propValue)); + } + + return defaultValue; + } + + /** + * Returns the parameter as a string or the + * defaultValue if the parameter is null. + * @param propValue the property value or null + * @param defaultValue the default string value + */ + public static String toString(Object propValue, String defaultValue) { + propValue = toObject(propValue); + return (propValue != null) ? propValue.toString() : defaultValue; + } + + /** + * Returns the parameter as a long or the + * defaultValue if the parameter is null or if + * the parameter is not a Long and cannot be converted to + * a Long from the parameter's string value. + * @param propValue the property value or null + * @param defaultValue the default long value + */ + public static long toLong(Object propValue, long defaultValue) { + propValue = toObject(propValue); + if (propValue instanceof Long) { + return (Long) propValue; + } else if (propValue != null) { + try { + return Long.valueOf(String.valueOf(propValue)); + } catch (NumberFormatException nfe) { + // don't care, fall through to default value + } + } + + return defaultValue; + } + + /** + * Returns the parameter as an integer or the + * defaultValue if the parameter is null or if + * the parameter is not an Integer and cannot be converted to + * an Integer from the parameter's string value. + * @param propValue the property value or null + * @param defaultValue the default integer value + */ + public static int toInteger(Object propValue, int defaultValue) { + propValue = toObject(propValue); + if (propValue instanceof Integer) { + return (Integer) propValue; + } else if (propValue != null) { + try { + return Integer.valueOf(String.valueOf(propValue)); + } catch (NumberFormatException nfe) { + // don't care, fall through to default value + } + } + + return defaultValue; + } + + /** + * Returns the parameter as a double or the + * defaultValue if the parameter is null or if + * the parameter is not a Double and cannot be converted to + * a Double from the parameter's string value. + * @param propValue the property value or null + * @param defaultValue the default double value + */ + public static double toDouble(Object propValue, double defaultValue) { + propValue = toObject(propValue); + if (propValue instanceof Double) { + return (Double) propValue; + } else if (propValue != null) { + try { + return Double.valueOf(String.valueOf(propValue)); + } catch (NumberFormatException nfe) { + // don't care, fall through to default value + } + } + + return defaultValue; + } + + /** + * Returns the parameter as a single value. If the + * parameter is neither an array nor a java.util.Collection the + * parameter is returned unmodified. If the parameter is a non-empty array, + * the first array element is returned. If the property is a non-empty + * java.util.Collection, the first collection element is returned. + * Otherwise null is returned. + * @param propValue the parameter to convert. + */ + public static Object toObject(Object propValue) { + if (propValue == null) { + return null; + } else if (propValue.getClass().isArray()) { + Object[] prop = (Object[]) propValue; + return prop.length > 0 ? prop[0] : null; + } else if (propValue instanceof Collection) { + Collection prop = (Collection) propValue; + return prop.isEmpty() ? null : prop.iterator().next(); + } else { + return propValue; + } + } + + /** + * Returns the parameter as an array of Strings. If + * the parameter is a scalar value its string value is returned as a single + * element array. If the parameter is an array, the elements are converted to + * String objects and returned as an array. If the parameter is a collection, the + * collection elements are converted to String objects and returned as an array. + * Otherwise (if the parameter is null) null is + * returned. + * @param propValue The object to convert. + */ + public static String[] toStringArray(Object propValue) { + return toStringArray(propValue, null); + } + + /** + * Returns the parameter as an array of Strings. If + * the parameter is a scalar value its string value is returned as a single + * element array. If the parameter is an array, the elements are converted to + * String objects and returned as an array. If the parameter is a collection, the + * collection elements are converted to String objects and returned as an array. + * Otherwise (if the property is null) a provided default value is + * returned. + * @param propValue The object to convert. + * @param defaultArray The default array to return. + */ + public static String[] toStringArray(Object propValue, String[] defaultArray) { + if (propValue == null) { + // no value at all + return defaultArray; + + } else if (propValue instanceof String) { + // single string + return new String[] { (String) propValue }; + + } else if (propValue instanceof String[]) { + // String[] + return (String[]) propValue; + + } else if (propValue.getClass().isArray()) { + // other array + Object[] valueArray = (Object[]) propValue; + List values = new ArrayList(valueArray.length); + for (Object value : valueArray) { + if (value != null) { + values.add(value.toString()); + } + } + return values.toArray(new String[values.size()]); + + } else if (propValue instanceof Collection) { + // collection + Collection valueCollection = (Collection) propValue; + List valueList = new ArrayList(valueCollection.size()); + for (Object value : valueCollection) { + if (value != null) { + valueList.add(value.toString()); + } + } + return valueList.toArray(new String[valueList.size()]); + } + + return defaultArray; + } +} \ No newline at end of file Index: httpclient-osgi/pom.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- httpclient-osgi/pom.xml (revision 1395548) +++ httpclient-osgi/pom.xml (revision ) @@ -54,7 +54,13 @@ ${commons-codec.version} compile - + + commons-logging + commons-logging + ${commons-logging.version} + provided + + org.apache.httpcomponents httpmime ${project.version} @@ -72,6 +78,30 @@ ${project.version} compile + + org.osgi + org.osgi.core + 4.2.0 + provided + + + org.osgi + org.osgi.compendium + 4.2.0 + provided + + + org.apache.felix + org.apache.felix.scr.annotations + 1.7.0 + provided + + + junit + junit + 4.8.2 + test + @@ -79,6 +109,8 @@ UTF-8 1.5 1.5 + true + true @@ -92,12 +124,42 @@ - + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compile.source} + ${maven.compile.target} + ${maven.compile.optimize} + ${maven.compile.deprecation} + + + - org.apache.felix + org.apache.felix + maven-scr-plugin + 1.8.0 + + + generate-scr-scrdescriptor + + scr + + + + + Apache Software Foundation + + + + + + + org.apache.felix maven-bundle-plugin true + org.apache.http.osgi.Activator Apache ${project.name} ${project.groupId}.httpclient <_exportcontents> @@ -113,26 +175,33 @@ *;scope=compile|runtime;inline=true - javax.crypto, - javax.crypto.spec, - javax.net.ssl,javax.security.auth.x500, - org.ietf.jgss, + javax.crypto, + javax.crypto.spec, + javax.net.ssl,javax.security.auth.x500, + org.ietf.jgss, + org.osgi.framework, + org.osgi.service.cm, + org.osgi.service.component, + org.osgi.util.tracker, - 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}, + 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}, + javax.servlet;resolution:=optional, - net.sf.ehcache.*;resolution:=optional, - net.spy.memcached.*;resolution:=optional + net.sf.ehcache.*;resolution:=optional, + net.spy.memcached.*;resolution:=optional + org.apache.avalon.framework.logger;resolution:=optional, + org.apache.log;resolution:=optional <_removeheaders>JAVA_1_3_HOME,JAVA_1_4_HOME Index: httpclient-osgi/src/main/resources/OSGI-INF/metatype/metatype.properties IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>MacRoman =================================================================== --- httpclient-osgi/src/main/resources/OSGI-INF/metatype/metatype.properties (revision ) +++ httpclient-osgi/src/main/resources/OSGI-INF/metatype/metatype.properties (revision ) @@ -0,0 +1,63 @@ +org.apache.http.osgi.proxy.ConfigurableProxySelector.name = Apache HTTP Components Proxy Selector +org.apache.http.osgi.proxy.ConfigurableProxySelector.description = Transparently provides central proxy configurations \ + for the HTTP Clients. + +org.apache.http.osgi.proxy.ProxyConfiguration.name = Apache HTTP Components Proxy Configuration +org.apache.http.osgi.proxy.ProxyConfiguration.description = Defines proxy configurations used by the Apache HTTP \ + Components Proxy Selector. + +proxy.enabled.name = Enable HTTP Proxy +proxy.enabled.description = Whether to enable or disable HTTP Proxying globally. \ + Setting this property to false disables HTTP Proxying and the other configurtion \ + properties have no effect. The default value is false. + +proxy.host.name = HTTP Proxy Host +proxy.host.description = Host name (or IP Address) of the HTTP Proxy. \ + This property is ignored if HTTP Proxying is disabled. This property does \ + not have default value. Enabling HTTP Proxying but not setting the HTTP \ + Proxy Host causes HTTP Proxying to actually be disabled. + +proxy.port.name = HTTP Proxy Port +proxy.port.description = Port number of the HTTP Proxy. \ + This property is ignored if HTTP Proxying is disabled. This property does \ + not have default value. Enabling HTTP Proxying but not setting the HTTP \ + Proxy Host causes HTTP Proxying to actually be disabled. + +proxy.user.name = HTTP Proxy User +proxy.user.description = The name of the user to authenticate as with the HTTP \ + Proxy Host. If this field is empty, the proxy is considered to not be \ + authenticated. The default is empty. This property is ignored if proxying is \ + disabled or the proxy host is not properly configured. + +proxy.password.name = HTTP Proxy Password +proxy.password.description = The password of the HTTP Proxy user to authenticate \ + with. The default is empty. This property is ignored if proxying is \ + disabled or the proxy host is not properly configured. + +proxy.ntlm.host.name = HTTP Proxy NTLM Host +proxy.ntlm.host.description = The host the authentication request is \ + originating from. Essentially, the computer name for this machine. By default \ + the credentials assume simple username password authentication. If the proxy \ + happens to be a Microsoft IIS Server using NTLM authentication this property \ + must be set to the NT Domain name of the user to authenticate as. This is \ + not set by default. + +proxy.ntlm.domain.name = HTTP Proxy NTLM Domain +proxy.ntlm.domain.description = The NTLM domain to authenticate within. By \ + default the credentials assume simple username password authentication. If \ + the proxy happens to be a Microsoft IIS Server using NTLM authentication this \ + property must be set to the NT Domain name of the user to authenticate as. \ + This is not set by default. + +proxy.exceptions.name = No Proxy For +proxy.exceptions.description = Lists domain names, host names, IP Addresses or \ + or network addresses for which the HTTP Proxy Host should not be used. A domain \ + name indicating all hosts of a domain is indicated by a leading dot, e.g. \ + ".day.com". A network address is indicated with subnet mask notation indicating \ + the number of bits make up the network address, e.g 192.168.1.0/24 means the \ + class C network "192.168.1". Note that for proxy selection, the host name of \ + URL is not resolved but directly compared to the list of exceptions. For this \ + reason you might want to indicate both the network address and the domain for \ + targets which should not be passed through the proxy. This property has no \ + effect if HTTP Proxying is disabled. The default value is [ localhost, \ + 127.0.0.1 ]. Index: httpclient/src/main/java/org/apache/http/impl/conn/PoolingClientConnectionManager.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- httpclient/src/main/java/org/apache/http/impl/conn/PoolingClientConnectionManager.java (revision 1395548) +++ httpclient/src/main/java/org/apache/http/impl/conn/PoolingClientConnectionManager.java (revision ) @@ -113,6 +113,9 @@ this.dnsResolver = dnsResolver; this.operator = createConnectionOperator(schemeRegistry); this.pool = new HttpConnPool(this.log, 2, 20, timeToLive, tunit); + + // have this connection manager centrally tracked via weak reference + ClientConnectionManagerTracker.getInstance().add(this); } @Override Index: httpclient/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- httpclient/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java (revision 1395548) +++ httpclient/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java (revision ) @@ -128,6 +128,9 @@ this.lastReleaseTime = -1L; this.alwaysShutDown = false; //@@@ from params? as argument? this.isShutDown = false; + + // have this connection manager centrally tracked via weak reference + ClientConnectionManagerTracker.getInstance().add(this); } /** Index: httpclient-osgi/src/main/java/org/apache/http/osgi/proxy/ProxyConfiguration.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- httpclient-osgi/src/main/java/org/apache/http/osgi/proxy/ProxyConfiguration.java (revision ) +++ httpclient-osgi/src/main/java/org/apache/http/osgi/proxy/ProxyConfiguration.java (revision ) @@ -0,0 +1,373 @@ +/* + * ==================================================================== + * 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 + * . + * + */ +package org.apache.http.osgi.proxy; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.PropertyUnbounded; +import org.apache.felix.scr.annotations.Service; +import org.apache.http.osgi.util.PropertiesUtil; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.SocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * ProxyConfiguration... + */ +@Component(configurationFactory = true, metatype = true) +@Service(value = ProxyConfiguration.class) +@Properties({ + @Property(name = ProxyConfiguration.PROPERTYNAME_PROXY_ENABLED, + boolValue = ProxyConfiguration.PROPERTYDEFAULT_PROXY_ENABLED), + @Property(name = ProxyConfiguration.PROPERTYNAME_PROXY_HOSTNAME, + value = ProxyConfiguration.PROPERTYDEFAULT_PROXY_HOSTNAME), + @Property(name = ProxyConfiguration.PROPERTYNAME_PROXY_PORT, + intValue = ProxyConfiguration.PROPERTYDEFAULT_PROXY_PORT), + @Property(name = ProxyConfiguration.PROPERTYNAME_PROXY_USERNAME, + value = ProxyConfiguration.PROPERTYDEFAULT_PROXY_USERNAME), + @Property(name = ProxyConfiguration.PROPERTYNAME_PROXY_PASSWORD, + value = ProxyConfiguration.PROPERTYDEFAULT_PROXY_PASSWORD), + @Property(name = ProxyConfiguration.PROPERTYNAME_PROXY_NTLMHOST, + value = ProxyConfiguration.PROPERTYDEFAULT_PROXY_NTLMHOST), + @Property(name = ProxyConfiguration.PROPERTYNAME_PROXY_EXCEPTIONS, + unbounded = PropertyUnbounded.ARRAY) + }) +public class ProxyConfiguration { + + private final Log log = LogFactory.getLog(getClass()); + + /** + * Property indicating whether this particular proxy is enabled (shall be used or not). Defaults to true. + */ + static final String PROPERTYNAME_PROXY_ENABLED = "proxy.enabled"; + static final boolean PROPERTYDEFAULT_PROXY_ENABLED = true; + + /** + * Property representing the hostname of the proxy. Defaults to empty. + */ + static final String PROPERTYNAME_PROXY_HOSTNAME = "proxy.host"; + static final String PROPERTYDEFAULT_PROXY_HOSTNAME = ""; + + + /** + * Property representing the port of the proxy. Defaults to 0. + */ + static final String PROPERTYNAME_PROXY_PORT = "proxy.port"; + static final int PROPERTYDEFAULT_PROXY_PORT = 0; + + /** + * Property representing the username to authenticate with towards the proxy. Defaults to empty. + */ + static final String PROPERTYNAME_PROXY_USERNAME = "proxy.user"; + static final String PROPERTYDEFAULT_PROXY_USERNAME = ""; + + /** + * Property representing the password to authenticate with towards the proxy. Defaults to empty. + */ + static final String PROPERTYNAME_PROXY_PASSWORD = "proxy.password"; + static final String PROPERTYDEFAULT_PROXY_PASSWORD = ""; + + /** + * Property representing the NTLM domain to use for authentication towards the proxy. If this is set, authentication + * is switched from basic to NTLM. + */ + static final String PROPERTYNAME_PROXY_NTLMHOST = "proxy.ntlm.host"; + static final String PROPERTYDEFAULT_PROXY_NTLMHOST = ""; + + /** + * A multivalue property representing host patterns for which no proxy shall be used. By default localhost is + * excluded. + */ + static final String PROPERTYNAME_PROXY_EXCEPTIONS = "proxy.exceptions"; + static final String[] PROPERTYDEFAULT_PROXY_EXCEPTIONS = {"localhost", "127.0.0.1"}; + + /** + * The IP mask pattern against which hosts are matched. + */ + public static final Pattern IP_MASK_PATTERN = + Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})(/(\\d{1,2}))?"); + + private static final String SCHEME_HTTP = "http"; + private static final String SCHEME_HTTPS = "https"; + + private boolean enabled; + private String hostname; + private int port; + private String username; + private String password; + private String ntlmhost; + private HostMatcher[] exceptions; + + private Proxy proxy; + private boolean valid; + private SocketAddress address; + private int failed = 0; + + @Activate + @Modified + @SuppressWarnings("unused") + protected void configure(final Map properties) { + + /** + * Map the OSGi configuration properties to local fields in the correct type + */ + enabled = PropertiesUtil.toBoolean(properties.get(PROPERTYNAME_PROXY_ENABLED), PROPERTYDEFAULT_PROXY_ENABLED); + hostname = PropertiesUtil.toString(properties.get(PROPERTYNAME_PROXY_HOSTNAME), PROPERTYDEFAULT_PROXY_HOSTNAME); + port = PropertiesUtil.toInteger(properties.get(PROPERTYNAME_PROXY_PORT), PROPERTYDEFAULT_PROXY_PORT); + username = PropertiesUtil.toString(properties.get(PROPERTYNAME_PROXY_USERNAME), PROPERTYDEFAULT_PROXY_USERNAME); + password = PropertiesUtil.toString(properties.get(PROPERTYNAME_PROXY_PASSWORD), PROPERTYDEFAULT_PROXY_PASSWORD); + ntlmhost = PropertiesUtil.toString(properties.get(PROPERTYNAME_PROXY_NTLMHOST), PROPERTYDEFAULT_PROXY_NTLMHOST); + + setProxyExceptions(PropertiesUtil.toStringArray(properties.get(PROPERTYNAME_PROXY_EXCEPTIONS), + PROPERTYDEFAULT_PROXY_EXCEPTIONS)); + + try { + // we consider a proxy configuration valid if the address doesn't throw + address = new InetSocketAddress(getHostname(), getPort()); + valid = true; + } catch (IllegalArgumentException e) { + log.error("Invalid configuration: encountered invalid proxy address: host[" + + getHostname() + "] - port[" + + getPort() + "]: ", e); + } catch (SecurityException e) { + log.error("Invalid configuration: security error while accessing proxy address: host[" + + getHostname() + "] - port[" + + getPort() + "]: ", e); + } + } + + public SocketAddress getAddress() { + return address; + } + + public String getHostname() { + return hostname; + } + + public int getPort() { + return port; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getNtlmhost() { + return ntlmhost; + } + + public Proxy getProxy() { + if (null == proxy && isValid()) { + proxy = new Proxy(Proxy.Type.HTTP, address); + } + return proxy; + } + + /** + * Indicates whether this config is valid and can be used. A valid configuration is enabled by configuration and has + * a valid proxy address configured. + * + * @return true if the proxy configuration is valid. + */ + public boolean isValid() { + return enabled && valid; + } + + /** + * Indicates the number of times connecting to the proxy based on this configuration has failed. + * + * @return An int representing the number of connection failures. + */ + public int failed() { + return ++failed; + } + + @Override + public String toString() { + return getHostname() + ":" + getPort() + " (" + getUsername() + "/" + getNtlmhost() + ")"; + } + + /** + * Indicates whether the given uri is accepted by this proxy configuration, i.e. that the proxy is + * suitable for usage with that URI. All URIs of scheme HTTP and HTTPS are accepted. Additionally the hostname of + * the URI must not be on the configurable exclusion list. By default localhost is exluced. + * + * @param uri The {@link URI} to check. + * + * @return true if the URI is accepted. + */ + public boolean accepts(final URI uri) { + + final String scheme = uri.getScheme(); + if (SCHEME_HTTP.equalsIgnoreCase(scheme) || SCHEME_HTTPS.equalsIgnoreCase(scheme)) { + + // this host is on the exclusion list => no proxy (not this one) + for (final HostMatcher matcher : exceptions) { + if (matcher.matches(uri.getHost())) { + return false; + } + } + + } else { + return false; + } + + return true; + } + + private void setProxyExceptions(String[] exceptions) { + ArrayList exceptionList = new ArrayList(); + if (exceptions != null) { + for (final String exception : exceptions) { + exceptionList.add(createMatcher(exception.trim())); + } + } + this.exceptions = exceptionList.toArray(new HostMatcher[exceptionList.size()]); + } + + private static HostMatcher createMatcher(final String name) { + final NetworkAddress na = NetworkAddress.parse(name); + if (na != null) { + return new IPAddressMatcher(na); + } + + if (name.startsWith(".")) { + return new DomainNameMatcher(name); + } + + return new HostNameMatcher(name); + } + + private static interface HostMatcher { + + boolean matches(String host); + } + + private static class HostNameMatcher implements HostMatcher { + + private final String hostName; + + HostNameMatcher(String hostName) { + this.hostName = hostName; + } + + public boolean matches(String host) { + return hostName.equalsIgnoreCase(host); + } + } + + private static class DomainNameMatcher implements HostMatcher { + + private final String domainName; + + DomainNameMatcher(String domainName) { + this.domainName = domainName.toLowerCase(); + } + + public boolean matches(String host) { + return host.toLowerCase().endsWith(domainName); + } + } + + private static class IPAddressMatcher implements HostMatcher { + + private final NetworkAddress address; + + IPAddressMatcher(NetworkAddress address) { + this.address = address; + } + + public boolean matches(String host) { + NetworkAddress hostAddress = NetworkAddress.parse(host); + return hostAddress != null && address.address == (hostAddress.address & address.mask); + } + } + + private static class NetworkAddress { + + final int address; + + final int mask; + + static NetworkAddress parse(String adrSpec) { + + if (null != adrSpec) { + final Matcher nameMatcher = IP_MASK_PATTERN.matcher(adrSpec); + if (nameMatcher.matches()) { + try { + int i1 = toInt(nameMatcher.group(1), 255); + int i2 = toInt(nameMatcher.group(2), 255); + int i3 = toInt(nameMatcher.group(3), 255); + int i4 = toInt(nameMatcher.group(4), 255); + int ip = i1 << 24 | i2 << 16 | i3 << 8 | i4; + + int mask = toInt(nameMatcher.group(6), 32); + mask = (mask == 32) ? -1 : -1 - (-1 >>> mask); + + return new NetworkAddress(ip, mask); + } catch (NumberFormatException nfe) { + // not expected after the pattern match ! + } + } + } + + return null; + } + + private static int toInt(String value, int max) { + if (value == null || value.length() == 0) { + return max; + } + + int number = Integer.parseInt(value); + if (number > max) { + number = max; + } + return number; + } + + NetworkAddress(int address, int mask) { + this.address = address; + this.mask = mask; + } + } +} Index: httpclient/src/main/java/org/apache/http/impl/conn/ClientConnectionManagerTracker.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- httpclient/src/main/java/org/apache/http/impl/conn/ClientConnectionManagerTracker.java (revision ) +++ httpclient/src/main/java/org/apache/http/impl/conn/ClientConnectionManagerTracker.java (revision ) @@ -0,0 +1,90 @@ +/* + * ==================================================================== + * 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 + * . + * + */ +package org.apache.http.impl.conn; + +import org.apache.http.conn.ClientConnectionManager; + +import java.util.Set; +import java.util.WeakHashMap; + +/** + * The ClientConnectionManagerTracker should be used by any implementations of {@link + * ClientConnectionManager}, which should in the constructor add its instance via {@link + * #add(org.apache.http.conn.ClientConnectionManager)}: + *

+ * ClientConnectionManagerTracker.getInstance().add(this); + *

+ * ClientConnectionManagers added thus are tracked using {@link java.lang.ref.WeakReference}s. The purpose of the + * tracking is such, that all active ClientConnectionManagers can be retrieved via {@link #getAll()} and tasks, such as + * shutting down all clients in order to ensure proper stopping of an application using this library. + */ +public final class ClientConnectionManagerTracker { + + public static ClientConnectionManagerTracker instance; + + private final WeakHashMap managers + = new WeakHashMap(); + + // do not allow public construction + private ClientConnectionManagerTracker() { + } + + /** + * Returns a singleton ClientConnectionManagerTracker instance. The first call will construct the + * singleton. + * + * @return The ClientConnectionManagerTracker + */ + public static ClientConnectionManagerTracker getInstance() { + if (null == instance) { + instance = new ClientConnectionManagerTracker(); + } + return instance; + } + + /** + * Adds a {@link ClientConnectionManager} as a {@link java.lang.ref.WeakReference} to this tracker. + * + * @param manager The {@link ClientConnectionManager} to add. + */ + public void add(final ClientConnectionManager manager) { + synchronized (managers) { + // only the key is weakly referenced, the map value can be ignored + managers.put(manager, null); + } + } + + /** + * Returnes a {@link Set} of {@link java.lang.ref.WeakReference}s to {@link ClientConnectionManager}s. An element of + * the set may be null. + * + * @return A Set representing weakly referenced ClientConnectionManagers. + */ + public Set getAll() { + return managers.keySet(); + } +} Index: httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java (revision 1395548) +++ httpclient/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java (revision ) @@ -41,6 +41,7 @@ import org.apache.http.conn.ConnectionPoolTimeoutException; import org.apache.http.conn.ManagedClientConnection; import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.impl.conn.ClientConnectionManagerTracker; import org.apache.http.params.HttpParams; import org.apache.http.impl.conn.DefaultClientConnectionOperator; import org.apache.http.impl.conn.PoolingClientConnectionManager; @@ -139,6 +140,9 @@ this.connOperator = createConnectionOperator(schreg); this.pool = createConnectionPool(connTTL, connTTLTimeUnit) ; this.connectionPool = this.pool; + + // have this connection manager centrally tracked via weak reference + ClientConnectionManagerTracker.getInstance().add(this); } /**