Retrofit、OkHttp中使用https与httpdns导致的问题

使用httpdns

Retrofit、OkHttp单独使用httpdns最简单的方式就是通过实现一个拦截器,在Request执行之前将域名替换成ip地址并设置请求头的host即可。

private static HttpDnsService httpdns;

httpdns = HttpDns.getService(context, BuildConfig.HTTP_DNS_ID);
// 设置预解析域名列表
httpdns.setPreResolveHosts(hosts);
client.newBuilder().addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
              Request request = chain.request();

              HttpUrl httpUrl = request.url();

              String ip = httpdns.getIpByHostAsync(httpUrl.host());

            //如果ip为空则不做处理
              if (StringUtils.isNotEmpty(ip)) {
                request = request.newBuilder()
                    .url(
                        httpUrl.newBuilder()
                            .host(ip)
                            .build()
                    )
                    .build();
              }

              return chain.proceed(request);
            }
}).build();

这种方式有以下优点:

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

同样的也有很多缺点:

  • Https场景下ip直连出现的证书校验问题
  • 代理场景下的HttpDNS问题
  • ip访问的时候Cookie的问题

自建dns解析方案,解决https与httpdns冲突问题

OkHttp其实暴露了一个Dns接口,默认的实现是使用系统的方法发送udp请求进行dns解析。于是,我们就可以实现一个Dns接口,解析的方式使用httpdns,将解析结果返回,接口实现之后将系统默认的Dns接口替换成我们的Dns接口。

public class OkHttpDns implements Dns {
    private static final Dns SYSTEM = Dns.SYSTEM;
    private HttpDnsService httpdns;//httpdns 解析服务
    private static OkHttpDns instance = null;
    private OkHttpDns(Context context) {
        this.httpdns = HttpDns.getService(context, "account id");
    }
    public static OkHttpDns getInstance(Context context) {
        if(instance == null) {
            instance = new OkHttpDns(context);
        }
        return instance;
    }
    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        //通过异步解析接口获取ip
        String ip = httpdns.getIpByHostAsync(hostname);
        if(ip != null) {
            //如果ip不为null,直接使用该ip进行网络请求
            List<InetAddress> inetAddresses = Arrays.asList(InetAddress.getAllByName(ip));
            Log.e("OkHttpDns", "inetAddresses:" + inetAddresses);
            return inetAddresses;
        }
        //如果返回null,走系统DNS服务解析域名
        return Dns.SYSTEM.lookup(hostname);
    }
}
 OkHttpClient client = new OkHttpClient.Builder()
    .dns(OkHttpDns.getInstance(getApplicationContext()))
    .build();

通过这种方式有以下优点:

  • 这样做相当于还是用域名进行访问,只不过底层的dns解析换成了http协议。也就是和之前系统的dns解析没有差别,但是得保证httpdns返回的ip是正确的。
  • https下不会存在任何问题,证书校验依然使用域名进行校验cookie的问题也自然不存在。

缺点:

  • 容灾不好做,除非强制关闭Httpdns。服务器返回的ip如果不正确,这次请求就挂了,甚至下次也可能挂了。OkHttp默认对解析结果有一定时间的缓存,万一ttl过期了, okhttp可能依然会去使用,这时候也是有风险的。
  • 代理失效

版权声明:

本文标题:Retrofit、OkHttp中使用https与httpdns导致的问题

作者:Rabtman

原始链接:https://rabtman.com/2016/12/11/retrofit_okhttp_https_httpdns/

本文采用署名-非商业性使用-禁止演绎4.0进行许可。

非商业转载请保留以上信息。商业转载请联系作者本人。