OKHttp源码解析(6)----拦截器CallServerInterceptor

系列文章

OKio源码解析

OKHttp源码解析(1)----整体流程

OKHttp源码解析(2)----拦截器RetryAndFollowUpInterceptor

OKHttp源码解析(3)----拦截器BridgeInterceptor

OKHttp源码解析(4)----拦截器CacheInterceptor

OKHttp源码解析(5)----拦截器ConnectInterceptor

OKHttp源码解析(6)----拦截器CallServerInterceptor

1.简介

This is the last interceptor in the chain. It makes a network call to the server. 这是链中最后一个拦截器,它向服务器发起了一次网络访问

请求服务拦截器,负责向服务器发送请求数据、从服务器读取响应数据

2.源码解析

2.1流程

@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();
     // 整理请求头并写入
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
     // 检查是否为有 body 的请求方法
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there is a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we do not get that, return what
      // we did get (such as a 4xx response) without ever transmitting the request body.
      // 如果有 Expect: 100-continue在请求头中,那么要等服务器的响应。100-continue协议需要在post数据前,
      征询服务器情况,看服务器是否处理POST的数据
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
      //发送100-continue的请求
        httpCodec.flushRequest();
        //如果有中间级的响应,返回null
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        // 写入请求体
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      } 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) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

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

    int code = response.code();
     // 如果为 web socket 且状态码是 101 ,那么 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,此时并没有读取socket中的输入流
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }
     // 如果请求头中有 close 那么断开连接
    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;
  }

复制代码

首先我们要明确Source和Sink是什么: 在JAVA IO中有输入流和输出流,在OKio的IO体系中,Sourc就表示输入流,Sink表示输出流。OKio的IO体系我们将在新的一章中进行讲解,本章不进行展开。

  • Source: 输入流,这是一个接口类,其子类有:BufferedSource、PeekSource、ForwardingSource、GzipSource、InputStreamSource、InflaterSource
  • Sink: 输出流 ,接口类,其子类有:BufferedSink、ForwardingSink、GzipSink、OutputStreamSink、DeflaterSink、BlackholeSink

在本流程中,主干逻辑有以下4步:

  1. httpCodec.writeRequestHeaders(request)
  2. httpCodec.createRequestBody(request, contentLength)
  3. httpCodec.readResponseHeaders(true)
  4. httpCodec.openResponseBody(response)

以上4步分别完成了请求头的写入,请求体的写入,响应头的读取,响应体的读取。

2.2流的读取

当时看这里的时候,有个疑惑点,网络请求的数据及输入流中的数据在哪里,其实在第4步中获取到响应的body信息,只是获取一个流对象,只有在应用代码中调用流对象的读方法或者response.body().string()方法等,才会从socket的输入流中读取信息到应用的内存中使用。下面我们来分析一下流的读取过程。

response.body().string()

复制代码

ResponseBody:


public final String string() throws IOException {
  BufferedSource source = source();
    try {
      Charset charset = Util.bomAwareCharset(source, charset());
      return source.readString(charset);
    } finally {
      Util.closeQuietly(source);
    }
  }
复制代码

从源码可以看到source.readString(charset);将流转换为String进行输出。下面我们分两步来讨论:

  • source的来源
  • source.readString()之后的过程
2.2.1 source的来源

source来自于方法source(),ResponseBody中source()是个抽象方法:

  public abstract BufferedSource source();
复制代码

其实现类为RealResponseBody:

  @Override public BufferedSource source() {
    return source;
  }
复制代码

方法中的source为构造方法中传递过来的,创建RealResponseBody的是CallServerInterceptor源码中的httpCodec.openResponseBody(response),看一下httpCodec.openResponseBody()的源码:

  @Override public ResponseBody openResponseBody(Response response) throws IOException {
      ......
    if (!HttpHeaders.hasBody(response)) {
      Source source = newFixedLengthSource(0);
      return new RealResponseBody(contentType, 0, Okio.buffer(source));
    }
     ......
  }
复制代码

查看newFixedLengthSource(0)方法:

  public Source newFixedLengthSource(long length) throws IOException {
    if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
    state = STATE_READING_RESPONSE_BODY;
    return new FixedLengthSource(length);
  }
复制代码

查看FixedLengthSource()类的read方法:

    @Override public long read(Buffer sink, long byteCount) throws IOException {
      if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
      if (closed) throw new IllegalStateException("closed");
      if (bytesRemaining == 0) return -1;

      long read = super.read(sink, Math.min(bytesRemaining, byteCount));
      if (read == -1) {
        ProtocolException e = new ProtocolException("unexpected end of stream");
        endOfInput(false, e); // The server did not supply the promised content length.
        throw e;
      }

      bytesRemaining -= read;
      if (bytesRemaining == 0) {
        endOfInput(true, null);
      }
      return read;
    }
复制代码

read方法是将source数据写入Buffer中,我們來看source的来源,继续查看父类AbstractSource中的super.read():

    @Override public long read(Buffer sink, long byteCount) throws IOException {
      try {
        long read = source.read(sink, byteCount);
        if (read > 0) {
          bytesRead += read;
        }
        return read;
      } catch (IOException e) {
        endOfInput(false, e);
        throw e;
      }
    }
复制代码

查找source.read(sink, byteCount)中的source的来源,这个source是Http1Codec构造方法传递进行的:

  public Http1Codec(OkHttpClient client, StreamAllocation streamAllocation, BufferedSource source,
      BufferedSink sink) {
    this.client = client;
    this.streamAllocation = streamAllocation;
    this.source = source;
    this.sink = sink;
  }
复制代码

经过上溯代码,我们发现创建Http1Codec是在ConnectInterceptor中开始的,流程如下:

  @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);
  }
复制代码

streamAllocation.newStream(client, chain, doExtensiveHealthChecks);

  public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
     ....
    try {
     //创建连接connection,根据socket创建输入流sink和输出流source
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
     //创建HttpCodec,有Http1Codec和Http2Codec两个子类
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
     ...
    }
  }
复制代码

其中findHealthyConnection创建了source,resultConnection.newCodec创建了Http1Codec:

  • findHealthyConnection-->findConnection-->result.connect(RealConnection)-->connectSocket()--> source = Okio.buffer(Okio.source(rawSocket))

  • newCodec

  public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
      StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {
      return new Http2Codec(client, chain, streamAllocation, http2Connection);
    } else {
      socket.setSoTimeout(chain.readTimeoutMillis());
      source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
      return new Http1Codec(client, streamAllocation, source, sink);
    }
  }
复制代码

小结:

okhttp在ConnectInterceptor连接拦截器中,根据socket创建了网络设施connection,输出流sink和输入流source。并在CallServerInterceptor请求服务器拦截器中完成了请求的发送和响应头的读取,此时并没有读取响应体即我们关心的网络请求结果。响应体的读取是用户拿到response后,使用response.body().string()从socket中读取的。

2.2.2 source.readString()之后的过程:

source我们找到了,是RealBufferedSource类包含的引用了socket的source,那么我们在RealBufferedSource()中查看方法readString():

  override fun readString(charset: Charset): String {
    buffer.writeAll(source)
    return buffer.readString(charset)
  }
复制代码

流的读取分为两步:

  1. 将soucre写入缓存buffer
  2. 从缓存中把数据读取出来,返回。

本章在追源码,写的有点乱,开发者在看的时候,还是需要去读一下源码,自己跟踪一下。下一章将学习一下OKio的IO体系,结合OKio将对okhttp有更深的理解。OKio源码解析

References

http之100-continue(转)

OkHttp 源码学习笔记(三) 数据交换的流 HTTPCodec

转载于:https://juejin.im/post/5c0791695188251ba9057d23

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值