OkHttp完美实现HttpDns-非拦截器实现

image.png

OkHttp的拦截器来实现HttpDNS。在请求发出去之前,将URL中的域名替换成ip,再往Header中添加Host。这种方式有以下优点。

上层方便控制哪些请求使用了HttpDNS,可以做相应的容灾处理,比如ip请求失败时使用域名进行重试。
同样的也有很多缺点。

  1. Https场景下ip直连出现的证书校验问题

  2. 代理场景下的HttpDNS问题

  3. ip访问的时候Cookie的问题
    于是,不得不寻找一种更加的解决方案,OkHttp其实暴露了一个Dns接口,默认的实现是使用系统的方法发送udp请求进行dns解析。于是,我们就可以实现一个Dns接口,解析的方式使用httpdns,将解析结果返回,接口实现之后将系统默认的Dns接口替换成我们的Dns接口。

  4. 首先,新建HttpDns类,实现Dns接口。内部维持一个系统默认的Dns对象。

public class HttpDns implements Dns {
    private static final Dns SYSTEM = Dns.SYSTEM;

    @Override
    public List lookup(String hostname) throws UnknownHostException {
        Log.e("HttpDns", "lookup:" + hostname);

        return SYSTEM.lookup(hostname);
    }
}

我们只需要在lookup方法中调用HttpDns的SDK去获取IP,如果获取到的ip非空,并且ttl没有过期,则使用HttpDns。完整的方法的代码如下

public class HttpDns implements Dns {
    private static final Dns SYSTEM = Dns.SYSTEM;

    @Override
    public List lookup(String hostname) throws UnknownHostException {
        Log.e("HttpDns", "lookup:" + hostname);
        String ip = DNSHelper.getIpByHost(hostname);
        if (ip != null && !ip.equals("")) {
            List inetAddresses = Arrays.asList(InetAddress.getAllByName(ip));
            Log.e("HttpDns", "inetAddresses:" + inetAddresses);
            return inetAddresses;
        }
        return SYSTEM.lookup(hostname);
    }
}

DNSHelper类中做的就是将域名转换为ip,具体转换过程涉及到缓存,这里不展开,可以参考新浪的HTTPDNS库https://github.com/CNSRE/HTTPDNSLib

  1. 之后初始化OkHttp的时候将Dns替换为HttpDns对象。
 OkHttpClient client = new OkHttpClient.Builder()
                .dns(new HttpDns())
                .build();
  1. 这样,使用client对象发出去的请求都是走httpdns解析dns的,除非没有命中httpdns缓存。

这样做有什么好处呢?这样做相当于还是用域名进行访问,只不过底层的dns解析换成了http协议。也就是和之前系统的dns解析没有差别,但是得保证httpdns返回的ip是正确的。

https下不会存在任何问题,证书校验依然使用域名进行校验
cookie的问题也自然不存在。
同样的,解决了一部分问题后,也有一部分的风险。风险在哪呢?

过于底层,容灾不好做,除非强制关闭Httpdns。
服务器返回的ip如果不正确,这次请求就挂了,甚至下次也可能挂了。
OkHttp默认对解析结果有一定时间的缓存,万一ttl过期了,okhttp可能依然会去使用,这时候也是有风险的。
对比两种方案,各有各的优点,一个方便做容灾,但同时也暴露出了很多问题,一个不方便做容灾,但是之前暴露出来的问题都不存在。所以,这时候就得根据实际情况衡量选择哪一种方案了。

还有就是WebView的HttpDns,目前看来Android的Webview简直就是一个Bug的存在,没有什么好的解决方法。在IOS中,Webview的请求是一个正常的HttpRequest对象,不会像Android中存在这种问题,Andorid中比较好的解决方法就是在native层的网络库里开一个代理服务器,将Webview的所有请求转发至这个代理服务器,由这个代理服务器将请求通过httpdns转换成ip请求,将请求结果返回给webview。

或者通过WebView的资源拦截接口拦截资源请求,不过这种方式只能处理资源,处理正常的http/https请求会存在问题,在Android5.0以下存在cookie种不进去的问题。

总之WebView就是蛋疼的存在,没有什么好的办法,除非有自己的Webview的容器~

你可能感兴趣的:(OkHttp完美实现HttpDns-非拦截器实现)