Android端HTTPDNS+Weex最佳实践

更新时间: 2023-04-27 13:51:11

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

重要

当前最佳实践文档只针对结合使用时,如何使用HTTPDNS解析出的IP,关于HTTPDNS本身的解析服务,请先查看Android SDK接入

代码示例

HTTPDNS+Weex方案详细代码参见Weex+HTTPDNS Android Demo

概述

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

  • 通过Stream进行的网络请求

  • 标签指定的加载图片的网络请求

Stream网络请求+HTTPDNS

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

具体的逻辑如下:

  1. 创建自定义网络请求适配器,实现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。

  2. 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.setRequestPro