美文网首页
2019-11-04Okhttp源码解析-拦截器<一>

2019-11-04Okhttp源码解析-拦截器<一>

作者: 猫KK | 来源:发表于2019-11-04 16:53 被阅读0次

这里,来分析Okhttp中自己添加的拦截器

  //方法可能会抛出异常
 @Throws(IOException::class)
  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)

    .......
  }

1. RetryAndFollowUpInterceptor

先来看第一个,请求失败重试,或者重定向拦截器

class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    //获取请求
    var request = chain.request()
    val realChain = chain as RealInterceptorChain
    val transmitter = realChain.transmitter()
    var followUpCount = 0
    //获取请求返回数据
    var priorResponse: Response? = null
    //死循环,通过while(true) 循环来实现重试
    while (true) {
      //初始化,主要是初始化ExchangeFinder类
      transmitter.prepareToConnect(request)
      //如果取消了,直接抛出异常,终止循环
      if (transmitter.isCanceled) {
        throw IOException("Canceled")
      }

      var response: Response
      var success = false
      //通过try catch 来捕获请求出现的异常
      try {
        //获取请求回来的数据
        response = realChain.proceed(request, transmitter, null)
        //标记请求成功
        success = true
      } catch (e: RouteException) {
        // The attempt to connect via a route failed. The request will not have been sent.
        //请求失败,判断是否可以重连,如果可以,则continue然后再次请求
        //否则抛出异常,终止请求
        if (!recover(e.lastConnectException, transmitter, false, request)) {
          throw e.firstConnectException
        }
        continue
      } catch (e: IOException) {
        // An attempt to communicate with a server failed. The request may have been sent.
        //捕获IO异常,一样处理
        val requestSendStarted = e !is ConnectionShutdownException
        if (!recover(e, transmitter, requestSendStarted, request)) throw e
        continue
      } finally {
        // The network call threw an exception. Release any resources.
        if (!success) {
          transmitter.exchangeDoneDueToException()
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      //这个值第一次为null,重定向请求时不为null
      //不为null,说明为重定向,重新生成response
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                .body(null)
                .build())
            .build()
      }

      val exchange = response.exchange
      val route = exchange?.connection()?.route()
      //判断是否需要重定向
      val followUp = followUpRequest(response, route)
      //如果followUp为null,说明不需要重定向
      if (followUp == null) {
        if (exchange != null && exchange.isDuplex) {
          transmitter.timeoutEarlyExit()
        }
        //返回数据
        return response
      }

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

      response.body?.closeQuietly()
      if (transmitter.hasExchange()) {
        exchange?.detachWithViolence()
      }

      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw ProtocolException("Too many follow-up requests: $followUpCount")
      }
      //重定向,将重定向的request重新复制,通过死循环在次请求
      request = followUp
      priorResponse = response
    }
  }

通过上面,可以知道有两个方法比较关键

  1. recover():请求失败,是否可以再次请求
  2. followUpRequest():判断是否需要重定向

先来看第一个

private fun recover(
    e: IOException,
    transmitter: Transmitter,
    requestSendStarted: Boolean,
    userRequest: Request
  ): 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 (!transmitter.canRetry()) return false
    // For failure recovery, use the same route selector with a new connection.
    return true
  }

 //通过异常来判断是否需要重试
 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) {
      //socket 超时,不再重试
      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
      }
    }
    //SSLPeerUnverifiedException 异常
    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
  }

通过请求的异常等,判断是否需要再次重试。现在来看第二个方法,判断是否需要重定向

  @Throws(IOException::class)
  private fun followUpRequest(userResponse: Response, route: Route?): Request? {
    //获取请求码
    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)
      //308、307 需要重定向
      HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT -> {
        // "If the 307 or 308 status code is received in response to a request other than GET
        // or HEAD, the user agent MUST NOT automatically redirect the request"
        //如果请求方法不为GET、HEAD不进行重定向
        if (method != "GET" && method != "HEAD") {
          return null
        }
        //构建重定向的Request
        return buildRedirectRequest(userResponse, method)
      }
      //300、301、302、303 情况,都进行重定向
      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
      }
      //其他情况,进行重定向
      else -> return null
    }
  }

通过上面,http请求码为300、301、302、303情况下需要重定向,308、307 情况下为GET、HEAD 方法需要重定向,来看如何构建重定向的Request

private fun buildRedirectRequest(userResponse: Response, method: String): Request? {
    // 是否允许重定向
    if (!client.followRedirects) return null
    //判断头信息中是否包含Location,需要重定向时Location的值为重定向的连接
    val location = userResponse.header("Location") ?: return null
    // Don't follow redirects to unsupported protocols.
     //通过location 获取重定向的url
    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 maintainBody = HttpMethod.redirectsWithBody(method)
      if (HttpMethod.redirectsToGet(method)) {
        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()
  }

判断是否需要重定向,是感觉http返回的状态码,和返回的头信息中是否包含Location,重定向的连接就是Location的值。如果需要重定向,则通过while(true)再次请求实现重定向

2. BridgeInterceptor

配置请求头信息

    class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    //获取请求
    val userRequest = chain.request()
    //重新构建一个请求,用于配置信息
    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()
      if (contentLength != -1L) {
        //添加Content-Length
        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) {
      //添加Connection
      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) {
      //添加User-Agent
      requestBuilder.header("User-Agent", userAgent)
    }
    //发起请求
    val networkResponse = chain.proceed(requestBuilder.build())

    cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)

    val responseBuilder = networkResponse.newBuilder()
        .request(userRequest)
    //如果进行了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()
  }

这个拦截器主要是添加一些请求头信息,并判断是否进行了gzip压缩,如果压缩了,在返回数据中对返回数据解压

相关文章

网友评论

      本文标题:2019-11-04Okhttp源码解析-拦截器<一>

      本文链接:https://www.haomeiwen.com/subject/gmjdbctx.html