002-2020-网络-OkHttp

425 阅读6分钟

系列文章

比如缓存,cookie,拦截器等等如何自定义---

零散的点

  1. 使用EventListener对HTTP请求关键步骤进行监控
    • 深入理解OkHttp3:(七)事件(Events)
    • 基于OkHttp的Http监控
    • 本地简单示例
      public void testEventListener(View view) {
          OkHttpClient client = new OkHttpClient.Builder()
                  //在使用OkHttpClient.Builder时候设置自定义EventListener
                  .eventListener(new MyEventListener())
                  .build();
          Request request = new Request.Builder().url("https://www.baidu.com/").build();
          client.newCall(request).enqueue(new Callback() {
              @Override
              public void onFailure(Call call, IOException e) {
                  Log.d(TAG,"testEventListener onFailure");
              }
              @Override
              public void onResponse(Call call, Response response) throws IOException {
                  Log.d(TAG,"testEventListener onResponse \n " + response.body().string());
              }
          });
      }
      class MyEventListener extends EventListener{
          @Override
          public void callStart(Call call) {
              Log.d(TAG,"testEventListener MyEventListener callStart");
              super.callStart(call);
          }
          @Override
          public void dnsStart(Call call, String domainName) {
              Log.d(TAG,"testEventListener MyEventListener dnsStart");
              super.dnsStart(call, domainName);
          }
          @Override
          public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
              Log.d(TAG,"testEventListener MyEventListener dnsEnd");
              super.dnsEnd(call, domainName, inetAddressList);
          }
          @Override
          public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
              Log.d(TAG,"testEventListener MyEventListener connectStart");
              super.connectStart(call, inetSocketAddress, proxy);
          }
          @Override
          public void secureConnectStart(Call call) {
              Log.d(TAG,"testEventListener MyEventListener secureConnectStart");
              super.secureConnectStart(call);
          }
          @Override
          public void secureConnectEnd(Call call, @Nullable Handshake handshake) {
              Log.d(TAG,"testEventListener MyEventListener secureConnectEnd");
              super.secureConnectEnd(call, handshake);
          }
          @Override
          public void connectEnd(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, @Nullable Protocol protocol) {
              Log.d(TAG,"testEventListener MyEventListener connectEnd");
              super.connectEnd(call, inetSocketAddress, proxy, protocol);
          }
          @Override
          public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, @Nullable Protocol protocol, IOException ioe) {
              Log.d(TAG,"testEventListener MyEventListener connectFailed");
              super.connectFailed(call, inetSocketAddress, proxy, protocol, ioe);
          }
          @Override
          public void connectionAcquired(Call call, Connection connection) {
              Log.d(TAG,"testEventListener MyEventListener connectionAcquired");
              super.connectionAcquired(call, connection);
          }
          @Override
          public void connectionReleased(Call call, Connection connection) {
              Log.d(TAG,"testEventListener MyEventListener connectionReleased");
              super.connectionReleased(call, connection);
          }
          @Override
          public void requestHeadersStart(Call call) {
              Log.d(TAG,"testEventListener MyEventListener requestHeadersStart");
              super.requestHeadersStart(call);
          }
          @Override
          public void requestHeadersEnd(Call call, Request request) {
              Log.d(TAG,"testEventListener MyEventListener requestHeadersEnd");
              super.requestHeadersEnd(call, request);
          }
          @Override
          public void requestBodyStart(Call call) {
              Log.d(TAG,"testEventListener MyEventListener requestBodyStart");
              super.requestBodyStart(call);
          }
          @Override
          public void requestBodyEnd(Call call, long byteCount) {
              Log.d(TAG,"testEventListener MyEventListener requestBodyEnd");
              super.requestBodyEnd(call, byteCount);
          }
          @Override
          public void responseHeadersStart(Call call) {
              Log.d(TAG,"testEventListener MyEventListener responseHeadersStart");
              super.responseHeadersStart(call);
          }
          @Override
          public void responseHeadersEnd(Call call, Response response) {
              Log.d(TAG,"testEventListener MyEventListener responseHeadersEnd");
              super.responseHeadersEnd(call, response);
          }
          @Override
          public void responseBodyStart(Call call) {
              Log.d(TAG,"testEventListener MyEventListener responseBodyStart");
              super.responseBodyStart(call);
          }
          @Override
          public void responseBodyEnd(Call call, long byteCount) {
              Log.d(TAG,"testEventListener MyEventListener responseBodyEnd");
              super.responseBodyEnd(call, byteCount);
          }
          @Override
          public void callEnd(Call call) {
              Log.d(TAG,"testEventListener MyEventListener callEnd");
              super.callEnd(call);
          }
          @Override
          public void callFailed(Call call, IOException ioe) {
              Log.d(TAG,"testEventListener MyEventListener callFailed");
              super.callFailed(call, ioe);
          }
      }
      
      log:
      2020-10-29 23:45:54.461 D/OkHttpActivity: testEventListener MyEventListener callStart
      2020-10-29 23:45:54.479 D/OkHttpActivity: testEventListener MyEventListener dnsStart
      2020-10-29 23:45:54.502 D/OkHttpActivity: testEventListener MyEventListener dnsEnd
      2020-10-29 23:45:54.505 D/OkHttpActivity: testEventListener MyEventListener connectStart
      2020-10-29 23:45:54.528 D/OkHttpActivity: testEventListener MyEventListener secureConnectStart
      2020-10-29 23:45:54.629 D/OkHttpActivity: testEventListener MyEventListener secureConnectEnd
      2020-10-29 23:45:54.629 D/OkHttpActivity: testEventListener MyEventListener connectEnd
      2020-10-29 23:45:54.629 D/OkHttpActivity: testEventListener MyEventListener connectionAcquired
      2020-10-29 23:45:54.630 D/OkHttpActivity: testEventListener MyEventListener requestHeadersStart
      2020-10-29 23:45:54.630 D/OkHttpActivity: testEventListener MyEventListener requestHeadersEnd
      2020-10-29 23:45:54.631 D/OkHttpActivity: testEventListener MyEventListener responseHeadersStart
      2020-10-29 23:45:54.656 D/OkHttpActivity: testEventListener MyEventListener responseHeadersEnd
      2020-10-29 23:45:54.656 D/OkHttpActivity: testEventListener MyEventListener responseBodyStart
      2020-10-29 23:45:54.662 D/OkHttpActivity: testEventListener MyEventListener responseBodyEnd
      2020-10-29 23:45:54.662 D/OkHttpActivity: testEventListener MyEventListener connectionReleased
      2020-10-29 23:45:54.662 D/OkHttpActivity: testEventListener MyEventListener callEnd
      2020-10-29 23:45:54.662 D/OkHttpActivity: testEventListener onResponse 
           <!DOCTYPE html>
          <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> 
          <body link=#0000cc>***</body> </html>
      
  2. OkHttp可以设置:
    • 可以同时执行的最大最大数量. 默认是64.
    • 单个域名/host可以同时执行的最大请求数量. 默认是5.
      public final class Dispatcher {
        private int maxRequests = 64;
        private int maxRequestsPerHost = 5;
        ***
      }
      
      OkHttpClient client = new OkHttpClient.Builder().build();
      client.dispatcher().setMaxRequests(200);
      client.dispatcher().setMaxRequestsPerHost(10);
      
    • maxRequests 及 maxRequestsPerHost 生效位置,以enqueue为例
      OkHttpClient client = new OkHttpClient.Builder().build();
      Request request = new Request.Builder().url("https://www.baidu.com/").build();
      client.newCall(request).enqueue(***
      
      ->
      
      okhttp3/RealCall.java
      @Override public void enqueue(Callback responseCallback) {
        ***
        //创建AsyncCall实例,执行Dispatcher.enqueue
        client.dispatcher().enqueue(new AsyncCall(responseCallback));
      }
      
      ->
      
      okhttp3/Dispatcher.java
      void enqueue(AsyncCall call) {
        synchronized (this) {
          //将AsyncCall实例添加到Deque/readyAsyncCalls中
          readyAsyncCalls.add(call);
        }
        promoteAndExecute();
      }
      
      ->
      
      private boolean promoteAndExecute() {
        ***
        synchronized (this) {
          //遍历readyAsyncCalls
          for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
            //这里 maxRequests 和 maxRequestsPerHost 起作用了
            //如果执行中的异步请求数量已经>=最大请求数量,则中断遍历
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            //如果Call的域名正在执行的请求数量已经>=单个域名最大请求数量,忽略该请求
            if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
            //从readyAsyncCalls中将当前AsyncCall实例移除,防止后续请求来了,已经执行的请求重复执行
            i.remove();
            //请求数量未达到最大限制,则将AsyncCall实例加入executableCalls及runningAsyncCalls
            executableCalls.add(asyncCall);
            runningAsyncCalls.add(asyncCall);
          }
          isRunning = runningCallsCount() > 0;
        }
        for (int i = 0, size = executableCalls.size(); i < size; i++) {
          AsyncCall asyncCall = executableCalls.get(i);
          //这里执行'异步网络请求'
          asyncCall.executeOn(executorService());
        }
        return isRunning;
      }
      
  3. OkHttpClient中的配置
    1. connectionSpecs:指定Socket连接的配置,即使用HTTP还是HTTPS.
      • 如果是HTTPS链接,则ConnectionSpec指定了使用的TLS版本 及 密钥算法套件.
      • CLEARTEXT就是使用HTTP,不使用HTTPS. 明文和HTTP完全是一个意思.
        okhttp3/ConnectionSpec.java
        /** Unencrypted, unauthenticated connections for {@code http:} URLs. */
        public static final ConnectionSpec CLEARTEXT = new Builder(false).build();
        
    2. final CertificateChainCleaner certificateChainCleaner
      • CertificateChainCleaner 用于对TLS连接中的一系列证书信息进行整理
        • 忽略和当前TLS握手过程无关的证书.
        • 将有用的证书进行排序,组成证书链,便于客户端进行证书验证.
          • 证书链中的第一个证书就是目标网站的证书
          • 第二个是证书签发机构的证书,***
          • 最后1个是本地根证书
          • 参考HTTPS连接建立过程中的证书验证步骤
    3. final CertificatePinner certificatePinner
      • CertificatePinner用于做证书验证/自签名证书验证.
      • public Builder add(String pattern, String... pins) {
        • Each pin is a hash of a certificate's Subject Public Key
        • add方法配置的是证书的公钥
      • 本地示例:先随便填1,看报错信息,然后修改后试验
        String hostName = "www.baidu.com";
        CertificatePinner pinner = new CertificatePinner.Builder()
                //这里胡乱填
                .add(hostName,"sha256/fgsdklhgksdhghjsklg")
                .build();
        OkHttpClient client = new OkHttpClient.Builder()
                .certificatePinner(pinner)
                .build();
        Request request = new Request.Builder().url("https://" + hostName).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d(TAG,"testCertificatePinner onFailure \n " + e.getMessage());
                e.printStackTrace();
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.d(TAG,"testCertificatePinner onResponse \n " + response.body().string());
            }
        });
        -->
        log:
        2020-10-30 01:20:10.875 D/OkHttpActivity: testCertificatePinner onFailure 
             Certificate pinning failure!
              Peer certificate chain:
                sha256//558pd1Y5Vercv1ZoSqOrJWDsh9sTMEolM6T8csLucQ=: CN=baidu.com,O=Beijing Baidu Netcom Science Technology Co.\, Ltd,OU=service operation department,L=beijing,ST=beijing,C=CN
                sha256/IQBnNBEiFuhj+8x6X8XLgh01V9Ic5/V3IRQLNFFc7v4=: CN=GlobalSign Organization Validation CA - SHA256 - G2,O=GlobalSign nv-sa,C=BE
                sha256/K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q=: CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE
              Pinned certificates for www.baidu.com:
                sha256/fgsdklhgksdhghjsklg=
        
        根据log显示的信息修改,请求成功:
        CertificatePinner pinner = new CertificatePinner.Builder()
                .add(hostName,"sha256//558pd1Y5Vercv1ZoSqOrJWDsh9sTMEolM6T8csLucQ=")
                .add(hostName,"sha256/IQBnNBEiFuhj+8x6X8XLgh01V9Ic5/V3IRQLNFFc7v4=")
                .add(hostName,"sha256/K87oWBWM9UZfyddvDfoxL+8lpNyoUB2ptGtn0fv6G2Q=")
                .build();
        -->
        log:
        2020-10-30 01:27:41.439 D/OkHttpActivity: testCertificatePinner onResponse 
             <!DOCTYPE html>***
        
    4. final Authenticator authenticator
      • Authenticator用于自动重新认证. 401代表当前请求需要用户验证 或 用户认证失败. 配置authenticator之后,在收到401后,会直接调用authenticator,在header中加入Authorization自动重新发起请求.
      • Authorization两种主流方式:Basic , Bearer
      • 使用authenticator实现Basic认证:
        • 使用Credentials,直接填入 username,password . 实际上Credentials就是将username:password做了base64和Basic 做了拼接.
          public void showAuthenticatorBasic(){
              OkHttpClient client = new OkHttpClient.Builder()
                      .authenticator(new Authenticator() {
                          @Nullable
                          @Override
                          public Request authenticate(@Nullable Route route, Response response) throws IOException {
                              //使用Credentials工具类自动生成Basic认证字符串
                              //例如 HuanHaiLiuXin:Password
                              String credential = Credentials.basic("HuanHaiLiuXin", "Password");
                              return response.request().newBuilder()
                                      .header("Authorization", credential)
                                      .build();
                          }
                      })
                      .build();
              Request request = new Request.Builder().url("https://www.baidu.com/").build();
              client.newCall(request).enqueue(***);
          }
          
          okhttp3/Credentials.java
          public static String basic(String username, String password, Charset charset) {
            String usernameAndPassword = username + ":" + password;
            String encoded = ByteString.encodeString(usernameAndPassword, charset).base64();
            return "Basic " + encoded;
          }
          
      • 使用authenticator实现Basic认证:
        • 要自己实现字符串的拼接: "Bearer " + bearer token
          public void showAuthenticatorBearer(){
              OkHttpClient client = new OkHttpClient.Builder()
                      .authenticator(new Authenticator() {
                          @Nullable
                          @Override
                          public Request authenticate(@Nullable Route route, Response response) throws IOException {
                              //假设bearerToken是我们通过OAuth2/refreshtoken获取到的bearer token
                              String bearerToken = "fhdskh";
                              String bearerAuthorization  = "Bearer " + bearerToken;
                              return response.request().newBuilder()
                                      .header("Authorization", bearerAuthorization)
                                      .build();
                          }
                      })
                      .build();
              Request request = new Request.Builder().url("https://www.baidu.com/").build();
              client.newCall(request).enqueue();
          }
          
      • 避免再次重试,比如之前已经自动添加过Authorization,第二次重试还是验证不通过,可以判断后返回null
        @Nullable
        @Override
        public Request authenticate(@Nullable Route route, Response response) throws IOException {
            String credential = Credentials.basic("HuanHaiLiuXin", "Password");
            if(credential.equals(response.request().header("Authorization"))){
                //说明之前已经正确配置过Authorization,仍然认证失败,返回null,放弃重试.
                return null;
            }
            return response.request().newBuilder()
                    .header("Authorization", credential)
                    .build();
        }
        
      • 设置指定次数认证失败后,不再重试. 利用Response.priorResponse判断是第几次认证失败.
        @Nullable
        @Override
        public Request authenticate(@Nullable Route route, Response response) throws IOException {
            String credential = Credentials.basic("HuanHaiLiuXin", "Password");
            if(gainPriorResponseCount(response) >= 3){
                //如果已经是第三次认证失败,则不再重试
                return null;
            }
            return response.request().newBuilder()
                    .header("Authorization", credential)
                    .build();
        }
        
        public int gainPriorResponseCount(Response response) {
            int count = 1;
            while ((response = response.priorResponse()) != null) {
                count++;
            }
            return count;
        }
        
  4. OkHttp中3个重要概念: Connection , Stream , Call
  5. OkHttpClient中所有配置项作用简要描述,思维导图
  6. 所有拦截器