Index: httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java (revision 1391400)
+++ httpclient/src/main/java/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java (revision )
@@ -35,6 +35,7 @@
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
+import org.apache.http.osgi.ProxyWrapper;
import org.apache.http.protocol.HttpContext;
import org.apache.http.conn.routing.HttpRoute;
@@ -105,8 +106,10 @@
final InetAddress local =
ConnRouteParams.getLocalAddress(request.getParams());
- final HttpHost proxy =
- ConnRouteParams.getDefaultProxy(request.getParams());
+ HttpHost proxy = ConnRouteParams.getDefaultProxy(request.getParams());
+ if (null == proxy) {
+ proxy = ProxyWrapper.getProxy(target.getHostName());
+ }
final Scheme schm;
try {
Index: httpclient/src/main/java/org/apache/http/osgi/ProxySelector.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- httpclient/src/main/java/org/apache/http/osgi/ProxySelector.java (revision )
+++ httpclient/src/main/java/org/apache/http/osgi/ProxySelector.java (revision )
@@ -0,0 +1,314 @@
+package org.apache.http.osgi;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.NTCredentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.impl.client.AbstractHttpClient;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The proxy selector processes a proxy configuration (in #configure()). If no proxy configuration is present it assumes
+ * a default configuration (proxying enabled with no proxy host).
+ *
+ * The selector only provides a proxy host for targets that are not listed in the configurable host exclusion list.
+ */
+final class ProxySelector {
+
+ public static final String PROP_PROXY_ENABLED = "proxy.enabled";
+
+ public static final boolean DEFAULT_PROXY_ENABLED = false;
+
+ public static final String PROP_PROXY_HOST = "proxy.host";
+
+ public static final String DEFAULT_PROXY_HOST = null;
+
+ public static final String PROP_PROXY_EXCEPTIONS = "proxy.exceptions";
+
+ public static final String[] DEFAULT_PROXY_EXCEPTIONS = {"localhost",
+ "127.0.0.1"};
+
+ public static final String PROP_PROXY_USERNAME = "proxy.user";
+
+ public static final String DEFAULT_PROXY_USERNAME = null;
+
+ public static final String PROP_PROXY_PASSWORD = "proxy.password";
+
+ public static final String DEFAULT_PROXY_PASSWORD = null;
+
+ public static final String PROP_PROXY_NTLM_HOST = "proxy.ntlm.host";
+
+ public static final String DEFAULT_PROXY_NTLM_HOST = null;
+
+ public static final String PROP_PROXY_NTLM_DOMAIN = "proxy.ntlm.domain";
+
+ public static final String DEFAULT_PROXY_NTLM_DOMAIN = null;
+
+ static final Pattern IP_MASK_PATTERN =
+ Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})(/(\\d{1,2}))?");
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ private boolean proxyEnabled;
+
+ private HttpHost proxyHost;
+
+ private AuthScope proxyAuthScope;
+
+ private Credentials proxyCredentials;
+
+ private HostMatcher[] exceptions;
+
+ ProxySelector() {
+ this.proxyEnabled = false;
+ this.proxyHost = null;
+ this.exceptions = new HostMatcher[0];
+ }
+
+ final void configure(final Dictionary properties) {
+ if (properties != null) {
+ // updated configuration
+ boolean pEnabled = PropertiesUtil.toBoolean(properties.get(PROP_PROXY_ENABLED), DEFAULT_PROXY_ENABLED);
+ setProxyEnabled(pEnabled);
+ setProxyHost(pEnabled ? PropertiesUtil.toString(properties.get(PROP_PROXY_HOST),
+ DEFAULT_PROXY_HOST) : DEFAULT_PROXY_HOST);
+ setProxyExceptions(PropertiesUtil.toStringArray(
+ properties.get(PROP_PROXY_EXCEPTIONS), DEFAULT_PROXY_EXCEPTIONS));
+ setProxyCredentials(
+ PropertiesUtil.toString(properties.get(PROP_PROXY_USERNAME), DEFAULT_PROXY_USERNAME),
+ PropertiesUtil.toString(properties.get(PROP_PROXY_PASSWORD), DEFAULT_PROXY_PASSWORD),
+ PropertiesUtil.toString(properties.get(PROP_PROXY_NTLM_HOST), DEFAULT_PROXY_NTLM_HOST),
+ PropertiesUtil.toString(properties.get(PROP_PROXY_NTLM_DOMAIN), DEFAULT_PROXY_NTLM_DOMAIN)
+ );
+ } else {
+ // deleted or no configuration -> revert to default values
+ setProxyEnabled(DEFAULT_PROXY_ENABLED);
+ setProxyHost(DEFAULT_PROXY_HOST);
+ setProxyExceptions(DEFAULT_PROXY_EXCEPTIONS);
+ setProxyCredentials(DEFAULT_PROXY_USERNAME, DEFAULT_PROXY_PASSWORD,
+ DEFAULT_PROXY_NTLM_HOST, DEFAULT_PROXY_NTLM_DOMAIN);
+ }
+
+ log.info("ProxySelector reconfigured: enabled=" + proxyEnabled + ", host:" + proxyHost);
+ }
+
+ private void setProxyEnabled(boolean proxyEnabled) {
+ this.proxyEnabled = proxyEnabled;
+ }
+
+ private void setProxyHost(String proxyHostConfig) {
+
+ // reset the proxy host and authscope
+ proxyHost = null;
+ proxyAuthScope = null;
+
+ if (proxyHostConfig != null) {
+ String[] parts = proxyHostConfig.split(":");
+ if (parts.length != 2) {
+ log.error("ProxyHost: Missing coloing in format; expect host:port, get: " + proxyHostConfig);
+ } else if (parts[0].length() == 0) {
+ log.error("ProxyHost: Empty host name; expect host:port, get: " + proxyHostConfig);
+ } else if (parts[1].length() == 0) {
+ log.error("ProxyHost: Empty port number; expect host:port, get: " + proxyHostConfig);
+ } else {
+ // everything is fine so far
+ try {
+ InetAddress.getByName(parts[0]);
+ final int port = Integer.parseInt(parts[1]);
+ proxyHost = new HttpHost(parts[0], port);
+ proxyAuthScope = new AuthScope(parts[0], port);
+ } catch (NumberFormatException nfe) {
+ log.error("ProxyHost: Port not a number; expect host:port, get: " + proxyHostConfig);
+ } catch (UnknownHostException e) {
+ log.error("ProxyHost: Proxy Host name '" + parts[0] + "' does not resolve", e);
+ }
+ }
+ }
+ }
+
+ private void setProxyCredentials(String userName, String passWord, String host, String domain) {
+ if (userName != null && userName.length() > 0) {
+ if (host != null && host.length() > 0 && domain != null
+ && domain.length() > 0) {
+ proxyCredentials = new NTCredentials(userName, passWord, host, domain);
+ } else {
+ proxyCredentials = new UsernamePasswordCredentials(userName, passWord);
+ }
+ } else {
+ proxyCredentials = null;
+ }
+ }
+
+ 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()]);
+ }
+
+ final HttpHost getProxy(String targetHost) {
+ HttpHost proxy;
+ if (proxyEnabled && proxyHost != null) {
+ proxy = this.proxyHost;
+ HostMatcher[] exceptions = this.exceptions;
+ for (final HostMatcher exception : exceptions) {
+ if (exception.matches(targetHost)) {
+ proxy = null;
+ break;
+ }
+ }
+ } else {
+ proxy = null;
+ }
+
+ return proxy;
+ }
+
+ final void setProxyCredentials(AbstractHttpClient client, HttpHost proxy) {
+ if (proxy != null
+ && proxy.equals(this.proxyHost)
+ && proxyEnabled
+ && proxyAuthScope != null
+ && proxyCredentials != null) {
+
+ client.getCredentialsProvider()
+ .setCredentials(new AuthScope(proxy.getHostName(), proxy.getPort()), proxyCredentials);
+ }
+ }
+
+ 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);
+ }
+
+ static interface HostMatcher {
+
+ boolean matches(String host);
+ }
+
+ static class HostNameMatcher implements HostMatcher {
+
+ private final String hostName;
+
+ HostNameMatcher(String hostName) {
+ this.hostName = hostName;
+ }
+
+ public boolean matches(String host) {
+ return hostName.equalsIgnoreCase(host);
+ }
+
+ public String getHostName() {
+ return hostName;
+ }
+ }
+
+ 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);
+ }
+
+ public String getDomainName() {
+ return domainName;
+ }
+ }
+
+ 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);
+ }
+
+ public int getIpAddress() {
+ return address.address;
+ }
+
+ public int getNetMask() {
+ return address.mask;
+ }
+ }
+
+ 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/osgi/Activator.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- httpclient/src/main/java/org/apache/http/osgi/Activator.java (revision )
+++ httpclient/src/main/java/org/apache/http/osgi/Activator.java (revision )
@@ -0,0 +1,54 @@
+package org.apache.http.osgi;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ManagedService;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+/**
+ * This is the bundle activator. It registers the activator as a service, so it can receive configuration properties.
+ * Once a configuration is updated, it sets up proxies according to 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 proxy setup is cleared.
+ */
+public class Activator extends ProxyWrapper implements BundleActivator, ManagedService {
+
+ private ServiceRegistration configurator;
+
+ public void start(BundleContext context) throws Exception {
+
+ // ensure we receive configurations for the proxy selector
+ Hashtable props = new Hashtable();
+ props.put(Constants.SERVICE_PID, "org.apache.http.client");
+ props.put(Constants.SERVICE_VENDOR, context.getBundle().getHeaders(Constants.BUNDLE_VENDOR));
+ props.put(Constants.SERVICE_DESCRIPTION, "Apache HttpComponents Client Configurator");
+ configurator = context.registerService(ManagedService.class.getName(), this, props);
+ }
+
+ public void stop(BundleContext context) throws Exception {
+
+ // stop receiving configuration
+ if (configurator != null) {
+ configurator.unregister();
+ }
+
+ // ensure all connections are terminated
+ // TODO MultiThreadedHttpConnectionManager.shutdownAll();
+
+ // clear the proxy selector again
+ setupProxies(null);
+ }
+
+ // ---------- ManagedService
+
+ @SuppressWarnings("unchecked")
+ public void updated(@SuppressWarnings("rawtypes") final Dictionary properties) {
+ setupProxies(properties);
+ }
+}
Index: httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java (revision 1391400)
+++ httpclient/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java (revision )
@@ -86,6 +86,7 @@
import org.apache.http.impl.cookie.NetscapeDraftSpecFactory;
import org.apache.http.impl.cookie.RFC2109SpecFactory;
import org.apache.http.impl.cookie.RFC2965SpecFactory;
+import org.apache.http.osgi.ProxyWrapper;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
@@ -879,6 +880,8 @@
: (HttpHost) determineParams(request).getParameter(
ClientPNames.DEFAULT_HOST);
HttpRoute route = routePlanner.determineRoute(targetForRoute, request, execContext);
+
+ ProxyWrapper.setProxyCredentials(this, route.getProxyHost());
HttpResponse out;
try {
Index: httpclient/src/main/java/org/apache/http/osgi/ProxyWrapper.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- httpclient/src/main/java/org/apache/http/osgi/ProxyWrapper.java (revision )
+++ httpclient/src/main/java/org/apache/http/osgi/ProxyWrapper.java (revision )
@@ -0,0 +1,36 @@
+package org.apache.http.osgi;
+
+import org.apache.http.HttpHost;
+import org.apache.http.impl.client.AbstractHttpClient;
+
+import java.util.Dictionary;
+
+/**
+ * The proxy wrapper holds a proxy selector, with which it can process proxy configurations and retrieve proxy hosts
+ * from the selector. Also, the wrapper is able to set credentials on a proxy.
+ */
+public abstract class ProxyWrapper {
+
+ private static ProxySelector proxySelector = new ProxySelector();
+
+ protected void setupProxies(Dictionary configuration) {
+ proxySelector.configure(configuration);
+ }
+
+ public static HttpHost getProxy(String targetHost) {
+ ProxySelector ps = proxySelector;
+ if (ps != null) {
+ return proxySelector.getProxy(targetHost);
+ }
+
+ return null;
+ }
+
+ public static void setProxyCredentials(AbstractHttpClient client, final HttpHost proxy) {
+ ProxySelector ps = proxySelector;
+ if (proxy != null && ps != null) {
+ proxySelector.setProxyCredentials(client, proxy);
+ }
+ }
+
+}
Index: httpclient/pom.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- httpclient/pom.xml (revision 1391400)
+++ httpclient/pom.xml (revision )
@@ -66,6 +66,30 @@
mockito-core
test
+
+ org.osgi
+ org.osgi.core
+ 4.2.0
+ compile
+
+
+ org.osgi
+ org.osgi.compendium
+ 4.2.0
+ compile
+
+
+ javax.servlet
+ servlet-api
+ 2.4
+ compile
+
+
+ org.apache.sling
+ org.apache.sling.commons.osgi
+ 2.1.0
+ compile
+
Index: httpclient/src/main/java/org/apache/http/osgi/ProxyContextListener.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- httpclient/src/main/java/org/apache/http/osgi/ProxyContextListener.java (revision )
+++ httpclient/src/main/java/org/apache/http/osgi/ProxyContextListener.java (revision )
@@ -0,0 +1,28 @@
+package org.apache.http.osgi;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+public class ProxyContextListener extends ProxyWrapper implements ServletContextListener {
+
+ public void contextInitialized(ServletContextEvent servletContextEvent) {
+ ServletContext context = servletContextEvent.getServletContext();
+ @SuppressWarnings("unchecked")
+ Enumeration e = context.getInitParameterNames();
+ Dictionary properties = new Hashtable();
+ while (e.hasMoreElements()) {
+ String paramName = e.nextElement();
+ properties.put(paramName, context.getInitParameter(paramName));
+ }
+ setupProxies(properties);
+ }
+
+ public void contextDestroyed(ServletContextEvent servletContextEvent) {
+ setupProxies(null);
+ }
+
+}