Index: main/java/org/apache/http/conn/DnsResolver.java =================================================================== --- main/java/org/apache/http/conn/DnsResolver.java (revision 0) +++ main/java/org/apache/http/conn/DnsResolver.java (revision 0) @@ -0,0 +1,52 @@ +/* + * ==================================================================== + * 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.conn; + +import java.net.InetAddress; + +/** + * Users may implement this interface to override the normal DNS lookup offered + * by the OS. + */ + +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); + +} \ No newline at end of file Index: main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java =================================================================== --- main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java (revision 1165192) +++ main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java (working copy) @@ -53,6 +53,8 @@ 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,6 +91,9 @@ /** 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. @@ -100,7 +105,27 @@ throw new IllegalArgumentException("Scheme registry amy not be null"); } this.schemeRegistry = schemes; + this.dnsResolver = null; } + + /** + * 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 amy not be null"); + } + + this.schemeRegistry = schemes; + this.dnsResolver = dnsResolver; + } public OperatedClientConnection createConnection() { return new DefaultClientConnection(); @@ -232,6 +257,10 @@ /** * Resolves the given host name to an array of corresponding IP addresses, based on the * configured name service on the system. + * + * If a custom DNS resolver is provided, the given host will be searched in + * it first. If the host is not configured, the default OS DNS-lookup + * mechanism is used. * * @param host host name to resolve * @return array of IP addresses @@ -240,6 +269,13 @@ * @since 4.1 */ protected InetAddress[] resolveHostname(final String host) throws UnknownHostException { + if (dnsResolver != null) { + InetAddress[] overridenAddress = dnsResolver.resolve(host); + if (overridenAddress != null) { + return overridenAddress; + } + } + return InetAddress.getAllByName(host); } Index: main/java/org/apache/http/impl/conn/InMemoryDnsResolverImpl.java =================================================================== --- main/java/org/apache/http/impl/conn/InMemoryDnsResolverImpl.java (revision 0) +++ main/java/org/apache/http/impl/conn/InMemoryDnsResolverImpl.java (revision 0) @@ -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 + * . + * + */ +package org.apache.http.impl.conn; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.util.InetAddressUtils; +import org.apache.http.conn.DnsResolver; + +/** + * In-memory DNS resolver implementation with entries built using the + * {@link InMemoryDnsResolverImpl#add(String, String) method}. + * + * Currently this class supports only IPv4 addresses. + * + */ +public class InMemoryDnsResolverImpl implements DnsResolver { + + /** Logger associated to this class. */ + private static final Log log = LogFactory.getLog(InMemoryDnsResolverImpl.class); + + /** + * In-memory collection that will hold the associations between a host name + * and an array of InetAddress instances. + */ + private Map dnsMap; + + /** + * Builds a DNS resolver that will resolve the host names against a + * collection held in-memory. + */ + public InMemoryDnsResolverImpl() { + dnsMap = new HashMap(); + } + + /** + * Associates the given IP address to the given host in this DNS overrider. + * + * @param host + * The host name to be associated with the given IP. + * @param ip + * IPv4 address to be resolved by this DNS overrider to the given + * host name. + * + * @throws IllegalArgumentException + * if the given IP is not a valid IPv4 address or an InetAddress + * instance cannot be built based on the given IPv4 address. + * + * @see InetAddress#getByAddress + */ + public void add(final String host, final String ip) { + if (!InetAddressUtils.isIPv4Address(ip)) { + throw new IllegalArgumentException(ip + " must be a valid IPv4 address"); + } + + String[] ipParts = ip.split("\\."); + + byte[] byteIpAddress = new byte[4]; + + for (int i = 0; i < 4; i++) { + byteIpAddress[i] = Integer.decode(ipParts[i]).byteValue(); + } + + try { + dnsMap.put(host, new InetAddress[] { InetAddress.getByAddress(byteIpAddress) }); + } catch (UnknownHostException e) { + log.error("Unable to build InetAddress for " + ip, e); + throw new IllegalArgumentException(e); + } + + } + + /** + * {@inheritDoc} + */ + public InetAddress[] resolve(String host) { + InetAddress[] resolvedAddresses = dnsMap.get(host); + if (log.isInfoEnabled()) { + log.info("Resolving " + host + " to " + Arrays.deepToString(resolvedAddresses)); + } + return resolvedAddresses; + } + +} \ No newline at end of file Index: main/java/org/apache/http/impl/conn/PoolingClientConnectionManager.java =================================================================== --- main/java/org/apache/http/impl/conn/PoolingClientConnectionManager.java (revision 1165192) +++ main/java/org/apache/http/impl/conn/PoolingClientConnectionManager.java (working copy) @@ -47,6 +47,7 @@ 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 @@ -76,10 +77,17 @@ 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()); @@ -93,9 +101,23 @@ throw new IllegalArgumentException("Scheme registry may not be null"); } this.schemeRegistry = schemeRegistry; + this.dnsResolver = null; this.operator = createConnectionOperator(schemeRegistry); this.pool = new HttpConnPool(this.log, 2, 20, timeToLive, tunit); } + + 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"); + } + 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 { @@ -119,7 +141,7 @@ * @return the connection operator to use */ protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg) { - return new DefaultClientConnectionOperator(schreg); + return new DefaultClientConnectionOperator(schreg, this.dnsResolver); } public SchemeRegistry getSchemeRegistry() { Index: test/java/org/apache/http/impl/conn/TestDefaultClientConnectOperator.java =================================================================== --- test/java/org/apache/http/impl/conn/TestDefaultClientConnectOperator.java (revision 0) +++ test/java/org/apache/http/impl/conn/TestDefaultClientConnectOperator.java (revision 0) @@ -0,0 +1,98 @@ +/* + * ==================================================================== + * 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 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() { + DnsResolver dnsResolver = mock(DnsResolver.class); + + try { + 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")); + + try { + Assert.assertNull(operator.resolveHostname("unknown.example.com")); + Assert.fail("unknown.example.com shouldn't be resolved"); + } catch (UnknownHostException ex) { + // expected + } + + } catch (Exception e) { + e.printStackTrace(); + Assert.fail("Unexpected exception " + e); + } + + } + + @Test + public void testNullDnsResolver() { + DefaultClientConnectionOperator operator = new DefaultClientConnectionOperator( + SchemeRegistryFactory.createDefault()); + + try { + Assert.assertNull(operator.resolveHostname("unknown.example.com")); + Assert.fail("unknown.example.com shouldn't be resolved"); + } catch (UnknownHostException ex) { + // expected + } + } + + 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) }; + + } + +} \ No newline at end of file