什么是HttpDns
HttpDns是通过网络请求的方式,获取即将发送的业务请求所需要的ip地址。
为什么要用HttpDns
在使用HttpDns时,android发送网络请求时会请求本地dns或本地运营商 的dns服务获取目标ip,但是一旦你使用的这个默认的dns不靠谱,不受信任,则请求稳定性将会降低,甚至可能被劫持。
因此,如果能够使用自己信任的dns服务器做dns域名解析,将大大降低这种风险。
基于OkHttp3如何定制?
OkHttp3一大亮点在其强大的Interceptor机制。因此HttpDns在整个request发射过程中就有了两个结合点:
- 使用
Interceptor
,直接将域名替换为ip地址 - 使用OkHttp提供的dns接口,新建Dns子类,实现
lookup()
方法。
使用Interceptor做ip直连,则会存在以下优点:
- 对Dns的控制偏上层,可更加细化,控制灵活。
- 容灾处理更容易
但也会存在比较致命的缺点,一切跟域名有关的处理全部失效,具体有: - 在Https下处理SSL证书会出现校验问题
- ip访问时出现Cookie校验问题。
若使用OkHttp自带的dns(),优点在于:
- Https下不会存在证书校验问题,保证流程正常执行
- 种Cookie时不会存在问题
缺点在于: - 时机过于底层,容灾控制都不方便。
- okhttp自身存在缓存,一旦dns自身ttl过期,okhttp缓存有可能还在使用,会存在一定的风险。
综上来看,使用OkHttp原生Dns接口更加科学。除非不要求Cookie,不使用Https,使用Interceptor做简单的场景才比较合适。
实现
setDNS(new Dns() {
@Override
public List<InetAddress> lookup(@NonNull String hostname) throws UnknownHostException {
List<InetAddress> result = DnsManager.getInstance().getIps(hostname);
if (result == null)
result = new ArrayList<>();
return result;
}
})
OkHttp内使用RouteDatabase
进行每次使用ip的监控和反馈:
public final class RouteDatabase {
private final Set<Route> failedRoutes = new LinkedHashSet<>();
/** Records a failure connecting to {@code failedRoute}. */
public synchronized void failed(Route failedRoute) {
failedRoutes.add(failedRoute);
}
/** Records success connecting to {@code route}. */
public synchronized void connected(Route route) {
failedRoutes.remove(route);
}
/** Returns true if {@code route} has failed recently and should be avoided. */
public synchronized boolean shouldPostpone(Route route) {
return failedRoutes.contains(route);
}
}
为了能够复用shouldPostpone()
获取okhttp自身对ip可用性的判断结果,使得自定义manager对ip的可用性的判断与okhttp一致,从而更新Manager自身的ip名单,可以通过
Internal.instance.routeDatabase(getConnectionPool());
得到RouteDatabase对象。
Internal.instance
在OkHttpClient实例化之后就被赋值,事实上,Internal.instance就是OkHttpClient。因此可以使用NetworkInterceptor获取RouteDatabase。即:
addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
try {
Connection connection = chain.connection();
RouteDatabase routeDatabase = Internal.instance.routeDatabase(getConnectionPool());
Route route = connection.route();
Log.d("GI", "Route:" + route + "=======Can Trust?:" + !routeDatabase.shouldPostpone(route));
if (routeDatabase.shouldPostpone(route)) {
DnsManager.getInstance().putToBlackList(route.socketAddress().getAddress().getHostName(), route.socketAddress().getAddress().getHostAddress());
DnsManager.getInstance().removeFromWhiteList(route.socketAddress().getAddress().getHostName(), route.socketAddress().getAddress().getHostAddress());
}
} catch (Exception e) {
e.printStackTrace();
}
return chain.proceed(request);
}
})
Manager内维护两个名单Map,
- 白名单:存储Dns下发的ip及经过检验可靠的ip
- 黑名单:存储使用过程中不可靠的ip
根据每次的dns请求和RouteDatabase进行反馈更新。
附:OkHttp 链式调用原理
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
interceptors.addAll(client.interceptors())
是将addInterceptor时的所有Interceptor加入列表,然后再加入OkHttpCore核心处理Interceptor。
如果本次请求是一个需要走网络的请求,还会继续添加addNetInterceptor时所有的Interceptor加入列表。最后才加入CallServerInterceptor用来处理真正的网络请求。
这个顺序能够保证在递归调用过程中,自定义拦截器只会影响到OkHttpCore处理流程之前或者之后,而Core内的核心流程不会受到影响。