美文网首页
OkHttp源码阅读(三) —— 拦截器之拦截器链

OkHttp源码阅读(三) —— 拦截器之拦截器链

作者: Sherlock丶Aza | 来源:发表于2021-04-11 08:21 被阅读0次

      之前对OkHttp的分析可以知道一个请求获取的Response是通过一个叫做拦截器链的东西得到的,Response result = getResponseWithInterceptorChain();,其中拦截器链的设计和实现也是OkHttp设计精妙之处之一。

    责任链

      在了解OkHttp的拦截器链之前,首先要之知道一个设计模式叫责任链,责任链的设计模式最好的体现就是在网络请求的设计上,我们知道一个网络请求发出后要经过应用程->传输层->网络层->连接层->物理层,收到到一个响应时也会反过来经过物理层->连接层->网络层->传输层->应用层,每一次都可以对请求进行封装,也可以对响应进行处理,并且可以终端连接,以自身为终点返回响应。

    拦截器链

      有了上边对责任链的了解,我们看下OkHttp中的拦截器链式怎么使用责任链设计模式的,首先,OkHttp内置了有几个拦截器,其中包括:RetryAndFollowUpInterceptorBridgeInterceptorCacheInterceptorConnectInterceptorCallServerInterceptorInterceptor是一个接口,客户端可以自定义拦截器加入到拦截器链中。一个完整的网络请求发起到最后得到响应的流程是通过拦截器链一层一层流转工作而来,具体如下图:

    1.jpeg

    代码实现

      我们看下OkHttp的拦截器链是怎么实现链式结构,从而发起请求和响应的呢,还是通过Response result = getResponseWithInterceptorChain()跟进可以发现:

    2.jpeg

    将上述说道几种拦截器放到了一个结合当中,形成一个interceptors这个的一个list,再通过下面代码将拦截器链初始化

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

    我们可以看到真正的拦截器链的对象是一个实现了Interceptor.Chain接口的实现类RealInterceptorChain,他的初始化参数就是刚刚的拦截器集合,网络请求以及其他参数,最后调用chain.proceed(originalRequest)真正实现拦截器链的工作。所以拦截器的重点是chain.proceed(originalRequest)方法实现,由于Interceptor.Chain是个接口,所以proceed方法的实现是在RealInterceptorChain中,

    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;
      }
    

    代码较长但是不难发现其中的重点,就是下面一段代码:

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

    由注释我们也可以看出这段代码的意思是调用拦截器链中的下一个拦截器,其中index是拦截器链中不同拦截器的索引,拦截器链初始化完成后第一次调用的是index = 0的拦截器,执行Interceptor接口中定义的Response intercept(Chain chain) throws IOException;方法,其中的参数是一个拦截器链,这个拦截器链起始index是+1的,也就是除去自身以外后边的所有拦截器,再看interceptor.intercept()方法, 由于Interceptor是一个接口,我们随便拿一个拦截器举例(RetryAndFollowUpInterceptor)看下它的intercept()方法时怎么实现的,

    @Override public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        Call call = realChain.call();
        EventListener eventListener = realChain.eventListener();
    
        StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(request.url()), call, eventListener, callStackTrace);
        this.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.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;
        }
      }
    
    

    同样代码很长,其它代码都先不用管,后面会说明,着重看这行代码:

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

    我们可以发现这个拦截器的intercept方法调用中,Response的响应还是交给了拦截器链,而这个拦截器链是通过RealIonterceptorChain中的proceed方法获取,每次调用index都会+1,

    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
            connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
            writeTimeout);
    

    这样一来就形成链式结构,拦截器链调用proceed方法,proceed方法会获取当前拦截器,执行该拦截器的intercept(), intercept()方法又会调用传进来的自己后边拦截器链的proceed方法,这样一来反反复复的调用,知道所有拦截器都被调用为止。

    总结

      责任链的设计模式在OkHttp的设计中体现的淋漓尽致,一开始看拦截器链的代码时也是出于懵逼状态,所以一开始先了解下责任链设计模式,后面我们针对不同的拦截器做细致的分析。

    相关文章

      网友评论

          本文标题:OkHttp源码阅读(三) —— 拦截器之拦截器链

          本文链接:https://www.haomeiwen.com/subject/nkaykltx.html