OKhttp源码学习(八)—— 拦截器_CallServerInterceptor

源码地址:https://github.com/square/okhttp

不知不觉已经来到了最后一个拦截器,前面做了各种处理,也建立了连接。接下来的CallServerInterceptor,应该就是对数据进行交换、读取以及构建结果的类了。

不过其实在这个拦截器之前,还可以自定义networkInterceptors。
在Okhttp的拦截器链条里面有两个地方可以自定义拦截:

  1. 最开始的时候(Interceptors):对发出去的请求做最初的处理,以及在拿到最后Reponse时候做最后的处理;

  2. 最后数据交换前(networkInterceptors):对发出去的请求做最后的处理,以及在拿到结果时候做最初的处理。

我们可以自定义拦截器,去处理我们需要做的事情。

下面言归正传:

intercept

和其他拦截器一样,最重要的方法就是这个intercept方法,拦截方法。

这个拦截方法主要步骤:

  1. 获取几个前面已经创建的重要类(httpCodec, streamAllocation, request);

  2. 先向sink(OutputStream)中写头信息(sink, 在创建连接时候已经创建好);

  3. 判断是否有请求体,如有,走4,5的操作,没有直接到6;

  4. 如果头部添加了"100-continue", 相对于一次见到的握手操作,只有拿到服务的结果再继续;

  5. 当“100-continue”成功或者不需要这个简单握手的,写入请求实体;

  6. finishRequest( 实际是调用了 sink.flush(), 来刷数据 )

  7. 读取头部信息(通过source(InputStream), 读取头部信息,状态码等)

  8. 构建Response, 写入原请求,握手情况,请求时间,得到的结果时间

  9. 通过Response 状态码判断以及是否webSocket判断,是否返回一个空的body, 或者读取Body信息(通过 source(InputStream) 读取)

  10. 读取到请求时的连接close ,或者服务器返回的 close, 进行断开操作;

  11. 对于204,205的特殊状态码进行处理。

** 有两个比较重要的类,就是sink以及source, 这个两个是Okio里面的一个重要概念,相当于我们熟悉的 OutputStream 以及 InputStream输入输出流。** 这里就不展开,有兴趣的可以了解一下 Okio这个 I/O 库。这两个类都是在创建连接的时候就已经建立,而在个拦截器里面就是对数据进行交换。

下面是源码阅读:

  @Override public Response intercept(Chain chain) throws IOException {

    // 1.获取几个前面拦截方法创建的,重要类
    HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
    StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
    Request request = chain.request();
    long sentRequestMillis = System.currentTimeMillis();

    //2. 先向sink(OutputStream)中写头信息
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;

   // 3.判断是否有请求实体的请求,用method判断
    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.
   
      //4. 如果头部添加了"100-continue", 相对于一次见到的握手操作,只有拿到服务的结果再继续
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      //5. 当前面的"100-continue",需要握手,但又握手失败,这个时候responseBuilder不是空的
      // Write the request body, unless an "Expect: 100-continue" expectation failed.
      if (responseBuilder == null) {
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        // 回调RequestBody的writeTo,写相应的数据
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      }
    }

    //6. 这里也是调用了一次 sink.flush()
    httpCodec.finishRequest();

    //7. 读取头部信息,状态码,信息等
    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    //8. 构建Response, 写入原请求,握手情况,请求时间,得到的结果时间
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    //9. 通过状态码判断以及是否webSocket判断,是否返回一个空的body
    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 {
      //读取Body信息
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    //10 .如果设置了连接 close ,断开连接
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    //11. HTTP 204(no content) 代表响应报文中包含若干首部和一个状态行,但是没有实体的主体内容。
    //HTTP 205(reset content) 表示响应执行成功,重置页面(Form表单),方便用户下次输入
    //这里做了同样的处理,就是抛出协议异常。
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

总结:

这个拦截器主要是通过连接好的通道进行数据的交换,有些细节没有展开,只对基本的流程进行了学习记录。留下两个可研究的点:

  1. HttpCodec是一个接口,其实有两个实现类,分别是HttpCodec1、HttpCodec2, 对应着http1 的读写数据和 http2的 读写数据。两者是有差异的;

  2. 还有前面提到的Okio,可以去了解一下。

系列(简书地址):
OKhttp源码学习(一)—— 基本请求流程
OKhttp源码学习(二)—— OkHttpClient
OKhttp源码学习(三)—— Request, RealCall
OKhttp源码学习(四)—— RetryAndFollowUpInterceptor拦截器
OKhttp源码学习(五)—— BridgeInterceptor拦截器
OKhttp源码学习(六)—— CacheInterceptor拦截器
OKhttp源码学习(七)—— ConnectInterceptor拦截器
OKhttp源码学习(九)—— 任务管理(Dispatcher)

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