OkHttp 源码阅读笔记(五)

OkHttp 源码阅读笔记(五)

第一篇文章中介绍了 OkHttp 的同步调用和异步调用,Dispatcher 的任务调度器工作方式和 RealInterceptorChain 拦截器链的工作方式:OkHttp 源码阅读笔记(一)

第二篇文章中介绍了 OkHttp 如何从缓存中获取链接,如何创建链接以及 ConnectionPool 的工作原理:OkHttp 源码阅读笔记(二)

第三篇文章中介绍了 OkHttp 中的系统拦截器 RetryAndFollowUpInterceptorBridgeInterceptorOkHttp 源码阅读笔记(三)

第四篇文章中介绍了 OkHttp 中的系统拦截器 CacheInterceptorOkHttp 源码阅读笔记(四)

本篇文章是系列文章的第五篇,也是系列文章中的最后一篇,介绍最后一个系统拦截器 CallServerInterceptor

CallServerInterceptor

CallServerInterceptor 的前面的系统拦截器,已经处理好了 Request,后续要返回的 Response 处理逻辑也准备就绪。网络链接相关的工作也都已经处理好了。所以 CallServerInterceptor 的工作就是将 Request 中的数据通过网络链接传输到服务器,同时通过网络链接接收服务器发送过来的 Response 的相关数据,然后返回上一层拦截器。同样的以 intercept() 方法作为分析该流程的入口函数:

Kotlin 复制代码
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.exchange!!
    val request = realChain.request
    val requestBody = request.body
    val sentRequestMillis = System.currentTimeMillis()

    var invokeStartEvent = true
    var responseBuilder: Response.Builder? = null
    var sendRequestException: IOException? = null
    try {
      // 写入 Request Header
      exchange.writeRequestHeaders(request)
      
      // 判断是否能够写入 Request Header,只有 PUT 和 GET 不允许写入。
      if (HttpMethod.permitsRequestBody(request.method) && requestBody != 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.
        // 如果 Reqeust Header 中有 "Expect: 100-continue",表示不能够写入 Request Body,需要等待 Response 返回 100+。后面会看到这部分逻辑。
        if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
          exchange.flushRequest()
          // 直接读 Response Header
          responseBuilder = exchange.readResponseHeaders(expectContinue = true)
          exchange.responseHeadersStart()
          invokeStartEvent = false
        }
        if (responseBuilder == null) {
          // 以下是 Http2 相关逻辑跳过
          if (requestBody.isDuplex()) {
            // ...
          } else {
            // Http 1.x 相关逻辑
            // Write the request body if the "Expect: 100-continue" expectation was met.
            // 写入 Request Body。
            val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
            requestBody.writeTo(bufferedRequestBody)
            bufferedRequestBody.close()
          }
        } else {
          exchange.noRequestBody()
          if (!exchange.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.
            exchange.noNewExchangesOnConnection()
          }
        }
      } else {
        exchange.noRequestBody()
      }

      if (requestBody == null || !requestBody.isDuplex()) {
        exchange.finishRequest()
      }
    } catch (e: IOException) {
      if (e is ConnectionShutdownException) {
        throw e // No request was sent so there's no response to read.
      }
      if (!exchange.hasFailure) {
        throw e // Don't attempt to read the response; we failed to send the request.
      }
      sendRequestException = e
    }

    try {
      if (responseBuilder == null) {
        // 读取 Response Header
        responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
        if (invokeStartEvent) {
          exchange.responseHeadersStart()
          invokeStartEvent = false
        }
      }
      var response = responseBuilder
          .request(request)
          .handshake(exchange.connection.handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build()
      var code = response.code
      
      // 判断 Response Code 是不是 100+,如果是的话还需要再读取一次 Response Header。
      if (shouldIgnoreAndWaitForRealResponse(code)) {
        // 再次读取 Response Header。
        responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
        if (invokeStartEvent) {
          exchange.responseHeadersStart()
        }
        response = responseBuilder
            .request(request)
            .handshake(exchange.connection.handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build()
        code = response.code
      }

      exchange.responseHeadersEnd(response)

      response = if (forWebSocket && code == 101) {
        // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
        // WebSocket 逻辑,没有 Response Body。
        response.newBuilder()
            .body(EMPTY_RESPONSE)
            .build()
      } else {
        response.newBuilder()
            // 构建 Response Body,注意这里的 Response Body 并没有读取到内存中,需要库的使用者真正使用时才会去读取,后续会分析这部分代码。
            .body(exchange.openResponseBody(response))
            .build()
      }
      // 如果 Request Header 或者 Response Header 中有 "Connection: close" 就表示后续不能够再使用这个链接,然后会把这个链接标记为不可用。
      if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
          "close".equals(response.header("Connection"), ignoreCase = true)) {
        exchange.noNewExchangesOnConnection()
      }
      if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
        throw ProtocolException(
            "HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
      }
      // 返回最后的结果
      return response
    } catch (e: IOException) {
      if (sendRequestException != null) {
        sendRequestException.addSuppressed(e)
        throw sendRequestException
      }
      throw e
    }
  }

我们先来理一下普通逻辑:

  1. 调用 Exchange#writeRequestHeaders() 方法写入 Request Header,后续再分析这个方法。
  2. 写入 Request Body
    • GETHEAD 以外的请求,同时 Request Body 不为空才能够写入。
    • 如果 Request Header 中有 Expect: 100-continue 不能写入 Request Body,表示需要等待 Response 中返回 100+ 才做后续操作。
    • 通过 Exchange#createRequestBody() (后续详细分析) 方法创建一个对应的 Sink (可以理解为 OutputStream),然后将 Request Body 写入上面的 Sink
  3. 通过 Exchange#readResponseHeaders() (后续详细分析)方法读取 Response Header
  4. 通过 Exchange#openResponseBody() (后续详细分析)方法打开 Response Body,注意这里并没有读取 Body 到内存中,只库的使用者真正使用时才会去读取。
  5. 判断 Request Header 或者 Response Heander 中是否有 Connection: close,这表示该链接在使用完后不能够再重用,通过调用 Exchange#noNewExchangesOnConnection() 来标记,最终会设置 RealConnection#noNewExchanges 属性为 true,如果有看这个系列的第二篇文章,应该还对这个参数有印象。

再简单说明一下 Response Code100+ 的情况:

  1. 如果 Request Header 中有 Expect: 100-continue,就表示它需要等待 Response Code100+ 才继续后续操作。
  2. 如果 Response Header 中返回的是 100+,就还需要再次读取 Response Header

具体 Repsonse Code100+Expect: 100-continue 这种情况,不熟悉的话建议去网上找找 Http 协议的相关描述。

Exchange#writeRequestHeaders()

我这里先给一个 Request Header 的例子,以帮助大家阅读后面的源码:

text 复制代码
GET /docs/tutorials/linux/shellscripts/howto.html HTTP/1.1
Host: Linode.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.8) Gecko/20091102 Firefox/3.5.5
Accept: text/html,application/xhtml+xml,
Accept-Language: en-us
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8
Cache-Control: no-cache

然后看看 Exhcnage#writeRequestHeaders() 方法的源码:

Kotlin 复制代码
  @Throws(IOException::class)
  fun writeRequestHeaders(request: Request) {
    try {
      // 通知 eventListener 写 RequestHeader 开始
      eventListener.requestHeadersStart(call)
      // 调用 Codec#writeRequestHeaders() 方法写入 Requst Header
      codec.writeRequestHeaders(request)
      // 通知 eventListener 写 RequestHeader 结束
      eventListener.requestHeadersEnd(call, request)
    } catch (e: IOException) {
      eventListener.requestFailed(call, e)
      trackFailure(e)
      throw e
    }
  }

上面的 Codec 有两个实现类 Http1ExchangeCodecHttp2ExchangeCodec 分别表示 Http 1Http 2 协议的写入逻辑,我们后续的分析也都是针对 Http1ExchangeCodec

我们继续看看 Http1ExchangeCodec#writeRequestHeaders() 方法的实现:

Kotlin 复制代码
  override fun writeRequestHeaders(request: Request) {
    // 获取 Request 方法,协议,路径等等字符串,例如:GET /docs/tutorials/linux/shellscripts/howto.html HTTP/1.1
    val requestLine = RequestLine.get(request, connection.route().proxy.type())
    writeRequest(request.headers, requestLine)
  }
  
    /** Returns bytes of a request header for sending on an HTTP transport. */
  fun writeRequest(headers: Headers, requestLine: String) {
    check(state == STATE_IDLE) { "state: $state" }
    // 写入协议行
    sink.writeUtf8(requestLine).writeUtf8("\r\n")
    // 写入 Rquest Header,单个 Header 以换行符结束。
    for (i in 0 until headers.size) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n")
    }
    // 再写入一个空的行,用来标记 Header 已经写完了,后续就是写 Request Body。
    sink.writeUtf8("\r\n")
    state = STATE_OPEN_REQUEST_BODY
  }

最开始的行是协议相关的行,格式就是 请求方法 + + 请求路径 + + 协议,例如:GET /docs/tutorials/linux/shellscripts/howto.html HTTP/1.1。后续就是写入 Request Header,格式是 Key + : + Value,例如 Content-Type: application/json。当 Header 写完了后,还会写一个空行,表示写完了,后续就是 Request Body 的写入。(如果有的话)这里的 Sink 对象是在构建链接成功后就会创建,它是相当于 OutputStream ,也就是对应写,对应读的是 Source,相当于 InputStream。他们都是 OkIo 中的实现。前面介绍 OkHttp 链接相关的时候有介绍。

Exchange#createRequestBody()

这里再看看 Exchange#createRequestBody() 的实现:

Kotlin 复制代码
  @Throws(IOException::class)
  fun createRequestBody(request: Request, duplex: Boolean): Sink {
    this.isDuplex = duplex
    // 获取 Request Body 的长度
    val contentLength = request.body!!.contentLength()
    // 通知 eventListener 开始写 Request Body
    eventListener.requestBodyStart(call)
    val rawRequestBody = codec.createRequestBody(request, contentLength)
    return RequestBodySink(rawRequestBody, contentLength)
  }

上面代码比较简单主要是通过 Codec#createRequestBody() 构建一个 Sink 对象,然后再创建一个 RequestBodySink 对象。我们先看看 Http1ExchangeCodec#createRequestBody() 方法的实现:

Kotlin 复制代码
  override fun createRequestBody(request: Request, contentLength: Long): Sink {
    return when {
      request.body != null && request.body.isDuplex() -> throw ProtocolException(
          "Duplex connections are not supported for HTTP/1")
      request.isChunked -> newChunkedSink() // Stream a request body of unknown length.
      contentLength != -1L -> newKnownLengthSink() // Stream a request body of a known length.
      else -> // Stream a request body of a known length.
        throw IllegalStateException(
            "Cannot stream a request body without chunked encoding or a known content length!")
    }
  }

如果有 Content-Length,会调用 newKnownLengthSink() 方法创建 Sink,反之调用 newChunkedSink() 方法,我们以 newKnownLengthSink() 为例子,看后续的源码:

Kotlin 复制代码
  private fun newKnownLengthSink(): Sink {
    check(state == STATE_OPEN_REQUEST_BODY) { "state: $state" }
    state = STATE_WRITING_REQUEST_BODY
    return KnownLengthSink()
  }
  
  private inner class KnownLengthSink : Sink {
    // 写超时对象
    private val timeout = ForwardingTimeout(sink.timeout())
    private var closed: Boolean = false
   
    override fun timeout(): Timeout = timeout

    override fun write(source: Buffer, byteCount: Long) {
      check(!closed) { "closed" }
      // 检查 byteCount 是否已经超过了 Buffer 的最大值
      checkOffsetAndCount(source.size, 0, byteCount)
      // 直接写入到链接的 Sink 中
      sink.write(source, byteCount)
    }

    override fun flush() {
      if (closed) return // Don't throw; this stream might have been closed on the caller's behalf.
      sink.flush()
    }

    override fun close() {
      if (closed) return
      closed = true
      // 关闭超时
      detachTimeout(timeout)
      state = STATE_READ_RESPONSE_HEADERS
    }
  }

KnownLengthSink 对象是 Http1ExchangeCodec 中的内部类,它的处理方式很简单,只有一些简单的状态判断,写完时还会把写超时的计时器关闭,最终写入链接创建成功时的 SocketSink 中。

我们再来看看 RequestBodySink 中的实现:

Kotlin 复制代码
  private inner class RequestBodySink(
    // 这个就是上面分析的 KnownLengthSink。
    delegate: Sink,
    /** The exact number of bytes to be written, or -1L if that is unknown. */
    private val contentLength: Long
  ) : ForwardingSink(delegate) {
    private var completed = false
    private var bytesReceived = 0L
    private var closed = false

    @Throws(IOException::class)
    override fun write(source: Buffer, byteCount: Long) {
      check(!closed) { "closed" }
      // 检查写的 Request Body 是否超过了设置的长度
      if (contentLength != -1L && bytesReceived + byteCount > contentLength) {
        throw ProtocolException(
            "expected $contentLength bytes but received ${bytesReceived + byteCount}")
      }
      try {
        super.write(source, byteCount)
        this.bytesReceived += byteCount
      } catch (e: IOException) {
        throw complete(e)
      }
    }

    @Throws(IOException::class)
    override fun flush() {
      try {
        super.flush()
      } catch (e: IOException) {
        throw complete(e)
      }
    }

    @Throws(IOException::class)
    override fun close() {
      if (closed) return
      closed = true
      // 检查写的长度
      if (contentLength != -1L && bytesReceived != contentLength) {
        throw ProtocolException("unexpected end of stream")
      }
      try {
        super.close()
        // 通知写完成
        complete(null)
      } catch (e: IOException) {
        throw complete(e)
      }
    }

    private fun <E : IOException?> complete(e: E): E {
      if (completed) return e
      completed = true
      // 这个方法非常重要,最终会通知 RealCall,写 Request Body 已经完成,这个方法也会通知读 Response Body 已经完成。  
      return bodyComplete(bytesReceived, responseDone = false, requestDone = true, e = e)
    }
  }

RequestBodySinkExchange 中的内部类,它的处理方式也非常简单,简单的判断了写入的 Request Body 的长度,如果长度错误会抛出异常,然后还有一个非常重要的地方,写入完成后会调用 bodyComplete() 方法,这个方法最终会通知到 RealCall 对象中去,Response Body 读完成,也是调用这个方法,后面会单独分析。

Exchange#readResponseHeaders()

接着看看读取 Response Header 的实现:

Kotlin 复制代码
  @Throws(IOException::class)
  fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
    try {
      val result = codec.readResponseHeaders(expectContinue)
      result?.initExchange(this)
      return result
    } catch (e: IOException) {
      // 通知 eventListener 读取 header 失败
      eventListener.responseFailed(call, e)
      trackFailure(e)
      throw e
    }
  }

上面的代码很简单,我们继续看 Http1ExchangeCodec#readResponseHeaders() 的实现:

为了更好的理解源码这里贴一个 Response 的例子:

text 复制代码
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache/2.2.14 (Win32)
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
Content-Length: 88
Content-Type: text/html
Connection: Closed

<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
Kotlin 复制代码
  override fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
    // 状态检查
    check(state == STATE_OPEN_REQUEST_BODY ||
        state == STATE_WRITING_REQUEST_BODY ||
        state == STATE_READ_RESPONSE_HEADERS) {
      "state: $state"
    }

    try {
      // 读取 Response 的状态行,例如:HTTP/1.1 200 OK
      val statusLine = StatusLine.parse(headersReader.readLine())

      val responseBuilder = Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          // 读取 header
          .headers(headersReader.readHeaders())

      return when {
        expectContinue && statusLine.code == HTTP_CONTINUE -> {
          null
        }
        statusLine.code == HTTP_CONTINUE -> {
          state = STATE_READ_RESPONSE_HEADERS
          responseBuilder
        }
        statusLine.code in (102 until 200) -> {
          // Processing and Early Hints will mean a second headers are coming.
          // Treat others the same for now
          state = STATE_READ_RESPONSE_HEADERS
          responseBuilder
        }
        else -> {
          state = STATE_OPEN_RESPONSE_BODY
          responseBuilder
        }
      }
    } catch (e: EOFException) {
      // Provide more context if the server ends the stream before sending a response.
      val address = connection.route().address.url.redact()
      throw IOException("unexpected end of stream on $address", e)
    }
  }

首先读取状态行:协议 + + Response Code + + Response Message,例如:HTTP/1.1 200 OK,最后的 Response Header 的读取,会调用 HeadersReader#readHeaders() 方法。我们来看看它的实现:

Kotlin 复制代码
  /** Reads headers or trailers. */
  fun readHeaders(): Headers {
    // 构建 header builder 对象
    val result = Headers.Builder()
    while (true) {
      // 读取行
      val line = readLine()
      // 如果读取到的行为空,表示 header 读取已经结束
      if (line.isEmpty()) break
      // 通过方法解析 Response Header
      result.addLenient(line)
    }
    
    // 构建 Headers 对象。
    return result.build()
  }

上面的方法也非常简单,一行字符串就表示一个 Response Header,通过 Headers.Builder#addLenient() 方法来解析这个字符串,如果字符串为空就表示 Response Header 已经读完了,这个和 Request Header 也是类似的,后面的内容就是对应的 Response Body

Exchange#openResponseBody()

现在继续看看 Response Body 的处理方法:

Kotlin 复制代码
  @Throws(IOException::class)
  fun openResponseBody(response: Response): ResponseBody {
    try {
      val contentType = response.header("Content-Type")
      val contentLength = codec.reportedContentLength(response)
      // 构建 Response Body 的 Source
      val rawSource = codec.openResponseBodySource(response)
      // 再构建一个新的 Source
      val source = ResponseBodySource(rawSource, contentLength)
      // 构建新的 Response Body。
      return RealResponseBody(contentType, contentLength, source.buffer())
    } catch (e: IOException) {
      eventListener.responseFailed(call, e)
      trackFailure(e)
      throw e
    }
  }

继续看看 Http1ExchangeCodec#openResponseBodySource() 方法的实现:

Kotlin 复制代码
  override fun openResponseBodySource(response: Response): Source {
    return when {
      !response.promisesBody() -> newFixedLengthSource(0)
      response.isChunked -> newChunkedSource(response.request.url)
      else -> {
        val contentLength = response.headersContentLength()
        if (contentLength != -1L) {
          // 有固定长度的 Response Body
          newFixedLengthSource(contentLength)
        } else {
          // 无固定长度的 Response Body
          newUnknownLengthSource()
        }
      }
    }
  }

Request Body 一样,有固定长度的和没有固定长度的两种,我们以固定长度的为例子来看看 newFixedLengthSource() 方法的实现:

Kotlin 复制代码
  private fun newFixedLengthSource(length: Long): Source {
    check(state == STATE_OPEN_RESPONSE_BODY) { "state: $state" }
    state = STATE_READING_RESPONSE_BODY
    return FixedLengthSource(length)
  }

这里构建了一个 FixedLengthSource 对象,我们来看看它的实现:

Kotlin 复制代码
  private inner class FixedLengthSource(private var bytesRemaining: Long) :
      AbstractSource() {

    init {
      if (bytesRemaining == 0L) {
        // 如果长度为 0,直接通知读 Response Body 已经完成
        responseBodyComplete()
      }
    }

    override fun read(sink: Buffer, byteCount: Long): Long {
      // 读的数量判断
      require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
      // 状态判断
      check(!closed) { "closed" }
      if (bytesRemaining == 0L) return -1
      
      // 读 Sink
      val read = super.read(sink, minOf(bytesRemaining, byteCount))
      if (read == -1L) {
        connection.noNewExchanges() // The server didn't supply the promised content length.
        val e = ProtocolException("unexpected end of stream")
        responseBodyComplete()
        throw e
      }

      bytesRemaining -= read
      if (bytesRemaining == 0L) {
        // 已经读取完毕
        responseBodyComplete()
      }
      return read
    }

    override fun close() {
      if (closed) return

      if (bytesRemaining != 0L &&
          !discard(ExchangeCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
        connection.noNewExchanges() // Unread bytes remain on the stream.
        // 读取完毕
        responseBodyComplete()
      }

      closed = true
    }
  }

上面的代码也是比较简单,只是简单的判断了读取的大小状态和流的开关状态。

我们再来简单看看 ResponseBodySource 的实现:

Kotlin 复制代码
  internal inner class ResponseBodySource(
    // 上面的 FixedLengthSource 对象
    delegate: Source,
    private val contentLength: Long
  ) : ForwardingSource(delegate) {
    private var bytesReceived = 0L
    private var invokeStartEvent = true
    private var completed = false
    private var closed = false

    init {
      // 如果长度为 0 就直接完成
      if (contentLength == 0L) {
        complete(null)
      }
    }

    @Throws(IOException::class)
    override fun read(sink: Buffer, byteCount: Long): Long {
    
      // 关状态判断
      check(!closed) { "closed" }
      try {
        // 真正的读
        val read = delegate.read(sink, byteCount)

        if (invokeStartEvent) {
          invokeStartEvent = false
          // 通知 eventListener Response 读开始
          eventListener.responseBodyStart(call)
        }

        if (read == -1L) {
          // 已经读取完毕
          complete(null)
          return -1L
        }

        val newBytesReceived = bytesReceived + read
        if (contentLength != -1L && newBytesReceived > contentLength) {
          throw ProtocolException("expected $contentLength bytes but received $newBytesReceived")
        }

        bytesReceived = newBytesReceived
        if (newBytesReceived == contentLength) {
          // 读取完毕
          complete(null)
        }

        return read
      } catch (e: IOException) {
        throw complete(e)
      }
    }

    @Throws(IOException::class)
    override fun close() {
      if (closed) return
      closed = true
      try {
        super.close()
        // 读取完毕
        complete(null)
      } catch (e: IOException) {
        throw complete(e)
      }
    }

    fun <E : IOException?> complete(e: E): E {
      if (completed) return e
      completed = true
      // If the body is closed without reading any bytes send a responseBodyStart() now.
      if (e == null && invokeStartEvent) {
        invokeStartEvent = false
        eventListener.responseBodyStart(call)
      }
      // 通知 RealCall Response Body 读取完毕
      return bodyComplete(bytesReceived, responseDone = true, requestDone = false, e = e)
    }
  }

ResponseBodySource 中也是做了简单的状态判断,这里很重要的一个方法是在读取完成后,提前关闭后和出现异常后会调用 complete() 方法,然后在它之中还会调用 bodyComplete() 方法来通知 RealCall,这里和 RequestBodySink 是类似的,不过这里要注意 ResponseBodySource 它是只有在库的调用地方真正使用这些数据时才会读,在 Response 返回的时候并没有读取这里的数据,这个和 CachedInterceptor 返回的缓存 Response Body 也是一样的。

一次 Http 请求结束的标志

在前面的文章中分析网络链接创建的过程中,有说到,在网络链接创建完成后 RealConnection 会被添加到 RealCall 中,对应的 RealCall 引用也会被添加到 RealConnection 中去。那什么时候才会把 RealConnection 中的 RealCall 引用移除呢???我们貌似没有看到这部分代码,返回 Response 时也没有发现这部分代码。

其实就是在一次 RealCall 真正的完成时就会清除 RealConnection 中的引用,而 Response 返回时,RealCall 并没有真正的完成,而是当请求的 Request Body 写完成和 Response Body 读完成时才标志请求完成了,在上面一节我们分析了他们的读写,完成后最终都会调用 Exchange#bodyComplete() 方法,我们再来看看它的代码实现:

Kotlin 复制代码
  fun <E : IOException?> bodyComplete(
    bytesRead: Long,
    responseDone: Boolean,
    requestDone: Boolean,
    e: E
  ): E {
    if (e != null) {
      // 记录失败
      trackFailure(e)
    }
    if (requestDone) {
      if (e != null) {
        // 通知 eventListener 请求 Request 失败
        eventListener.requestFailed(call, e)
      } else {
        // 通知 eventListener 请求 Request Body 结束
        eventListener.requestBodyEnd(call, bytesRead)
      }
    }
    if (responseDone) {
      if (e != null) {
        // 通知 eventListener 请求 Response 失败
        eventListener.responseFailed(call, e)
      } else {
        // 通知 eventListener 请求 Response Body 结束
        eventListener.responseBodyEnd(call, bytesRead)
      }
    }
    // 通知 RealCall
    return call.messageDone(this, requestDone, responseDone, e)
  }

上面的代码只是简单通知 eventListener 各种请求状态,其主要是调用了 RealCall#messageDone() 方法,我们来看看它的实现:

Kotlin 复制代码
  internal fun <E : IOException?> messageDone(
    exchange: Exchange,
    requestDone: Boolean,
    responseDone: Boolean,
    e: E
  ): E {
    if (exchange != this.exchange) return e // This exchange was detached violently!

    var bothStreamsDone = false
    var callDone = false
    synchronized(this) {
      // 各种状态的更新
      if (requestDone && requestBodyOpen || responseDone && responseBodyOpen) {
        if (requestDone) requestBodyOpen = false
        if (responseDone) responseBodyOpen = false
        bothStreamsDone = !requestBodyOpen && !responseBodyOpen
        callDone = !requestBodyOpen && !responseBodyOpen && !expectMoreExchanges
      }
    }

    if (bothStreamsDone) {
      this.exchange = null
      // 增加成功次数
      this.connection?.incrementSuccessCount()
    }
    
    if (callDone) {
      // 完成
      return callDone(e)
    }

    return e
  }

如果请求完成后会接着调用 callDone() 方法,我们继续看:

Kotlin 复制代码
  private fun <E : IOException?> callDone(e: E): E {
    assertThreadDoesntHoldLock()

    val connection = this.connection
    if (connection != null) {
      connection.assertThreadDoesntHoldLock()
      val socket = synchronized(connection) {
        // 检查 Connection 状态,如果返回 Socket 不为空,就表示要手动释放。
        releaseConnectionNoEvents() // Sets this.connection to null.
      }
      if (this.connection == null) {
        socket?.closeQuietly()
        // 通知 eventListener 链接已经释放
        eventListener.connectionReleased(this, connection)
      } else {
        check(socket == null) // If we still have a connection we shouldn't be closing any sockets.
      }
    }

    val result = timeoutExit(e)
    if (e != null) {
      // 通知 eventListener 请求失败
      eventListener.callFailed(this, result!!)
    } else {
      // 通知 event Listener 请求结束
      eventListener.callEnd(this)
    }
    return result
  }

上面代码会调用 releaseConnectionNoEvents() 方法来释放 RealCallConnection 的引用,如果返回的 Socket 不为空就表示链接需要手动释放。我们再来看看它的源码实现:

Kotlin 复制代码
  internal fun releaseConnectionNoEvents(): Socket? {
    val connection = this.connection!!
    connection.assertThreadHoldsLock()

    val calls = connection.calls
    val index = calls.indexOfFirst { it.get() == this@RealCall }
    check(index != -1)
    // 将当前 RealCall 从 RealConnection 的引用中移除
    calls.removeAt(index)
    // 当前 RealCall 停止对 Connection 引用
    this.connection = null
    // 如果 RealConnection 中的引用 RealCall 变成了空,就表示它已经闲置了
    if (calls.isEmpty()) {
      connection.idleAtNs = System.nanoTime()
      // 通知 ConnectionPool,当前 connection 已经闲置。  
      if (connectionPool.connectionBecameIdle(connection)) {
        // 需要把当前链接真正关闭。  
        return connection.socket()
      }
    }
    // 不需要真正关闭链接
    return null
  }

首先将 RealConnection#calls 中的引用移除,然后判断 RealConnection#calls 是否为空,如果已经为空了,就表示当前 RealConnection 已经闲置了,然后通知 ConnectionPool#connectionBecameIdle(),如果这个方法返回的 Socket 不为空就表示需要释放。这个和 Request HeaderResponse Header 有关,如果他们指定了 Connection: close,就表示不复用链接,然后 RealConnection#noNewExchanges 对象就会设置为 true (这部分可以看看 CallServerInterceptor),然后上面的 Socket 对象就不会为空。

我们接着看看 RealConnectionPool#connectionBecameIdle() 方法:

Kotlin 复制代码
  fun connectionBecameIdle(connection: RealConnection): Boolean {
    connection.assertThreadHoldsLock()

    return if (connection.noNewExchanges || maxIdleConnections == 0) {
      // 需要关闭链接
      connection.noNewExchanges = true
      connections.remove(connection)
      if (connections.isEmpty()) cleanupQueue.cancelAll()
      true
    } else {
      // 不需要关闭,开启一个新的清除任务 Task。
      cleanupQueue.schedule(cleanupTask)
      false
    }
  }

上面的方法我在第二篇文章中也分析过,也比较简单。

这里的话请求结束的相关逻辑也就分析完了,可以简单的做一个总结:当请求的 Response 成功返回时请求并没有完成,需要等待 Response Body 处理完成(读取完成或者出现异常),如果你不需要 Response Body 里面的数据可以手动把它关闭。通过以上的操作就可以把 RealCallRealConnection 中的引用移除。如果没有做上面的操作就可能出现 RealCall 引用泄漏的问题,如果出现引用的泄漏问题,OkHttp 也能够探测到,RealConnectionPool 中会有一个探测闲置 ConnectioncleanupTask(第二篇文章中有详细分析),他会调用以下的方法:

Kotlin 复制代码
  private fun pruneAndGetAllocationCount(connection: RealConnection, now: Long): Int {
    connection.assertThreadHoldsLock()

    val references = connection.calls
    var i = 0
    while (i < references.size) {
      val reference = references[i]
      // 引用不为空就表示没有泄漏
      if (reference.get() != null) {
        i++
        continue
      }
     
      // 后续就表示泄漏了
      // We've discovered a leaked call. This is an application bug.
      val callReference = reference as CallReference
      val message = "A connection to ${connection.route().address.url} was leaked. " +
          "Did you forget to close a response body?"
      Platform.get().logCloseableLeak(message, callReference.callStackTrace)
      // 主动移除泄漏的 RealCall
      references.removeAt(i)
      connection.noNewExchanges = true

      // If this was the last allocation, the connection is eligible for immediate eviction.
      if (references.isEmpty()) {
        connection.idleAtNs = now - keepAliveDurationNs
        return 0
      }
    }

    return references.size
  }

通过上面的方法就能够探测到泄漏问题。

最后

通过五篇文章终于讲完了所有的 OkHttp,如果你有耐心能够看完的话,相信你对 OkHttp 有一个全新的理解。

相关推荐
工业甲酰苯胺44 分钟前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3431 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee2 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯3 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey4 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!6 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟7 小时前
Android音频采集
android·音视频
小白也想学C8 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程8 小时前
初级数据结构——树
android·java·数据结构
闲暇部落10 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin