編輯:關於android開發
首先,需要明確一個概念,什麼叫做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 請求參數實例
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 請求參數實例:
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有利有弊,需要權衡後使用,沒必要給自己添加毫無必要的麻煩。
Android修行筆記(六): GridView點擊空白地方事件擴展,androidgridview 我們通常在ListView或者GridView響應點
Android點擊Button水波紋效果 先上圖,看看接下來我要向大家介紹的是個什麼東西,如下圖: public View findTargetView(fl
Android中的 Multiple dex files define 編譯錯誤引發的思考 昨天我龍哥問我一個問題,他說如果一個工程中,有一個com.x.A枚舉,導入
Mac搭建Android開發環境,mac搭建android Mac上搭建Android開發環境一般有兩種選擇: 1.Android studio 2.ec