简书链接:什么是HTTPDNS跟随阿里的httpdnsdemo一步一步了解httpdns
文章字数:2044,阅读全文大约需要8分钟

阿里巴巴是这样说的

HTTPDNS使用HTTP协议进行域名解析,代替现有基于UDP的DNS协议,域名解析请求直接发送到阿里云的HTTPDNS服务器,从而绕过运营商的Local DNS,能够避免Local DNS造成的域名劫持问题和调度不精准问题。

分析demo

https://github.com/aliyun/alicloud-android-demo.git
image.png

普通场景 就是普通的http请求
sni场景 就是 server name Indication 场景
选择里面的httpdns_android_demo打开MainActivity。

1
2
3
4
5
6
7
private static final String APPLE_URL = "www.apple.com";
private static final String TAOBAO_URL = "m.taobao.com";
private static final String DOUBAN_URL = "dou.bz";
private static final String ALIYUN_URL = "aliyun.com";
private static final String HTTP_SCHEMA = "http://";
private static final String HTTPS_SCHEMA = "https://";
private static final String TAG = "httpdns_android_demo";

先看看普通请求,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 通过IP直连方式发起普通请求示例
* 1. 通过IP直连时,为绕开服务域名检查,需要在Http报头中设置Host字段
*/
private void normalReqeust() {
pool.execute(new Runnable() {
@Override
public void run() {
try {
// 发送网络请求
String originalUrl = HTTP_SCHEMA + TAOBAO_URL;
URL url = new URL(originalUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 异步接口获取IP
String ip = httpdns.getIpByHostAsync(url.getHost());

if (ip != null) {
// 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
sendConsoleMessage("Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");

String newUrl = originalUrl.replaceFirst(url.getHost(), ip);
conn = (HttpURLConnection) new URL(newUrl).openConnection();
// 设置HTTP请求头Host域
conn.setRequestProperty("Host", url.getHost());
}
DataInputStream dis = new DataInputStream(conn.getInputStream());
int len;
byte[] buff = new byte[4096];
StringBuilder response = new StringBuilder();
while ((len = dis.read(buff)) != -1) {
response.append(new String(buff, 0, len));
}

Log.d(TAG, "Response: " + response.toString());
sendConsoleMessage("Get response from " + url.getHost() + ". Please check response detail from log.");
} catch (Throwable throwable) {
Log.e(TAG, "normal request failed.", throwable);
sendConsoleMessage("Normal request failed." + " Please check error detail from log.");
}
}
});

}

从这例子不难看出,阿里的demo首先是创建一个url连接,获取host
host就是不包含http 的域名(比如s.taobao.com)然后调用通过sdk中的 String ip = httpdns.getIpByHostAsync(url.getHost()); 也就是通过阿里自己的http请求查询这个host对应的ip地址,如果查询成功,那么HttpURLConnection会被重新创建,而且是通过ip进行创建,另外为了不丢失域名,所以这里做了一个操作就是设置请求头"Host"
也就是调用conn.setRequestProperty("Host", url.getHost());

整个过程就是通过 域名查询ip,然后通过ip进行请求的操作,
但是这个业务逻辑本身是dns自身做的事情,现在已经绕过了,直接交给阿里的http dns服务器进行操作。

不过不管怎么操作,这httpdns自身还是得通过运营商的dns进行请求,当然他们自己的也可以做缓存,或者ip地址可靠也可以直接访问比如
http://203.107.1.33/100000/d?host=www.aliyun.com
他们是这样说的

考虑到服务IP防攻击之类的安全风险,为保障服务可用性,HTTPDNS同时提供多个服务IP,当某个服务IP在异常情况下不可用时,可以使用其它服务IP进行重试。上述文档中使用的203.107.1.33是其中一个服务IP。

HTTPDNS提供Android SDKiOS SDK,两个平台的SDK中已经做了多IP轮转和出错重试的策略,通常情况下,建议开发者直接集成SDK即可,不需要自己手动调用HTTP API接口。

如果使用场景特殊,无法使用SDK,需要直接访问HTTP API接口,请提工单联系我们,我们将根据您的具体使用场景,为您提供多个服务IP和相关的安全建议。

具体参考https://help.aliyun.com/document_detail/52679.html?spm=a2c4g.11186623.2.21.11321d22lF9Vbp#1.1 访问方式

再看看https

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* 通过IP直连方式发起https请求示例
* 1. 通过IP直连时,为绕开服务域名检查,需要在Http报头中设置Host字段
* 2. 为通过证书检查,需要自定义证书验证逻辑
*/
private void httpsRequest() {
pool.execute(new Runnable() {
@Override
public void run() {
try {
String originalUrl = HTTPS_SCHEMA + TAOBAO_URL + "/?sprefer=sypc00";
URL url = new URL(originalUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
// 同步接口获取IP
String ip = httpdns.getIpByHostAsync(url.getHost());
if (ip != null) {
// 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
sendConsoleMessage("Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");

String newUrl = originalUrl.replaceFirst(url.getHost(), ip);
conn = (HttpsURLConnection) new URL(newUrl).openConnection();
// 设置HTTP请求头Host域
conn.setRequestProperty("Host", url.getHost());
}
final HttpsURLConnection finalConn = conn;
conn.setHostnameVerifier(new HostnameVerifier() {
/*
* 关于这个接口的说明,官方有文档描述:
* This is an extended verification option that implementers can provide.
* It is to be used during a handshake if the URL's hostname does not match the
* peer's identification hostname.
*
* 使用HTTPDNS后URL里设置的hostname不是远程的主机名(如:m.taobao.com),与证书颁发的域不匹配,
* Android HttpsURLConnection提供了回调接口让用户来处理这种定制化场景。
* 在确认HTTPDNS返回的源站IP与Session携带的IP信息一致后,您可以在回调方法中将待验证域名替换为原来的真实域名进行验证。
*
*/
@Override
public boolean verify(String hostname, SSLSession session) {
String host = finalConn.getRequestProperty("Host");
if (null == host) {
host = finalConn.getURL().getHost();
}
return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
}
});
DataInputStream dis = new DataInputStream(conn.getInputStream());
int len;
byte[] buff = new byte[4096];
StringBuilder response = new StringBuilder();
while ((len = dis.read(buff)) != -1) {
response.append(new String(buff, 0, len));
}
Log.d(TAG, "Response: " + response.toString());
sendConsoleMessage("Get reponse from " + url.getHost() + ". Please check response detail from log.");
} catch (Exception e) {
e.printStackTrace();
sendConsoleMessage("Get reponse failed. Please check error detail from log.");
}
}
});
}

处理重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
private void recursiveRequest(String path, String reffer) {
URL url;
HttpsURLConnection conn = null;
try {
url = new URL(path);
conn = (HttpsURLConnection) url.openConnection();
// 同步接口获取IP
String ip = httpdns.getIpByHostAsync(url.getHost());
if (ip != null) {
// 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
sendConsoleMessage("Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
String newUrl = path.replaceFirst(url.getHost(), ip);
conn = (HttpsURLConnection) new URL(newUrl).openConnection();
// 设置HTTP请求头Host域
conn.setRequestProperty("Host", url.getHost());
}
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(false);
HttpDnsTLSSniSocketFactory sslSocketFactory = new HttpDnsTLSSniSocketFactory(conn);
conn.setSSLSocketFactory(sslSocketFactory);
final HttpsURLConnection finalConn = conn;
conn.setHostnameVerifier(new HostnameVerifier() {
/*
* 关于这个接口的说明,官方有文档描述:
* This is an extended verification option that implementers can provide.
* It is to be used during a handshake if the URL's hostname does not match the
* peer's identification hostname.
*
* 使用HTTPDNS后URL里设置的hostname不是远程的主机名(如:m.taobao.com),与证书颁发的域不匹配,
* Android HttpsURLConnection提供了回调接口让用户来处理这种定制化场景。
* 在确认HTTPDNS返回的源站IP与Session携带的IP信息一致后,您可以在回调方法中将待验证域名替换为原来的真实域名进行验证。
*
*/
@Override
public boolean verify(String hostname, SSLSession session) {
String host = finalConn.getRequestProperty("Host");
if (null == host) {
host = finalConn.getURL().getHost();
}
return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
}
});
int code = conn.getResponseCode();// Network block
if (needRedirect(code)) {
//临时重定向和永久重定向location的大小写有区分
String location = conn.getHeaderField("Location");
if (location == null) {
location = conn.getHeaderField("location");
}
if (!(location.startsWith(HTTP_SCHEMA) || location
.startsWith(HTTPS_SCHEMA))) {
//某些时候会省略host,只返回后面的path,所以需要补全url
URL originalUrl = new URL(path);
location = originalUrl.getProtocol() + "://"
+ originalUrl.getHost() + location;
}
recursiveRequest(location, path);
} else {
// redirect finish.
DataInputStream dis = new DataInputStream(conn.getInputStream());
int len;
byte[] buff = new byte[4096];
StringBuilder response = new StringBuilder();
while ((len = dis.read(buff)) != -1) {
response.append(new String(buff, 0, len));
}
Log.d(TAG, "Response: " + response.toString());
sendConsoleMessage("Get reponse from " + url.getHost() + ". Please check response detail from log.");
}
} catch (MalformedURLException e) {
Log.w(TAG, "recursiveRequest MalformedURLException", e);
} catch (IOException e) {
Log.w(TAG, "recursiveRequest IOException");
} catch (Exception e) {
Log.w(TAG, "unknow exception");
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
private boolean needRedirect(int code) {
return code >= 300 && code < 400;
}

预解析域名
顾名思义,在请求某个东西之前先请求,比如app刚打开的时候。
这样通过sdk进行查询就会直接从缓存中取出。

1
2
3
4
5
6
7
8
9
/**
* 设置预解析域名列表代码示例
*/
private void setPreResoveHosts() {
// 设置预解析域名列表
// 可以替换成您在后台配置的域名
httpdns.setPreResolveHosts(new ArrayList<>(Arrays.asList(APPLE_URL, ALIYUN_URL, TAOBAO_URL, DOUBAN_URL)));
sendConsoleMessage("设置预解析域名成功");
}

降级解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 自定义降级逻辑代码示例
*/
private void setDegrationFilter() {
DegradationFilter filter = new DegradationFilter() {
@Override
public boolean shouldDegradeHttpDNS(String hostName) {
// 此处可以自定义降级逻辑,例如www.taobao.com不使用HttpDNS解析
// 参照HttpDNS API文档,当存在中间HTTP代理时,应选择降级,使用Local DNS
return hostName.equals(DOUBAN_URL);
}
};
// 将filter传进httpdns,解析时会回调shouldDegradeHttpDNS方法,判断是否降级
httpdns.setDegradationFilter(filter);
sendConsoleMessage("自定义降级逻辑成功");
}

降级解析就是不用他们的dns,使用运营商的。

处理webview

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249

class WebviewTlsSniSocketFactory extends SSLSocketFactory {
private final String TAG = WebviewTlsSniSocketFactory.class.getSimpleName();
HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
private HttpsURLConnection conn;

public WebviewTlsSniSocketFactory(HttpsURLConnection conn) {
this.conn = conn;
}

@Override
public Socket createSocket() throws IOException {
return null;
}

@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return null;
}

@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return null;
}

@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return null;
}

@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return null;
}

// TLS layer

@Override
public String[] getDefaultCipherSuites() {
return new String[0];
}

@Override
public String[] getSupportedCipherSuites() {
return new String[0];
}

@Override
public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException {
String peerHost = this.conn.getRequestProperty("Host");
if (peerHost == null)
peerHost = host;
Log.i(TAG, "customized createSocket. host: " + peerHost);
InetAddress address = plainSocket.getInetAddress();
if (autoClose) {
// we don't need the plainSocket
plainSocket.close();
}
// create and connect SSL socket, but don't do hostname/certificate verification yet
SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);
SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(address, port);

// enable TLSv1.1/1.2 if available
ssl.setEnabledProtocols(ssl.getSupportedProtocols());

// set up SNI before the handshake
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Log.i(TAG, "Setting SNI hostname");
sslSocketFactory.setHostname(ssl, peerHost);
} else {
Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection");
try {
java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
setHostnameMethod.invoke(ssl, peerHost);
} catch (Exception e) {
Log.w(TAG, "SNI not useable", e);
}
}

// verify hostname and certificate
SSLSession session = ssl.getSession();

if (!hostnameVerifier.verify(peerHost, session))
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + peerHost);

Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
" using " + session.getCipherSuite());

return ssl;
}
}
webView.setWebViewClient(new WebViewClient() {
@SuppressLint("NewApi")
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String scheme = request.getUrl().getScheme().trim();
String method = request.getMethod();
Map<String, String> headerFields = request.getRequestHeaders();
String url = request.getUrl().toString();
Log.e(TAG, "url:" + url);
// 无法拦截body,拦截方案只能正常处理不带body的请求;
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))
&& method.equalsIgnoreCase("get")) {
try {
URLConnection connection = recursiveRequest(url, headerFields, null);

if (connection == null) {
Log.e(TAG, "connection null");
return super.shouldInterceptRequest(view, request);
}

// 注*:对于POST请求的Body数据,WebResourceRequest接口中并没有提供,这里无法处理
String contentType = connection.getContentType();
String mime = getMime(contentType);
String charset = getCharset(contentType);
HttpURLConnection httpURLConnection = (HttpURLConnection)connection;
int statusCode = httpURLConnection.getResponseCode();
String response = httpURLConnection.getResponseMessage();
Map<String, List<String>> headers = httpURLConnection.getHeaderFields();
Set<String> headerKeySet = headers.keySet();
Log.e(TAG, "code:" + httpURLConnection.getResponseCode());
Log.e(TAG, "mime:" + mime + "; charset:" + charset);


// 无mime类型的请求不拦截
if (TextUtils.isEmpty(mime)) {
Log.e(TAG, "no MIME");
return super.shouldInterceptRequest(view, request);
} else {
// 二进制资源无需编码信息
if (!TextUtils.isEmpty(charset) || (isBinaryRes(mime))) {
WebResourceResponse resourceResponse = new WebResourceResponse(mime, charset, httpURLConnection.getInputStream());
resourceResponse.setStatusCodeAndReasonPhrase(statusCode, response);
Map<String, String> responseHeader = new HashMap<String, String>();
for (String key: headerKeySet) {
// HttpUrlConnection可能包含key为null的报头,指向该http请求状态码
responseHeader.put(key, httpURLConnection.getHeaderField(key));
}
resourceResponse.setResponseHeaders(responseHeader);
return resourceResponse;
} else {
Log.e(TAG, "non binary resource for " + mime);
return super.shouldInterceptRequest(view, request);
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return super.shouldInterceptRequest(view, request);
}

@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
// API < 21 只能拦截URL参数
return super.shouldInterceptRequest(view, url);
}
});
webView.loadUrl(targetUrl);
}


public URLConnection recursiveRequest(String path, Map<String, String> headers, String reffer) {
HttpURLConnection conn;
URL url = null;
try {
url = new URL(path);
conn = (HttpURLConnection) url.openConnection();
// 异步接口获取IP
String ip = httpdns.getIpByHostAsync(url.getHost());
if (ip != null) {
// 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
String newUrl = path.replaceFirst(url.getHost(), ip);
conn = (HttpURLConnection) new URL(newUrl).openConnection();

if (headers != null) {
for (Map.Entry<String, String> field : headers.entrySet()) {
conn.setRequestProperty(field.getKey(), field.getValue());
}
}
// 设置HTTP请求头Host域
conn.setRequestProperty("Host", url.getHost());
} else {
return null;
}
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(false);
if (conn instanceof HttpsURLConnection) {
final HttpsURLConnection httpsURLConnection = (HttpsURLConnection)conn;
WebviewTlsSniSocketFactory sslSocketFactory = new WebviewTlsSniSocketFactory((HttpsURLConnection) conn);

// sni场景,创建SSLScocket
httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
// https场景,证书校验
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
String host = httpsURLConnection.getRequestProperty("Host");
if (null == host) {
host = httpsURLConnection.getURL().getHost();
}
return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
}
});
}
int code = conn.getResponseCode();// Network block
if (needRedirect(code)) {
// 原有报头中含有cookie,放弃拦截
if (containCookie(headers)) {
return null;
}

String location = conn.getHeaderField("Location");
if (location == null) {
location = conn.getHeaderField("location");
}

if (location != null) {
if (!(location.startsWith("http://") || location
.startsWith("https://"))) {
//某些时候会省略host,只返回后面的path,所以需要补全url
URL originalUrl = new URL(path);
location = originalUrl.getProtocol() + "://"
+ originalUrl.getHost() + location;
}
Log.e(TAG, "code:" + code + "; location:" + location + "; path" + path);
return recursiveRequest(location, headers, path);
} else {
// 无法获取location信息,让浏览器获取
return null;
}
} else {
// redirect finish.
Log.e(TAG, "redirect finish");
return conn;
}
} catch (MalformedURLException e) {
Log.w(TAG, "recursiveRequest MalformedURLException");
} catch (IOException e) {
Log.w(TAG, "recursiveRequest IOException");
} catch (Exception e) {
Log.w(TAG, "unknow exception");
}
return null;
}

其他demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class NetworkRequestUsingHttpDNS {

private static HttpDnsService httpdns;
// 填入您的HTTPDNS accoutID信息,您可以从HTTPDNS控制台获取该信息
private static String accountID = "100000";
// 您的热点域名
private static final String[] TEST_URL = {"http://www.aliyun.com", "http://www.taobao.com"};

public static void main(final Context ctx) {
try {
// 设置APP Context和Account ID,并初始化HTTPDNS
httpdns = HttpDns.getService(ctx, accountID);
// DegradationFilter用于自定义降级逻辑
// 通过实现shouldDegradeHttpDNS方法,可以根据需要,选择是否降级
DegradationFilter filter = new DegradationFilter() {
@Override
public boolean shouldDegradeHttpDNS(String hostName) {
// 此处可以自定义降级逻辑,例如www.taobao.com不使用HttpDNS解析
// 参照HttpDNS API文档,当存在中间HTTP代理时,应选择降级,使用Local DNS
return hostName.equals("www.taobao.com") || detectIfProxyExist(ctx);
}
};
// 将filter传进httpdns,解析时会回调shouldDegradeHttpDNS方法,判断是否降级
httpdns.setDegradationFilter(filter);
// 设置预解析域名列表,真正使用时,建议您将预解析操作放在APP启动函数中执行。预解析操作为异步行为,不会阻塞您的启动流程
httpdns.setPreResolveHosts(new ArrayList<>(Arrays.asList("www.aliyun.com", "www.taobao.com")));
// 允许返回过期的IP,通过设置允许返回过期的IP,配合异步查询接口,我们可以实现DNS懒更新策略
httpdns.setExpiredIPEnabled(true);

// 发送网络请求
String originalUrl = "http://www.aliyun.com";
URL url = new URL(originalUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 异步接口获取IP,当IP TTL过期时,由于采用DNS懒更新策略,我们可以直接从内存获得最近的DNS解析结果,同时HTTPDNS SDK在后台自动更新对应域名的解析结果
ip = httpdns.getIpByHostAsync(url.getHost());
if (ip != null) {
// 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
Log.d("HTTPDNS Demo", "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
String newUrl = originalUrl.replaceFirst(url.getHost(), ip);
conn = (HttpURLConnection) new URL(newUrl).openConnection();
}
DataInputStream dis = new DataInputStream(conn.getInputStream());
int len;
byte[] buff = new byte[4096];
StringBuilder response = new StringBuilder();
while ((len = dis.read(buff)) != -1) {
response.append(new String(buff, 0, len));
}
Log.e("HTTPDNS Demo", "Response: " + response.toString());

} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 检测系统是否已经设置代理,请参考HttpDNS API文档。
*/
public static boolean detectIfProxyExist(Context ctx) {
boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
String proxyHost;
int proxyPort;
if (IS_ICS_OR_LATER) {
proxyHost = System.getProperty("http.proxyHost");
String port = System.getProperty("http.proxyPort");
proxyPort = Integer.parseInt(port != null ? port : "-1");
} else {
proxyHost = android.net.Proxy.getHost(ctx);
proxyPort = android.net.Proxy.getPort(ctx);
}
return proxyHost != null && proxyPort != -1;
}
}

参考
https://help.aliyun.com/document_detail/30143.html
okhttp接入httpdns最佳实践
https://help.aliyun.com/document_detail/52008.html