okhttp-process

OkHttp利用Socket构建HTTP请求报文和接收响应的整体流程如上图所示(同步请求)。

关于Interceptors的责任链模式就不多说了,网络上有很多相关的文章,这里聊一聊主要的处理流程。

先说结论:

  1. 最主要的两个Interceptor是ConnectInterceptorCallServerInterceptor
  2. ConnectInterceptor用来创建Socket,并建立Socket连接;
  3. CallServerInterceptor用来构造HTTP 请求报文和解析HTTP响应报文;

从OkHttpClient到RealInterceptorChain

先看看一般的调用方式:

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

如果跟踪源码的调用,会发现最终会调用到RealCall中的一段代码,用于返回response:

val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors
    interceptors += RetryAndFollowUpInterceptor(client)
    interceptors += BridgeInterceptor(client.cookieJar)
    interceptors += CacheInterceptor(client.cache)
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)

    val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
        client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)

val response = chain.proceed(originalRequest)

从这里可以看到,代码添加了很多Interceptor,然后通过RealInterceptorChain来实现责任链模式,多个Interceptor依次处理。

现在可以看看最主要的两个Interceptor了。

ConnectInterceptor

ConnectInterceptor的源码比较简单,但是包含的内容却是不少:

object ConnectInterceptor : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val request = realChain.request()
    val transmitter = realChain.transmitter()

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    val doExtensiveHealthChecks = request.method != "GET"
    val exchange = transmitter.newExchange(chain, doExtensiveHealthChecks)

    return realChain.proceed(request, transmitter, exchange)
  }
}

可以看到其中有两个比较有意思的对象:

  • Transmitter
  • Exchange

Transmitter

Transmitter是OkHttp的应用层与网络层之间的桥梁,这么说比较抽象,通俗的说是下面这个过程:

  1. 我们请求接口使用的是url,比如https://jiangkang.tech,这就是应用层的信息;
  2. 网络层需要的信息是host,端口号,使用的网络协议等信息,如host为jiangkang.tech,端口号是443,协议是https;在OkHttp中网络层中的这些信息封装成了Address

Transmitter主要功能之一就是从应用层信息转换成网络成信息,即从url转换成Address:

  private fun createAddress(url: HttpUrl): Address {
    var sslSocketFactory: SSLSocketFactory? = null
    var hostnameVerifier: HostnameVerifier? = null
    var certificatePinner: CertificatePinner? = null
    if (url.isHttps) {
      sslSocketFactory = client.sslSocketFactory
      hostnameVerifier = client.hostnameVerifier
      certificatePinner = client.certificatePinner
    }

    return Address(url.host, url.port, client.dns, client.socketFactory,
        sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator,
        client.proxy, client.protocols, client.connectionSpecs, client.proxySelector)
  }

Transmitter第二个主要的功能就是创建一个Exchange对象.

Exchange本身并不处理具体的编解码,而是代理给ExchangeCodec,是ExchangeCodec对request数据进行编码,对response数据进行解码的。

  internal fun newExchange(chain: Interceptor.Chain, doExtensiveHealthChecks: Boolean): Exchange {

    val codec = exchangeFinder!!.find(client, chain, doExtensiveHealthChecks)
    val result = Exchange(this, call, eventListener, exchangeFinder!!, codec)

  }

ConnectInterceptor中创建Exchange的过程会先利用ExchangeFinder对应去找到对应的ExchangeCodec实现类。

OkHttp提供了两种实现类:

  • Http1ExchangeCodec 对基于HTTP 1.x的请求进行编解码
  • Http2ExchangeCodec 对基于HTTP 2.0的请求进行编解码

后面会具体讨论这两个类。

在find方法中,会创建一个RealConnection对象,然后执行它的connect()方法,这个方法中会根据不同平台(Java和Android)创建对应的socket,并建立socket连接。

在后面的Interceptor中,可以获取到RealConnection对象,而RealConnection持有了socket,故而后续流程可以继续处理这里创建的socket。

向Socket中写入HTTP请求报文

这个过程是在CallServerInterceptor中利用Exchange实现的:

  1. exchange.writeRequestHeaders(request)
  2. exchange.flushRequest()
  3. exchange.responseHeadersStart()
  4. exchange.readResponseHeaders()
  5. exchange.createRequestBody(request, true)
  6. requestBody.writeTo(bufferedRequestBody)
  7. exchange.responseHeadersStart()
  8. responseBuilder = exchange.readResponseHeaders(false)
response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build()

从代码上看,过程很直观,创建HTTP请求,写报文,读响应header和body构建response。

而实际上Exchange本身并不处理这些,都是委托给对应的ExchangeCodec去执行的。那么为什么不直接在Exchange中处理呢?答案有两个:

  1. Exchange中添加了EventListener钩子回调,用来监听请求的过程;
  2. Exchange中可以根据协议不同选择不同的编解码实现;

随便找一个方法就可以看出来了:

  fun writeRequestHeaders(request: Request) {
    try {
      eventListener.requestHeadersStart(call)
      codec.writeRequestHeaders(request)
      eventListener.requestHeadersEnd(call, request)
    } catch (e: IOException) {
      eventListener.requestFailed(call, e)
      trackFailure(e)
      throw e
    }
  }

这里以HTTP 1.x协议的编解码为例,看看具体的编解码过程:

写HTTP报文(编码)

  override fun writeRequestHeaders(request: Request) {
    val requestLine = RequestLine.get(
        request, realConnection!!.route().proxy.type())
    writeRequest(request.headers, requestLine)
  }

  fun writeRequest(headers: Headers, requestLine: String) {
    check(state == STATE_IDLE) { "state: $state" }
    sink.writeUtf8(requestLine).writeUtf8("\r\n")
    for (i in 0 until headers.size) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n")
    }
    sink.writeUtf8("\r\n")
    state = STATE_OPEN_REQUEST_BODY
  }

报文如下:

GET / HTTP/2
Host: jiangkang.tech
User-Agent: curl/7.64.1
Accept: `*/*`

上面写了协议和请求headers,再来看下请求体的写入:

  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!")
    }
  }

 // 写入requestbody报文
 val bufferedRequestBody = 
 exchange.createRequestBody(request, true).buffer()
 requestBody.writeTo(bufferedRequestBody)

这里会创建对应请求类型的Sink,向其中写入信息。

解析HTTP response

还是先读取响应头:

  private fun readHeaderLine(): String {
    val line = source.readUtf8LineStrict(headerLimit)
    headerLimit -= line.length.toLong()
    return line
  }

 override fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
    check(state == STATE_OPEN_REQUEST_BODY || state == STATE_READ_RESPONSE_HEADERS) {
      "state: $state"
    }

    try {
      val statusLine = StatusLine.parse(readHeaderLine())

      val responseBuilder = Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          .headers(readHeaders())

      return when {
        expectContinue && statusLine.code == HTTP_CONTINUE -> {
          null
        }
        statusLine.code == HTTP_CONTINUE -> {
          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 = realConnection?.route()?.address?.url?.redact() ?: "unknown"
      throw IOException("unexpected end of stream on $address", e)
    }
  }


一般的响应头如下:

 HTTP/2 200
 content-language: en-US
 content-type: text/html;charset=UTF-8
 date: Tue, 29 Oct 2019 14:28:38 GMT
 server: Caddy

上面的代码就是解析这个报文,然后封装到responseBuilder对象中。

在来看下responsebody的解码过程:

  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) {
          newFixedLengthSource(contentLength)
        } else {
          newUnknownLengthSource()
        }
      }
    }
  }

  fun openResponseBody(response: Response): ResponseBody {
    try {
      eventListener.responseBodyStart(call)
      val contentType = response.header("Content-Type")
      val contentLength = codec.reportedContentLength(response)
      val rawSource = codec.openResponseBodySource(response)
      val source = ResponseBodySource(rawSource, contentLength)
      return RealResponseBody(contentType, contentLength, source.buffer())
    } catch (e: IOException) {
      eventListener.responseFailed(call, e)
      trackFailure(e)
      throw e
    }
  }

还是选择对应的Source实现类,从中解码出对应信息,最后封装多ResponseBody对象中。

总结

OkHttp本质上还是利用Socket进行Http请求的。流程如下:

  1. 创建Socket,并建立连接,即创建RealConnection;
  2. 向socket流中写入http报文,并解析http响应数据封装到response中并返回;