閱讀365 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Android端WEEX + HTTPDNS 最佳實踐

由於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("https://") || 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("https://") || 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

最後更新:2017-11-03 20:33:34

  上一篇:go  ali1688
  下一篇:go  企業找網站托管維護到底好不好