转载

四、深入理解OkHttp:CallServerIntercepter

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

二、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规范了网络交互过程中的写请求和读响应的动作。具体的如下:

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

三、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;
  }
复制代码

三、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。这几篇文章是本人在自学中,总结记录。有不对的地方欢迎指出。最后,放上一张总体架构图,有助于整体理解:

四、深入理解OkHttp:CallServerIntercepter
( 图片来源感谢: yq.aliyun.com/articles/78… )

最后,在这里需要鸣谢以下博文:

www.jianshu.com/p/82f74db14…

www.jianshu.com/p/7624b45fb…

www.jianshu.com/p/227cee9c8…

本文引用的图片如有涉权,请联系本人删除,谢谢!

原文  https://juejin.im/post/5eef00c051882565784a1832
正文到此结束
Loading...