基于OkHttp3 加入HttpDns功能

什么是HttpDns

HttpDns是通过网络请求的方式,获取即将发送的业务请求所需要的ip地址。

为什么要用HttpDns

在使用HttpDns时,android发送网络请求时会请求本地dns或本地运营商 的dns服务获取目标ip,但是一旦你使用的这个默认的dns不靠谱,不受信任,则请求稳定性将会降低,甚至可能被劫持。
因此,如果能够使用自己信任的dns服务器做dns域名解析,将大大降低这种风险。

基于OkHttp3如何定制?

image.png

OkHttp3一大亮点在其强大的Interceptor机制。因此HttpDns在整个request发射过程中就有了两个结合点:

  1. 使用Interceptor,直接将域名替换为ip地址
  2. 使用OkHttp提供的dns接口,新建Dns子类,实现lookup()方法。

使用Interceptor做ip直连,则会存在以下优点:

  • 对Dns的控制偏上层,可更加细化,控制灵活。
  • 容灾处理更容易
    但也会存在比较致命的缺点,一切跟域名有关的处理全部失效,具体有:
  • 在Https下处理SSL证书会出现校验问题
  • ip访问时出现Cookie校验问题。

若使用OkHttp自带的dns(),优点在于:

  • Https下不会存在证书校验问题,保证流程正常执行
  • 种Cookie时不会存在问题
    缺点在于:
  • 时机过于底层,容灾控制都不方便。
  • okhttp自身存在缓存,一旦dns自身ttl过期,okhttp缓存有可能还在使用,会存在一定的风险。

综上来看,使用OkHttp原生Dns接口更加科学。除非不要求Cookie,不使用Https,使用Interceptor做简单的场景才比较合适。

实现

image.png
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内的核心流程不会受到影响。

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容