探究OkHttpClient的运行原理(2---RetryAnd

2021-01-18  本文已影响0人  零星瓢虫

上一篇文章我们分析了 OkHttpClient 创建请求,以及相关队列操作的一些方法。具体可查看
探究Okhttp的运行原理(1)
此篇文章我们继续来看 OkHttpClient 另外一个重要的流程getResponseWithInterceptorChain() 方法去获取请求响应的。

getResponseWithInterceptorChain 方法

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

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis()); // 初始化 RealInterceptorChain 内部变量

    return chain.proceed(originalRequest); // 执行
  }

getResponseWithInterceptorChain 加入了五个主要的拦截器,拦截器即拦截请求,做一些对应的处理;

了解拦截器工作原理之前,首先看下 RealInterceptorChain 类,Okhttp 通过 RealInterceptorChain 使用责任链模式处理下发的请求;

RealInterceptorChain 类内部维护了 Request 请求、interceptors 拦截器容器、当前拦截器(初始为0);

RealInterceptorChain

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final RealConnection connection;
  private final int index;
  private final Request request;
  private final Call call;
  private final EventListener eventListener;
  private final int connectTimeout;
  private final int readTimeout;
  private final int writeTimeout;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
      EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
    this.call = call;
    this.eventListener = eventListener;
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
    this.writeTimeout = writeTimeout;
  }

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    ......

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);  // 创建下一个RealInterceptorChain 
    Interceptor interceptor = interceptors.get(index);// 获取到 index 的拦截器
    Response response = interceptor.intercept(next); // 执行拦截器的 intercept
   ......

    return response;
  }

拦截器调用 proceed 方法主要做了一下操作:
1 、创建下一个拦截器(传参为 index + 1);
2、 获取当前 index 值(初始为 0)的拦截器,第一个即为 RetryAndFollowUpInterceptor 拦截器
3、执行 RetryAndFollowUpInterceptor 的 intercept 方法;

intercept 方法具有传递效果(即链式传递给下一个拦截器),在 RetryAndFollowUpInterceptor 内部,会再次调用 RealInterceptorChain (即传入的 next 参数)的 proceed 方法,index 会继续自增,拿到下一个拦截器,从而完成责任传递效果;

直到最终返回结果的处理,过程如下图所示:

拦截器.png

接下来,分析第一个拦截器的 intercept 方法 RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor 请求重定向拦截器

  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request(); // 获取请求
    RealInterceptorChain realChain = (RealInterceptorChain) chain;// 获取拦截器
    Call call = realChain.call(); // 获取 RealCall 对象
    EventListener eventListener = realChain.eventListener();// 获取监听事件

    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation; // 创建 streamAllocation 实例

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) { // 请求取消 释放资源
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Request followUp = followUpRequest(response, streamAllocation.route()); // 如果地址被重定向,重新组装重定向的请求

      if (followUp == null) {// 没有重定向 释放资源
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      closeQuietly(response.body());

      if (++followUpCount > MAX_FOLLOW_UPS) { // 重定向超过一定次数 释放资源
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) { 
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }

      if (!sameConnection(response, followUp.url())) { // 不是同一个连接,重定向操作 重新创建 StreamAllocation 实例
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(followUp.url()), call, eventListener, callStackTrace);
        this.streamAllocation = streamAllocation;
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;
      priorResponse = response;
    }
  }

RetryAndFollowUpInterceptor 拦截器主要去处理请求的重定向相关操作,拦截器先创建了 StreamAllocation 实例对象,而后通过调用 followUpRequest 方法去查看相应返回 response 数据,判断是否需要进行重定向;

  private Request followUpRequest(Response userResponse, Route route) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    int responseCode = userResponse.code();

    final String method = userResponse.request().method();
    switch (responseCode) {
      ......
      case HTTP_MULT_CHOICE: // 300
      case HTTP_MOVED_PERM:// 301
      case HTTP_MOVED_TEMP:// 302
      case HTTP_SEE_OTHER: // 302
        // 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);

        // Don't follow redirects to unsupported protocols.
        if (url == null) return null;

        // If configured, don't follow redirects between SSL and non-SSL.
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;

        // Most redirects don't include a request body.
        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");
          }
        }

        // When redirecting across hosts, drop all authentication headers. This
        // is potentially annoying to the application layer since they have no
        // way to retain them.
        if (!sameConnection(userResponse, url)) {
          requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();
        ......
    }
  }

当返回码 300、301、302、303 时代表请求需要重定向,此时重新构建 Request 请求并进行返回;

当 RetryAndFollowUpInterceptor 拦截器发现请求需要重定向的时候。即 followUpRequest 返回的 Requset 不为空的时候,会重新创建 StreamAllocation 实例对象;

这里分析下 StreamAllocation 对象,后续的拦截器会使用到;

StreamAllocation

实例方法
new StreamAllocation(client.connectionPool(),createAddress(request.url()), call, eventListener, callStackTrace);

client.connectionPool() ------主要维护 RealConnection 类的队列;

createAddress ------- 维护请求地址的相关信息

  private Address createAddress(HttpUrl url) {
    SSLSocketFactory sslSocketFactory = null;
    HostnameVerifier hostnameVerifier = null;
    CertificatePinner certificatePinner = null;
    if (url.isHttps()) {
      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()); // 包括 host 、port 、dns 等数据
  }

call ------ RealCall 实例

eventListener ------ 监听实例

callStackTrace ------ 记录

看下 StreamAllocation 类;

public final class StreamAllocation {
  public final Address address;
  private RouteSelector.Selection routeSelection;
  private Route route;
  private final ConnectionPool connectionPool;
  public final Call call;
  public final EventListener eventListener;
  private final Object callStackTrace;

  // State guarded by connectionPool.
  private final RouteSelector routeSelector;
  private int refusedStreamCount;
  private RealConnection connection;
  private boolean reportedAcquired;
  private boolean released;
  private boolean canceled;
  private HttpCodec codec;

  public StreamAllocation(ConnectionPool connectionPool, Address address, Call call,
      EventListener eventListener, Object callStackTrace) {
    this.connectionPool = connectionPool;
    this.address = address;
    this.call = call;
    this.eventListener = eventListener;
    this.routeSelector = new RouteSelector(address, routeDatabase(), call, eventListener);
    this.callStackTrace = callStackTrace;
  }
}

StreamAllocation 主要维护了上述变量的相关方法,这里先存储着,等到后续拦截器进行使用;

RetryAndFollowUpInterceptor 的拦截器到这里就分析完了,总结 RetryAndFollowUpInterceptor 主要做了以下事情:

1、 创建 StreamAllocation 实例进行保存,内部维护了 RealConnection 类的队列池,同时保存请求相关的 Host、Port、DNS 等信息供后续拦截器使用;
2、 对请求返回的重定向进行重新创建 Request 、StreamAllocation ;

上一篇下一篇

猜你喜欢

热点阅读