美文网首页Android进阶安卓网络
基于OkHttp3 加入HttpDns功能

基于OkHttp3 加入HttpDns功能

作者: GhostInMatrix | 来源:发表于2018-01-30 18:52 被阅读486次

    什么是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

    相关文章

      网友评论

        本文标题:基于OkHttp3 加入HttpDns功能

        本文链接:https://www.haomeiwen.com/subject/uymczxtx.html