OkHttp拦截器之责任链模式

Posted on By ᵇᵒ

Prologue

一般来说,对于公司的的普通员工请假流程大致都是这样的:员工在OA系统提交请假申请,系统自动转发给直属leader,leader审批通过再自动转发给部门经理审批,经理审批通过再转到HR处理。
如果请假天数过多,可能公司章程规定还需要CEO审批,于是在部门经理和HR之间还要插入CEO审批。当然如果临时短暂请假,还可能不需要经理审批。
这其实就是一个典型的责任链模式,该模式设计的好处是可以方便的增加或者删减审批链中的某个结点。

OkHttp

Android开发涉及到网络访问时,大多都会使用比较流行的开源框架OkHttp,下面是一个简单的OkHttp(v3.9.1)例子:

OkHttpClient client = new OkHttpClient.Builder()
    .readTimeout(20_1000, TimeUnit.MILLISECONDS)
    .writeTimeout(20_1000, TimeUnit.MILLISECONDS)
    .addInterceptor(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            // Todo:do your stuff here.
            // ···
            Response response = chain.proceed(request);
            return response;
        }
    })
    .build();

Request request = new Request.Builder()
    .url(ENDPOINT)
    .build();

Response response = client.newCall(request).execute()

OkHttp中的拦截器使用了责任链模式,通过addInterceptor()添加自定义拦截器,在拦截器实现的intercept()方法中通过chain获取到request,在对request进行需要处理的动作后,再通过proceed()方法返回response。

下面通过分析OkHttp的源码来了解其责任链模式的实现,涉及的核心类为如下几个:

Interceptor  
RealInterceptorChain  
RealCall  
OkHttpClient  
HttpLoggingInterceptor  
ConnectInterceptor  
CallServerInterceptor

Note
严格来说,HttpLoggingInterceptorConnectInterceptorCallServerInterceptor不算责任链模式的核心类,只是OkHttp内部实现的自定义拦截器,列举出来是方便描述如何实现自定义拦截器以及拦截器顺序。

Interceptor

Interceptor的核心源码(方便描述,去掉了不太相关的代码,后续同):

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;
  }
}

可以看到Interceptor接口有一个Intercept()方法,该方法传入的参数是一个内部接口Chain(当然这个接口也可以写成一个单独的类文件),Chain接口中有两个关键方法request()proceed(),通过前面的OkHttp小例子我们知道自定义的拦截器实现intercept(chain)方法获取到参数chain,再通过chain的request()方法获取到request,便可以对request进行操作,处理结束后通过chain的proceed(request)方法递归调用下一个拦截器处理,所有的拦截器都处理完成后依次返回response。
proceed(request)方法是如何做到递归调用下一个拦截器的呢?这就涉及到RealInterceptorChain类了。

RealInterceptorChain

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final int index;
  private final Request request;
  // ···

  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.index = index;
    this.request = request;
    // ···
  }

  @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }

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

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

    // ···

    return response;
  }
}

两个proceed()方法,第一个方法传入默认参数调用的还是第二个proceed()方法,在第二个通过index自增获取下一个拦截器,同时新建了一个名为next的RealInterceptorChain链,该链对index进行自增,再将新建的next链作为参数传入下一个拦截器,下一个拦截器调用intercept(next)方法处理,处理完成得到response并返回。
其中下一个拦截器实现的intercept()方法中获取到next链,处理完成后又会调用链的proceed()方法,于是形成了递归调用。
interceptors会原封不动地传入到每一个新建的链中,通过index的自增依次递归调用interceptors列表中的拦截器,直至最后一个处理完成,依次返回response。
两个问题:

  • 原始的拦截器列表interceptors又是从哪来的呢?
  • index自增不会溢出吗?

RealCall

final class RealCall implements Call {
    final OkHttpClient client;
    // ···

    @Override public Response execute() throws IOException {
        // ···
        Response result = getResponseWithInterceptorChain();
        // ···
    }

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

可以看到RealCall在执行execute()方法的时候会调用拦截器链,在这里先添加所有OkHttClient的拦截器,然后依次添加OkHttp内部默认实现的拦截器。

OkHttpClient

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
    final List<Interceptor> interceptors;

    OkHttpClient(Builder builder) {
        this.interceptors = Util.immutableList(builder.interceptors);
        // ···
    }

    public List<Interceptor> interceptors() {
        return interceptors;
    }

    public static final class Builder {
        final List<Interceptor> interceptors = new ArrayList<>();
        // ···

        public Builder addInterceptor(Interceptor interceptor) {
            if (interceptor == null) throw new IllegalArgumentException("interceptor == null");
            interceptors.add(interceptor);
            return this;
        }

        public OkHttpClient build() {
            return new OkHttpClient(this);
        }
    }
}

OkHttpClient使用了构造者模式,存在一个内部类Builder,Builder的addInterceptor()方法把库使用者自定义的拦截器给添加进来。
在Builder的build()方法中构造OkHttpClient,并把interceptors传递给OkHttpClient。

CallServerInterceptor

回到RealInterceptorChain末尾的第二个问题:index自增不会溢出吗?
不会,因为OkHttp默认实现了一个拦截器CallServerInterceptor 并添加到列表最后,在该拦截器里不再调用proceed方法。

public final class CallServerInterceptor implements Interceptor {
  // ···

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

      long sentRequestMillis = System.currentTimeMillis();

      realChain.eventListener().requestHeadersStart(realChain.call());
      httpCodec.writeRequestHeaders(request);
      realChain.eventListener().requestHeadersEnd(realChain.call(), request);

      Response.Builder responseBuilder = null;
      if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
        // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
        // Continue" response before transmitting the request body. If we don't get that, return
        // what we did get (such as a 4xx response) without ever transmitting the request body.
        if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
          httpCodec.flushRequest();
          realChain.eventListener().responseHeadersStart(realChain.call());
          responseBuilder = httpCodec.readResponseHeaders(true);
        }

        if (responseBuilder == null) {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          realChain.eventListener().requestBodyStart(realChain.call());
          long contentLength = request.body().contentLength();
          CountingSink requestBodyOut =
              new CountingSink(httpCodec.createRequestBody(request, contentLength));
          BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

          request.body().writeTo(bufferedRequestBody);
          bufferedRequestBody.close();
          realChain.eventListener()
              .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
        } else if (!connection.isMultiplexed()) {
          // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
          // from being reused. Otherwise we're still obligated to transmit the request body to
          // leave the connection in a consistent state.
          streamAllocation.noNewStreams();
        }
      }

      httpCodec.finishRequest();

      if (responseBuilder == null) {
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(false);
      }

      Response response = responseBuilder
          .request(request)
          .handshake(streamAllocation.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

      realChain.eventListener()
          .responseHeadersEnd(realChain.call(), response);

      int code = response.code();
      if (forWebSocket && code == 101) {
        // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
        response = response.newBuilder()
            .body(Util.EMPTY_RESPONSE)
            .build();
      } else {
        response = response.newBuilder()
            .body(httpCodec.openResponseBody(response))
            .build();
      }

      if ("close".equalsIgnoreCase(response.request().header("Connection"))
          || "close".equalsIgnoreCase(response.header("Connection"))) {
        streamAllocation.noNewStreams();
      }

      if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
        throw new ProtocolException(
            "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
      }

      return response;
    }

    // ···
}

可以看到在实现的intercept()方法中始终没有调用chain.proceed()方法。作为列表的最后一个拦截器,结束递归调用。

HttpLoggingInterceptor

HttpLoggingInterceptor是OkHttp内部实现的一个log分级的拦截器,实现intercept()方法,通过chain获取request,处理完成后再调用chain.proceed(request)方法。

public final class HttpLoggingInterceptor implements Interceptor {

    @Override public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        // ···
        return chain.proceed(request);
   }

}

ConnectInterceptor

ConnectInterceptor同样是OkHttp内部实现的一个拦截器,与HttpLoggingInterceptor不同的是,chain的递归调用方法:

public final class ConnectInterceptor implements Interceptor {
  public final OkHttpClient client;

  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }

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

ConnectInterceptor实现的intercept()方法中,首先是把chain强转为RealInterceptorChain,通过RealInterceptorChain的proceed()方法来实现递归调用,与HttpLoggingInterceptor的差别在于proceed()方法的参数不同。
还记得RealInterceptorChain中的两个proceed()方法吗?其实对应的就是这两种方式。
ConnectInterceptor这种带参数递归调用的方式可以改变后续递归链的属性。
自定义拦截器可以根据自己需求来决定使用何种调用方法。