Android开发Android开发经验谈Android技术知识

OkHttp原理解析2(拦截器篇)(更新中...)

2020-02-23  本文已影响0人  酱爆大头菜
image.png

Hello小伙伴们,现在公司已经恢复了正常办公,但是疫情依旧还在继续。最近工作实在是有点小忙,导致更新有点缓慢,实在抱歉,本文是OkHttp原理解析的第二篇, 主要针对OkHttp中几个 默认拦截器 的具体实现逻辑进行分析。

因为OkHttp的很大一部分逻辑都在拦截器中,因此本文会比较长,同时采用连载更新的方式进行描述,每完成一个拦截器的逻辑分析都会进行更新。

如有对OkHttp的框架流程不太了解的可优先阅读网我上篇博客 OkHttp原理解析1(框架流程篇)


我又要开始表演了~~~

但为了方便后续描述,我还是简单对上文做了个总结。大概分为以下几步。

而拦截器的启动与运行依赖 责任链 模式,大概分为以下3步。

我们从添加拦截的的位置开始本文介绍,其实上文已经做了描述,源码如下所示。

//RealCall.getResponseWithInterceptorChain();
  Response getResponseWithInterceptorChain() throws IOException {
    // 建立一个完整的拦截器堆栈
    List<Interceptor> interceptors = new ArrayList<>();
     //将创建okhttpclient时的拦截器添加到interceptors
    interceptors.addAll(client.interceptors());
    //重试拦截器,负责处理失败后的重试与重定向
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //请求转化拦截器(用户请求转为服务器请求,服务器响应转为用户响应)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //缓存拦截器。负责
    //1.根据条件,缓存配置,有效期等返回缓存响应,也可增加到缓存。
    //2.设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改)
    //3.可配置自定义的缓存拦截器。
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //网络连接拦截器,主要负责和服务器建立连接。
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //创建okhttpclient时设置的networkInterceptors
      interceptors.addAll(client.networkInterceptors());
    }
    //数据流拦截器,主要负责像服务器发送和读取数据,请求报文封装和解析。
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //责任链模式的创建。
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      //启动责任链
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

主要看我注释的部分,OkHttp主要涉及了以下几个默认拦截器

OkHttp会把用户自定义的拦截器默认放到拦截器列表的头部,以方面优先执行,然后通过创建RealInterceptorChain对象,并调用RealInterceptorChain.proceed()启动第一个拦截器,然后调用拦截器的interceptor.intercept(next)执行第一个拦截器的逻辑并将下一个拦截器RealInterceptorChain对象传入依此类推。接下来我们就一个个进行分析。


1. RetryAndFollowUpInterceptor(client) 连接失败重试拦截器

首先开始的是RetryAndFollowUpInterceptor失败重试拦截器,这个拦截器是可以在OkHttpClient.Builder对象中通过retryOnConnectionFailure(boolean retryOnConnectionFailure)设置是否开启,默认构建的OkHttpClient对象是开启的。
该拦截器主要的职责官方注释解释了这么几点。

说实话,这描述略显抽象,要不是我用过,我真不知道他在扯啥,咱们还是通过源码看下真像吧。

//我们应该尝试多少重定向和验证挑战?Chrome遵循21个重定向;Firefox、curl和wget遵循20;Safari遵循16;HTTP/1.0建议5。
private static final int MAX_FOLLOW_UPS = 20;

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      //通过transmitter创建ExchangeFinder,Address,RouteSelector三个对象
      transmitter.prepareToConnect(request);
      //判断如果当前请求结束了则抛出异常,可通过transmitter终止请求。
      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        //启动下一个拦截器
        response = realChain.proceed(request, transmitter, null);
        //执行顺利则通过该字段退出循环。
        success = true;
      } catch (RouteException e) {
        // 如果是路由异常。请求还没发送。
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
          throw e.getFirstConnectException();
        }
        continue;
      } catch (IOException e) {
        // 尝试与服务器通信失败。请求可能已发送。
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, transmitter, requestSendStarted, request)) throw e;
        continue;
      } finally {
        //网络调用引发异常。释放所有资源。
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }

      // 如果body不为空
      if (priorResponse != null) {
        //获得新的response
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Exchange exchange = Internal.instance.exchange(response);
      Route route = exchange != null ? exchange.connection().route() : null;
      // 调用followUpRequest()查看响应是否需要重定向,不需要就返回当前请求,如果需要返回新的请求
      Request followUp = followUpRequest(response, route);
      // 不需要重定向或无法重定向
      if (followUp == null) {
        if (exchange != null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        return response;
      }

      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }
      //如果重定向次数超过最大次数抛出异常
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      request = followUp;
      priorResponse = response;
    }
  }

  //RetryAndFollowUpInterceptor.recover()
  //报告并尝试从与服务器通信失败中恢复。如果{@code e}可恢复,则返回true;
  //如果失败是永久性的,则返回false。只有在缓冲了正文或在发送请求之前发生故障时,才可以恢复具有正文的请求。
 private boolean recover(IOException e, Transmitter transmitter,
      boolean requestSendStarted, Request userRequest) {
    // 用户设置的禁止重试
    if (!client.retryOnConnectionFailure()) return false;

    // 不能再发送请求体
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;

    // 异常是致命的
    if (!isRecoverable(e, requestSendStarted)) return false;

    // 没有更多的路线可以尝试。
    if (!transmitter.canRetry()) return false;

    //对于故障恢复,请对新连接使用同一路由选择器
    return true;
  }

  /**
   * 查看响应是否需要重定向,不需要就返回当前请求,如果需要返回新的请求。
   * 找出接收{@code userResponse}时要发出的HTTP请求。这将添加身份验证头、遵循重定向或处理客户端请求超时。
   * 如果后续操作不必要或不适用,则返回null。
   */
  private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    //获取响应码
    int responseCode = userResponse.code();
    //请求方法
    final String method = userResponse.request().method();
    //响应码分类处理
    switch (responseCode) {
      //407 代理需要身份认证
      case HTTP_PROXY_AUTH:
        Proxy selectedProxy = route != null
            ? route.proxy()
            : client.proxy();
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
        }
        return client.proxyAuthenticator().authenticate(route, userResponse);
      //401 需要身份认证
      case HTTP_UNAUTHORIZED:
        return client.authenticator().authenticate(route, userResponse);

      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        // "If the 307 or 308 status code is received in response to a request other than GET
        // or HEAD, the user agent MUST NOT automatically redirect the request"
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
       // 300多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
      case HTTP_MULT_CHOICE:
      // 301永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。
      case HTTP_MOVED_PERM:
      // 302临时移动。与301类似。但资源只是临时被移动。
      case HTTP_MOVED_TEMP:
      // 303查看其它地址。与301类似。使用GET和POST请求查看
      case HTTP_SEE_OTHER:
        // Does the client allow redirects?
        if (!client.followRedirects()) return null;

        String location = userResponse.header("Location");
        if (location == null) return null;
        HttpUrl url = userResponse.request().url().resolve(location);

        // 不要遵循重定向到不支持的协议。
        if (url == null) return null;

        //如果已配置,请不要遵循SSL和非SSL之间的重定向。
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;

        // 大多数重定向不包括请求正文。
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {
          final boolean maintainBody = HttpMethod.redirectsWithBody(method);
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
          } else {
            RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
            requestBuilder.method(method, requestBody);
          }
          if (!maintainBody) {
            requestBuilder.removeHeader("Transfer-Encoding");
            requestBuilder.removeHeader("Content-Length");
            requestBuilder.removeHeader("Content-Type");
          }
        }

        // 跨主机重定向时,删除所有身份验证头。这对应用程序层来说可能很烦人,因为它们无法保留它们。
        if (!sameConnection(userResponse.request().url(), url)) {
          requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();
      //408 超时
      case HTTP_CLIENT_TIMEOUT:
        // 408's are rare in practice, but some servers like HAProxy use this response code. The
        // spec says that we may repeat the request without modifications. Modern browsers also
        // repeat the request (even non-idempotent ones.)
        if (!client.retryOnConnectionFailure()) {
          // 应用层指示我们不要重试请求
          return null;
        }

        RequestBody requestBody = userResponse.request().body();
        if (requestBody != null && requestBody.isOneShot()) {
          return null;
        }

        if (userResponse.priorResponse() != null
            && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
          // We attempted to retry and got another timeout. Give up.
          return null;
        }

        if (retryAfter(userResponse, 0) > 0) {
          return null;
        }

        return userResponse.request();
      // 503 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
      case HTTP_UNAVAILABLE:
        if (userResponse.priorResponse() != null
            && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
          // We attempted to retry and got another timeout. Give up.
          return null;
        }

        if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
          // specifically received an instruction to retry without delay
          return userResponse.request();
        }

        return null;

      default:
        return null;
    }
  }


//Transmitter.prepareToConnect()
public void prepareToConnect(Request request) {
    if (this.request != null) {
      //判断是不是相同的连接,如果是则用之前的。
      if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
        return; // Already ready.
      }
      if (exchange != null) throw new IllegalStateException();
      //exchangeFinder
      if (exchangeFinder != null) {
        maybeReleaseConnection(null, true);
        exchangeFinder = null;
      }
    }

    this.request = request;
    this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
        call, eventListener);
  }

//Transmitter.createAddress()
 private Address createAddress(HttpUrl url) {
    SSLSocketFactory sslSocketFactory = null;
    HostnameVerifier hostnameVerifier = null;
    CertificatePinner certificatePinner = null;
    if (url.isHttps()) {
      //https的设置
      sslSocketFactory = client.sslSocketFactory();
      hostnameVerifier = client.hostnameVerifier();
      certificatePinner = client.certificatePinner();
    }

    return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
        sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
        client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
  }

//ExchangeFinder.ExchangeFinder()
  ExchangeFinder(Transmitter transmitter, RealConnectionPool connectionPool,
      Address address, Call call, EventListener eventListener) {
    this.transmitter = transmitter;
    this.connectionPool = connectionPool;
    this.address = address;
    this.call = call;
    this.eventListener = eventListener;
    //创建路由选择器
    this.routeSelector = new RouteSelector(
        address, connectionPool.routeDatabase, call, eventListener);
  }
//RouteSelector.RouteSelector()
  RouteSelector(Address address, RouteDatabase routeDatabase, Call call,
      EventListener eventListener) {
    this.address = address;
    this.routeDatabase = routeDatabase;
    this.call = call;
    this.eventListener = eventListener;

    resetNextProxy(address.url(), address.proxy());
  }

该拦截器的逻辑很多,但其实主要做两件事,一个是重试,一个是重定向,逻辑流程大概是这样的。

总结来说:
通过while死循环来获取response,每次循环开始都根据条件获取下一个request,如果没有request,则返回response,退出循环。而获取的request 的条件是根据上一次请求的response 状态码确定的,在循环体中同时创建了一些后续需要的对象

上一篇下一篇

猜你喜欢

热点阅读