当前位置: 首页 > article >正文

OKHttp3 源码阅读 - Kotlin版本

本篇文章基于 OKHttp 4.11.0 版本阅读的。

1. 介绍

OKHttp 是由 Square 公司开源的,广泛应用于 Android 开发中,并且是 Retrofit 的底层实现。它是一个高效的 HTTP 客户端,适用于 Android 和 Java 应用程序。它支持 HTTP/2、连接池、GZIP 压缩、缓存等功能,能够帮助开发者更高效地处理网络请求。

从 Android 4.4 开始 HttpURLConnection 的底层实现采用的是OKHttp。

2. 基本使用

(1)基本使用

  • 添加依赖
implementation "com.squareup.okhttp3.okhttp:$version"
  • 使用OkHttp发送一个GET请求
var client = OkHttpClient()

var request: Request = Request.Builder()
    .url("https://api.example.com/data")
    .build()

var response: Response = client.newCall(request).execute()
var responseData: String = response.body.toString()
  • 使用OkHttp发送一个POST请求
OkHttpClient client = new OkHttpClient();

RequestBody body = new FormBody.Builder()
        .add("key1", "value1")
        .add("key2", "value2")
        .build();

Request request = new Request.Builder()
        .url("https://api.example.com/submit")
        .post(body)
        .build();

Response response = client.newCall(request).execute();
String responseData = response.body().string();

(2)调用流程

OkHttp 的工作流程可以概括为以下几个步骤:

  1. 创建请求:先创建一个OKHttpClient对象,然后通过 Request.Builder 构建一个 Request 对象。
  2. 执行请求:通过 OkHttpClient.newCall(request) 创建一个 Call 对象,调用 execute() (同步)或 enqueue() (异步)方法执行请求。
  3. 分发器:Dispatcher 分发任务,它内部维护队列与线程池,完成请求调配。
  4. 拦截器链:请求会经过一系列的拦截器(如重试、缓存、网络拦截器等),最终发送到服务器。
  5. 获取响应:服务器返回的响应会经过拦截器链处理,最终返回给调用者。
    在这里插入图片描述

3. 源码阅读

3.1 创建请求

(1)首先在 OkHttpClient 构造方法中,通过 Builder 模式构建了 OkHttpClient 对象。作为全局的客户端对象,负责管理请求的配置(如超时、缓存、拦截器等)。

// OkHttpClient.kt
...
constructor() : this(Builder())

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
    ...
}
...

(2)然后通过 Request.Builder 构建一个 Request 对象,也是用了 build 建造者模式。封装了 HTTP 请求到 URL、方法(GET/POST)、请求头、请求体等信息。

// Request.kt
...
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()
    }
    ...
 }
 ...

3.2 执行请求 和 分发器分发请求

(1)首先通过 OkHttpClient.newCall(request) 创建一个 Call 对象,Call 是一个接口,真正的实现是在 RealCall 中。

override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)

(2)然后再通过 RealCall 去执行异步请求或同步请求。

  • 异步请求 enqueue()
// RealCall.kt
override fun enqueue(responseCallback: Callback) {
	// 代码1 用 AtomicBoolean 检查 realcall 是否被用过了
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    // 代码2 调用分发器执行异步请求
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

代码1,用 AtomicBoolean 检查 realcall 是否被用过了。
代码2,调用分发器执行异步请求。这里传的是 AsyncCall,一个 runnable。

下面我们就看一下 Dispatcher 的 enqueue() 方法,

// Dispatcher.kt
...
  // 代码1 异步等待队列
  private val readyAsyncCalls = ArrayDeque<AsyncCall>()

  // 代码2 异步运行队列
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()

  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
    // 代码3 将Call 加入到异步等待队列
      readyAsyncCalls.add(call)

      // 代码4 判断请求是不是 websocket
      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    // 代码5
    promoteAndExecute()
  }
  
   private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      // 代码6 迭代异步等待队列
      while (i.hasNext()) {
        val asyncCall = i.next()
        //代码7 所有同时请求数不能大于64
        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        // 代码8 同一个host同时请求数不能大于5。
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.

		// 代码9 将 call 从异步等待队列移除
        i.remove()
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        // 代码10 将 call 加入到异步正在执行队列中
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      // 代码11 开始执行
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }
  ...

在 enqueue() 中,先将我们的 Call 加入到异步等待队列,然后判断请求是不是 websocket,如果不是的,调用 findExistingCallWithHost() 查找有没有已经存在的 host。如果不存在调用 promoteAndExecute(),迭代异步等待队列。这里有两个限制,所有同时请求数不能大于64。同一个host同时请求数不能大于5。如果两个限制条件都满足,将 call 从异步等待队列移除,并加入到异步正在执行队列中,然后将请求任务交给线程池去执行请求。

接着,我们再看一下 AsyncCall 的 run() 方法,这里是分发器异步请求分发流程,

// RealCall.kt
...
internal inner class AsyncCall(
    private val responseCallback: Callback
  ) : Runnable {
  // 代码1
    @Volatile var callsPerHost = AtomicInteger(0)
      private set

    fun reuseCallsPerHostFrom(other: AsyncCall) {
      this.callsPerHost = other.callsPerHost
    }

    val host: String
      get() = originalRequest.url.host

    val request: Request
        get() = originalRequest

    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 {
      // 代码2 将异步任务放到线程池中
        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!
        }
      }
    }

    override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        timeout.enter()
        try {
        // 代码3 调用责任分发请求
          val response = getResponseWithInterceptorChain()
          signalledCallback = true
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
          if (signalledCallback) {
            // Do not signal the callback twice!
            Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
          } else {
            responseCallback.onFailure(this@RealCall, e)
          }
        } catch (t: Throwable) {
          cancel()
          if (!signalledCallback) {
            val canceledException = IOException("canceled due to $t")
            canceledException.addSuppressed(t)
            responseCallback.onFailure(this@RealCall, canceledException)
          }
          throw t
        } finally {
        // 代码4 请求结束回调
          client.dispatcher.finished(this)
        }
      }
    }
  }

将异步任务放到线程池中后,通过 getResponseWithInterceptorChain() 执行请求,请求完成之后调用分发器 dispatch 的 finished() 方法

internal fun finished(call: AsyncCall) {
    call.callsPerHost.decrementAndGet()
    finished(runningAsyncCalls, call)
  }

  private fun <T> finished(calls: Deque<T>, call: T) {
    val idleCallback: Runnable?
    synchronized(this) {
      if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
      idleCallback = this.idleCallback
    }

    val isRunning = promoteAndExecute()

    if (!isRunning && idleCallback != null) {
      idleCallback.run()
    }
  }

在 finish() 方法中,对请求对 host 数减1,并去启动执行任务的方法。

我们再看一下异步请求中的线程池,

// Dispatcher.kt
  private var executorServiceOrNull: ExecutorService? = null

  @get:Synchronized
  @get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }

Dispatcher 中的线程池是一个核心线程数为 0,最大线程数没有上限,当有任务加入都有新建线程,这是为了能让新来的任务及时执行,而不是等待。

  • 同步请求 executed()
// RealCall.kt
 override fun execute(): Response {
 	// 代码1 同 enqueue() 用 AtomicBoolean 检查 realcall 是否被用过了
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    timeout.enter()
    callStart()
    try {
    	// 代码2 调用分发器执行同步请求
      client.dispatcher.executed(this)
      // 代码3 通过拦截器执行请求
      return getResponseWithInterceptorChain()
    } finally {
    // 代码4 请求完执行同步队列的finished。
      client.dispatcher.finished(this)
    }
  }

代码1,同异步请求。
代码2,调用分发器执行同步请求,这里传入的是 RealCall。
代码3,调用 getResponseWithInterceptorChain() 方法,通过拦截器执行请求,后面再看。先看分发器的逻辑。
代码4,请求完执行同步队列的finished。

接着看分发器的 executed() 实现,

// Dispatcher.kt
// 代码1 同步正在运行队列
private val runningSyncCalls = ArrayDeque<RealCall>()

  /** Used by [Call.execute] to signal it is in-flight. */
  @Synchronized internal fun executed(call: RealCall) {
  	// 代码2 将 Call 添加到同步正在运行队列中
    runningSyncCalls.add(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) {
      if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
      idleCallback = this.idleCallback
    }

	// 代码3 执行一次异步等待队列到异步正在执行队列的检查
    val isRunning = promoteAndExecute()

    if (!isRunning && idleCallback != null) {
      idleCallback.run()
    }
  }

在 Dispatcher 的 executed() 中逻辑很简单,就是将 Call 添加到同步正在运行队列中。同时在同步请求的 finish() 方法中也会执行一次异步等待队列到异步正在执行队列的检查。

3.3 OkHttp 拦截器责任链设计模式

  • 先复习一下责任链模式:
    它是对象行为型模式,为请求创建了一个接收者对象的链,在处理请求的时候执行过滤(各司其职)。
    责任链上的处理者负责处理请求,客户只需要将请求发送到责任链即可,无须关心请求到处理细节和请求到传递,所以职责链将请求的发送者和请求的处理者解耦了。
  • 接着看 RealCall 的 getResponseWithInterceptorChain() 方法,在请求需要执行时,通过 getResponseWithInterceptorChain() 获得请求的结果:Response。
// RealCall.kt
@Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    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
    )

    var calledNoMoreExchanges = false
    try {
      val response = chain.proceed(originalRequest)
      if (isCanceled()) {
        response.closeQuietly()
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
      calledNoMoreExchanges = true
      throw noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        noMoreExchanges(null)
      }
    }
  }

在 getResponseWithInterceptorChain() 方法中,默认有五大拦截器,也可以自定义拦截器,在 OkHttpClient 中 addInterceptor() 自定义应用拦截器(在请求发送前和响应返回后执行)或addNetworkInterceptor() 自定义网络拦截器(在请求发送到网络之前执行)。
将一系列拦截器生成 RealInterceptorChain 拦截器链,通过 proceed(request) 方法将请求传递给下一个拦截器。

  • addInterceptor() 和 addNetworkInterceptor() 的区别:
    getResponseWithInterceptorChain() 中先去创建一个集合对象,addInterceptor() 自定义拦截器在第一个添加到集合中的,addNetworkInterceptor() 自定义网络拦截器是在倒数第二添加的。并且 networkInterceptor 拦截器只能在 http 请求中添加,如果socket 请求中不会添加。
    比如:添加自定义日志拦截器,
    如果是自定义拦截器,那打印的是用户提交的请求;
    如果是自定义网络拦截器,打印的是真正的网络请求的日志;

  • 默认的五大拦截器:

    • 重试重定向拦截器 RetryAndFollowUpInterceptor:在交给下一个拦截器之前,负责判断用户是否取消了请求;在获得了结果之后,会根据响应码判断是否需要重定向,如果满足条件那么就会重启执行所有拦截器。
    • 桥接拦截器 BridgeInterceptor:在交给下一个拦截器之前,负责将 HTTP 协议必备的请求头加入其中(如:Host)并添加一些默认的行为(如:GZIP 压缩);在获得了结果后,调用保存 cookie 接口并解析 GZIP 数据。
    • 缓存拦截器 CacheInterceptor:在交给下一个拦截器之前,读取并判断是否使用缓存;获得结果后判断是否缓存。
    • 连接拦截器 ConnectInterceptor:在交给下一个拦截器之前,负责找到或者新建一个连接,并获得对应的 socket 流;在获得结果后不进行额外的处理。
    • 请求服务器拦截器 CallServerInterceptor:进行真正的与服务器的通信,向服务器发送数据;解析读取的响应数据。

3.4 五大拦截器

(1)重试重定向拦截器 RetryAndFollowUpInterceptor

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

  @Throws(IOException::class)
  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>()
    // 代码1
    while (true) {
    	// 代码2 创建一个ExchangeFinder对象,获取连接(ConnectInterceptor中使用)。
      call.enterNetworkInterceptorExchange(request, newExchangeFinder)

      var response: Response
      var closeActiveExchange = true
      // 是否进行重试逻辑开始
      try {
      	// 如果请求取消了 抛出异常
        if (call.isCanceled()) {
          throw IOException("Canceled")
        }

        try {
        	// 代码3 将请求交给了下一个拦截器
          response = realChain.proceed(request)
          newExchangeFinder = true
        } catch (e: RouteException) {
          // The attempt to connect via a route failed. The request will not have been sent.
          if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
            throw e.firstConnectException.withSuppressed(recoveredFailures)
          } else {
            recoveredFailures += e.firstConnectException
          }
          newExchangeFinder = false
          continue
        } catch (e: IOException) {
          // An attempt to communicate with a server failed. The request may have been sent.
          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
        // 代码4
        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()

        if (++followUpCount > MAX_FOLLOW_UPS) {
          throw ProtocolException("Too many follow-up requests: $followUpCount")
        }

        request = followUp
        priorResponse = response
        // ------------是否进行重定向逻辑结束
      } finally {
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
    }
  }

  // 是否重试
  private fun recover(
    e: IOException,
    call: RealCall,
    userRequest: Request,
    requestSendStarted: Boolean
  ): Boolean {
    // okhttpclient配置不重试
    if (!client.retryOnConnectionFailure) return false

    // 不重试:
    // 1. 如果是 IO 异常(非http2中断异常)表示请求可能发出
    // 2. 如果请求体只能被使用一次(默认是false)
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false

    // 异常不重试:协议异常、IO中断异常(除Socket读写超时之外),ssl认证异常
    if (!isRecoverable(e, requestSendStarted)) return false

    // 是否有更多的路线
    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
  }

 
  @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
        // 需要是HTTP请求
        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)

	// 响应码 3XX 重定向响应
      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 -> {
      
        if (!client.retryOnConnectionFailure) {
          // 如果应用层指示我们不要重试请求,则直接返回null
          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) {
           // 如果之前已经尝试过重试并且再次超时,则放弃重试
          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 从当前客户端所在的 IP 地址到服务器的连接数超过了服务器许可的最大范围
      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
    }
  }

/**
 * 构建基于用户响应的重定向请求。
 * 
 * 该函数用于处理重定向逻辑,根据客户端配置和响应的具体信息判断是否需要跟随重定向。如果条件满足,它将构建一个新的请求对象用于重定向。
 * 
 * @param userResponse 原始请求的响应对象,包含重定向所需的信息,例如重定向URL。
 * @param method 原始请求的HTTP方法,可能会影响重定向请求的构造方式。
 * @return 如果条件满足,返回一个新的重定向请求对象;否则返回null。
 */
  private fun buildRedirectRequest(userResponse: Response, method: String): Request? {
    // 检查客户端是否允许重定向
    if (!client.followRedirects) return null

 // 从响应头中获取重定向位置
    val location = userResponse.header("Location") ?: return null
    // 不支持重定向到无效协议的情况
    val url = userResponse.request.url.resolve(location) ?: return null

    // 如果配置禁止跨SSL和非SSL重定向,则检查协议一致性
    val sameScheme = url.scheme == userResponse.request.url.scheme
    if (!sameScheme && !client.followSslRedirects) return null

    // 大多数重定向不需要请求体,根据方法判断是否保留或修改请求体
    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")
      }
    }

    // 跨主机重定向时,移除所有身份验证头信息
    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 {
    // 重定向最大次数限制
    private const val MAX_FOLLOW_UPS = 20
  }
}

  • 重试限制(请求)

代码1,用了一个 while(true) 死循环。
代码2,创建一个ExchangeFinder对象,获取连接(ConnectInterceptor中使用)。
代码3,调用 realChain.proceed(request),将请求交给了下一个拦截器。如果这里出现了异常就会判断是否需要重试。

具体可以看 recover() 方法,有4种情况不重试:
1.okhttpclient配置不重试
2.如果请求体只能被使用一次
3.异常不重试:协议异常、IO中断异常(除Socket读写超时之外),ssl认证异常
4.是否有更多的路线

  • 重定向规则(响应)

根据得到的 response 判断要不要重定向,判断 followUpRequest() 返回的是否为空,如果是空,不需要重定向,直接返回 response;否则需要重定向。

如果重定向次数超过 20 次,也不会重定向。否则执行 while 中下一次。

followUpRequest() 中主要是根据响应码判断是否要重定向。

响应码说明重定向条件
407代理需要授权,如付费代理,需要验证身份通过proxyAuthenticator获得到了Request。例:添加Proxy-Authorization请求头
401服务器需要授权,如某些接口需要登陆通过authenticator获得到了Request。例:添加Authorization请求头
3XX重定向响应OkHttpClient配置允许重定向
408请求超时。1、用户允许自动重试(默认允许) 2、本次请求的结果不是响应408的重试结果 3、服务器未响应Retry-After(稍后重试),或者响应Retry-After:0。
503服务不可用1、本次请求的结果不是响应503的重试结果 2、服务器明确响应Retry-After:0,立即重试
421从当前客户端所在的IP地址到服务器的连接数超过了服务器许可的最大范围自动再次使用另一个连接对象发起请求

(2)桥接拦截器
桥接拦截器的作用是补全请求头和响应后的处理。

  • 补全请求头
请求头说明
Content-Type请求体类型,如:application/x-www-form-urlencoded
Content-Length/Transfer-Encoding请求体解析方式
Host请求的主机站点
Connection: Keep-Alive默认保持长连接
Accept-Encoding: gzip接收响应体使用gzip压缩
CookieCookie身份识别
User-Agent用户信息,如:操作系统、浏览器等
  • 响应后处理
    得到响应:
    1. 读取 Set-Cookie 响应头并调用接口告知用户,在下次请求则会读取对应的数据设置进入请求头,默认 CookieJar 无实现;
    2. 响应头 Content-Encoding 为 gzip,使用 GzipSource 包装解析。

(3)缓存拦截器

作用:缓存 HTTP 响应,减少重复请求。

  • 缓存规则:
    Http 的缓存我们可以按照行为将他们分为:强缓存和协商缓存。
    • 命中强缓存时,浏览器并不会将请求发送给服务器。强缓存是利用 Http 的返回头中的 Expires 或者 Cache-Control 两个字段来控制的,用来表示资源的缓存时间;
    • 若未命中强缓存,则浏览器会将请求发送至服务器。服务器根据 http 头信息中的 Last-Modify/If-Modify-Since或Etag/If-None-Match 来判断是否命中了协商缓存。如果命中,则 http 返回码为 304,客户端从缓存中加载资源。
  • 缓存策略
    拦截器通过 CacheStrategy 判断使用缓存或发起网络请求。此对象中的 networkRequest 与 cacheResponse 分别代表需要发起请求或者直接使用缓存。
networkRequestcacheResponse说明
NullNot Null直接使用缓存
Not NullNull向服务器发起请求
NullNull要求使用缓存,但是没有缓存,okHttp直接返回504
Not NullNot Null发起请求,若得到响应为304,则更新缓存响应并返回

即:networkRequest 存在则优先发起网络请求,否则使用 cacheResponse 缓存,若都不存在则请求失败。

(4)连接拦截器

object ConnectInterceptor : Interceptor {
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    // 代码1
    val exchange = realChain.call.initExchange(chain)
    val connectedChain = realChain.copy(exchange = exchange)
    return connectedChain.proceed(realChain.request)
  }
}
  • 新建连接 realChain.call.initExchange(chain)
    在 ConnectInterceptor 中调用了 initExchange() 方法,
// RealCall.kt
internal fun initExchange(chain: RealInterceptorChain): Exchange {
    synchronized(this) {
      check(expectMoreExchanges) { "released" }
      check(!responseBodyOpen)
      check(!requestBodyOpen)
    }

    val exchangeFinder = this.exchangeFinder!!
    // 代码2
    val codec = exchangeFinder.find(client, chain)
    val result = Exchange(this, eventListener, exchangeFinder, codec)
    this.interceptorScopedExchange = result
    this.exchange = result
    synchronized(this) {
      this.requestBodyOpen = true
      this.responseBodyOpen = true
    }

    if (canceled) throw IOException("Canceled")
    return result
  }

initExchange() 方法又调用了 exchangeFinder.find() 方法,返回 ExchangeCodec 对象。

// ExchangeFinder.kt
  fun find(
    client: OkHttpClient,
    chain: RealInterceptorChain
  ): ExchangeCodec {
    try {
    // 代码3 生成 RealConnection 对象,是对 Socket 的封装
      val resultConnection = findHealthyConnection(
          connectTimeout = chain.connectTimeoutMillis,
          readTimeout = chain.readTimeoutMillis,
          writeTimeout = chain.writeTimeoutMillis,
          pingIntervalMillis = client.pingIntervalMillis,
          connectionRetryEnabled = client.retryOnConnectionFailure,
          doExtensiveHealthChecks = chain.request.method != "GET"
      )
      // 代码4
      return resultConnection.newCodec(client, chain)
    } catch (e: RouteException) {
      trackFailure(e.lastConnectException)
      throw e
    } catch (e: IOException) {
      trackFailure(e)
      throw RouteException(e)
    }
  }

代码3,生成 RealConnection 对象,是对 Socket 的封装。
代码4,调用 newCodec() 方法,生成 ExchangeCodec 对象。

@Throws(SocketException::class)
  internal fun newCodec(client: OkHttpClient, chain: RealInterceptorChain): ExchangeCodec {
    val socket = this.socket!!
    val source = this.source!!
    val sink = this.sink!!
    val http2Connection = this.http2Connection
	// 代码5 判断使用Http2还是Http1
    return if (http2Connection != null) {
      Http2ExchangeCodec(client, this, chain, http2Connection)
    } else {
      socket.soTimeout = chain.readTimeoutMillis()
      source.timeout().timeout(chain.readTimeoutMillis.toLong(), MILLISECONDS)
      sink.timeout().timeout(chain.writeTimeoutMillis.toLong(), MILLISECONDS)
      Http1ExchangeCodec(client, this, source, sink)
    }
  }

在 newCodec() 中,代码5 判断使用Http2还是Http1。

因为Http是基于TCP/IP协议,最终还是调用connect()方法通过socket建立连接

  • 连接池
    在 ExchangeFinder 中,生成 RealConnection 对象之前,会先判断能不能拿到连接对象复用
// ExchangeFinder.kt
 @Throws(IOException::class)
  private fun findHealthyConnection(
    connectTimeout: Int,
    readTimeout: Int,
    writeTimeout: Int,
    pingIntervalMillis: Int,
    connectionRetryEnabled: Boolean,
    doExtensiveHealthChecks: Boolean
  ): RealConnection {
    while (true) {
      val candidate = findConnection(
          connectTimeout = connectTimeout,
          readTimeout = readTimeout,
          writeTimeout = writeTimeout,
          pingIntervalMillis = pingIntervalMillis,
          connectionRetryEnabled = connectionRetryEnabled
      )

      // Confirm that the connection is good.
      if (candidate.isHealthy(doExtensiveHealthChecks)) {
        return candidate
      }

      // If it isn't, take it out of the pool.
      candidate.noNewExchanges()

      // Make sure we have some routes left to try. One example where we may exhaust all the routes
      // would happen if we made a new connection and it immediately is detected as unhealthy.
      if (nextRouteToTry != null) continue

      val routesLeft = routeSelection?.hasNext() ?: true
      if (routesLeft) continue

      val routesSelectionLeft = routeSelector?.hasNext() ?: true
      if (routesSelectionLeft) continue

      throw IOException("exhausted all routes")
    }
  }
  @Throws(IOException::class)
  private fun findConnection(
    connectTimeout: Int,
    readTimeout: Int,
    writeTimeout: Int,
    pingIntervalMillis: Int,
    connectionRetryEnabled: Boolean
  ): RealConnection {
    if (call.isCanceled()) throw IOException("Canceled")

    // Attempt to reuse the connection from the call.
    val callConnection = call.connection // This may be mutated by releaseConnectionNoEvents()!
    if (callConnection != null) {
      var toClose: Socket? = null
      synchronized(callConnection) {
        if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
          toClose = call.releaseConnectionNoEvents()
        }
      }

      // If the call's connection wasn't released, reuse it. We don't call connectionAcquired() here
      // because we already acquired it.
      if (call.connection != null) {
        check(toClose == null)
        return callConnection
      }

      // The call's connection was released.
      toClose?.closeQuietly()
      eventListener.connectionReleased(call, callConnection)
    }

    // We need a new connection. Give it fresh stats.
    refusedStreamCount = 0
    connectionShutdownCount = 0
    otherFailureCount = 0

    // Attempt to get a connection from the pool.
    if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
      val result = call.connection!!
      eventListener.connectionAcquired(call, result)
      return result
    }

    // Nothing in the pool. Figure out what route we'll try next.
    val routes: List<Route>?
    val route: Route
    if (nextRouteToTry != null) {
      // Use a route from a preceding coalesced connection.
      routes = null
      route = nextRouteToTry!!
      nextRouteToTry = null
    } else if (routeSelection != null && routeSelection!!.hasNext()) {
      // Use a route from an existing route selection.
      routes = null
      route = routeSelection!!.next()
    } else {
      // Compute a new route selection. This is a blocking operation!
      var localRouteSelector = routeSelector
      if (localRouteSelector == null) {
        localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
        this.routeSelector = localRouteSelector
      }
      val localRouteSelection = localRouteSelector.next()
      routeSelection = localRouteSelection
      routes = localRouteSelection.routes

      if (call.isCanceled()) throw IOException("Canceled")

      // Now that we have a set of IP addresses, make another attempt at getting a connection from
      // the pool. We have a better chance of matching thanks to connection coalescing.
      if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
        val result = call.connection!!
        eventListener.connectionAcquired(call, result)
        return result
      }

      route = localRouteSelection.next()
    }

    // Connect. Tell the call about the connecting call so async cancels work.
    val newConnection = RealConnection(connectionPool, route)
    call.connectionToCancel = newConnection
    try {
      newConnection.connect(
          connectTimeout,
          readTimeout,
          writeTimeout,
          pingIntervalMillis,
          connectionRetryEnabled,
          call,
          eventListener
      )
    } finally {
      call.connectionToCancel = null
    }
    call.client.routeDatabase.connected(newConnection.route())

    // If we raced another call connecting to this host, coalesce the connections. This makes for 3
    // different lookups in the connection pool!
    if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
      val result = call.connection!!
      nextRouteToTry = route
      newConnection.socket().closeQuietly()
      eventListener.connectionAcquired(call, result)
      return result
    }

    synchronized(newConnection) {
      connectionPool.put(newConnection)
      call.acquireConnectionNoEvents(newConnection)
    }

    eventListener.connectionAcquired(call, newConnection)
    return newConnection
  }

connectionPool: RealConnectionPool 就是连接池,其实就是一个对象池。

class RealConnectionPool(
  taskRunner: TaskRunner,
  /** The maximum number of idle connections for each address. */
  private val maxIdleConnections: Int,
  keepAliveDuration: Long,
  timeUnit: TimeUnit
) {
...
private val cleanupTask = object : Task("$okHttpName ConnectionPool") {
    override fun runOnce() = cleanup(System.nanoTime())
  }
  private val connections = ConcurrentLinkedQueue<RealConnection>()
  ...
   fun put(connection: RealConnection) {
    connection.assertThreadHoldsLock()

    connections.add(connection)
    cleanupQueue.schedule(cleanupTask)
  }
  }
// ConnectionPool.kt
class ConnectionPool internal constructor(
  internal val delegate: RealConnectionPool
) {
  constructor(
    maxIdleConnections: Int,
    keepAliveDuration: Long,
    timeUnit: TimeUnit
  ) : this(RealConnectionPool(
      taskRunner = TaskRunner.INSTANCE,
      maxIdleConnections = maxIdleConnections,
      keepAliveDuration = keepAliveDuration,
      timeUnit = timeUnit
  ))

  constructor() : this(5, 5, TimeUnit.MINUTES)
  }

连接池就是一个装载 RealConnection 的ConcurrentLinkedQueue 队列。

在put方法中,除了往队列中添加 connection,还启动了一个周期性任务 cleanupTask,在任务中调用cleanup(),清除无效连接。
在默认连接池中,最大允许的空闲连接数是 5,连接最大允许的空闲时间 5 分钟。
1、连接池中 连接对象 闲置了多久 超过了 5 分钟没用的连接,就清理掉
2、连接池中 存放了 大量的 空闲连接对象,超过 5个空闲连接,如何清理?
把空闲时间最长的连接一个个清理掉,至少不超过 5 个(LRU思想)
connection:keep-alive

(5)请求服务拦截器 CallServerInterceptor

/**
 * 此拦截器是拦截链中的最后一个拦截器。它负责向服务器发起网络请求。
 *
 * @param forWebSocket 指示此拦截器是否用于 WebSocket 连接
 */
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    // 将传入的 Chain 转换为 RealInterceptorChain,以获取更详细的上下文信息
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.exchange!!
    val request = realChain.request
    val requestBody = request.body
    val sentRequestMillis = System.currentTimeMillis()

    var invokeStartEvent = true
    var responseBuilder: Response.Builder? = null
    var sendRequestException: IOException? = null
    try {
      // 写入请求头到服务器
      exchange.writeRequestHeaders(request)

      if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
        // 如果请求方法允许带有请求体,并且请求体不为空,则处理请求体
        if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
          // 如果请求头中包含 "Expect: 100-continue",则等待服务器返回 "HTTP/1.1 100 Continue"
          exchange.flushRequest()
          responseBuilder = exchange.readResponseHeaders(expectContinue = true)
          exchange.responseHeadersStart()
          invokeStartEvent = false
        }
        if (responseBuilder == null) {
          if (requestBody.isDuplex()) {
            // 准备一个双工请求体,以便应用程序稍后发送请求体
            exchange.flushRequest()
            val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
            requestBody.writeTo(bufferedRequestBody)
          } else {
            // 如果 "Expect: 100-continue" 的期望已满足,则写入请求体
            val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
            requestBody.writeTo(bufferedRequestBody)
            bufferedRequestBody.close()
          }
        } else {
          // 如果没有收到 "HTTP/1.1 100 Continue" 响应,则不发送请求体
          exchange.noRequestBody()
          if (!exchange.connection.isMultiplexed) {
            // 如果 "Expect: 100-continue" 的期望未满足,防止 HTTP/1 连接被重用
            exchange.noNewExchangesOnConnection()
          }
        }
      } else {
        // 如果请求方法不允许带有请求体或请求体为空,则不发送请求体
        exchange.noRequestBody()
      }

      if (requestBody == null || !requestBody.isDuplex()) {
        // 完成请求发送
        exchange.finishRequest()
      }
    } catch (e: IOException) {
      if (e is ConnectionShutdownException) {
        throw e // 请求未发送,因此没有响应可读取
      }
      if (!exchange.hasFailure) {
        throw e // 请求发送失败,不要尝试读取响应
      }
      sendRequestException = e
    }

    try {
      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 (shouldIgnoreAndWaitForRealResponse(code)) {
        // 如果响应码为 100 或者在 102 到 199 之间,则忽略并等待实际响应
        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) {
        // 如果是 WebSocket 升级连接,确保拦截器看到非空响应体
        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)) {
        // 如果请求或响应头中包含 "Connection: close",则关闭连接
        exchange.noNewExchangesOnConnection()
      }
      if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
        // 如果响应码为 204 或 205,但响应体长度不为零,则抛出异常
        throw ProtocolException(
            "HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
      }
      return response
    } catch (e: IOException) {
      if (sendRequestException != null) {
        sendRequestException.addSuppressed(e)
        throw sendRequestException
      }
      throw e
    }
  }

  /**
   * 判断是否应忽略当前响应并等待实际响应。
   *
   * @param code HTTP 响应码
   * @return 如果应忽略当前响应并等待实际响应,则返回 true;否则返回 false
   */
  private fun shouldIgnoreAndWaitForRealResponse(code: Int): Boolean = when {
    // 如果服务器发送了 100-continue,即使我们没有请求它,也应再次尝试读取实际响应状态
    code == 100 -> true

    // 处理 Processing (102) 和 Early Hints (103),以及任何新的 1xx 状态码,但不包括 100 和 101
    code in (102 until 200) -> true

    else -> false
  }
}

请求头中 Expect 为 100-continue:

  • 使用场景:一般出现于上传大容量请求体或者需要验证。代表了先询问服务器是否愿意接收发送请求体数据。
  • OkHttp 的做法:
    • 如果服务器允许则返回100, 客户端继续发送请求体。
    • 如果服务器不允许则直接返回给用户。
    • 同时服务器也可能会忽略此请求头,一致无法读取应答,此时抛出超时异常。

4. 其它

4.1 OKHttp 如何处理大文件上传和下载?

  • 大文件上传:可以通过 RequestBody 的 create 方法创建流式请求体,避免一次性加载大文件到内存中。
    实现步骤:
    • 创建 RequestBody,使用 File 或 InputStream 作为数据源,避免一次性加载大文件到内存中。
    • 将 RequestBody 封装到 MultipartBody 中,支持文件上传。
    • 构建 Request 并执行上传。
// 1. 创建文件
File file = new File("path/to/large/file.zip");

// 2. 创建 RequestBody
RequestBody requestBody = new MultipartBody.Builder()
    .setType(MultipartBody.FORM)
    .addFormDataPart("file", file.getName(), RequestBody.create(file, MediaType.parse("application/octet-stream")))
    .build();

// 3. 构建 Request
Request request = new Request.Builder()
    .url("https://example.com/upload")
    .post(requestBody)
    .build();

// 4. 执行上传
OkHttpClient client = new OkHttpClient();
try (Response response = client.newCall(request).execute()) {
    if (response.isSuccessful()) {
        System.out.println("Upload successful!");
    } else {
        System.out.println("Upload failed: " + response.code());
    }
}
  • 大文件下载:可以通过 ResponseBody 的 source 方法获取输入流,逐步写入文件,避免内存溢出。
    实现步骤:
    • 构建 Request,设置下载 URL。
    • 执行请求并获取 ResponseBody。
    • 使用 BufferedSink 将响应体写入文件。
// 1. 构建 Request
Request request = new Request.Builder()
    .url("https://example.com/largefile.zip")
    .build();

// 2. 执行下载
OkHttpClient client = new OkHttpClient();
try (Response response = client.newCall(request).execute()) {
    if (response.isSuccessful()) {
        // 3. 获取 ResponseBody
        ResponseBody body = response.body();
        if (body != null) {
            // 4. 创建文件输出流
            File file = new File("path/to/save/largefile.zip");
            try (BufferedSink sink = Okio.buffer(Okio.sink(file))) {
                // 5. 将响应体写入文件
                sink.writeAll(body.source());
            }
            System.out.println("Download successful!");
        }
    } else {
        System.out.println("Download failed: " + response.code());
    }
}
  • 断点续传:通过设置 Range 请求头,下载文件的指定部分。记录已下载的文件大小,从断点处继续下载。
    实现步骤:
    • 检查本地已下载的文件大小。
    • 设置 Range 请求头,从断点处开始下载。
    • 将下载的数据追加到本地文件中。
// 1. 检查本地文件大小
File file = new File("path/to/save/largefile.zip");
long fileSize = file.exists() ? file.length() : 0;

// 2. 构建 Request,设置 Range 请求头
Request request = new Request.Builder()
    .url("https://example.com/largefile.zip")
    .header("Range", "bytes=" + fileSize + "-") // 从断点处开始下载
    .build();

// 3. 执行下载
OkHttpClient client = new OkHttpClient();
try (Response response = client.newCall(request).execute()) {
    if (response.isSuccessful()) {
        ResponseBody body = response.body();
        if (body != null) {
            // 4. 将下载的数据追加到本地文件
            try (BufferedSink sink = Okio.buffer(Okio.appendingSink(file))) {
                sink.writeAll(body.source());
            }
            System.out.println("Download successful!");
        }
    } else {
        System.out.println("Download failed: " + response.code());
    }
}
  • 扩展:可以提到 OKHttp 的 ProgressListener 可以用于监控上传和下载的进度。

4.2 OKHttp 如何处理 SSL/TLS?

  • OKHttp 默认支持 SSL/TLS,并且会自动处理证书验证。开发者可以通过 OkHttpClient.Builder 自定义 SSL 配置,例如设置自定义的信任管理器(TrustManager)或证书(X509Certificate)。

  • 扩展: 可以提到 OKHttp 支持 HTTP/2 的 ALPN(应用层协议协商),能够自动选择最佳的协议进行通信。

4.3 OKHttp如何复用TCP连接

  • OKHttp中有一个连接池,连接池就是一个对象池,用了一个 ConcurrentLinkedQueue 缓存所有的有效连接对象,当我们需要一个连接发起请求时,我们先去连接池中查找。
  • 能够满足复用连接的对象,一定是和本次请求的域名、端口、设置的代理、设置的DNS解析等参数一定是相同的,才能复用。
  • 连接池也会去清理垃圾连接。如超过了5分钟没用过的连接,还有超过了5个闲置连接后,从最久闲置的连接开始执行清理

5. 总结

OkHttp是一个功能强大且灵活的HTTP客户端库,适用于各种网络请求场景。

核心设计思想是链式处理,通过拦截器机制将请求和响应的处理过程分解为多个独立的步骤。它主要由OkHttpClient、Request、Response、Call和Interceptor等组件组成。工作流程包括创建请求、执行请求、经过拦截器链处理、获取响应等。拦截器链是OkHttp的核心机制,内置了重试、缓存、连接、网络等拦截器。此外,OkHttp通过连接池复用连接,通过调度器管理异步请求的并发数量。它的性能优化措施包括HTTP/2支持、GZIP压缩和缓存等。


http://www.kler.cn/a/584185.html

相关文章:

  • IIC通信协议详解与STM32实战指南
  • 如何在Ubuntu上构建编译LLVM和ISPC,以及Ubuntu上ISPC的使用方法
  • Fiora聊天系统本地化部署:Docker搭建与远程在线聊天的实践指南
  • 广告牌倾斜安全监测:保障公共安全的智能化解决方案
  • OpenMCU(三):STM32F103 FreeRTOS移植
  • 【学习笔记】《逆向工程核心原理》03.abex‘crackme-2、函数的调用约定、视频讲座-Tut.ReverseMe1
  • 【LangChain】理论及应用实战(4):Memory
  • 视觉语言模型VLM发展脉络
  • windows第十二章 MFC控件常用消息
  • FANUC机器人几种常用的通讯网络及接口
  • Gone v2 中 Gone-Gin 性能测试报告
  • AUTOSAR_CP_EthernetSwitchDriver
  • 人工智能之数学基础:线性变换及其机器学习领域中的应用
  • Flutter_学习记录_connectivity_plus 检测网络
  • Yashan DB 应用开发
  • Python里matplotlib不显示中文的问题
  • MoonSharp 文档五
  • 前瞻技术:未来生活的新动力与改变
  • spark实验
  • 树与二叉树的遍历