OkHttp在各个层面其实都为我们做到用户拓展,今天我们来简单了解下DNS
适读人群
- 具有JAVA基础开发知识
- 接触过HTTP,HTTPS,HTTP2,TCP,Socket等相关知识
- 简单了解设计模式
背景
一般情况下,我们的针对一个网络请求的控制,如果只是加密或者解密,又或者简单记录log,OKhttp的拦截器基本可以满足我们的需求,但是对于我们日新月异发展的app来讲,各个方面也要求的也越来越高;最被大家关心的无疑就是网络请求的响应速度要求,我们都知道请求的其中一环便是DNS解析,但是一般想到DNS解析优化我们一般都想到这是后台关心的事情,对于我们前台有何干系?但是OKhttp还是提供了一个DNS方面的简单拓展,那我们来一起看看对我们前台可以做哪些文章……
DNS拓展设计方案一览
废话不多说,先上成果,笔者目前发现DNS的拓展在我们客户端有两种场景可以用到
- 开发阶段,利用dns拓展可以实现 动态的网络环境切换,而无需更换app安装包
- 生产阶段,利用okhttp的dns拓展可以实现搭建 HTTPDNS,以达到提供更快的解析速度,和更好的容灾能力
具体实现且听笔者娓娓道来…..
OKhttp的DNS拓展api展示
首先,简单了解下OKhttp的拓展形式,定义了这样的一个接口
public interface Dns {
List<InetAddress> lookup(String hostname) throws UnknownHostException;
}
传入一个hostname返回一组inetAddress(针对多机房,解析可以返回不止一个ip地址);默认情况下,OKhtttp使用的是系统默认提供的JAVA的api去直接获取ip
/**
* A DNS that uses {@link InetAddress#getAllByName} to ask the underlying operating system to
* lookup IP addresses. Most custom {@link Dns} implementations should delegate to this instance.
*/
Dns SYSTEM = hostname -> {
if (hostname == null) throw new UnknownHostException("hostname == null");
try {
return Arrays.asList(InetAddress.getAllByName(hostname));
} catch (NullPointerException e) {
UnknownHostException unknownHostException =
new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);
unknownHostException.initCause(e);
throw unknownHostException;
}
};
然后,会在okhttpClientBuilder构建时,没有指定dns时,将默认dns指定进去
public Builder() {
//....忽略
dns = Dns.SYSTEM;
//....
}
简单重写demo展示
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.dns(new Dns() {
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
List<InetAddress> adds = Dns.SYSTEM.lookup(hostname);
return adds;
}
});
我们可以通过builder直接将自定义dns设置进去
拓展方法详解
app内部一键网络切换
一直以来我们android的app在开发期间,都会把baseUrl根据环境定义在gradle的buildType当中,根据不同需求打出对应的包
debug {
buildConfigField 'String', 'BASE_URL', '"http://axxx.cn/xxx/api/"'
}
release {
buildConfigField 'String', 'BASE_URL', '"http://bxxx.cn/xxx/api/"'
}
优势:在于我们可以不改动代码,直接在gradle配置文件中设值 劣势:如果是需要对比开发环境,测试环境的接口差异,又或者只是为了测试不同阶段的接口,反复还要安装不同环境app无疑是浪费时间
痛点需求分析
那么我们就可以将上面的劣势拓展成以下需求
- 测试接口可以在app中利用某个开关控制当前app处于哪个网络环境,即访问哪一个baseUrl
- 要易于开启和关闭,不得造成线上问题
- 对于实现该功能,尽量做到和业务代码分离减少入侵性,在场景不适用时可以方便和业务代码剥离
方案介绍
基于以上分析,笔者想到了使用定制我们的okhttp的dns解析类,耦合与OKHTTP框架层面,基于上文我们可以知道定制主要就是覆盖重写一个类,所以,只要在不需要该类的情况下移除该类即可
方案具体实现
由于涉及许多类合作,故笔者只简单介绍核心部分的一个思路
public class Example {
public static String flag = "dev";//1. 有一个可以控制环境的变量的开关
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.dns(new Dns() {
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
//2.将所有环境的host组装成列表,方便在dns判断是否拦截切换
List<String> list = getTargetHosts();
//3.判断只拦截指定的host
if(list.contains(hostname)){
//对应的 flag开关,将指定的 ip地址替换掉
if("dev".equals(flag)){
return getDev();
}
if("release".equals(flag)){
return getRelease();
}
}
List<InetAddress> adds = Dns.SYSTEM.lookup(hostname);
return adds;
}
//组装所有环境下的host
private List<String> getTargetHosts(){
List<String> list = new ArrayList<>();
list.add("a.com");
list.add("b.com");
return list;
}
//返回开发环境的 ip地址
private List<InetAddress> getDev(){
InetAddress address = InetAddress.getByName("a.com");
List<InetAddress> list = new ArrayList<>();
list.add(address);
return list;
}
//返回生产环境的 ip地址
private List<InetAddress> getRelease(){
InetAddress address = InetAddress.getByName("b.com");
List<InetAddress> list = new ArrayList<>();
list.add(address);
return list;
}
});
}
方案劣势
当然此方案也有有一定劣势,就是url多个环境一定要除了域名以前其他都是一致的 例如:
dev: http://a.com/simpleProject/api/login
release: http://b.com/simpleProject/api/login
比如以下几种不规范情况就不行了
- 项目被修改
dev: http://a.com/simpleProject/api/login
release: http://b.com/simpleProject1/api/login
- http和https混用
dev: http://a.com/simpleProject/api/login
release: https://b.com/simpleProject/api/login
故仍有一定局限性
HTTPDNS的定制使用
首先,由于HTTPDNS搭建涉及面很广,无法用简单一个示例展示清楚,故只做一个简单科普性质的分享,提供读者一个解决问题的思路!
传统dns的痛点
要了解httpDNS,我们需要传统dns到底有怎样的问题,这里就简单举几类:
域名缓存问题
- 一般为了减少时间和资源的消耗一般dns服务器都会做本地缓存,而非每次都访问权威DNS服务器,所以,当我们ip发生改变而dns服务器缓存没有及时刷新就会产生问题
- 同时由于缓存的不一定是每一个用户离的最近的服务器,这就导致全局负载均衡的失效
- 有的运营商甚至进静态界面都进行缓存,但是平常界面没有问题,可以一旦原始界面有更新,而缓存没有更新都会造成影响
域名转发
有些运营商(下文就代称为A)比较偷懒,,故直接把自己的解析的请求转发给其他运营商(下文代称为B),B运营商通过一系列DNS查询,得到了一个距离B运营商比较近的ip地址返回给A时会造成每一次的访问都是跨运营商的,速度会很慢!
解析延迟问题
抛开运营商的问题,DNS本身查询是一个递归遍历的过程,所以这个本身也会带来时延,甚至解析超时!
方案介绍
传统DNS问题众多,那我们怎么办呢?直接使用IP地址肯定不可取,所以就有了HTTPDNS! 即不走传统的DNS解析,通过自己的HTTP接口将ip地址返回,即每家基于http协议自己实现自己的域名解析,而一般使用这种方案的都是手机应用;通过在手机端嵌入支持HTTPDNS的客户端的sdk从而实现效果!由于本文重点不在HTTPDNS,故不做过多分析解释…..
方案实现
HttpDNS服务提供商 DNSPOD | D+ 推荐几个网络比较推荐第三方的sdk
接入说明,大家可以网上大佬的详细博文说明,本文参考自 https://www.jianshu.com/p/6bd131de81d3
借用这位大佬的一个示例响应本次的主题,这是他们封装的一个解析类,代码实现效果并非重点,笔者也只是想用这个示例来为大家提供一种客户化DNS的场景与思路!
public class HttpDns implements Dns {
private DnsManager dnsManager;
public HttpDns() {
IResolver[] resolvers = new IResolver[1];
try {
resolvers[0] = new Resolver(getByName("119.29.29.29"));
dnsManager = new DnsManager(NetworkInfo.normal, resolvers);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
if (dnsManager == null) //当构造失败时使用默认解析方式
return Dns.SYSTEM.lookup(hostname);
try {
String[] ips = dnsManager.query(hostname); //获取HttpDNS解析结果
if (ips == null || ips.length == 0) {
return Dns.SYSTEM.lookup(hostname);
}
List<InetAddress> result = new ArrayList<>();
for (String ip : ips) { //将ip地址数组转换成所需要的对象列表
result.addAll(Arrays.asList(getAllByName(ip)));
}
return result;
} catch (IOException e) {
e.printStackTrace();
}
//当有异常发生时,使用默认解析
return Dns.SYSTEM.lookup(hostname);
}
}
总结
说了这么多,更多是希望大家可以有所了解DNS在OKhttp的使用场景,其实我们框架层面的很多拓展性,我们看起来没有用到不代表没有任何作用,开源大神的很多设计都是基于对场景掌握的数量很多,才能对很多关键性的位置都留下用户拓展的空间!一起共勉向大佬们学习!
NEW FEATURE
- RetryAndFollowUpInterceptor重试重定向的分析
- BridgeInterceptor的分析
- CacheInterceptor缓存机制分析
- ConnectInterceptor连接复用相关分析
- CallServerInterceptor实际IO操作分析
- OKhttp同步与异步calls的调用管理分析