1. 程式人生 > >監控自己APP的http/https網路請求的地址和請求耗時

監控自己APP的http/https網路請求的地址和請求耗時

關於監控http/https網路請求的思路, 目前想到兩種實現思路:

第一種實現思路是hook http請求的根介面, 目前大致是分為HttpURLConnection和Apache-Http-Client這兩種, 當然也有可能使用square/okhttp或者koush/AndroidAsync, 但本質上是一樣的, 不過因為使用的介面的不同, 有兩個方案:
  • 一個方案是URLStreamHandlerFactory, 當使用(HttpURLConnection)URL.openConnection()時, 裡面是去請求URLStreamHandlerFactory獲得一個URLStreamHandler, 並由URLStreamHandler提供URLConnection子類(這裡是HttpURLConnection或HttpsURLConnection), 所以控制住URLStreamHandlerFactory即可使用代理模式來監控, 可喜的是URL有個靜態方法setURLStreamHandlerFactory來讓末端程式設計師注入這種控制, 這裡可參考’com.squareup.okhttp3:okhttp-urlconnection:3.3.1’ 或

    https://github.com/square/okhttp/tree/master/okhttp-urlconnection/src/main/java/okhttp3裡的OkUrlFactory.java;

  • 另一個方案比較徹底, 是通過以下三個靜態方法設定預設的tcp/udp socket實現類SocketImpl和DatagramSocketImpl:
    java.net.Socket.setSocketImplFactory(SocketImplFactory);
    java.net.ServerSocket.setSocketFactory(SocketImplFactory);
    java.net.DatagramSocket.setDatagramSocketImplFactory(DatagramSocketImplFactory); //不常用
    其中, 無網路代理(比如SOCKS或http代理)的情況下SocketImpl和DatagramSocketImpl可參考java.net.PlainSocketImpl和java.net.PlainDatagramSocketImpl, 通過反射和委託實現監控;
    注意: 不是javax.net.ServerSocketFactory, javax.net.SocketFactory, javax.net.ssl.SSLServerSocketFactory, javax.net.ssl.SSLSocketFactory;
    問題: 不過也很遺憾, 這種方式基本對https(SSL方式)無效, 不過也能理解, 按理說ssl方式的請求不應該被hook攔截. 不過還是討論一下, 僅就javax.net.ssl.SSLSocketFactory調研發現, Android中, 它是com.android.org.conscrypt.OpenSSLSocketFactoryImpl型別, 其建立的SSLSocket, 是com.android.org.conscrypt.OpenSSLSocketImpl; 而在java中, 它是sun.security.ssl.SSLSocketFactoryImpl型別, 其建立的SSLSocket, 是sun.security.ssl.SSLSocketImpl; 而且雖然這兩個SSLSocket子類間接繼承自Socket, 但是它們可能沒有使用SocketImpl, 所以setSocketImplFactory對其無作用, 由SSLSocket的一般使用方式: SSLSocketFactory.getDefault().createSocket()而知, 可能的方法是反射SSLSocketFactory, 既然是getDefault(), 肯定裡面有個私有靜態屬性儲存這個預設的SSLSocketFactory例項, 那麼將這個私有靜態屬性賦值你的SSLSocketFactory的實現並實現SSLSocket, 我想應該就可以了, 但是工作量比較大;

第二種實現思路是aop/動態代理, 這種實現思路有非常高的參考價值, 它可以用於其他監控, 但是目前看它又是很難實踐的, 具體分靜態和動態兩個方案:
  • 一個方案是靜態織入位元組碼或dex檔案,可以使用aspectj或者其他處理位元組碼的工具, 或者使用dex2jar/smali的類庫操作dex檔案, 目的是在所有網路請求的呼叫前後座aop切面;
    理論上可行, 因為我們織入的切點在method call而非method execute, 但是使用網路庫的方式多種多樣, 有的是multi-part/form-data, 有的是沒返回值, 如果想統計一次網路耗時的話, 很難找到合適的開始點和結束點, 比如HttpURLConnection的connect方法呼叫前切入, 但是使用者可能會呼叫getOutputStream/getInputStream, 可能close這些stream, 也可能呼叫disconnect, 也可能不呼叫, 找不到合適的結束切入點, 而且如何做到匹配開始和結束切入點呢? 因為開始和結束的method顯然不同, 你也許會說分裝一個http網路輔助類, 但是對於你依賴的類庫顯然不會遵守這個規矩;

  • 另一個方案是執行時動態生成代理類或者乾脆利用DexClassLoader的DexPathList實現替換http實現類, 比如動態代理出HttpURLConnection的子類並替換掉ClassLoader中現存的HttpURLConnection實現類;
    可以使用的工具有java自帶的Proxy和InvocationHandler, 也可以使用ow2/asmdex或crimsonwoods/javassist-android工具;
    實踐發現, 由於HttpURLConnection這樣的基礎類是由BootClassLoader載入的, 目前很難能做到替換, 這不像你的自定義類那樣實現熱修復替換, 或許以後在這方面會有更好的思路, 我一直在關注alibaba/AndFix, 它在這方面的實現方式是少有限制的, 不知道相容性上有無風險;

監控自己app的網路請求的需求還是現實的, 如果只是自己的程式碼, 那麼封裝一個類庫, 比如基於okhttp即可, 但是有很多依賴類庫/三方SDK會在內部做網路請求, 你不希望這些外部因素沒用好網路的責任推在你的身上吧, 不過理想很美好, 但這不是坦途~