聊聊HttpClient的DnsResolver

本文主要研究一下HttpClient的DnsResolver

DnsResolver

org/apache/http/conn/DnsResolver.java

复制代码
/**
 * 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;

}

DnsResolver定义了resolve方法,可用于替换OS提供的DNS lookup

InMemoryDnsResolver

org/apache/http/impl/conn/InMemoryDnsResolver.java

复制代码
/**
 * In-memory {@link DnsResolver} 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 final 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) {
        Args.notNull(host, "Host name");
        Args.notNull(ips, "Array of IP addresses");
        dnsMap.put(host, ips);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public InetAddress[] resolve(final String host) throws UnknownHostException {
        final 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;
    }

}

InMemoryDnsResolver实现了DnsResolver接口,它用一个ConcurrentHashMap来存放dns信息,提供add方法往map添加host及对应的ip地址,然后其resolve就是从这个map来读取对应的ip地址信息

SystemDefaultDnsResolver

org/apache/http/impl/conn/SystemDefaultDnsResolver.java

复制代码
/**
 * DNS resolver that uses the default OS implementation for resolving host names.
 *
 * @since 4.2
 */
public class SystemDefaultDnsResolver implements DnsResolver {

    public static final SystemDefaultDnsResolver INSTANCE = new SystemDefaultDnsResolver();

    @Override
    public InetAddress[] resolve(final String host) throws UnknownHostException {
        return InetAddress.getAllByName(host);
    }

}

SystemDefaultDnsResolver实现了DnsResolver,它用的就是jdk提供的InetAddress.getAllByName,默认是走的OS的DNS,可以通过sun.net.spi.nameservice.provider.<n>去自定义

DefaultHttpClientConnectionOperator

org/apache/http/impl/conn/DefaultHttpClientConnectionOperator.java

复制代码
@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
public class DefaultHttpClientConnectionOperator implements HttpClientConnectionOperator {

    static final String SOCKET_FACTORY_REGISTRY = "http.socket-factory-registry";

    private final Log log = LogFactory.getLog(getClass());

    private final Lookup<ConnectionSocketFactory> socketFactoryRegistry;
    private final SchemePortResolver schemePortResolver;
    private final DnsResolver dnsResolver;

    public DefaultHttpClientConnectionOperator(
            final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
            final SchemePortResolver schemePortResolver,
            final DnsResolver dnsResolver) {
        super();
        Args.notNull(socketFactoryRegistry, "Socket factory registry");
        this.socketFactoryRegistry = socketFactoryRegistry;
        this.schemePortResolver = schemePortResolver != null ? schemePortResolver :
            DefaultSchemePortResolver.INSTANCE;
        this.dnsResolver = dnsResolver != null ? dnsResolver :
            SystemDefaultDnsResolver.INSTANCE;
    }

    @Override
    public void connect(
            final ManagedHttpClientConnection conn,
            final HttpHost host,
            final InetSocketAddress localAddress,
            final int connectTimeout,
            final SocketConfig socketConfig,
            final HttpContext context) throws IOException {
        final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(context);
        final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());
        if (sf == null) {
            throw new UnsupportedSchemeException(host.getSchemeName() +
                    " protocol is not supported");
        }
        final InetAddress[] addresses = host.getAddress() != null ?
                new InetAddress[] { host.getAddress() } : this.dnsResolver.resolve(host.getHostName());
        final int port = this.schemePortResolver.resolve(host);
        for (int i = 0; i < addresses.length; i++) {
            final InetAddress address = addresses[i];
            final boolean last = i == addresses.length - 1;

            Socket sock = sf.createSocket(context);
            sock.setSoTimeout(socketConfig.getSoTimeout());
            sock.setReuseAddress(socketConfig.isSoReuseAddress());
            sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
            sock.setKeepAlive(socketConfig.isSoKeepAlive());
            if (socketConfig.getRcvBufSize() > 0) {
                sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
            }
            if (socketConfig.getSndBufSize() > 0) {
                sock.setSendBufferSize(socketConfig.getSndBufSize());
            }

            final int linger = socketConfig.getSoLinger();
            if (linger >= 0) {
                sock.setSoLinger(true, linger);
            }
            conn.bind(sock);

            final InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
            if (this.log.isDebugEnabled()) {
                this.log.debug("Connecting to " + remoteAddress);
            }
            try {
                sock = sf.connectSocket(
                        connectTimeout, sock, host, remoteAddress, localAddress, context);
                conn.bind(sock);
                if (this.log.isDebugEnabled()) {
                    this.log.debug("Connection established " + conn);
                }
                return;
            } catch (final SocketTimeoutException ex) {
                if (last) {
                    throw new ConnectTimeoutException(ex, host, addresses);
                }
            } catch (final ConnectException ex) {
                if (last) {
                    final String msg = ex.getMessage();
                    throw "Connection timed out".equals(msg)
                                    ? new ConnectTimeoutException(ex, host, addresses)
                                    : new HttpHostConnectException(ex, host, addresses);
                }
            } catch (final NoRouteToHostException ex) {
                if (last) {
                    throw ex;
                }
            }
            if (this.log.isDebugEnabled()) {
                this.log.debug("Connect to " + remoteAddress + " timed out. " +
                        "Connection will be retried using another IP address");
            }
        }
    }

    //......
}    

DefaultHttpClientConnectionOperator的connect方法会通过dnsResolver.resolve解析host

小结

HttpClient提供了DnsResolver接口,可以用于自定义DNS解析,是除了使用sun.net.spi.nameservice.provider.<n>去自定义JDK全局dns解析外的另外一种方案。

相关推荐
Sammyyyyy4 分钟前
2025年,Javascript后端应该用 Bun、Node.js 还是 Deno?
开发语言·javascript·node.js
橘子郡1234 分钟前
观察者模式和发布订阅模式对比,Java示例
java
指针满天飞5 分钟前
Collections.synchronizedList是如何将List变为线程安全的
java·数据结构·list
Java技术小馆6 分钟前
重构 Controller 的 7 个黄金法则
java·后端·面试
金銀銅鐵26 分钟前
[Java] 以 IntStream 为例,浅析 Stream 的实现
java·后端
William一直在路上36 分钟前
Python数据类型转换详解:从基础到实践
开发语言·python
看到我,请让我去学习1 小时前
Qt— 布局综合项目(Splitter,Stacked,Dock)
开发语言·qt
GUET_一路向前1 小时前
【C语言防御性编程】if条件常量在前,变量在后
c语言·开发语言·if-else·防御性编程
曳渔1 小时前
UDP/TCP套接字编程简单实战指南
java·开发语言·网络·网络协议·tcp/ip·udp
三千道应用题2 小时前
WPF&C#超市管理系统(6)订单详情、顾客注册、商品销售排行查询和库存提示、LiveChat报表
开发语言·c#·wpf