利用ASM记录OkHttp网络请求

在项目中记录或打印OkHttp网络请求,我们一般会通过添加HttpLoggingInterceptor到我们的OkHttpClient来实现。但在一个较大的APP中,有些业务模块是通过AAR依赖到项目中。对于这些模块的网络请求监控,我们可以直接将监控逻辑代码插桩到OkHttp源码中来实现。

通过查看OKhttp3源码,定位到需要将代码织入的位置为RealCall类的getResponseWithInterceptorChain()函数

 Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    Response response = chain.proceed(originalRequest);
    if (retryAndFollowUpInterceptor.isCanceled()) {
      closeQuietly(response);
      throw new IOException("Canceled");
    }
    //织入记录Response的函数
    //recordOkHttp(response)
    return response;
  }

接下来实现插件的transform,在MethodVisitor中的onMethodExit函数编写织入代码逻辑:

@Override
 protected void onMethodExit(int opcode) {
                super.onMethodExit(opcode)
                //RealCall类在OkHttp3各版本中存在的包名不固定
                //getResponseWithInterceptorChain函数的逻辑各版本也存在差异
                if (className.endsWith('/RealCall') && nameDesc.startsWith('getResponseWithInterceptorChain')) {
                    if (opcode == ARETURN) {
                        mv.visitInsn(Opcodes.DUP);
                        methodVisitor.visitMethodInsn(INVOKESTATIC, SDK_API_CLASS, "recordOkHttp", "(Lokhttp3/Response;)V", false)
                    }
                }
            }

编写完插件后,最后来实现recordOkHttp函数:

public static void recordOkHttp(Response response) {
        try {
            //屏蔽掉不需要记录的host
            if (!ExcludeHosts.shouldRecord(response.request().url().host())) {
                return;
            }
            //只记录特定content type
            if(isIgnoreContentType(response.body().contentType().toString())){
                return;
            }
            String request = response.request().toString();
            HttpUrl httpUrl = response.request().url();
            URL url = httpUrl.url();
            ResponseBody responseBody = response.body();
            BufferedSource source = responseBody.source();
            source.request(Long.MAX_VALUE); 
            Buffer buffer = source.buffer();
            //读取buffer之前要先执行clone
            String responseBodyString = buffer.clone().readString(Charset.forName("UTF-8"));
            //构建保存接口信息的实体类
            ResponseBean responseBean = new ResponseBean();
            responseBean.url = url.toString();
            responseBean.requestTime = response.sentRequestAtMillis();
            //记录Post的参数
            if("POST".equals(response.request().method()) && response.request().body() instanceof FormBody){
                FormBody body = (FormBody) response.request().body();
                StringBuffer sb = new StringBuffer("{");
                for (int i = 0; i < body.size(); i++) {
                    sb.append(body.encodedName(i)).append(":").append(body.encodedValue(i));
                    if(i != body.size() - 1){
                        sb.append(",");
                    }
                }
                if(body.size() > 0){
                    responseBean.postParams = sb.append("}").toString();
                }
            }
            responseBean.headers = response.request().headers().toString();
            responseBean.response = responseBodyString;
            responseBean.responseTime = response.receivedResponseAtMilli();;
            //实现保存逻辑
        } catch (Exception e) {
            LogUtils.E(e);
        }
    }

通过上述的流程,我们就可以记录或打印以OkHttp进行的网络请求的信息了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容