Simple analysis of okhttp request process

As the network request framework okhttp is mainly promoted by Google, many times we use it directly without knowing how to do it internally. If you know the internal process, it will be much easier to expand and deal with some bugs. Next, let’s briefly analyze the request process.
First we create an okhttpclient

//Use default configuration
   val client = OkHttpClient.Builder().build()

Next, we will explain the get request and post request respectively.

Initialize our url request information

// This is a configuration operation, you can also write the URL directly
   val httpurl = HttpUrl.Builder()
            .scheme("https")
            .host("www.baidu.com")
            // add path
            .addPathSegments("mobileapi2")
            .addPathSegments("index.php")
            //Add parameters
            .addQueryParameter("act", "goods")
            .addQueryParameter("op", "goods_list")
            .build()
           //The final url is like this https//"www.baidu.com/mobileapi2/index.php?act=goods & amp;op=goods_list

If it is a get request, we can then use the addQueryParameter method to add the parameters we need. If it is a post request, we need to create a request body.

 val builder = FormBody.Builder()
        builder.addEncoded("page", "10")
        builder.addEncoded("curpage", "1")
        val formBody = builder.build()

Then build one of our requests by creating request

 val request = Request.Builder()
            .url(httpurl) //Configured url information
          // .get() get request
          // .post(formBody) post request post unique request body
            .build()

finally passed

//Asynchronous
 client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
            }

            override fun onResponse(call: Call, response: Response) {
            }
        })
// Synchronize
client.newCall(request).execute()

Our request has been completed, but how is it handled internally?
Our first choice is to look at what the newCall method does

// Created a new RealCall. RealCall inherits Call. The first parameter is our client.
  override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)

Let’s look at the enqueue method inside RealCall

//When our client is built, the dispatcher is initialized by default, and the dispatcher maintains a thread pool and a request queue internally.
 client.dispatcher.enqueue(AsyncCall(responseCallback))

Create an AsyncCall. This AsyncCall is actually a Runnable, which can be regarded as a task we requested. The dispatcher.enqueue method is to put the task into the waiting queue.

// dispatcher class
 internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
    //Put the task into the waiting queue
      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) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    //Start processing the task
    promoteAndExecute()
  }
// dispatcher class
  private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      while (i.hasNext()) {
        val asyncCall = i.next()
          //The number of requests is greater than or equal to 64, end the loop
        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        // The same host is greater than or equal to 5, end the loop
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
        //Remove from waiting queue
        i.remove()
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        //Put the task into the run queue
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      // This method is to put our tasks into the thread pool and hand them over to the thread pool for processing
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }
//AsyncCall class
  fun executeOn(executorService: ExecutorService) {
      client.dispatcher.assertThreadDoesntHoldLock()

      var success = false
      try {
      //Put the task into the thread pool
        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!
        }
      }
    }

Then the run method of AsyncCall will be executed.

 override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signaledCallback = false
        timeout.enter()
        try {
        // Use the five interceptors provided by default to redirect, configure request headers, cache, establish connections, and make network calls to services
      
          val response = getResponseWithInterceptorChain()
          signaledCallback = true
          // Callback will call back the response body we got
          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 {
          // Error message callback
            responseCallback.onFailure(this@RealCall, e)
          }
        } catch (t: Throwable) {
          cancel()
          if (!signalledCallback) {
            val canceledException = IOException("canceled due to $t")
            Exception.addSuppressed(t) canceled
            responseCallback.onFailure(this@RealCall, canceledException)
          }
          throw t
        } finally {
          client.dispatcher.finished(this)
        }
      }
    }
  }

At this point we have completed the post request
The get request process is simpler than post

// newCall is the same as post, mainly depends on execute
 val response= client.newCall(request).execute()
//
override fun execute(): Response {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    timeout.enter()
    callStart()
    try {
      // this is the call that puts the task into the runningSyncCalls queue through our dispatcher
      client.dispatcher.executed(this)
      // Get the response data of our service through the default five interceptors
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }