OkHttp源码解析(三)Interceptor

        这篇文章,对okhttp的另一个非常重要的概念-拦截器(Interceptor)进行源码分析。或许,有的朋友就要说了,前面两篇文章分别总结了两种请求的源码以及Dispatcher的源码,为什么突然扯到Interceptor了呢?接下来,我们先了解一下,拦截器是什么。

一、Interceptor是什么

        Interceptor翻译过来就叫拦截器。引用官网的解释:拦截器是okhttp的一种强大的机制。他可以实现网络监听、请求以及重写响应、请求失败重试等功能。

二、从同步请求说起

        我们前面的源码分析,在讲同步请求的时候,我们对response的获取一笔带过,并没有深入去追究response是如何获取到的。我们先把同步请求执行的相关源码贴一下:

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      e = timeoutExit(e);
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }

        上面的代码,我们在同步请求源码解析中,大部分都做了解析。但是,有很重要的一行,我一笔带过。也就是:

Response result = getResponseWithInterceptorChain();

        这个方法,乍一看很简单,就是在发起请求后,获取到请求的结果。但实际上真的那么简单吗?当然不是。我们通过同步请求和异步请求的执行可以看出,请求的过程特别是异步请求还是比较麻烦的。因此。请求结果的获取,也并不是一行代码就轻松得到的,而是通过拦截器链一步一步执行获得的。

三、拦截器

        首先,我们看一下okhttp的拦截器链:

        我们接着上面的方法,跟进去源码。我们看到,代码量不多,就十几行代码。但是,我们看到了这中间有很多个我们从来没见到过的类,各种XXXInterceptor。这其实就是okhttp的拦截器链:

  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());

    return chain.proceed(originalRequest);
  }

         简单说一下这个方法,其实,如果我们忽视掉这些拦截器的功能,单纯看代码的话,还是比较好理解的。首先,我们往一个拦截器的List中添加了很多个拦截器。然后,我们通过这个拦截器的List等参数去创建了一个拦截器链,注意,这个地方传入的index为0,也就是第一个拦截器。最后,这个拦截器链调用proceed方法。可见,核心的代码实现是在proceed方法中,我们跟进去看一下做了什么。

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

        我们看一下几处核心代码,首先是下面这一段。我们看到,跟我们在进入proceed方法前一样,创建了一个拦截器链,但是这里的index是index+1,也就是拦截器链中当前index的下一个拦截器,这其实就是拦截器链的实现。然后,获取当前index的拦截器,去执行下一个拦截器,代码如下:

// Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

        我们看一下intercept方法的实现。Interceptor是一个接口,我们看一下实现类的方法实现。在上面我们说过,拦截器有很多个,他们是链式调用的。第一个拦截器,其实就是RetryAndFollowUpInterceptor,它的intercept方法具体实现在这里,我们先不去关注具体实现,我们只看下面这么一行代码:

response = realChain.proceed(request, streamAllocation, null, null

        是的,每个拦截器的intercept方法,其实又调用了proceed方法,而proceed方法,又去调用下一个拦截器的intercept方法。接下来,我们简单看一下每一种拦截器的作用。

1、RetryAndFollowUpInterceptor

        这个中文名字,我们叫他重试和跟踪拦截器。他的主要作用就是发起网络请求,如果该请求需要重定向,那么进行重定向请求,并且在请求失败的情况下,发起重试。

(1)创建网络请求

    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);

(2)重定向

      // 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;
      try {
        followUp = followUpRequest(response, streamAllocation.route());
      } catch (IOException e) {
        streamAllocation.release();
        throw e;
      }

      if (followUp == null) {
        streamAllocation.release();
        return response;
      }

(3)重试

        重试次数不是无限的,在这里设置的是20次。 

 private static final int MAX_FOLLOW_UPS = 20;
     
 if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
 }

2、BridgeInterceptor        

        中文叫桥接拦截器,他的主要作用是添加头部信息(例如User-Agent,,Host,Cookie等),使请求成为一个标准的http请求并发起请求,并且处理返回给我们的response,使之转化为用户可用的response。

(1)添加头部信息

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

(2)处理response

if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));

 3、CacheInterceptor

        中文叫缓存拦截器。试想一下,是不是我们每次发起网络请求都真正的去服务器请求呢?当然不是,Okhttp也有自己的缓存策略。当我们发起请求的时候,会去判断是否有缓存,如果有缓存则直接从缓存中拿这个请求。如果没有缓存,那么去使用网络发起请求。这些缓存功能的实现,就是缓存拦截器来做的。

4、ConnectInterceptor

       网络连接拦截器,在经过上面一系列拦截器的处理后(是否重试和重定向,拼接头部信息,是否使用缓存等),终于到了和服务器连接的时刻,网络连接拦截器就是和服务器进行连接的拦截器。我们看一下源码:

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

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

        网络连接拦截器的工作流程如下:

        (1)获取前面的拦截器传过来的StreamAllocation

        (2)把StreamAllocation,httpCodec,RealConnection等等传递给后面的拦截器。

        HttpCodec是一个非常关键的类,看到Codec,我们能猜出它的作用,一般是编解码相关的类。HttpCodec就是负责对http请求和响应进行编解码的类。

        RealConnection也是一个非常关键的类,他是真正的网络连接类,它维护了一个网络连接池ConnectionPool。

5、CallServerInterceptor

        访问服务端拦截器。这个拦截器从名字可以看出,它是真正向服务端发起请求的拦截器。这里面都是和网络请求相关的一些方法和特殊情况的处理,最后会返回我们所需要的response。

        最后,简单总结一下,okhttp有多个拦截器,这些拦截器一起构成了拦截器链,每一个链接器在执行自己功能的同时,会调用下一个拦截器,同时对response进行处理,返回上一个拦截器,这样完成了拦截器的链式调用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章