1. 程式人生 > >Android OkHttp實現HttpDns的最佳實踐(非攔截器)

Android OkHttp實現HttpDns的最佳實踐(非攔截器)

之前寫過一篇文章 Android 使用OkHttp支援HttpDNS,該文章中使用的是OkHttp的攔截器來實現HttpDNS。在請求發出去之前,將URL中的域名替換成ip,再往Header中新增Host。這種方式有以下優點。

  • 上層方便控制哪些請求使用了HttpDNS,可以做相應的容災處理,比如ip請求失敗時使用域名進行重試。

同樣的也有很多缺點。

  • Https場景下ip直連出現的證書校驗問題
  • 代理場景下的HttpDNS問題
  • ip訪問的時候Cookie的問題

於是,不得不尋找一種更加的解決方案,OkHttp其實暴露了一個Dns介面,預設的實現是使用系統的方法傳送udp請求進行dns解析。於是,我們就可以實現一個Dns介面,解析的方式使用httpdns,將解析結果返回,介面實現之後將系統預設的Dns介面替換成我們的Dns介面。

首先,新建HttpDns類,實現Dns介面。內部維持一個系統預設的Dns物件。

public class HttpDns implements Dns {
    private static final Dns SYSTEM = Dns.SYSTEM;

    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        Log.e("HttpDns", "lookup:" + hostname);

        return
SYSTEM.lookup(hostname); } }

我們只需要在lookup方法中呼叫HttpDns的SDK去獲取IP,如果獲取到的ip非空,並且ttl沒有過期,則使用HttpDns。完整的方法的程式碼如下

public class HttpDns implements Dns {
    private static final Dns SYSTEM = Dns.SYSTEM;

    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        Log.e("HttpDns"
, "lookup:" + hostname); String ip = DNSHelper.getIpByHost(hostname); if (ip != null && !ip.equals("")) { List<InetAddress> inetAddresses = Arrays.asList(InetAddress.getAllByName(ip)); Log.e("HttpDns", "inetAddresses:" + inetAddresses); return inetAddresses; } return SYSTEM.lookup(hostname); } }

DNSHelper類中做的就是將域名轉換為ip,具體轉換過程涉及到快取,這裡不展開,可以參考新浪的HTTPDNS庫https://github.com/CNSRE/HTTPDNSLib

之後初始化OkHttp的時候將Dns替換為HttpDns物件。

 OkHttpClient client = new OkHttpClient.Builder()
                .dns(new HttpDns())
                .build();

這樣,使用client物件發出去的請求都是走httpdns解析dns的,除非沒有命中httpdns快取。

這樣做有什麼好處呢?這樣做相當於還是用域名進行訪問,只不過底層的dns解析換成了http協議。也就是和之前系統的dns解析沒有差別,但是得保證httpdns返回的ip是正確的。

  • https下不會存在任何問題,證書校驗依然使用域名進行校驗
  • cookie的問題也自然不存在。

同樣的,解決了一部分問題後,也有一部分的風險。風險在哪呢?

  • 過於底層,容災不好做,除非強制關閉Httpdns。
  • 伺服器返回的ip如果不正確,這次請求就掛了,甚至下次也可能掛了。
  • OkHttp預設對解析結果有一定時間的快取,萬一ttl過期了,okhttp可能依然會去使用,這時候也是有風險的。

對比兩種方案,各有各的優點,一個方便做容災,但同時也暴露出了很多問題,一個不方便做容災,但是之前暴露出來的問題都不存在。所以,這時候就得根據實際情況衡量選擇哪一種方案了。

還有就是WebView的HttpDns,目前看來Android的Webview簡直就是一個Bug的存在,沒有什麼好的解決方法。在IOS中,Webview的請求是一個正常的HttpRequest物件,不會像Android中存在這種問題,Andorid中比較好的解決方法就是在native層的網路庫裡開一個代理伺服器,將Webview的所有請求轉發至這個代理伺服器,由這個代理伺服器將請求通過httpdns轉換成ip請求,將請求結果返回給webview。

或者通過WebView的資源攔截介面攔截資源請求,不過這種方式只能處理資源,處理正常的http/https請求會存在問題,在Android5.0以下存在cookie種不進去的問題。

總之WebView就是蛋疼的存在,沒有什麼好的辦法,除非有自己的Webview的容器~