4、深刻理解OkHttp:CallServerIntercepter

1、前言

通过前面几章的准备工做,咱们终于能够和服务器进行正式的交流了。而与服务器进行正式的数据通讯就发生在最后一个拦截器:ServerIntercepter.java。在解析这个类以前,须要先看一下其余的类:ExchangeCodec,这个类在上篇文章中就有涉及到,不过没有仔细讲下去。在这章中会讲一下。java

2、ExchangeCodec

【2.1】简介

ExchageCodec是一个接口,他是来规范网络请求的编码行为和网络回复的解码行为。他有2个子类 Http1ExchangeCodec 和 Http2ExchangeCodec。从名字上一看就知道,他们分别对应了Http1协议和Http2协议。下面是ExchageCodec主要的接口规范:缓存

public interface ExchangeCodec {
  ...
  
  /** 将请求体转化为输出流*/
  Sink createRequestBody(Request request, long contentLength) throws IOException;

  /** 写请求头*/
  void writeRequestHeaders(Request request) throws IOException;

  /** 将在缓存区的请求刷新到输出流 */
  void flushRequest() throws IOException;

  /** 通知已经完成请求动做 */
  void finishRequest() throws IOException;

    
 /** 读取响应体 */
  Source openResponseBodySource(Response response) throws IOException;

  /** 读取响应头 */
  @Nullable Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;
  
  ...

  /** 取消请求 */
  void cancel();
 
}
  
复制代码

总结: 总的来讲,ExchageCodec.java规范了网络交互过程当中的写请求和读响应的动做。具体的以下:bash

  1. 将请求体转化为输出流。
  2. 写请求头。
  3. 将在请求刷新到底层的Socket。
  4. 通知完成请求动做。
  5. 读响应头。
  6. 读响应体。
  7. 取消请求。

3、Http1ExchangeCodec

【3.1】createRequestBody()

Http1ExchageCodec.java
@Override public Sink createRequestBody(Request request, long contentLength) throws IOException {
    if (request.body() != null && request.body().isDuplex()) {
      throw new ProtocolException("Duplex connections are not supported for HTTP/1");
    }

    //建立一个不知长度的输出流。
    if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
      return newChunkedSink();
    }

    //建立一个知道长度的输出流。
    if (contentLength != -1L) {
      return newKnownLengthSink();
    }

    throw new IllegalStateException(
        "Cannot stream a request body without chunked encoding or a known content length!");
  }
复制代码

总结: 该方法总的来讲就是根据请求的长度的肯定性生成响应的流类型服务器

【3.2】 writeRequestHeaders():写入请求头

@Override public void writeRequestHeaders(Request request) throws IOException {
    String requestLine = RequestLine.get(
        request, realConnection.route().proxy().type());
    writeRequest(request.headers(), requestLine);
  }
  
  public void writeRequest(Headers headers, String requestLine) throws IOException {
    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
    sink.writeUtf8(requestLine).writeUtf8("\r\n");
    for (int i = 0, size = headers.size(); i < size; i++) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n");
    }
    sink.writeUtf8("\r\n");
    state = STATE_OPEN_REQUEST_BODY;
  }
复制代码

【3.3】flushRequest()/finishRequest()

@Override public void flushRequest() throws IOException {
    sink.flush();
  }
  
  
@Override public void finishRequest() throws IOException {
    sink.flush();
  }
复制代码

总结: 他们调用的都是flush方法。因此作的都是同一件是,把缓存区的数据刷新到底层Socket。网络

【3.4】openResponseBodySource():读取响应体

@Override public Source openResponseBodySource(Response response) {
    //1. 若是没有响应体,那么构建一个读取长度为0的输入流
    if (!HttpHeaders.hasBody(response)) {
      return newFixedLengthSource(0);
    }

    //2. 不肯定长度的输入流
    if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
      return newChunkedSource(response.request().url());
    }

    //3. 肯定长度的输入流
    long contentLength = HttpHeaders.contentLength(response);
    if (contentLength != -1) {
      return newFixedLengthSource(contentLength);
    }

    return newUnknownLengthSource();
  }
复制代码

【3.5】readResponseHeaders():读取响应头

@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
    ...

    try {
    //解析响应头的String。
      StatusLine statusLine = StatusLine.parse(readHeaderLine());

      //构建响应体
      Response.Builder responseBuilder = new Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          .headers(readHeaders());

      if (expectContinue && statusLine.code == HTTP_CONTINUE) {
        return null;
      } else if (statusLine.code == HTTP_CONTINUE) {
        state = STATE_READ_RESPONSE_HEADERS;
        return responseBuilder;
      }

      state = STATE_OPEN_RESPONSE_BODY;
      return responseBuilder;
    } catch (EOFException e) {
      ...
    }
  }
  
  ///读取响应头输入流
 private String readHeaderLine() throws IOException {
    String line = source.readUtf8LineStrict(headerLimit);
    headerLimit -= line.length();
    return line;
  }
复制代码

3、CallServerIntercepter.java: 最后一个拦截器

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();
    long sentRequestMillis = System.currentTimeMillis();

    //1. 写入请求头
    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    
    //2. 是否为有请求体的请求
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      //3. 若请求头里有"100-continue",表明先只有请求头的向服务器请求。
      // 须要等待服务器的响应头再进一步请求。
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        exchange.flushRequest();
        responseHeadersStarted = true;
        exchange.responseHeadersStart();
        responseBuilder = exchange.readResponseHeaders(true);
      }
      
      //4. 能够继续发出请求数据
      if (responseBuilder == null) {
        //5. 将请求体写入socket
        if (request.body().isDuplex()) {
          exchange.flushRequest();
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, true));
          request.body().writeTo(bufferedRequestBody);
        } else {
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, false));
          request.body().writeTo(bufferedRequestBody);
          bufferedRequestBody.close();
        }
      } else {
        exchange.noRequestBody();
        if (!exchange.connection().isMultiplexed()) {
          
          exchange.noNewExchangesOnConnection();
        }
      }
    } else {
      exchange.noRequestBody();
    }

    //6. 通知结束请求。
    if (request.body() == null || !request.body().isDuplex()) {
      exchange.finishRequest();
    }

    if (!responseHeadersStarted) {
      exchange.responseHeadersStart();
    }

    //7. 读取响应头
    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false);
    }

    //8. 构建响应体
    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    //9. 若是响应码=100,须要再请求一次。
    int code = response.code();
    if (code == 100) {
      response = exchange.readResponseHeaders(false)
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

      code = response.code();
    }

    exchange.responseHeadersEnd(response);

    if (forWebSocket && code == 101) {
      //空链接
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      //10.读取响应体详细
      response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build();
    }

    //11. 若是有close头,那么关闭链接。
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      exchange.noNewExchangesOnConnection();
    }
    ...
    
    //12. 返回请求
    return response;
  }
复制代码

总结: CallServerIntercepter的拦截逻辑很简单,总的来讲就是将请求头,请求体写入Socket,而后读取Socket的响应头和响应体。而具体的IO操做,OkHttp是采用的okio,这是个优秀的IO库,具体的逻辑这里就不深挖了。具体的流程以下:架构

  1. 写入请求头。
  2. 若是请求头里有"100-continue", 表明先将请求头发送给服务器,看服务器的响应决定是否进行下一步请求体的发送。
  3. 写入请求体,并发送请求。
  4. 读取响应体,并构建一个Resonse
  5. 若是响应码为100,须要再请求一次。
  6. 读取详细的响应体。
  7. 若是响应头有“close”,那么关闭这条链接。
  8. 返回响应。

OkHttp的几个重要部分讲解就到这里所有结束了。回顾一下,咱们从网络的同步/异步请求,降到它的拦截链模式。而后着重讲了几个重要的拦截器:cacheIntercepter、ConnectInterpcet和CallServerIntercepter。这几篇文章是本人在自学中,总结记录。有不对的地方欢迎指出。最后,放上一张整体架构图,有助于总体理解:并发

( 图片来源感谢:yq.aliyun.com/articles/78…)

最后,在这里须要鸣谢如下博文:
www.jianshu.com/p/82f74db14…
www.jianshu.com/p/7624b45fb…
www.jianshu.com/p/227cee9c8…
本文引用的图片若有涉权,请联系本人删除,谢谢!异步

相关文章
相关标签/搜索