什么是HTTPDNS跟隨阿里的httpdns demo一步一步了解httpdns

阿里巴巴是這樣說的

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。

    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";

先看看普通請求,

 /**
     * 通過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

/**
     * 通過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.");
                }
            }
        });
    }

處理重定向

   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進行查詢就會直接從緩存中取出。

    /**
     * 設置預解析域名列表代碼示例
     */
    private void setPreResoveHosts() {
        // 設置預解析域名列表
        // 可以替換成您在后臺配置的域名
        httpdns.setPreResolveHosts(new ArrayList<>(Arrays.asList(APPLE_URL, ALIYUN_URL, TAOBAO_URL,  DOUBAN_URL)));
        sendConsoleMessage("設置預解析域名成功");
    }

降級解析

    /**
     * 自定義降級邏輯代碼示例
     */
    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


    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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容

  • DNS(Domain Name System,域名系統),因特網上作為域名和IP地址相互映射的一個分布式數據庫,能...
    一直在努力hard閱讀 4,659評論 3 19
  • 非常好的文章,怕博主刪除,再也找不到這么好的文章了,所以復制了一份,博主是2016年寫,但是是到現在為止看到的,思...
    吭聲_cfdc閱讀 1,756評論 0 4
  • ******科普片** 1、DNS劫持的危害 不知道大家有沒有發現這樣一個現象,在打開一些網頁的時候會彈出一些與所...
    茉莉兒閱讀 31,356評論 84 217
  • 最近,終于要把《WEB請求處理系列》提上日程了,一直答應小伙伴們給分享一套完整的WEB請求處理流程:從瀏覽器、Ng...
    七寸知架構閱讀 31,551評論 27 253
  • 昨天是平安夜吧, 恩,公司倒是沒有任何福利,中午老板從食堂回來,忽悠著大家樓下掃碼送水果,啊哈這leader也是醉...
    一顆小雞蛋閱讀 100評論 0 0