Android HttpDns:我的域名我做主
之前公司的產品遇到了用戶忽然無法登陸使用的問題,後來查明是因爲在用戶發佈的內容裏有一張被判定爲黃圖的圖片,導致我們的域名被運營商封鎖。還有一種情況,部分地區的用戶使用我們的產品時頁面上被莫名插入了廣告,後來發現也是運營商搞的鬼,劫持了我們的網站數據,並私自插入了廣告。這裏暴露出了依賴網絡運營商進行域名解析的一些問題,總結如下:
1. 域名封鎖。運營商可以因爲各種理由封鎖你的域名,導致所有請求你的網站的請求都因爲無法解析域名而失敗。
2. 域名劫持。運營商可以根據你訪問的域名進行有針對性的廣告注入,有損產品的用戶體驗。
爲了避免自己的命運被其他人操縱的局面,我們採用了HttpDns技術來繞開運營商的控制,解決這類問題。
HttpDns是一種通過Http服務器提供域名解析功能的技術,通過向Http服務器的80端口進行請求,代替傳統的DNS服務器的53端口進行請求。利用這種技術,我們直接向我們信賴的Http服務器(我們自己搭建的,或是第三方提供的)詢問我們要訪問的域名對應的IP,而不再依賴網絡運營商進行域名解析,從而解決了上面兩個問題。接下來介紹一下HttpDns技術的使用。
HttpDns第三方服務主要有兩個提供商:
DNSPod D+
阿里雲 HttpDns
以DNSPod D+爲例介紹HttpDns的使用。數據請求和應答均使用 http 協議。
請求格式爲:
dn表示要查詢的域名。該請求返回以分號分隔的ip地址,如下:
106.75.8.170;106.75.28.177
如果查詢失敗返回空結果。
我們發送網絡請求使用的是OkHttp庫,可以通過OkHttpClient的setDns方法實現默認Dns的替換,代碼如下:
首先繼承默認的Dns,重寫lookup方法:
public class HttpDns implements Dns {
// 需要進行httpdns處理轉換的host列表,這裏我們只對部分跟我們公司服務相關的域名
// 利用HttpDns進行解析,其他的還是用默認的域名解析方式
private CopyOnWriteArrayList<String> mHostList;
public HttpDns(List<String> hostList) {
mHostList = new CopyOnWriteArrayList<>();
mHostList.addAll(hostList);
}
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
if (hostname == null) {
throw new UnknownHostException("host == null");
} else {
if (mHostList != null && mHostList.contains(hostname)) {
try {
String ipAddr = HttpDnsHelper.getInstance().getIpStrForHost(hostname);
if (!TextUtils.isEmpty(ipAddr)) {
List<InetAddress> addresses = new ArrayList<>();
addresses.add(InetAddress.getByName(ipAddr));
Log.d("httpdns", "HttpDns hint, host:" + hostname + " ip:" + ipAddr);
return addresses;
}
} catch (Exception ignored) {}
}
//未能從httpdns獲取ip地址,使用系統dns獲取
return SYSTEM.lookup(hostname);
}
}
}
在lookup代碼中,我們先判斷要查找的hostname是不是在指定列表中,如果在裏面,我們就通過HttpDns請求其ip地址,否則用默認的dns進行解析。HttpDns請求的過程通過函數getIpStrForHost實現,該函數如下:
private Map<String, DnsInfo> mDnsMap;
public String getIpStrForHost(String host) {
if (!TextUtils.isEmpty(host)) {
DnsInfo dnsInfo = mDnsMap.get(host);
if (dnsInfo != null && dnsInfo.isIpValid() && !dnsInfo.isExpired()) {
//如果可以獲得合法的dns info,返回ip str
return dnsInfo.getIpStr();
} else {
//無法獲得合法的dns info, 異步更新host對應的cache
updateCacheForHost(host);
}
}
return null;
}
這裏對HttpDns的結果使用了緩存機制,首先判斷是否已經緩存了該host的ip地址,如果是,並且沒過期,直接從緩存獲取;否則重新獲取並更新緩存。進行HttpDns請求並更新緩存的代碼在updateCacheForHost中:
public void updateCacheForHost(final String host) {
String url = String.format("http://119.29.29.29/d?dn=%s&ttl=1", host);
Request request = new Request.Builder().url(url).build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
Log.d("httpdns", "get ip for host(" + host + ") failed");
}
@Override
public void onResponse(Response response) throws IOException {
InputStream inputStream = response.body().byteStream();
String data = readInputStream(inputStream);
Log.d("httpdns", "get ip for host(" + host + ") success :" + data);
DnsInfo dnsInfo = parseResponseString(host, data);
if (dnsInfo != null) {
mDnsMap.put(host, dnsInfo);
}
}
});
}
通過一個異步請求,發送Http請求到HttpDns服務器,返回ip地址,得到域名對應的ip地址,完成Dns過程,並更新本地緩存。
最後將這個改進後的HttpDns替換掉OkHttpClient默認的dns,完成OkHttp新dns的設置:
okHttpClient.setDns(new HttpDns(getHttpDnsHost(context)));
參考:
http://blog.csdn.net/sbsujjbcy/article/details/50532797
http://mp.weixin.qq.com/s?__biz=MzA3ODgyNzcwMw==&mid=201837080&idx=1&sn=b2a152b84df1c7dbd294ea66037cf262&scene=2&from=timeline&isappinstalled=0&utm_source=tuicool
http://blog.csdn.net/charleslei/article/details/41154139