不一样的Okhttp解析

533 阅读17分钟

OkHttp

Square公司的一个处理网络请求的开源项目,是目前Android使用最广泛的网络框架。从Android 4.4开始httpURLConnection的底层实现的Okhttp。

  • 支持Http/2并允许对统一主机的所有请求共享一个套接字
  • 通过连接池,减少了请求延迟
  • 默认通过GZip压缩数据
  • 响应缓存,避免了重复请求的网络
  • 请求失败自动充实主机的其他ip,自动重定向

OkHttp从4.0开始采用Kotlin语言编写

OkHttp使用流程

首先先列举出Okhttp中重要的类

说明
OkHttpClient客户端对象
Request访问请求类,用于创建get、post等请求
RequestBody请求数据类,在post、put、delete请求中用到
Response网络请求的响应类
MediaType数据类型,用来表明数据是json,image,pdf等一系列格式
Call请求接口
Call.execute()同步的请求方法
Call.enqueue(Callback)异步的请求方法,不能在此进行UI更新操作

1.创建一个OkHttpClient客户端

方式一: 直接调用OkHttpClient构造函数的方式

OkHttpClient client = new OkHttpClient();

方式二: 调用OkHttpClient的Builder构建自定义参数的方式, 可以配置读写超时, 添加自定义拦截器, 配置缓冲等等

//采用建造者模式
client = new OkHttpClient.Builder()
      .connectTimeout(10 * 1000, TimeUnit.MICROSECONDS) //连接超时阈值
      .callTimeout(10 * 1000, TimeUnit.MILLISECONDS)  //请求超时阈值
      .writeTimeout(10 * 1000, TimeUnit.MILLISECONDS) //写超时阈值
      .readTimeout(10 * 1000, TimeUnit.MICROSECONDS)  //读超时阈值
      .retryOnConnectionFailure(true) //失败重试
      .cache(new Cache(new File(context.getCacheDir(), "OkHttpCache"), 10 * 1024 * 1024))//添加缓存
      .addInterceptor(new LoggingInterceptor()) //Log日志拦截器
      .addInterceptor(new CacheInterceptor())   //Cache缓存拦截器
      .build();

2.创建一个请求

  • 创建get请求
Request request = new Request.Builder()
                  .url(url)
                  .get()
                  .addHeader("token", "") //添加header
                  .build();
  • 创建post请求
//创建MediaType
MediaType JSON = MediaType.get("application/json; charset=utf-8");
//创建一个RequestBody
RequestBody body = RequestBody.create("json字符串", JSON);
//创建请求
Request request = new Request.Builder()
                .url(url)
                .post(body)
                .addHeader("token", "") //添加header
                .build();
  • OkHttp还支持putdeletepatch请求类型,与post类型无多大差异,此处不讨论了

3.创建一个Call

Call call = client.newCall(request);

4.发起请求

发起一个同步请求

Response response = call.execute();

发起一个异步请求

//发起异步请求
call.enqueue(new Callback() {
    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {
​
    }
​
    @Override
    public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
    
    }
});

5.获取请求响应

response.body();

OkHttp源码剖析

OkHttp内部执行流程大致如下图所示:

OkHttp经过传OkHttpClient.Builder() 参数的构造函数创建出OkHttpClient,同时Request经过传Request.Builder() 参数的构造函数创建出Request,然后OkHttpClient客户端通过newCall方法,传入Request请求实例,返回一个Call实例,Call在调用executeenqueue方法后,底层经过了分发器拦截器后完成整个请求,并返回了Response

OkHttpClient

OkhttpClient采用Builder设计模式,其内部管理了一个Builder类, 其内部维护了HTTP请求过程中大量的配置参数,如下:

  class Builder constructor() {
    //分发器
    internal var dispatcher: Dispatcher = Dispatcher()
    //
    internal var connectionPool: ConnectionPool = ConnectionPool()
    internal val interceptors: MutableList<Interceptor> = mutableListOf()
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
    internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
    internal var retryOnConnectionFailure = true
    internal var authenticator: Authenticator = Authenticator.NONE
    internal var followRedirects = true
    internal var followSslRedirects = true
    internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
    internal var cache: Cache? = null
    internal var dns: Dns = Dns.SYSTEM
    internal var proxy: Proxy? = null
    internal var proxySelector: ProxySelector? = null
    internal var proxyAuthenticator: Authenticator = Authenticator.NONE
    internal var socketFactory: SocketFactory = SocketFactory.getDefault()
    internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
    internal var x509TrustManagerOrNull: X509TrustManager? = null
    internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
    internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
    internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
    internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
    internal var certificateChainCleaner: CertificateChainCleaner? = null
    internal var callTimeout = 0
    internal var connectTimeout = 10_000
    internal var readTimeout = 10_000
    internal var writeTimeout = 10_000
    internal var pingInterval = 0
    internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
    internal var routeDatabase: RouteDatabase? = null
​
    internal constructor(okHttpClient: OkHttpClient) : this() {
      this.dispatcher = okHttpClient.dispatcher
      this.connectionPool = okHttpClient.connectionPool
      this.interceptors += okHttpClient.interceptors
      this.networkInterceptors += okHttpClient.networkInterceptors
      this.eventListenerFactory = okHttpClient.eventListenerFactory
      this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure
      this.authenticator = okHttpClient.authenticator
      this.followRedirects = okHttpClient.followRedirects
      this.followSslRedirects = okHttpClient.followSslRedirects
      this.cookieJar = okHttpClient.cookieJar
      this.cache = okHttpClient.cache
      this.dns = okHttpClient.dns
      this.proxy = okHttpClient.proxy
      this.proxySelector = okHttpClient.proxySelector
      this.proxyAuthenticator = okHttpClient.proxyAuthenticator
      this.socketFactory = okHttpClient.socketFactory
      this.sslSocketFactoryOrNull = okHttpClient.sslSocketFactoryOrNull
      this.x509TrustManagerOrNull = okHttpClient.x509TrustManager
      this.connectionSpecs = okHttpClient.connectionSpecs
      this.protocols = okHttpClient.protocols
      this.hostnameVerifier = okHttpClient.hostnameVerifier
      this.certificatePinner = okHttpClient.certificatePinner
      this.certificateChainCleaner = okHttpClient.certificateChainCleaner
      this.callTimeout = okHttpClient.callTimeoutMillis
      this.connectTimeout = okHttpClient.connectTimeoutMillis
      this.readTimeout = okHttpClient.readTimeoutMillis
      this.writeTimeout = okHttpClient.writeTimeoutMillis
      this.pingInterval = okHttpClient.pingIntervalMillis
      this.minWebSocketMessageToCompress = okHttpClient.minWebSocketMessageToCompress
      this.routeDatabase = okHttpClient.routeDatabase
    }
​
    /**
     * Sets the dispatcher used to set policy and execute asynchronous requests. Must not be null.
     */
    fun dispatcher(dispatcher: Dispatcher) = apply {
      this.dispatcher = dispatcher
    }
    
    .....
​
    fun build(): OkHttpClient = OkHttpClient(this)
  }
​

Request

Request request = new Request.Builder()
                  .url(url)
                  .get()
                  .addHeader("token", "") //添加header
                  .build();

Request采用Builder设计模式,其内部管理了一个Builder类, 其内部维护了四个变量,

  • 请求url
  • 请求方法method
  • 请求头Headers.Builder
  • 请求体RequestBody

同时,还提供了各种快捷方法

方法说明
Builder()默认构造函数,默认method为Get
Builder(request: Request)带Request的构造方法
url(url: HttpUrl)配置url
url(url: String)配置url
url(url: URL)配置url
header(name: String, value: String)配置header
addHeader(name: String, value: String)配置header
removeHeader(name: String)配置header
headers(headers: Headers)配置header
cacheControl(cacheControl: CacheControl)配置缓存控制
get()get请求
head()
post(body: RequestBody)post请求
delete(body: RequestBody? = EMPTY_REQUEST)delete请求
put(body: RequestBody)put请求
patch(body: RequestBody)patch请求
method(method: String, body: RequestBody?)method方法
tag(tag: Any?)配置Tag
tag(type: Class, tag: T?)配置Tag
build()构建实例

如下:

  open class Builder {
    internal var url: HttpUrl? = null
    internal var method: String
    internal var headers: Headers.Builder
    internal var body: RequestBody? = null
​
    /** A mutable map of tags, or an immutable empty map if we don't have any. */
    internal var tags: MutableMap<Class<*>, Any> = mutableMapOf()
​
    constructor() {
      this.method = "GET"
      this.headers = Headers.Builder()
    }
​
    internal constructor(request: Request) {
      this.url = request.url
      this.method = request.method
      this.body = request.body
      this.tags = if (request.tags.isEmpty()) {
        mutableMapOf()
      } else {
        request.tags.toMutableMap()
      }
      this.headers = request.headers.newBuilder()
    }
​
    open fun url(url: HttpUrl): Builder = apply {
      this.url = url
    }
​
    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if [url] is not a valid HTTP or HTTPS URL. Avoid this
     *     exception by calling [HttpUrl.parse]; it returns null for invalid URLs.
     */
    open fun url(url: String): Builder {
      // Silently replace web socket URLs with HTTP URLs.
      val finalUrl: String = when {
        url.startsWith("ws:", ignoreCase = true) -> {
          "http:${url.substring(3)}"
        }
        url.startsWith("wss:", ignoreCase = true) -> {
          "https:${url.substring(4)}"
        }
        else -> url
      }
​
      return url(finalUrl.toHttpUrl())
    }
​
    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if the scheme of [url] is not `http` or `https`.
     */
    open fun url(url: URL) = url(url.toString().toHttpUrl())
​
    /**
     * Sets the header named [name] to [value]. If this request already has any headers
     * with that name, they are all replaced.
     */
    open fun header(name: String, value: String) = apply {
      headers[name] = value
    }
​
    /**
     * Adds a header with [name] and [value]. Prefer this method for multiply-valued
     * headers like "Cookie".
     *
     * Note that for some headers including `Content-Length` and `Content-Encoding`,
     * OkHttp may replace [value] with a header derived from the request body.
     */
    open fun addHeader(name: String, value: String) = apply {
      headers.add(name, value)
    }
​
    /** Removes all headers named [name] on this builder. */
    open fun removeHeader(name: String) = apply {
      headers.removeAll(name)
    }
​
    /** Removes all headers on this builder and adds [headers]. */
    open fun headers(headers: Headers) = apply {
      this.headers = headers.newBuilder()
    }
​
    /**
     * Sets this request's `Cache-Control` header, replacing any cache control headers already
     * present. If [cacheControl] doesn't define any directives, this clears this request's
     * cache-control headers.
     */
    open fun cacheControl(cacheControl: CacheControl): Builder {
      val value = cacheControl.toString()
      return when {
        value.isEmpty() -> removeHeader("Cache-Control")
        else -> header("Cache-Control", value)
      }
    }
​
    open fun get() = method("GET", null)
​
    open fun head() = method("HEAD", null)
​
    open fun post(body: RequestBody) = method("POST", body)
​
    @JvmOverloads
    open fun delete(body: RequestBody? = EMPTY_REQUEST) = method("DELETE", body)
​
    open fun put(body: RequestBody) = method("PUT", body)
​
    open fun patch(body: RequestBody) = method("PATCH", body)
​
    open fun method(method: String, body: RequestBody?): Builder = apply {
      require(method.isNotEmpty()) {
        "method.isEmpty() == true"
      }
      if (body == null) {
        require(!HttpMethod.requiresRequestBody(method)) {
          "method $method must have a request body."
        }
      } else {
        require(HttpMethod.permitsRequestBody(method)) {
          "method $method must not have a request body."
        }
      }
      this.method = method
      this.body = body
    }
​
    /** Attaches [tag] to the request using `Object.class` as a key. */
    open fun tag(tag: Any?): Builder = tag(Any::class.java, tag)
​
    /**
     * Attaches [tag] to the request using [type] as a key. Tags can be read from a
     * request using [Request.tag]. Use null to remove any existing tag assigned for [type].
     *
     * Use this API to attach timing, debugging, or other application data to a request so that
     * you may read it in interceptors, event listeners, or callbacks.
     */
    open fun <T> tag(type: Class<in T>, tag: T?) = apply {
      if (tag == null) {
        tags.remove(type)
      } else {
        if (tags.isEmpty()) {
          tags = mutableMapOf()
        }
        tags[type] = type.cast(tag)!! // Force-unwrap due to lack of contracts on Class#cast()
      }
    }
​
    open fun build(): Request {
      return Request(
          checkNotNull(url) { "url == null" },
          method,
          headers.build(),
          body,
          tags.toImmutableMap()
      )
    }
  }
​

Call

Call里定义了整个请求所需要的接口

interface Call : Cloneable {
  //返回Request
  fun request(): Request
  //同步请求
  @Throws(IOException::class)
  fun execute(): Response
  //异步请求
  fun enqueue(responseCallback: Callback)
  //取消请求
  fun cancel()
  //是否已经发送了请求
  fun isExecuted(): Boolean
  //是否取消了请求
  fun isCanceled(): Boolean
   //超时
  fun timeout(): Timeout

  public override fun clone(): Call

  fun interface Factory {
    fun newCall(request: Request): Call
  }
}

Call的具体实现类是RealCall, RealCall主要属性及方法如下

  • 构造函数

    class RealCall(
      val client: OkHttpClient,
      val originalRequest: Request,
      val forWebSocket: Boolean
    ) : Call
    
  • 主要属性

      //连接池
      private val connectionPool: RealConnectionPool = client.connectionPool.delegate
      //Event监听器
      internal val eventListener: EventListener = client.eventListenerFactory.create(this)
    
      private val timeout = object : AsyncTimeout() {
        override fun timedOut() {
          cancel()
        }
      }.apply {
        timeout(client.callTimeoutMillis.toLong(), MILLISECONDS)
      }
    
      private var callStackTrace: Any? = null
    
      private var exchangeFinder: ExchangeFinder? = null
    
      var connection: RealConnection? = null
        private set
      private var timeoutEarlyExit = false
    
      internal var interceptorScopedExchange: Exchange? = null
        private set
    
      private var requestBodyOpen = false
    
      private var responseBodyOpen = false
    
      private var expectMoreExchanges = true
    
    
      @Volatile private var canceled = false
      @Volatile private var exchange: Exchange? = null
      @Volatile var connectionToCancel: RealConnection? = null
    
  • 重写函数Request()

      override fun request(): Request = originalRequest //构造函数里传入
    
  • 重写函数execute()

      override fun execute(): Response {
        check(executed.compareAndSet(false, true)) { "Already Executed" }
    
        timeout.enter()
        callStart()
        try {
          //调用Diapatcher,runningSyncCalls队列里添加任务,可见分发器里的同步请求
          client.dispatcher.executed(this)
          //发起请求,在Okhttp中,不管是同步请求,还是异步请求,最终都会调用此方法发起请求
          return getResponseWithInterceptorChain()
        } finally {
          client.dispatcher.finished(this)
        }
      }
    
  • 重写函数enqueue(responseCallback: Callback)

     override fun enqueue(responseCallback: Callback) {
        check(executed.compareAndSet(false, true)) { "Already Executed" }
    
        callStart()
        //调用Diapatcher
        client.dispatcher.enqueue(AsyncCall(responseCallback))
      }
    
  • 重写函数cancel()

      override fun cancel() {
        if (canceled) return // Already canceled.
    
        canceled = true
        exchange?.cancel()
        connectionToCancel?.cancel()
    
        eventListener.canceled(this)
      }
    
  • 重写函数isExecuted()

      override fun isExecuted(): Boolean = executed.get()
    
  • 重写函数isCanceled()

      override fun isCanceled() = canceled
    
  • 重写函数timeout()

      private val timeout = object : AsyncTimeout() {
        override fun timedOut() {
          cancel()
        }
      }.apply {
        timeout(client.callTimeoutMillis.toLong(), MILLISECONDS)
      }
    
      override fun timeout() = timeout
    
  • 内部类AsyncCall

    internal inner class AsyncCall(
      private val responseCallback: Callback
    ) : Runnable {
      
      //同一Host的连接计数器
      @Volatile var callsPerHost = AtomicInteger(0)
        private set
      
      //设置计数器
      fun reuseCallsPerHostFrom(other: AsyncCall) {
        this.callsPerHost = other.callsPerHost
      }
      
      //请求的域名
      val host: String
        get() = originalRequest.url.host
      
      //请求Request
      val request: Request
          get() = originalRequest
      
      //Call
      val call: RealCall
          get() = this@RealCall
    
      /**
       * Attempt to enqueue this async call on [executorService]. This will attempt to clean up
       * if the executor has been shut down by reporting the call as failed.
       * 
       * 
       */
      fun executeOn(executorService: ExecutorService) {
        client.dispatcher.assertThreadDoesntHoldLock()
    
        var success = false
        try {
          //线程池执行任务
          executorService.execute(this)
          success = true
        } catch (e: RejectedExecutionException) {
          val ioException = InterruptedIOException("executor rejected")
          ioException.initCause(e)
          noMoreExchanges(ioException)
          responseCallback.onFailure(this@RealCall, ioException)
        } finally {
          if (!success) {
            client.dispatcher.finished(this) // This call is no longer running!
          }
        }
      }
    

Okhttp分发器

Dispatcher 分发器就是来调配请求任务的,内部会包含一个线程池,管理线程资源和分配。可以在创建 OkHttpClient 时,传递我们 自己定义的线程池来创建分发器。其主要属性有

  //异步请求同时存在的最大请求数,注意是异步不包括同步的。
  @get:Synchronized var maxRequests = 64
    set(maxRequests) {
      require(maxRequests >= 1) { "max < 1: $maxRequests" }
      synchronized(this) {
        field = maxRequests
      }
      promoteAndExecute()
    }

  //异步请求同一域名同时存在的最大请求数,注意是异步不包括同步的。
  @get:Synchronized var maxRequestsPerHost = 5
    set(maxRequestsPerHost) {
      require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
      synchronized(this) {
        field = maxRequestsPerHost
      }
      promoteAndExecute()
    }

  private var executorServiceOrNull: ExecutorService? = null
  
  //异步请求使用的线程池
  @get:Synchronized
  @get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        //corePoolSize = 0
        //maximumPoolSize = Int.MAX_VALUE
        //keepAliveTime = 60s
        //workQueue = SynchronousQueue()
        //非守护线程
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }

  //闲置任务(没有请求时候可执行一些任务,由使用者设置)
  @set:Synchronized
  @get:Synchronized
  var idleCallback: Runnable? = null

  //异步请求等待执行的队列
  /** Ready async calls in the order they'll be run. */
  private val readyAsyncCalls = ArrayDeque<AsyncCall>()

  //异步请求正在执行队列
  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()

  //同步请求正在执行的队列
  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private val runningSyncCalls = ArrayDeque<RealCall>()
  • 同步请求

    /** Used by [Call.execute] to signal it is in-flight. */
      @Synchronized internal fun executed(call: RealCall) {
         //直接把Call请求加入同步队列
         runningSyncCalls.add(call)
      }
    
  • 异步请求

      internal fun enqueue(call: AsyncCall) {
        synchronized(this) {
          //1.将AsyncCall加入等待执行队列
          readyAsyncCalls.add(call)
    
          // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
          // the same host.
          if (!call.call.forWebSocket) {
            //查找是否已经存在至少一个相同Host的AsyncCall对象,并且返回最先查到的AsyncCall
            val existingCall = findExistingCallWithHost(call.host)
            //如果已经存在,就把之前AsyncCall对象的计数器也设置给当前的AsyncCall对象
            if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
          }
        }
        //3.
        promoteAndExecute()
      }
    
      //
      private fun promoteAndExecute(): Boolean {
        this.assertThreadDoesntHoldLock()
    	//创建空的可执行AsyncCall集合
        val executableCalls = mutableListOf<AsyncCall>()
        //是否运行状态
        val isRunning: Boolean
        //同步锁
        synchronized(this) {
          //对异步请求正在执行队列进行迭代循环
          val i = readyAsyncCalls.iterator()
          while (i.hasNext()) {
            //拿到一个asyncCall
            val asyncCall = i.next()
            //如果正在请求的队列数量大于最大请求数,如果是就跳出迭代
            if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
            //判断同一Host的连接计数器的值是否大于maxRequestsPerHost,如果是就跳出迭代循环
            if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
    	    //从readyAsyncCalls移除
            i.remove()
            //同一Host的连接计数器自增1
            asyncCall.callsPerHost.incrementAndGet()
            //添加到可执行集合中
            executableCalls.add(asyncCall)
            //添加到正在执行的任务队列中====该asyncCall对象已经是被当作执行中状态的了
            runningAsyncCalls.add(asyncCall)
          }
          //fun runningCallsCount(): Int = runningAsyncCalls.size + runningSyncCalls.size
          //如果正在执行Call数量>0,isRunning为真
          isRunning = runningCallsCount() > 0
        }
    	//遍历可执行集合
        for (i in 0 until executableCalls.size) {
          val asyncCall = executableCalls[i]
          //调用asyncCall.executeOn方法
          asyncCall.executeOn(executorService)
        }
        return isRunning
      }
    
      //查找是否已经存在至少一个相同Host的AsyncCall对象,并且返回最先查到的AsyncCall
      private fun findExistingCallWithHost(host: String): AsyncCall? {
        for (existingCall in runningAsyncCalls) {
          if (existingCall.host == host) return existingCall
        }
        for (existingCall in readyAsyncCalls) {
          if (existingCall.host == host) return existingCall
        }
        return null
      }
    
  • finished

    当同步或者异步网络请求结束之后,会将该call从对应的队列中删除

    /** Used by [AsyncCall.run] to signal completion. */
    internal fun finished(call: AsyncCall) {
      //同一Host连接计数器递减 1
      call.callsPerHost.decrementAndGet()
      finished(runningAsyncCalls, call)
    }
    
    /** Used by [Call.execute] to signal completion. */
    internal fun finished(call: RealCall) {
      finished(runningSyncCalls, call)
    }
    
    private fun <T> finished(calls: Deque<T>, call: T) {
      val idleCallback: Runnable?
      synchronized(this) {
        //从指定队列中移除call
        if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
        idleCallback = this.idleCallback
      }
    
      val isRunning = promoteAndExecute()
    
      if (!isRunning && idleCallback != null) {
        idleCallback.run()
      }
    }
    

Okhttp拦截器

拦截器采用了责任链设计模式,这样的好处是将请求的发送和处理分开,而且能够动态添加中间的处理方实现对请求的处理等操作

Okhttp 五大拦截器

  • RetryAndFollowUpInterceptor

    重试及重定向拦截器在交给下一个拦截器之前,负责判断用户是否取消了请求;

    在获得了结果之后,会根据响应码判断是否需要重定向,如果满足条件那么就会重启执行所有拦截器。

  • BridgeInterceptor

    桥接拦截器在交给下一个拦截器之前,负责将HTTP协议必备的请求头加入其中,比如Host,并添加一些默认的行为,比如GZIP压缩;

    在获得了结果后,调用保存cookie接口并解析GZIP数据。

  • CacheInterceptor

    缓存拦截器在交给下一个拦截器之前,读取并判断是否使用缓存;

    获得结果后判断是否使用缓存。

  • ConnectInterceptor

    连接拦截器在交给下一个拦截器之前,负责找到或者新建一个连接,并获得相应的socket流;

    在获得结果后不进行额外处理

  • CallServerInterceptor

    请求服务器拦截器进行真正的与服务器的通信,向服务器发送数据,解析读取的响应数据

OkHttp 五大拦截器完成整个http请求的流程如下:

拦截器

  • 在OKHttp中,我们发出的请求并不是直接连接到服务器获取结果,而是由Okhttp中拦截器截获我们发出的请求,对请求进行观察、修改等操作,然后返回结果
  • 在OkHttp中,Interceptor是一个接口,如下,它定义了intercept(Chain)函数来实现拦截功能,Chain接口定义了一系列方法,其中最重要的是proceed函数
fun interface Interceptor{
  @Throws(IOException::class)
  fun intercept(chain: Chain): Response

  companion object {

    inline operator fun invoke(crossinline block: (chain: Chain) -> Response):Interceptor = Interceptor { block(it) }
  }

  interface Chain {
    fun request(): Request

    @Throws(IOException::class)
    fun proceed(request: Request): Response

    fun connection(): Connection?

    fun call(): Call

    fun connectTimeoutMillis(): Int

    fun withConnectTimeout(timeout: Int, unit: TimeUnit): Chain

    fun readTimeoutMillis(): Int

    fun withReadTimeout(timeout: Int, unit: TimeUnit): Chain

    fun writeTimeoutMillis(): Int

    fun withWriteTimeout(timeout: Int, unit: TimeUnit): Chain
  }
}

当拦截器链开始工作时,取出第一个拦截器,并实例化一个新的拦截器,执行intercept() 方法,如果拦截器需要下一个拦截器获取响应,那就通过参数拿到拦截器链再次执行proceed() 方法,这里取出第二个拦截器,并继续调用该拦截器的 intercept方法,并以此类推,直到获取响应。

拦截器接口的基本使用套路如下:

//自定义一个Logging拦截器
public class LoggingInterceptor implements Interceptor {

    @NonNull
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        
        //这个chain里面包含了request和response,所以你要什么都可以从这里拿
        //1. 获取Request
        Request request = chain.request();

        //1.x 处理Request
        long t1 = System.nanoTime();//请求发起的时间
        Log.i("Leo", String.format("发送请求 %s on %s%n%s", request.url(), chain.connection(), request.headers()));

        //2. 获取Response
        Response response = chain.proceed(request);

        //2.x 处理Response
        long t2 = System.nanoTime();//收到响应的时间
        //这里不能直接使用response.body().string()的方式输出日志
        //因为response.body().string()之后,response中的流会被关闭,程序会报错,我们需要创建出一
        //个新的response给应用层处理
        ResponseBody responseBody = response.peekBody(1024 * 1024);
        Log.i("Leo", String.format("接收响应: [%s] %n返回json:【%s】 %.1fms%n%s",
                response.request().url(),
                responseBody.string(),
                (t2 - t1) / 1e6d,
                response.headers()));
        
        //3.返回Response
        return response;
    }
}

在讲五大拦截器之前,先介绍下接口Chain的具体实现类:RealInterceptorChain,如下

//实例化位置 RealCall->getResponseWithInterceptorChain->
//val chain = RealInterceptorChain(
//        call = this,
//        interceptors = interceptors,
//        index = 0,
//        exchange = null,
//        request = originalRequest,
//        connectTimeoutMillis = client.connectTimeoutMillis,
//        readTimeoutMillis = client.readTimeoutMillis,
//        writeTimeoutMillis = client.writeTimeoutMillis
    )
class RealInterceptorChain(
  internal val call: RealCall,
  private val interceptors: List<Interceptor>, //拦截器列表,装载了所有拦截器,其中五大拦截器在后面
  private val index: Int, //拦截器列表索引
  internal val exchange: Exchange?, 
  internal val request: Request, //封装的请求对象
  internal val connectTimeoutMillis: Int, //连接超时时间
  internal val readTimeoutMillis: Int, //读超时时间
  internal val writeTimeoutMillis: Int //写超时时间
) : Interceptor.Chain {
  //proceed方法执行次数
  private var calls: Int = 0

  //copy一份实例
  internal fun copy(
    index: Int = this.index,
    exchange: Exchange? = this.exchange,
    request: Request = this.request,
    connectTimeoutMillis: Int = this.connectTimeoutMillis,
    readTimeoutMillis: Int = this.readTimeoutMillis,
    writeTimeoutMillis: Int = this.writeTimeoutMillis
  ) = RealInterceptorChain(call, interceptors, index, exchange, request, connectTimeoutMillis,
      readTimeoutMillis, writeTimeoutMillis)
  //
  override fun connection(): Connection? = exchange?.connection
  //
  override fun connectTimeoutMillis(): Int = connectTimeoutMillis
  //
  override fun withConnectTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain {
    check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" }

    return copy(connectTimeoutMillis = checkDuration("connectTimeout", timeout.toLong(), unit))
  }

  override fun readTimeoutMillis(): Int = readTimeoutMillis

  override fun withReadTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain {
    check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" }

    return copy(readTimeoutMillis = checkDuration("readTimeout", timeout.toLong(), unit))
  }

  override fun writeTimeoutMillis(): Int = writeTimeoutMillis

  override fun withWriteTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain {
    check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" }

    return copy(writeTimeoutMillis = checkDuration("writeTimeout", timeout.toLong(), unit))
  }

  override fun call(): Call = call

  override fun request(): Request = request

  @Throws(IOException::class)
  override fun proceed(request: Request): Response {
    //1.检查当前索引是否合法
    check(index < interceptors.size)
    //2.记录本方法使用次数
    calls++
    
    if (exchange != null) {
      check(exchange.finder.sameHostAndPort(request.url)) {
        "network interceptor ${interceptors[index - 1]} must retain the same host and port"
      }
      check(calls == 1) {
        "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
      }
    }

    // Call the next interceptor in the chain.
    // 创建新的拦截器链对象RealInterceptorChain,并将index+1
    val next = copy(index = index + 1, request = request)
    // 获取下一个拦截器
    val interceptor = interceptors[index]
	// 执行拦截器的intercept方法获取结果,并将next拦截器链对象传入
    @Suppress("USELESS_ELVIS")
    val response = interceptor.intercept(next) ?: throw NullPointerException("interceptor $interceptor returned null")

    if (exchange != null) {
      check(index + 1 >= interceptors.size || next.calls == 1) {
        "network interceptor $interceptor must call proceed() exactly once"
      }
    }
    check(response.body != null) { "interceptor $interceptor returned a response with no body" }
    return response
  }
}

RetryAndFollowUpInterceptor

重试及重定向拦截器 创建请求时,如果没有自定义拦截器,那第一个拦截器就是该拦截器

该拦截器作用是 连接失败后进行重试,对请求结果跟进后进行重定向

异常有

  • RouteException
  • IOException
  • ProtocolException 协议异常
  • FileNotFoundException
  • InterruptedIOException 中断异常
  • SSLHandshakeException SSL 握手异常
  • CertificateException
  • SSLPeerUnverifiedException SSL握手未授权异常
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    // 获取拦截器链
    val realChain = chain as RealInterceptorChain
    // 获取请求request
    var request = chain.request
    // 获取realCall
    val call = realChain.call
    // 重复次数
    var followUpCount = 0
    // 前一个响应
    var priorResponse: Response? = null
    var newExchangeFinder = true
    var recoveredFailures = listOf<IOException>()
    while (true) {
      call.enterNetworkInterceptorExchange(request, newExchangeFinder)

      var response: Response
      var closeActiveExchange = true
      try {
        if (call.isCanceled()) {
          throw IOException("Canceled")
        }

        //=============================================
        try {
          // 处理响应
          response = realChain.proceed(request)
          newExchangeFinder = true
        } catch (e: RouteException) {
		  //RouteException 路由异常, 抛出异常,则检测连接是否还可以继续
          if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
            throw e.firstConnectException.withSuppressed(recoveredFailures)
          } else {
            recoveredFailures += e.firstConnectException
          }
          newExchangeFinder = false
          //继续循环
          continue
        } catch (e: IOException) {
          //IOException IO异常, 和服务端建立连接失败
          if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
            throw e.withSuppressed(recoveredFailures)
          } else {
            recoveredFailures += e
          }
          newExchangeFinder = false
          //继续循环
          continue
        }
        //=============================================

        // Attach the prior response if it exists. Such responses never have a body.
        //如果存在,请附上先前的响应。 这样的反应永远不会有实体。
        if (priorResponse != null) {
          response = response.newBuilder()
              .priorResponse(priorResponse.newBuilder()
                  .body(null)
                  .build())
              .build()
        }

        val exchange = call.interceptorScopedExchange
        val followUp = followUpRequest(response, exchange)

        if (followUp == null) {
          if (exchange != null && exchange.isDuplex) {
            call.timeoutEarlyExit()
          }
          closeActiveExchange = false
          return response
        }

        val followUpBody = followUp.body
        if (followUpBody != null && followUpBody.isOneShot()) {
          closeActiveExchange = false
          return response
        }

        response.body?.closeQuietly()
		//重定向次数不能超过MAX_FOLLOW_UPS,MAX_FOLLOW_UPS默认值是20
        if (++followUpCount > MAX_FOLLOW_UPS) {
          throw ProtocolException("Too many follow-up requests: $followUpCount")
        }

        request = followUp
        priorResponse = response
      } finally {
        // closeExchange
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
    }
  }

  /**
   * Report and attempt to recover from a failure to communicate with a server. Returns true if
   * `e` is recoverable, or false if the failure is permanent. Requests with a body can only
   * be recovered if the body is buffered or if the failure occurred before the request has been
   * sent.
   */
  private fun recover(
    e: IOException,
    call: RealCall,
    userRequest: Request,
    requestSendStarted: Boolean
  ): Boolean {
    // The application layer has forbidden retries.
    if (!client.retryOnConnectionFailure) return false

    // We can't send the request body again.
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false

    // This exception is fatal.
    if (!isRecoverable(e, requestSendStarted)) return false

    // No more routes to attempt.
    if (!call.retryAfterFailure()) return false

    // For failure recovery, use the same route selector with a new connection.
    return true
  }

  private fun requestIsOneShot(e: IOException, userRequest: Request): Boolean {
    val requestBody = userRequest.body
    return (requestBody != null && requestBody.isOneShot()) || e is FileNotFoundException
  }

  private fun isRecoverable(e: IOException, requestSendStarted: Boolean): Boolean {
    // If there was a protocol problem, don't recover.
    if (e is ProtocolException) {
      return false
    }

    // If there was an interruption don't recover, but if there was a timeout connecting to a route
    // we should try the next route (if there is one).
    if (e is InterruptedIOException) {
      return e is SocketTimeoutException && !requestSendStarted
    }

    // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
    // again with a different route.
    if (e is SSLHandshakeException) {
      // If the problem was a CertificateException from the X509TrustManager,
      // do not retry.
      if (e.cause is CertificateException) {
        return false
      }
    }
    if (e is SSLPeerUnverifiedException) {
      // e.g. a certificate pinning error.
      return false
    }
    // An example of one we might want to retry with a different route is a problem connecting to a
    // proxy and would manifest as a standard IOException. Unless it is one we know we should not
    // retry, we return true and try a new route.
    return true
  }

  /**
   * Figures out the HTTP request to make in response to receiving [userResponse]. This will
   * either add authentication headers, follow redirects or handle a client request timeout. If a
   * follow-up is either unnecessary or not applicable, this returns null.
   */
  @Throws(IOException::class)
  private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
    val route = exchange?.connection?.route()
    val responseCode = userResponse.code

    val method = userResponse.request.method
    when (responseCode) {
      //407
      HTTP_PROXY_AUTH -> {
        val selectedProxy = route!!.proxy
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
        }
        return client.proxyAuthenticator.authenticate(route, userResponse)
      }
      //401
      HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)

      //重定向
      HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
        return buildRedirectRequest(userResponse, method)
      }
      //408
      HTTP_CLIENT_TIMEOUT -> {
        // 408's are rare in practice, but some servers like HAProxy use this response code. The
        // spec says that we may repeat the request without modifications. Modern browsers also
        // repeat the request (even non-idempotent ones.)
        if (!client.retryOnConnectionFailure) {
          // The application layer has directed us not to retry the request.
          return null
        }

        val requestBody = userResponse.request.body
        if (requestBody != null && requestBody.isOneShot()) {
          return null
        }
        val priorResponse = userResponse.priorResponse
        if (priorResponse != null && priorResponse.code == HTTP_CLIENT_TIMEOUT) {
          // We attempted to retry and got another timeout. Give up.
          return null
        }

        if (retryAfter(userResponse, 0) > 0) {
          return null
        }

        return userResponse.request
      }
      //503
      HTTP_UNAVAILABLE -> {
        val priorResponse = userResponse.priorResponse
        if (priorResponse != null && priorResponse.code == HTTP_UNAVAILABLE) {
          // We attempted to retry and got another timeout. Give up.
          return null
        }

        if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
          // specifically received an instruction to retry without delay
          return userResponse.request
        }

        return null
      }
      //421
      HTTP_MISDIRECTED_REQUEST -> {
        // OkHttp can coalesce HTTP/2 connections even if the domain names are different. See
        // RealConnection.isEligible(). If we attempted this and the server returned HTTP 421, then
        // we can retry on a different connection.
        val requestBody = userResponse.request.body
        if (requestBody != null && requestBody.isOneShot()) {
          return null
        }

        if (exchange == null || !exchange.isCoalescedConnection) {
          return null
        }

        exchange.connection.noCoalescedConnections()
        return userResponse.request
      }

      else -> return null
    }
  }
  //重定向操作
  private fun buildRedirectRequest(userResponse: Response, method: String): Request? {
    // Does the client allow redirects?
    if (!client.followRedirects) return null

    val location = userResponse.header("Location") ?: return null
    // Don't follow redirects to unsupported protocols.
    val url = userResponse.request.url.resolve(location) ?: return null

    // If configured, don't follow redirects between SSL and non-SSL.
    val sameScheme = url.scheme == userResponse.request.url.scheme
    if (!sameScheme && !client.followSslRedirects) return null

    // Most redirects don't include a request body.
    val requestBuilder = userResponse.request.newBuilder()
    if (HttpMethod.permitsRequestBody(method)) {
      val responseCode = userResponse.code
      val maintainBody = HttpMethod.redirectsWithBody(method) ||
          responseCode == HTTP_PERM_REDIRECT ||
          responseCode == HTTP_TEMP_REDIRECT
      if (HttpMethod.redirectsToGet(method) && responseCode != HTTP_PERM_REDIRECT && responseCode != HTTP_TEMP_REDIRECT) {
        requestBuilder.method("GET", null)
      } else {
        val requestBody = if (maintainBody) userResponse.request.body else null
        requestBuilder.method(method, requestBody)
      }
      if (!maintainBody) {
        requestBuilder.removeHeader("Transfer-Encoding")
        requestBuilder.removeHeader("Content-Length")
        requestBuilder.removeHeader("Content-Type")
      }
    }

    // When redirecting across hosts, drop all authentication headers. This
    // is potentially annoying to the application layer since they have no
    // way to retain them.
    if (!userResponse.request.url.canReuseConnectionFor(url)) {
      requestBuilder.removeHeader("Authorization")
    }

    return requestBuilder.url(url).build()
  }

  private fun retryAfter(userResponse: Response, defaultDelay: Int): Int {
    val header = userResponse.header("Retry-After") ?: return defaultDelay

    // https://tools.ietf.org/html/rfc7231#section-7.1.3
    // currently ignores a HTTP-date, and assumes any non int 0 is a delay
    if (header.matches("\d+".toRegex())) {
      return Integer.valueOf(header)
    }
    return Integer.MAX_VALUE
  }

  companion object {
    /**
     * How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,
     * curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
     */
    private const val MAX_FOLLOW_UPS = 20
  }
}
//禁止重定向,交给用户自己操作
new OkHttpClient().newBuilder()
                   .followRedirects(false)  //禁止OkHttp的重定向操作,我们自己处理重定向
                   .followSslRedirects(false)//https的重定向也自己处理

总结

  • 本拦截器是五大拦截器中的第一个,首次接触到Request、最后一次接触到Response,该拦截器主要作用是判断是否需要重试和重定向
  • 重试的前提是出现了RouteExceptionIOException,一旦出现该两个异常,就会通过recover方法进行判断是否需要重试
  • 重定向f安生在重试之后,如果不满足重试的条件,还需要进一步调用 followUpRequest 根据Response的响应码,如果直接请求失败,Response都不存在就会抛出异常,如果遇到3xx,就需要调用重定向方法buildRedirectRequest
  • 重定向最大发生20次,MAX_FOLLOW_UPS=20

BridgeInterceptor

BridgeInterceptor连接应用程序和服务器的桥梁,我们发出的请求会经过该拦截器的处理才能发给服务器。其主要作用如下

  • 在request阶段配置请求头,cookie信息等
  • 在response阶段,处理并保存返回的cookie信息,判断是否需要gzip解压

桥接拦截器所处理的操作一般有设置请求内容的长度,编码,gzip压缩,cookie等,获取响应后保存Cookie等操作

请求头说明
Content-type请求体类型,application/json
Content-Length/Transfer-Encoding请求头解析方式,内容编码/传输编码 Content-Encoding 通常用于对实体内容进行压缩编码,目的是优化传输,例如用 gzip 压缩文本文件,能大幅减小体积。 Transfer-Encoding ????
Host请求的主机站点
Connection:Keep-Alive保持长连接
Accept-Encoding:gzip接收响应支持gzip压缩
Cookiecookie身份辨别
User-Agent请求的用户信息,如操作系统、浏览器等等

其主要操作流程如下

  1. 先从chain中获取request,然后从request中获取body
  2. 从body中获取contentType,如果不为空就设置Content-Type请求头类型
  3. 从body中获取body内容长度contentLength,如果contentLength=-1,就添加Content-Length,移除Transfer-Encoding,反之,就添加Transfer-Encoding,移除Content-Length
  4. 配置Host请求头
  5. 配置Connection请求头
  6. 配置Accept-Encoding头为gzip压缩
  7. 配置Cookie头
  8. 配置User-Agent头
  9. 处理并保存返回的cookie信息
  10. 判断是否需要gzip解压
//桥接拦截器
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    // 获取Request
    val userRequest = chain.request()
    // 获取Request的Builder
    val requestBuilder = userRequest.newBuilder()
    // 获取请求体
    val body = userRequest.body
    if (body != null) {
      val contentType = body.contentType()
      if (contentType != null) {
        //设置请求体类型Content-Type
        requestBuilder.header("Content-Type", contentType.toString())
      }

      val contentLength = body.contentLength()
      //Content-Length和Transfer-Encoding互斥
      if (contentLength != -1L) {
        //内容编码
        requestBuilder.header("Content-Length", contentLength.toString())
        requestBuilder.removeHeader("Transfer-Encoding")
      } else {
        //传输编码
        requestBuilder.header("Transfer-Encoding", "chunked")
        requestBuilder.removeHeader("Content-Length")
      }
    }

    if (userRequest.header("Host") == null) {
      // 设置请求主机站点 Host
      requestBuilder.header("Host", userRequest.url.toHostHeader())
    }

    if (userRequest.header("Connection") == null) {
       // 设置保持长连接
      requestBuilder.header("Connection", "Keep-Alive")
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    var transparentGzip = false
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true
      // 接收响应支持gzip压缩
      requestBuilder.header("Accept-Encoding", "gzip")
    }

    val cookies = cookieJar.loadForRequest(userRequest.url)
    if (cookies.isNotEmpty()) {
      //设置cookie
      requestBuilder.header("Cookie", cookieHeader(cookies))
    }

    if (userRequest.header("User-Agent") == null) {
      //设置用户请求信息,okhttp的userAgent是"okhttp/${OkHttp.VERSION}"
      requestBuilder.header("User-Agent", userAgent)
    }
      
    /////////////////////////////////////// 
    
    // 获取响应
    val networkResponse = chain.proceed(requestBuilder.build())
    //响应header, 如果没有自定义配置cookieJar==null,则什么都不做
    cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)

    val responseBuilder = networkResponse.newBuilder()
        .request(userRequest)

    //判断服务器是否支持gzip压缩格式,如果支持则交给okio压缩
    if (transparentGzip && "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
        networkResponse.promisesBody()) {
      val responseBody = networkResponse.body
      if (responseBody != null) {
        val gzipSource = GzipSource(responseBody.source())
        val strippedHeaders = networkResponse.headers.newBuilder()
            .removeAll("Content-Encoding")
            .removeAll("Content-Length")
            .build()
        responseBuilder.headers(strippedHeaders)
        val contentType = networkResponse.header("Content-Type")
        responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
      }
    }

    return responseBuilder.build()
  }

  /** Returns a 'Cookie' HTTP request header with all cookies, like `a=b; c=d`. */
  private fun cookieHeader(cookies: List<Cookie>): String = buildString {
    cookies.forEachIndexed { index, cookie ->
      if (index > 0) append("; ")
      append(cookie.name).append('=').append(cookie.value)
    }
  }
}

总结

  • 桥接拦截器对用户构建的Request 进行添加或者删除相关头部信息,以转换为能够真正进行网络请求的Request,将符合网络请求规范的Request交给下一个拦截器处理
  • 获取Response,如果响应体经过了GZIP压缩,那就需要解压,再构建成用户可用的Response并返回

CacheInterceptor

缓存拦截器 主要是通过一个缓存策略来判断当前是使用网络请求的response还是缓存的response,判断流程如下

  • 判断是否配置了缓存(Cache对象需要在OkhtpClient的Builder中手动配置)如果配置缓存,则通过request获取到对应的response缓存

    new OkHttpClient.Builder()
    	.cache(new Cache(new File(context.getCacheDir(), "OkHttpCache"), 10 * 1024 * 1024))//添加缓存
    
  • 创建缓存策略类,用于处理怎么使用缓存

  • 如果当前不允许使用网络,而且缓存为空,则返回状态码为504的response

  • 如果不能使用网络,但是有缓存,则直接返回缓存

  • 如果使用网络,则调用下一个拦截器处理并返回对应的response对象

  • 如果当前缓存和网络都可用,则判断当前网络response的状态码,如果状态码为304(表示内容没有变化),则使用缓存,否则使用网络

  • 如果当前有配置缓存(Cache对象),则缓存response(只能缓存GET请求

class CacheInterceptor(internal val cache: Cache?) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val call = chain.call()
    // 如果配置了缓存,则通过request获取Response缓存,默认cache为空
    val cacheCandidate = cache?.get(chain.request())

    val now = System.currentTimeMillis()
    // 创建缓存策略类,用于处理怎么使用缓存
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    // 如果不使用网络,则 networkRequest为 null 
    val networkRequest = strategy.networkRequest
    // 如果不使用缓存,则 cacheResponse为 null
    val cacheResponse = strategy.cacheResponse
	
    // 更新统计指标, 增加命中率
    cache?.trackResponse(strategy)
    val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE

    if (cacheCandidate != null && cacheResponse == null) {
      // The cache candidate wasn't applicable. Close it.
      cacheCandidate.body?.closeQuietly()
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    // 如果当前不允许使用网络,而且缓存为空,则返回状态码为504的response
    // HTTP_GATEWAY_TIMEOUT = 504
    if (networkRequest == null && cacheResponse == null) {
      return Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(HTTP_GATEWAY_TIMEOUT)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build().also {
            listener.satisfactionFailure(call, it)
          }
    }

    // If we don't need the network, we're done.
    // 如果不能使用网络,但是有缓存,则直接返回缓存
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }

    if (cacheResponse != null) {
      listener.cacheConditionalHit(call, cacheResponse)
    } else if (cache != null) {
      listener.cacheMiss(call)
    }

    // 使用网络,则调用下一个拦截器处理并返回对应的response对象
    var networkResponse: Response? = null
    try {
      networkResponse = chain.proceed(networkRequest)
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        cacheCandidate.body?.closeQuietly()
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    // 在发送了request的情况下,如果当前缓存可用,则判断当前网络response的状态码,如果状态码为304(表示内容没有变化),则使用缓存,否则使用网络
    if (cacheResponse != null) {
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
        //304 请求内容并没有改变,使用缓存
        val response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers, networkResponse.headers))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis)
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build()

        networkResponse.body!!.close()

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache!!.trackConditionalCacheHit()
        // 更新缓存
        cache.update(cacheResponse, response)
        return response.also {
          listener.cacheHit(call, it)
        }
      } else {
        // 关闭缓存流
        cacheResponse.body?.closeQuietly()
      }
    }
	// 使用网络请求得到的response
    val response = networkResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()

    if (cache != null) {
      // 缓存网络response
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        val cacheRequest = cache.put(response)
        return cacheWritingResponse(cacheRequest, response).also {
          if (cacheResponse != null) {
            // This will log a conditional cache miss only.
            listener.cacheMiss(call)
          }
        }
      }
      // 只能缓存GET请求,不是则移除request
      if (HttpMethod.invalidatesCache(networkRequest.method)) {
        try {
          cache.remove(networkRequest)
        } catch (_: IOException) {
          // The cache cannot be written.
        }
      }
    }
    // 返回response
    return response
  }

  /**
   * Returns a new source that writes bytes to [cacheRequest] as they are read by the source
   * consumer. This is careful to discard bytes left over when the stream is closed; otherwise we
   * may never exhaust the source stream and therefore not complete the cached response.
   */
  @Throws(IOException::class)
  private fun cacheWritingResponse(cacheRequest: CacheRequest?, response: Response): Response {
    // Some apps return a null body; for compatibility we treat that like a null cache request.
    if (cacheRequest == null) return response
    val cacheBodyUnbuffered = cacheRequest.body()

    val source = response.body!!.source()
    val cacheBody = cacheBodyUnbuffered.buffer()

    val cacheWritingSource = object : Source {
      private var cacheRequestClosed = false

      @Throws(IOException::class)
      override fun read(sink: Buffer, byteCount: Long): Long {
        val bytesRead: Long
        try {
          bytesRead = source.read(sink, byteCount)
        } catch (e: IOException) {
          if (!cacheRequestClosed) {
            cacheRequestClosed = true
            cacheRequest.abort() // Failed to write a complete cache response.
          }
          throw e
        }

        if (bytesRead == -1L) {
          if (!cacheRequestClosed) {
            cacheRequestClosed = true
            cacheBody.close() // The cache response is complete!
          }
          return -1
        }

        sink.copyTo(cacheBody.buffer, sink.size - bytesRead, bytesRead)
        cacheBody.emitCompleteSegments()
        return bytesRead
      }

      override fun timeout() = source.timeout()

      @Throws(IOException::class)
      override fun close() {
        if (!cacheRequestClosed &&
            !discard(ExchangeCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
          cacheRequestClosed = true
          cacheRequest.abort()
        }
        source.close()
      }
    }

    val contentType = response.header("Content-Type")
    val contentLength = response.body.contentLength()
    return response.newBuilder()
        .body(RealResponseBody(contentType, contentLength, cacheWritingSource.buffer()))
        .build()
  }

  companion object {

    private fun stripBody(response: Response?): Response? {
      return if (response?.body != null) {
        response.newBuilder().body(null).build()
      } else {
        response
      }
    }

    /** Combines cached headers with a network headers as defined by RFC 7234, 4.3.4. */
    private fun combine(cachedHeaders: Headers, networkHeaders: Headers): Headers {
      val result = Headers.Builder()

      for (index in 0 until cachedHeaders.size) {
        val fieldName = cachedHeaders.name(index)
        val value = cachedHeaders.value(index)
        if ("Warning".equals(fieldName, ignoreCase = true) && value.startsWith("1")) {
          // Drop 100-level freshness warnings.
          continue
        }
        if (isContentSpecificHeader(fieldName) ||
            !isEndToEnd(fieldName) ||
            networkHeaders[fieldName] == null) {
          result.addLenient(fieldName, value)
        }
      }

      for (index in 0 until networkHeaders.size) {
        val fieldName = networkHeaders.name(index)
        if (!isContentSpecificHeader(fieldName) && isEndToEnd(fieldName)) {
          result.addLenient(fieldName, networkHeaders.value(index))
        }
      }

      return result.build()
    }

    /**
     * Returns true if [fieldName] is an end-to-end HTTP header, as defined by RFC 2616,
     * 13.5.1.
     */
    private fun isEndToEnd(fieldName: String): Boolean {
      return !"Connection".equals(fieldName, ignoreCase = true) &&
          !"Keep-Alive".equals(fieldName, ignoreCase = true) &&
          !"Proxy-Authenticate".equals(fieldName, ignoreCase = true) &&
          !"Proxy-Authorization".equals(fieldName, ignoreCase = true) &&
          !"TE".equals(fieldName, ignoreCase = true) &&
          !"Trailers".equals(fieldName, ignoreCase = true) &&
          !"Transfer-Encoding".equals(fieldName, ignoreCase = true) &&
          !"Upgrade".equals(fieldName, ignoreCase = true)
    }

    /**
     * Returns true if [fieldName] is content specific and therefore should always be used
     * from cached headers.
     */
    private fun isContentSpecificHeader(fieldName: String): Boolean {
      return "Content-Length".equals(fieldName, ignoreCase = true) ||
          "Content-Encoding".equals(fieldName, ignoreCase = true) ||
          "Content-Type".equals(fieldName, ignoreCase = true)
    }
  }
}

ConnectInterceptor

连接拦截器,打开与目标服务器的连接,正式开启网络请求,并执行下一个拦截器

object ConnectInterceptor : Interceptor {
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    // 
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.call.initExchange(chain)
    val connectedChain = realChain.copy(exchange = exchange)
    
    return connectedChain.proceed(realChain.request)
  }
}
​
class Exchange(
  internal val call: RealCall,
  internal val eventListener: EventListener,
  internal val finder: ExchangeFinder,
  private val codec: ExchangeCodec
) {
  /** Returns true if the request body need not complete before the response body starts. */
  internal var isDuplex: Boolean = false
    private set
​
  internal val connection: RealConnection = codec.connection
​
  internal val isCoalescedConnection: Boolean
    get() = finder.address.url.host != connection.route().address.url.host
​
  @Throws(IOException::class)
  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
    }
  }
​
  @Throws(IOException::class)
  fun createRequestBody(request: Request, duplex: Boolean): Sink {
    this.isDuplex = duplex
    val contentLength = request.body!!.contentLength()
    eventListener.requestBodyStart(call)
    val rawRequestBody = codec.createRequestBody(request, contentLength)
    return RequestBodySink(rawRequestBody, contentLength)
  }
​
  @Throws(IOException::class)
  fun flushRequest() {
    try {
      codec.flushRequest()
    } catch (e: IOException) {
      eventListener.requestFailed(call, e)
      trackFailure(e)
      throw e
    }
  }
​
  @Throws(IOException::class)
  fun finishRequest() {
    try {
      codec.finishRequest()
    } catch (e: IOException) {
      eventListener.requestFailed(call, e)
      trackFailure(e)
      throw e
    }
  }
​
  fun responseHeadersStart() {
    eventListener.responseHeadersStart(call)
  }
​
  @Throws(IOException::class)
  fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
    try {
      val result = codec.readResponseHeaders(expectContinue)
      result?.initExchange(this)
      return result
    } catch (e: IOException) {
      eventListener.responseFailed(call, e)
      trackFailure(e)
      throw e
    }
  }
​
  fun responseHeadersEnd(response: Response) {
    eventListener.responseHeadersEnd(call, response)
  }
​
  @Throws(IOException::class)
  fun openResponseBody(response: Response): ResponseBody {
    try {
      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
    }
  }
​
  @Throws(IOException::class)
  fun trailers(): Headers = codec.trailers()
​
  @Throws(SocketException::class)
  fun newWebSocketStreams(): RealWebSocket.Streams {
    call.timeoutEarlyExit()
    return codec.connection.newWebSocketStreams(this)
  }
​
  fun webSocketUpgradeFailed() {
    bodyComplete(-1L, responseDone = true, requestDone = true, e = null)
  }
​
  fun noNewExchangesOnConnection() {
    codec.connection.noNewExchanges()
  }
​
  fun cancel() {
    codec.cancel()
  }
​
  /**
   * Revoke this exchange's access to streams. This is necessary when a follow-up request is
   * required but the preceding exchange hasn't completed yet.
   */
  fun detachWithViolence() {
    codec.cancel()
    call.messageDone(this, requestDone = true, responseDone = true, e = null)
  }
​
  private fun trackFailure(e: IOException) {
    finder.trackFailure(e)
    codec.connection.trackFailure(call, e)
  }
​
  fun <E : IOException?> bodyComplete(
    bytesRead: Long,
    responseDone: Boolean,
    requestDone: Boolean,
    e: E
  ): E {
    if (e != null) {
      trackFailure(e)
    }
    if (requestDone) {
      if (e != null) {
        eventListener.requestFailed(call, e)
      } else {
        eventListener.requestBodyEnd(call, bytesRead)
      }
    }
    if (responseDone) {
      if (e != null) {
        eventListener.responseFailed(call, e)
      } else {
        eventListener.responseBodyEnd(call, bytesRead)
      }
    }
    return call.messageDone(this, requestDone, responseDone, e)
  }
​
  fun noRequestBody() {
    call.messageDone(this, requestDone = true, responseDone = false, e = null)
  }
​
  /** A request body that fires events when it completes. */
  private inner class RequestBodySink(
    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" }
      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
      return bodyComplete(bytesReceived, responseDone = false, requestDone = true, e = e)
    }
  }
​
  /** A response body that fires events when it completes. */
  internal inner class ResponseBodySource(
    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 {
      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.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)
      }
      return bodyComplete(bytesReceived, responseDone = true, requestDone = false, e = e)
    }
  }
}

总结

该拦截器作用是为了获取一份与目标服务器的连接,在这个连接上进行HTTP数据的收发

CallServerInterceptor

该拦截器主要作用是向服务器发送请求,最终返回Response

class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
​
  @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()
​
    // 写入请求头
    exchange.writeRequestHeaders(request)
​
    var invokeStartEvent = true
    var responseBuilder: Response.Builder? = null
    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.
      // 判断是否有请求体
      if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
        //询问是否有请求体
        exchange.flushRequest() //刷新请求
        responseBuilder = exchange.readResponseHeaders(expectContinue = true)
        exchange.responseHeadersStart()
        invokeStartEvent = false
      }
      if (responseBuilder == null) {
        // 
        if (requestBody.isDuplex()) {
          // Prepare a duplex body so that the application can send a request body later.
          //写入请求
          exchange.flushRequest()
          val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
          requestBody.writeTo(bufferedRequestBody)
        } else {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          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()
    }
    if (responseBuilder == null) {
      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
    if (code == 100) {
      // Server sent a 100-continue even though we did not request one. Try again to read the actual
      // response status.
      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.
      response.newBuilder()
          .body(EMPTY_RESPONSE)
          .build()
    } else {
      response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build()
    }
    // 如果请求头中 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
  }
}

OkHttp面试题汇总

  • OKHttp有哪些拦截器,分别起什么作⽤
  • OkHttp怎么实现连接池
  • OkHttp⾥⾯⽤到了什么设计模式
  • Okhttp是怎么复用的,线程池