Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android 使用OkHttp支持HttpDNS

Android 使用OkHttp支持HttpDNS

編輯:關於android開發

Android 使用OkHttp支持HttpDNS


首先,需要明確一個概念,什麼叫做HttpDNS以及為什麼要用HttpDNS。

HttpDNS是使用HTTP協議向DNS服務器的80端口進行請求,代替傳統的DNS協議向DNS服務器的53端口進行請求。也就是使用Http協議去進行dns解析請求,將服務器返回的解析結果,也就是域名對應的服務器ip獲得,直接向該ip發起對應的api服務請求,代替使用域名。

那麼為什麼要使用HttpDNS呢?主要原因有三點

LocalDNS劫持 平均訪問延遲下降 用戶連接失敗率下降

LocalDNS劫持: 由於HttpDNS是通過ip直接請求http獲取服務器A記錄地址,不存在向本地運營商詢問domain解析過程,所以從根本避免了劫持問題。 (對於http內容tcp/ip層劫持,可以使用驗證因子或者數據加密等方式來保證傳輸數據的可信度)

平均訪問延遲下降: 由於是ip直接訪問省掉了一次domain解析過程,(即使系統有緩存速度也會稍快一些‘毫秒級’)通過智能算法排序後找到最快節點進行訪問。

用戶連接失敗率下降: 通過算法降低以往失敗率過高的服務器排序,通過時間近期訪問過的數據提高服務器排序,通過歷史訪問成功記錄提高服務器排序。如果ip(a)訪問錯誤,在下一次返回ip(b)或者ip(c) 排序後的記錄。(LocalDNS很可能在一個ttl時間內(或多個ttl)都是返回記錄

至於HttpDNS更加詳細的內容,可以參考下面這篇文章

【鵝廠網事】全局精確流量調度新思路-HttpDNS服務詳解

那麼,在客戶端該如何實現httpDNS呢?目前,國內有一部分廠商已經提供了這個解析服務,我們可以使用它們的服務,也可以使用自建服務器進行中轉,至於自建服務器上如何實現,是調第三方呢還是自己去解析呢這個屬於服務器的事,對於客戶端來說是完全透明的。這篇文章主要是為了學習,為了方便起見,我們直接使用第三方服務。目前,提供httpdns解析服務的有:

阿裡巴巴 阿裡雲HttpDNS

DNSPod D+

無論是哪個api,都是直接調用它們暴露的restful api獲得解析結果,只不過收費問題不一樣,當然也有免費的,免費的是有限制的。

阿裡雲的HttpDNS服務的api比較標准,直接發一個Get請求,帶上請求參數,返回結果以json返回。

服務URL:http://203.107.1.1/d 請求方法:HTTP GET 請求參數 名稱 是否必須 描述 host 必須 要查詢的域名 ip 可選 用戶IP,如果沒有這個參數,將使用TCP連接的源IP作為用戶IP

實例

http://203.107.1.1/d?host=www.taobao.com&ip=42.120.74.196

請求成功時,返回結果如下

{
  "host": "www.taobao.com",
  "ips": [
    "115.238.23.241",
    "115.238.23.251"
  ],
  "ttl": 57
}

而DNSPod的API基本上和阿裡雲的沒什麼差別,只不過返回結果不是以json返回,而是直接返回ip地址。舉個例子:

服務URL:http://119.29.29.29/d 請求方法:HTTP GET 請求參數 名稱 是否必須 描述 dn 必須 要查詢的域名 ip 可選 用戶IP,如果沒有這個參數,將使用TCP連接的源IP作為用戶IP ttl 可選 ttl=1 表示要求 D+服務器在響應結果中攜帶解析結果的 ttl 值,返回的 ttl 和域名解析結果用英文逗號分割

實例:

http://119.29.29.29/d?dn=www.dnspod.cn&ip=1.1.1.1&ttl=1

請求成功則返回ip地址,但不是json格式,如果存在ttl=1,則以逗號分隔,這點個人有點不喜歡

59.37.116.101,60

介於阿裡雲的api更加標准,這裡以阿裡雲的api為例,進行舉例說明。

既然我們可以拿到域名對應的ip了,那麼拿到ip後我們需要做兩步:

將域名替換為ip地址 將請求頭中添加host屬性,值為域名對應的ip地址

做完了這兩步,我們就可以進行正常的請求了,當然,這只是針對http請求,對於https請求,可能比這個還要復雜。

我們以OkHttp作為網絡請求的底層支持,那麼這個實現就顯得格外的簡單,對用戶來說可以做到完全透明化,在用戶不知情的情況下完成這個操作。沒錯,答案就是攔截器,在發出請求之前做這個替換。

首先我們需要寫一個工具類,完成獲得域名對應的ip以及替換操作

public class HttpDNSUtil {
    /**
     * 轉換url 主機頭為ip地址
     *
     * @param url  原url
     * @param host 主機頭
     * @param ip   服務器ip
     * @return
     */
    public static String getIpUrl(String url, String host, String ip) {
        if (url == null) {
            Log.e("TAG", "URL NULL");
        }
        if (host == null) {
            Log.e("TAG", "host NULL");
        }
        if (ip == null) {
            Log.e("TAG", "ip NULL");
        }
        if (url == null || host == null || ip == null) return url;
        String ipUrl = url.replaceFirst(host, ip);
        return ipUrl;
    }
    /**
     * 根據url獲得ip,此方法只是最簡單的模擬,實際情況很復雜,需要做緩存處理
     *
     * @param host
     * @return
     */
    public static String getIPByHost(String host) {
        HttpUrl httpUrl = new HttpUrl.Builder()
                .scheme("http")
                .host("203.107.1.1")
                .addPathSegment("d")
                .addQueryParameter("host", host)
                .build();
        //與我們正式請求獨立,所以這裡新建一個OkHttpClient
        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder()
                .url(httpUrl)
                .get()
                .build();
        try {
            String result = null;
            /**
             * 子線程中同步去獲取
             */
            Response response = okHttpClient.newCall(request).execute();
            if (response.isSuccessful()) {
                String body = response.body().string();
                JSONObject jsonObject = new JSONObject(body);
                JSONArray ips = jsonObject.optJSONArray("ips");
                if (ips != null) {
                    result = ips.optString(0);
                }
            }
            return result;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }
}

getIpUrl方法是傳入原url和host以及host對應的ip,進行host替換ip操作,而getIPByHost方法則是根據host獲得ip地址,這個過程只是很簡單的在子線程中同步的去拿數據,其實這裡有一層HttpDNS的庫的存在,如果你想把這一層做出一個庫來使用,應該要考慮很多東西,包含緩存的處理,等等,你可以參考新浪微博的開源庫 HTTPDNSLib 的實現。

然後實現一個HttpDNSInterceptor攔截器去進行替換操作,拿到原始url和host,首先根據host查詢ip,得到ip,會對這個ip進行一次判斷,如果為null,也就是請求解析失敗,包括各種原因,我們不對host進行替換;否則,也就是請求解析成功的情況,調用之前替換url的方法對url進行替換操作,替換完成後開始發起替換後的請求。代碼實現如下

public class HttpDNSInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originRequest = chain.request();
        HttpUrl httpUrl = originRequest.url();

        String url = httpUrl.toString();
        String host = httpUrl.host();
        Log.e("HttpDNS", "origin url:" + url);
        Log.e("HttpDNS", "origin host:" + host);

        String hostIP = HttpDNSUtil.getIPByHost(host);

        Request.Builder builder = originRequest.newBuilder();
        if (hostIP != null) {
            builder.url(HttpDNSUtil.getIpUrl(url, host, hostIP));
            builder.header("host", hostIP);
            Log.e("HttpDNS", "the host has replaced with ip " + hostIP);
        } else {
            Log.e("HttpDNS", "can't get the ip , can't replace the host");
        }

        Request newRequest = builder.build();
        Log.e("HttpDNS", "newUrl:" + newRequest.url());
        Response newResponse = chain.proceed(newRequest);
        return newResponse;
    }
}

最後的一步,便是將這個攔截器設置到我們的請求中去。

OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.interceptors().add(new HttpDNSInterceptor());
OkHttpClient okHttpClient = builder.build();

找一個支持ip訪問的服務器測試下具體效果,看看和域名請求有沒有差別,沒有差別就成功了

Request.Builder requestBuilder = new Request.Builder();
requestBuilder.url("http://your.domain/path1/path2/path3?param1=value1");
okHttpClient.newCall(requestBuilder.build()).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        e.printStackTrace();
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        String result = response.body().string();
        Log.e("MainActivity", result);
    }
});

如果不出意外,上面的訪問會被替換為http://ip/path1/path2/path3?param1=value1進行訪問,其中ip為your.domain對應的ip地址。

總之,使用OkHttp作為網絡層,要支持HttpDNS是件很簡單的事,完全不用修改現有的網絡訪問代碼,直接加一個攔截器,便可透明的支持HttpDNS。使用HttpDNS有利有弊,需要權衡後使用,沒必要給自己添加毫無必要的麻煩。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved