OkHttp拦截器Interceptor

Quibbler 2021-3-18 1508

OkHttp拦截器Interceptor


        接着OkHttp网络请求流程一文,我们知道OkHttp网络请求最后是通过RealCallgetResponseWithInterceptorChain()方法完成。该方法完整实现如下:

  internal fun getResponseWithInterceptorChain(): 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(
        call = this,
        interceptors = interceptors,
        index = 0,
        exchange = null,
        request = originalRequest,
        connectTimeoutMillis = client.connectTimeoutMillis,
        readTimeoutMillis = client.readTimeoutMillis,
        writeTimeoutMillis = client.writeTimeoutMillis)
        
        
    try {
      val response = chain.proceed(originalRequest)
      ...
      return response
    } catch (e: IOException) {
      
    } finally {
      
    }
  }

        


1、拦截链

        OkHttp的核心就是拦截器,通过一组拦截器“递归”的完成网络请求中各个环节的处理。


1.1、Interceptors责任链

        在getResponseWithInterceptorChain()方法中我们看到了一段代码:

    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)

        上面这段代码按顺序添加拦截器:

        Client.Interceptors:自定义的应用拦截器。

        RetryAndFollowUpInterceptor:从故障中恢复,并根据需要进行重定向。

        BridgeInterceptor:应用和网络间的桥梁,根据客户端请求构建网络;将网络请求转换成客户端友好的响应。

        CacheInterceptor:读取缓存以及更新缓存。

        ConnectInterceptor:与服务器建立连接。

        Client.NewworkInterceptors:自定义网络拦截器。

        CallServerInterceptor:服务器读取响应数据。


1.2、RealInterceptorChain

        构造好拦截链之后,再通过RealInterceptorChain按顺序遍历执行interceptors集合中的拦截器:


        RealInterceptorChain将整个拦截器的调用过程连接起来,看看其中proceed(request:Rquest)方法的实现:

  @Throws(IOException::class)
  override fun proceed(request: Request): Response {
    check(index < interceptors.size)
    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"
      }
    }
    // 调用链中的下一个拦截器。copy()方法赋值构造下一个RealInterceptorChain实例
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]
    
    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
  }

        

        

2、拦截器:Interceptor

        让我们挨个看看这些拦截器实现哪些作用,先看OkHttp库中预设的这五个固定调用的拦截器:RetryAndFollowUpInterceptorBridgeInterceptorCacheInterceptorConnectInterceptorCallServerInterceptor


2.1、RetryAndFollowUpInterceptor

        RetryAndFollowUpInterceptor拦截器从故障中恢复,并根据需要进行重定向。 如果调用被取消,则可能抛出IOException。

  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    var request = chain.request
    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) {
          // 通过路由连接的尝试失败。请求将不会被发送。
          if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
            throw e.firstConnectException.withSuppressed(recoveredFailures)
          } else {
            recoveredFailures += e.firstConnectException
          }
          newExchangeFinder = false
          continue
        } catch (e: IOException) {
          // 试图与服务器通信失败。请求可能已经发送。
          if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
            throw e.withSuppressed(recoveredFailures)
          } else {
            recoveredFailures += e
          }
          newExchangeFinder = false
          continue
        }
        // 在本次response中设置上一次的response,其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()
        //重定向不超过20次
        if (++followUpCount > MAX_FOLLOW_UPS) {
          throw ProtocolException("Too many follow-up requests: $followUpCount")
        }
        //修改下次重定向的请求request
        request = followUp
        priorResponse = response
      } finally {
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
    }
  }

        重定向次数不超过20次,否则抛出异常。这个数值定义是根据各个主流浏览器重定向次数取的:

  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
  }


2.2、BridgeInterceptor

        BridgeInterceptor是从应用程序代码到网络代码的桥梁。把客户端构建的请求转换为向服务器请求的Request;在服务器返回响应后,再将服务器返回的响应转换为客户端能够使用的Response

  override fun intercept(chain: Interceptor.Chain): Response {
    val userRequest = chain.request()
    val requestBuilder = userRequest.newBuilder()
    val body = userRequest.body
    //将一些userRequest中的属性设置进builder中
    if (body != null) {
      val contentType = body.contentType()
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString())
      }
      val contentLength = body.contentLength()
      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) {
      requestBuilder.header("Host", userRequest.url.toHostHeader())
    }
    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive")
    }
    
    // 若未设置Accept-Encoding,自动设置gzip
    var transparentGzip = false
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true
      requestBuilder.header("Accept-Encoding", "gzip")
    }
    val cookies = cookieJar.loadForRequest(userRequest.url)
    if (cookies.isNotEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies))
    }
    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", userAgent)
    }
    val networkResponse = chain.proceed(requestBuilder.build())
    cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
    val responseBuilder = networkResponse.newBuilder()
        .request(userRequest)
    
    // 若之前设置了gzip压缩且response中也包含了gzip压缩,则进行gzip解压
    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()
  }


2.3、CacheInterceptor

        CacheInterceptor负责读取缓存,并将响应写入缓存。

  override fun intercept(chain: Interceptor.Chain): Response {
    val call = chain.call()
    val cacheCandidate = cache?.get(chain.request())
    val now = System.currentTimeMillis()
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    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()
    }
    // 根据缓存策略若不能使用网络且没有缓存,则请求失败,构建一个请求失败的Response并返回
    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 (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)
    }
    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()
      }
    }
    // 如果缓存中有缓存,并且请求的code为304,则结合缓存及网络请求结果后返回,并且更新缓存中的内容
    if (cacheResponse != null) {
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
        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()
      }
    }
    val response = networkResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()
    if (cache != null) {
      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)
          }
        }
      }
      if (HttpMethod.invalidatesCache(networkRequest.method)) {
        try {
          cache.remove(networkRequest)
        } catch (_: IOException) {
          // The cache cannot be written.
        }
      }
    }
    return response
  }


2.4、ConnectInterceptor

        ConnectInterceptor负责打开与目标服务器的连接,然后进入下一个拦截器(网络拦截器或者最后一个拦截器CallServerInterceptor

        别看代码简短,它可是涉及了OkHttp网络连接池、连接事务管理等等,因为都通过Exchange及其内部的ExchangeCodec类完成。这些在OkHttp连接池中会分析到,这篇主要将OkHttp的连接池。

  @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)
  }


2.5、CallServerInterceptor

        CallServerInterceptor是OkHttp拦截链中的最后一个,不会再继续调用proceed(Request)方法,最终完成对服务器响应数据的读取。

        实现也是几个拦截器中最为复杂的,代码贴在下面, 反正我是看了很长时间,才大致搞明白,不求甚解!

  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) {
      // 对 100-continue做特殊处理
      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()
    }
    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
  }



3、自定义拦截器

        OkHttp强大的地方还在于扩展性,预留了很多扩展给开发者。其中就包括添加自定义拦截器。


3.1、拦截器接口

        OkHttp库定义了Interceptor接口及一个接口方法intercept(chain: Chain),传入的参数正是1.2节中的RealInterceptorChain

  fun interface Interceptor {
    @Throws(IOException::class)
    fun intercept(chain: Chain): Response
  }

        实现Interceptor接口,标准模板如下。参考OkHttp Interceptors

  class AppInterceptor : Interceptor {
      override fun intercept(chain: Interceptor.Chain): Response {
          //通过参数Chain获取Request,修改调整Request; 或增加其它逻辑比如Log等
          val request: Request = chain.request()
          
          //调用Chain的proceed(Request)方法 "递归"下去执行后面的拦截器
          val response: Response = chain.proceed(request)
          
          //其它逻辑; 处理返回的Response,并返回最终Response
          return response
      }
  }

       

3.2、应用拦截器 interceptors

        通过OkHttpClient.Builder构造OkHttpClient,调用addInterceptor()方法将自定义拦截器添加为应用拦截器interceptors

    val client: OkHttpClient = OkHttpClient.Builder()
        .addInterceptor(AppInterceptor())
        .build()

        对应OkHttp.Builder中的实现,将应用拦截器添加到interceptors集合中:

    /**
     * Returns a modifiable list of interceptors that observe the full span of each call: from
     * before the connection is established (if any) until after the response source is selected
     * (either the origin server, cache, or both).
     */
    fun interceptors(): MutableList<Interceptor> = interceptors
    fun addInterceptor(interceptor: Interceptor) = apply {
      interceptors += interceptor
    }


3.3、网络拦截器 newworkInterceptors

        同样的,OkHttpClient.Builder构造OkHttpClient时调用addNetworkInterceptor()方法将自定义拦截器添加到网络拦截器networkInterceptors

    val client: OkHttpClient = OkHttpClient.Builder()
        .addNetworkInterceptor(NetworkLoggingInterceptor())
        ...
        .build()

        对应OkHttp.Builder中的实现,将网络拦截器添加到networkInterceptors集合中:

    /**
     * Returns a modifiable list of interceptors that observe a single network request and response.
     * These interceptors must call [Interceptor.Chain.proceed] exactly once: it is an error for a
     * network interceptor to short-circuit or repeat a network request.
     */
    fun networkInterceptors(): MutableList<Interceptor> = networkInterceptors
    fun addNetworkInterceptor(interceptor: Interceptor) = apply {
      networkInterceptors += interceptor
    }

        


参考资料:

        OkHttp Interceptors

        

不忘初心的阿甘
最新回复 (0)
    • AI笔记本-欢迎来到 AI 驱动博客时代 🚀
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com