全部产品
阿里云办公

Android端WEEX + HTTPDNS 最佳实践

更新时间:2017-11-04 10:27:22

由于WebView并未暴露处设置DNS的接口,因而在WebView场景下使用HttpDns存在很多无法限制,但如果接入WEEX,则可以较好地植入HTTPDNS,本文主要介绍在WEEX场景下接入HTTPDNS的方案细节。

WEEX运行时环境下,所有的逻辑最终都会转换到Native Runtime中执行,网络请求也不例外。同时WEEX也提供了自定义相应实现的接口,通过重写网络请求适配器,我们可以较为简单地接入HTTPDNS。在WEEX运行环境中,主要有两种网络请求:

  • 通过Stream进行的网络请求
  • 标签指定的加载图片的网络请求

1 Stream网络请求 + HTTPDNS

Stream网络请求在Android端最终会通过DefaultWXHttpAdapter完成,同时WEEX也提供了相应的接口自定义网络请求适配器。具体的逻辑如下:

第一步:创建自定义网络请求适配器,实现IWXHttpAdapter接口

  1. public class WXHttpdnsAdatper implements IWXHttpAdapter {
  2. @Override
  3. public void sendRequest(final WXRequest request, final OnHttpListener listener) {
  4. ......
  5. }
  6. }

该接口需要实现sendRequest方法,该方法传入一个WXRequest对象的实例,该对象提供了以下信息:

  1. public class WXRequest {
  2. // The request parameter
  3. public Map<String, String> paramMap;
  4. // The request URL
  5. public String url;
  6. // The request method
  7. public String method;
  8. // The request body
  9. public String body;
  10. // The request time out
  11. public int timeoutMs = WXRequest.DEFAULT_TIMEOUT_MS;
  12. // The default timeout
  13. public static final int DEFAULT_TIMEOUT_MS = 3000;
  14. }

通过该对象我们可以获取到请求报头、URL、方法以及body。

第二步:在WEEX初始化时注册自定义网络适配器,替换默认适配器:

  1. InitConfig config=new InitConfig.Builder()
  2. .setHttpAdapter(new WXHttpdnsAdatper()) // 注册自定义网络请求适配器
  3. ......
  4. .build();
  5. WXSDKEngine.initialize(this,config);

之后左右的网络请求都会通过WXHttpdnsAdatper实现,所以只需要在WXHttpdnsAdapter中植入HTTPDNS逻辑即可,具体逻辑可以参考如下代码:

  1. public class WXHttpdnsAdatper implements IWXHttpAdapter {
  2. ......
  3. private void execute(Runnable runnable){
  4. if(mExecutorService==null){
  5. mExecutorService = Executors.newFixedThreadPool(3);
  6. }
  7. mExecutorService.execute(runnable);
  8. }
  9. @Override
  10. public void sendRequest(final WXRequest request, final OnHttpListener listener) {
  11. if (listener != null) {
  12. listener.onHttpStart();
  13. }
  14. Log.e(TAG, "URL:" + request.url);
  15. execute(new Runnable() {
  16. @Override
  17. public void run() {
  18. WXResponse response = new WXResponse();
  19. WXHttpdnsAdatper.IEventReporterDelegate reporter = getEventReporterDelegate();
  20. try {
  21. // 创建连接
  22. HttpURLConnection connection = openConnection(request, listener);
  23. reporter.preConnect(connection, request.body);
  24. ......
  25. } catch (IOException |IllegalArgumentException e) {
  26. ......
  27. }
  28. }
  29. });
  30. }
  31. private HttpURLConnection openConnection(WXRequest request, OnHttpListener listener) throws IOException {
  32. URL url = new URL(request.url);
  33. // 创建一个植入HTTPDNS的连接
  34. HttpURLConnection connection = openHttpDnsConnection(request, request.url, listener, null);
  35. return connection;
  36. }
  37. private HttpURLConnection openHttpDnsConnection(WXRequest request, String path, OnHttpListener listener, String reffer) {
  38. HttpURLConnection conn = null;
  39. URL url = null;
  40. try {
  41. url = new URL(path);
  42. conn = (HttpURLConnection) url.openConnection();
  43. // 创建一个接入httpdns的连接
  44. HttpURLConnection tmpConn = httpDnsConnection(url, path);
  45. ......
  46. int code = conn.getResponseCode();// Network block
  47. Log.e(TAG, "code:" + code);
  48. // SNI场景下通常涉及重定向,重新建立新连接
  49. if (needRedirect(code)) {
  50. Log.e(TAG, "need redirect");
  51. String location = conn.getHeaderField("Location");
  52. if (location == null) {
  53. location = conn.getHeaderField("location");
  54. }
  55. if (location != null) {
  56. if (!(location.startsWith("http://") || location
  57. .startsWith("https://"))) {
  58. //某些时候会省略host,只返回后面的path,所以需要补全url
  59. URL originalUrl = new URL(path);
  60. location = originalUrl.getProtocol() + "://"
  61. + originalUrl.getHost() + location;
  62. }
  63. Log.e(TAG, "code:" + code + "; location:" + location + "; path" + path);
  64. return openHttpDnsConnection(request, location, listener, path);
  65. } else {
  66. return conn;
  67. }
  68. } else {
  69. // redirect finish.
  70. Log.e(TAG, "redirect finish");
  71. return conn;
  72. }
  73. }
  74. ......
  75. return conn;
  76. }
  77. private HttpURLConnection httpDnsConnection(URL url, String path) {
  78. HttpURLConnection conn = null;
  79. // 通过HTTPDNS SDK接口获取IP
  80. String ip = HttpDnsManager.getInstance().getHttpDnsService().getIpByHostAsync(url.getHost());
  81. if (ip != null) {
  82. // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
  83. Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
  84. String newUrl = path.replaceFirst(url.getHost(), ip);
  85. try {
  86. conn = (HttpURLConnection) new URL(newUrl).openConnection();
  87. } catch (IOException e) {
  88. return null;
  89. }
  90. // 设置HTTP请求头Host域
  91. conn.setRequestProperty("Host", url.getHost());
  92. // HTTPS场景
  93. if (conn instanceof HttpsURLConnection) {
  94. final HttpsURLConnection httpsURLConnection = (HttpsURLConnection)conn;
  95. WXTlsSniSocketFactory sslSocketFactory = new WXTlsSniSocketFactory((HttpsURLConnection) conn);
  96. // SNI场景,创建SSLScocket解决SNI场景下的证书问题
  97. conn.setInstanceFollowRedirects(false);
  98. httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
  99. // HTTPS场景,证书校验
  100. httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
  101. @Override
  102. public boolean verify(String hostname, SSLSession session) {
  103. String host = httpsURLConnection.getRequestProperty("Host");
  104. Log.e(TAG, "verify host:" + host);
  105. if (null == host) {
  106. host = httpsURLConnection.getURL().getHost();
  107. }
  108. return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
  109. }
  110. });
  111. }
  112. } else {
  113. Log.e(TAG, "no corresponding ip found, return null");
  114. return null;
  115. }
  116. return conn;
  117. }
  118. }

1.2 <image>网络请求 + HTTPDNS

WEEX并没有提供默认的图片适配器实现,所以用户必须自行实现才能完成图片请求逻辑,具体步骤分为以下几步:

第一步:自定义图片请求适配器,实现IWXImgLoaderAdapter接口

  1. public class HttpDnsImageAdapter implements IWXImgLoaderAdapter {
  2. @Override
  3. public void setImage(final String url, final ImageView view, WXImageQuality quality, final WXImageStrategy strategy) {
  4. ......
  5. }
  6. }

第二步:在WEEX初始化时注册该图片适配器:

  1. private void initWeex() {
  2. InitConfig config=new InitConfig.Builder()
  3. .setImgAdapter(new HttpDnsImageAdapter())
  4. .build();
  5. WXSDKEngine.initialize(this,config);
  6. ......
  7. }

所以同WXHttpdnsAdatper一样,我们只需在HttpDnsImageAdapter植入HTTPDNS逻辑即可。具体代码可参考:

  1. /**
  2. * Created by liyazhou on 2017/10/22.
  3. */
  4. public class WXHttpDnsImageAdapter implements IWXImgLoaderAdapter {
  5. @Override
  6. public void setImage(final String url, final ImageView view, WXImageQuality quality, final WXImageStrategy strategy) {
  7. Log.e(TAG, "img url:" + url);
  8. execute(new Runnable() {
  9. @Override
  10. public void run() {
  11. ......
  12. HttpURLConnection conn = null;
  13. try {
  14. conn = createConnection(url);
  15. ....
  16. // 将得到的数据转化成InputStream
  17. InputStream is = conn.getInputStream();
  18. // 将InputStream转换成Bitmap
  19. final Bitmap bitmap = BitmapFactory.decodeStream(is);
  20. WXSDKManager.getInstance().postOnUiThread(new Runnable() {
  21. @Override
  22. public void run() {
  23. view.setImageBitmap(bitmap);
  24. }
  25. }, 0);
  26. ......
  27. }
  28. });
  29. }
  30. protected HttpURLConnection createConnection(String originalUrl) throws IOException {
  31. mHttpDnsService = HttpDnsManager.getInstance().getHttpDnsService();
  32. if (mHttpDnsService == null) {
  33. URL url = new URL(originalUrl);
  34. return (HttpURLConnection) url.openConnection();
  35. } else {
  36. return httpDnsRequest(originalUrl, null);
  37. }
  38. }
  39. private HttpURLConnection httpDnsRequest(String path, String reffer) {
  40. HttpURLConnection httpDnsConn = null;
  41. HttpURLConnection originalConn = null;
  42. URL url = null;
  43. try {
  44. url = new URL(path);
  45. originalConn = (HttpURLConnection) url.openConnection();
  46. // 异步接口获取IP
  47. String ip = HttpDnsManager.getInstance().getHttpDnsService().getIpByHostAsync(url.getHost());
  48. if (ip != null) {
  49. // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
  50. Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
  51. String newUrl = path.replaceFirst(url.getHost(), ip);
  52. httpDnsConn = (HttpURLConnection) new URL(newUrl).openConnection();
  53. // 设置HTTP请求头Host域
  54. httpDnsConn.setRequestProperty("Host", url.getHost());
  55. // HTTPS场景
  56. if (httpDnsConn instanceof HttpsURLConnection) {
  57. final HttpsURLConnection httpsURLConnection = (HttpsURLConnection)httpDnsConn;
  58. WXTlsSniSocketFactory sslSocketFactory = new WXTlsSniSocketFactory((HttpsURLConnection) httpDnsConn);
  59. // sni场景,创建SSLScocket解决SNI场景下的证书问题
  60. httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
  61. // https场景,证书校验
  62. httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
  63. @Override
  64. public boolean verify(String hostname, SSLSession session) {
  65. String host = httpsURLConnection.getRequestProperty("Host");
  66. if (null == host) {
  67. host = httpsURLConnection.getURL().getHost();
  68. }
  69. return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
  70. }
  71. });
  72. }
  73. } else {
  74. return originalConn;
  75. }
  76. ......
  77. int code = httpDnsConn.getResponseCode();// Network block
  78. if (needRedirect(code)) {
  79. String location = httpDnsConn.getHeaderField("Location");
  80. if (location == null) {
  81. location = httpDnsConn.getHeaderField("location");
  82. }
  83. if (location != null) {
  84. if (!(location.startsWith("http://") || location
  85. .startsWith("https://"))) {
  86. //某些时候会省略host,只返回后面的path,所以需要补全url
  87. URL originalUrl = new URL(path);
  88. location = originalUrl.getProtocol() + "://"
  89. + originalUrl.getHost() + location;
  90. }
  91. Log.e(TAG, "code:" + code + "; location:" + location + "; path" + path);
  92. return httpDnsRequest(location, path);
  93. } else {
  94. return originalConn;
  95. }
  96. }
  97. return originalConn;
  98. }
  99. }

上述方案详细代码建:WeexAndroid

本文导读目录