由于WebView
并未暴露处设置DNS的接口,因而在WebView
场景下使用HttpDns
存在很多无法限制,但如果接入WEEX
,则可以较好地植入HTTPDNS
,本文主要介绍在WEEX
场景下接入HTTPDNS
的方案细节。
在WEEX
运行时环境下,所有的逻辑最终都会转换到Native Runtime
中执行,网络请求也不例外。同时WEEX
也提供了自定义相应实现的接口,通过重写网络请求适配器,我们可以较为简单地接入HTTPDNS。在WEEX运行环境中,主要有两种网络请求:
- 通过Stream进行的网络请求
- 标签指定的加载图片的网络请求
1 Stream
网络请求 + HTTPDNS
Stream
网络请求在Android端最终会通过DefaultWXHttpAdapter完成,同时WEEX
也提供了相应的接口自定义网络请求适配器。具体的逻辑如下:
第一步:创建自定义网络请求适配器,实现IWXHttpAdapter
接口
public class WXHttpdnsAdatper implements IWXHttpAdapter {
@Override
public void sendRequest(final WXRequest request, final OnHttpListener listener) {
......
}
}
该接口需要实现sendRequest
方法,该方法传入一个WXRequest
对象的实例,该对象提供了以下信息:
public class WXRequest {
// The request parameter
public Map<String, String> paramMap;
// The request URL
public String url;
// The request method
public String method;
// The request body
public String body;
// The request time out
public int timeoutMs = WXRequest.DEFAULT_TIMEOUT_MS;
// The default timeout
public static final int DEFAULT_TIMEOUT_MS = 3000;
}
通过该对象我们可以获取到请求报头、URL、方法以及body。
第二步:在WEEX
初始化时注册自定义网络适配器,替换默认适配器:
InitConfig config=new InitConfig.Builder()
.setHttpAdapter(new WXHttpdnsAdatper()) // 注册自定义网络请求适配器
......
.build();
WXSDKEngine.initialize(this,config);
之后左右的网络请求都会通过WXHttpdnsAdatper
实现,所以只需要在WXHttpdnsAdapter
中植入HTTPDNS逻辑即可,具体逻辑可以参考如下代码:
public class WXHttpdnsAdatper implements IWXHttpAdapter {
......
private void execute(Runnable runnable){
if(mExecutorService==null){
mExecutorService = Executors.newFixedThreadPool(3);
}
mExecutorService.execute(runnable);
}
@Override
public void sendRequest(final WXRequest request, final OnHttpListener listener) {
if (listener != null) {
listener.onHttpStart();
}
Log.e(TAG, "URL:" + request.url);
execute(new Runnable() {
@Override
public void run() {
WXResponse response = new WXResponse();
WXHttpdnsAdatper.IEventReporterDelegate reporter = getEventReporterDelegate();
try {
// 创建连接
HttpURLConnection connection = openConnection(request, listener);
reporter.preConnect(connection, request.body);
......
} catch (IOException |IllegalArgumentException e) {
......
}
}
});
}
private HttpURLConnection openConnection(WXRequest request, OnHttpListener listener) throws IOException {
URL url = new URL(request.url);
// 创建一个植入HTTPDNS的连接
HttpURLConnection connection = openHttpDnsConnection(request, request.url, listener, null);
return connection;
}
private HttpURLConnection openHttpDnsConnection(WXRequest request, String path, OnHttpListener listener, String reffer) {
HttpURLConnection conn = null;
URL url = null;
try {
url = new URL(path);
conn = (HttpURLConnection) url.openConnection();
// 创建一个接入httpdns的连接
HttpURLConnection tmpConn = httpDnsConnection(url, path);
......
int code = conn.getResponseCode();// Network block
Log.e(TAG, "code:" + code);
// SNI场景下通常涉及重定向,重新建立新连接
if (needRedirect(code)) {
Log.e(TAG, "need redirect");
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 openHttpDnsConnection(request, location, listener, path);
} else {
return conn;
}
} else {
// redirect finish.
Log.e(TAG, "redirect finish");
return conn;
}
}
......
return conn;
}
private HttpURLConnection httpDnsConnection(URL url, String path) {
HttpURLConnection conn = null;
// 通过HTTPDNS SDK接口获取IP
String ip = HttpDnsManager.getInstance().getHttpDnsService().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);
try {
conn = (HttpURLConnection) new URL(newUrl).openConnection();
} catch (IOException e) {
return null;
}
// 设置HTTP请求头Host域
conn.setRequestProperty("Host", url.getHost());
// HTTPS场景
if (conn instanceof HttpsURLConnection) {
final HttpsURLConnection httpsURLConnection = (HttpsURLConnection)conn;
WXTlsSniSocketFactory sslSocketFactory = new WXTlsSniSocketFactory((HttpsURLConnection) conn);
// SNI场景,创建SSLScocket解决SNI场景下的证书问题
conn.setInstanceFollowRedirects(false);
httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
// HTTPS场景,证书校验
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
String host = httpsURLConnection.getRequestProperty("Host");
Log.e(TAG, "verify host:" + host);
if (null == host) {
host = httpsURLConnection.getURL().getHost();
}
return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
}
});
}
} else {
Log.e(TAG, "no corresponding ip found, return null");
return null;
}
return conn;
}
}
1.2 <image>
网络请求 + HTTPDNS
WEEX
并没有提供默认的图片适配器实现,所以用户必须自行实现才能完成图片请求逻辑,具体步骤分为以下几步:
第一步:自定义图片请求适配器,实现IWXImgLoaderAdapter
接口
public class HttpDnsImageAdapter implements IWXImgLoaderAdapter {
@Override
public void setImage(final String url, final ImageView view, WXImageQuality quality, final WXImageStrategy strategy) {
......
}
}
第二步:在WEEX
初始化时注册该图片适配器:
private void initWeex() {
InitConfig config=new InitConfig.Builder()
.setImgAdapter(new HttpDnsImageAdapter())
.build();
WXSDKEngine.initialize(this,config);
......
}
所以同WXHttpdnsAdatper
一样,我们只需在HttpDnsImageAdapter
植入HTTPDNS
逻辑即可。具体代码可参考:
/**
* Created by liyazhou on 2017/10/22.
*/
public class WXHttpDnsImageAdapter implements IWXImgLoaderAdapter {
@Override
public void setImage(final String url, final ImageView view, WXImageQuality quality, final WXImageStrategy strategy) {
Log.e(TAG, "img url:" + url);
execute(new Runnable() {
@Override
public void run() {
......
HttpURLConnection conn = null;
try {
conn = createConnection(url);
....
// 将得到的数据转化成InputStream
InputStream is = conn.getInputStream();
// 将InputStream转换成Bitmap
final Bitmap bitmap = BitmapFactory.decodeStream(is);
WXSDKManager.getInstance().postOnUiThread(new Runnable() {
@Override
public void run() {
view.setImageBitmap(bitmap);
}
}, 0);
......
}
});
}
protected HttpURLConnection createConnection(String originalUrl) throws IOException {
mHttpDnsService = HttpDnsManager.getInstance().getHttpDnsService();
if (mHttpDnsService == null) {
URL url = new URL(originalUrl);
return (HttpURLConnection) url.openConnection();
} else {
return httpDnsRequest(originalUrl, null);
}
}
private HttpURLConnection httpDnsRequest(String path, String reffer) {
HttpURLConnection httpDnsConn = null;
HttpURLConnection originalConn = null;
URL url = null;
try {
url = new URL(path);
originalConn = (HttpURLConnection) url.openConnection();
// 异步接口获取IP
String ip = HttpDnsManager.getInstance().getHttpDnsService().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);
httpDnsConn = (HttpURLConnection) new URL(newUrl).openConnection();
// 设置HTTP请求头Host域
httpDnsConn.setRequestProperty("Host", url.getHost());
// HTTPS场景
if (httpDnsConn instanceof HttpsURLConnection) {
final HttpsURLConnection httpsURLConnection = (HttpsURLConnection)httpDnsConn;
WXTlsSniSocketFactory sslSocketFactory = new WXTlsSniSocketFactory((HttpsURLConnection) httpDnsConn);
// sni场景,创建SSLScocket解决SNI场景下的证书问题
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);
}
});
}
} else {
return originalConn;
}
......
int code = httpDnsConn.getResponseCode();// Network block
if (needRedirect(code)) {
String location = httpDnsConn.getHeaderField("Location");
if (location == null) {
location = httpDnsConn.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 httpDnsRequest(location, path);
} else {
return originalConn;
}
}
return originalConn;
}
}
上述方案详细代码建:WeexAndroid