This is the second day of my participation in the August Text Challenge.More challenges in August

Got a problem

OkHttp calls the string() method on the data returned in the background, converts it to a string, and then serializes it. Recently, when I connected to an old system, the format of the data returned in the background was GB2312 instead of UTF-8, and no content-Type field was set in the Header. As a result, when I directly obtained the data, OkHttp would use UTF-8 by default to convert the data, resulting in garbled characters. Specific analysis of the source code is as follows:

  fun string(a): String = source().use { source ->
    source.readString(charset = source.readBomAsCharset(charset()))
  }
  
  private fun charset(a)= contentType()? .charset(UTF_8) ? : UTF_8Copy the code

You get the ContentType in Exchange,

val contentType = response.header("Content-Type")
Copy the code

For the sake of requirements, the byte array can only be obtained directly through Response during use and then converted manually, which results in some additional operations being added to the encapsulated calling method, for now.

Seek a solution

When I looked at the code later, I wondered if I could handle it with an interceptor, which turned out to be too young.

Write an interceptor first and then set it in the OkHttp constructor.

class LoggingInterceptor implements Interceptor {
    @Override public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        if (response.request().url().encodedPath().toString().equals("/xxx/xxx")) {
            return response.newBuilder().header("Content-Type"."GB2312").build();
        }
        returnresponse; }}Copy the code

When the setup was complete, I felt that the problem must have been solved. Did not expect garbled code or no change, after a little bit, look for, it seems to see some eyebrows.

We call responseBody.string () to get the data, and we call source.readString(charset) to process the data.

  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
    }
  }
Copy the code

Our interceptor is in effect, but here the response has no content-type, which means that the method was called before our interceptor. So is this the case? The best solution is to read the source code.

So let’s first look at the constructor,

Here we use the Builder pattern to construct objects,

class Builder constructor() {
    internal var dispatcher: Dispatcher = Dispatcher()
    internal var connectionPool: ConnectionPool = ConnectionPool()
    // Incoming interceptor
    internal val interceptors: MutableList<Interceptor> = mutableListOf()
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
    ......
  }
Copy the code

Once constructed, we create a Request to initiate the Request through mokHttpClient.newCall (Request).enqueue(). The Request class contains some request-related parameters. Let’s look at the newCall method, which encapsulates our Request into a RealCall object and calls the enQueue method. Let’s go ahead and look at this method.

override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
Copy the code
override fun enqueue(responseCallback: Callback) {
  check(executed.compareAndSet(false.true)) { "Already Executed" }

  callStart()
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}
Copy the code

Check whether the command is successfully executed. If the command is not successfully executed, the command continues. The next callback method, the third line encapsulates our own callback as AsyncCall and then calls the dispatcher.enqueue method, and we move on.

internal fun enqueue(call: AsyncCall) {
  synchronized(this) {
    readyAsyncCalls.add(call)
    // Find a request for the same host
    if(! call.call.forWebSocket) {val existingCall = findExistingCallWithHost(call.host)
      if(existingCall ! =null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  // Execute the request
  promoteAndExecute()
}

// The code is truncated
private fun promoteAndExecute(a): Boolean { 
  synchronized(this) {
    while (i.hasNext()) {
      // The maximum number of requests and the maximum number of requests per host are limited
      if (runningAsyncCalls.size >= this.maxRequests) break 
      if (asyncCall.callsPerHost.get() > =this.maxRequestsPerHost) continue 
      / /...}}for (i in 0 until executableCalls.size) {
    val asyncCall = executableCalls[i]
    // execute through the thread pool
    asyncCall.executeOn(executorService)
  }
  return isRunning
}
Copy the code

This thread pool has a core thread count of 0 and a maximum thread count of int.max_value, so let’s keep going.

fun executeOn(executorService: ExecutorService) {
  var success = false
  try {
    executorService.execute(this)
    success = true
  } catch (e: RejectedExecutionException) {
    responseCallback.onFailure(this@RealCall, ioException)
  }
}
Copy the code

Here you can see that our callback method is invoked for request failure. So AsyncCall is just a Runnable and it’s given to the thread pool to run, so we find the run method and we continue.

override fun run(a) {
  threadName("OkHttp ${redactedUrl()}") {
    try {
      // This is where the chain of responsibility mode is invoked
      val response = getResponseWithInterceptorChain()
      signalledCallback = true
      responseCallback.onResponse(this@RealCall, response)
    } catch (e: IOException) {
      if (signalledCallback) {
      } else {
        responseCallback.onFailure(this@RealCall, e)
      }
    } catch (t: Throwable) {
      ......
    } finally{}}}internal fun getResponseWithInterceptorChain(a): Response {
  // Add our interceptor and the system interceptor
  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)// Build the request chain
  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 {
    // start the call
    val response = chain.proceed(originalRequest)
  } catch (e: IOException) {
  } finally{}}Copy the code

Here you can see that we first add our interceptor and the system interceptor, then build the chain and start calling the Proceded method.

override fun proceed(request: Request): Response {
  ......
  // Call the next interceptor in the chain.
  val next = copy(index = index + 1, request = request)
  val interceptor = interceptors[index]
  val response = interceptor.intercept(next) ?: throw NullPointerException(
      "interceptor $interceptor returned null")...return response
}
Copy the code

In this, copy method is used to obtain the next Chain and then call the next interceptor in the list of interceptors for request. The next interceptor will call in turn, and finally form a call Chain.

The logic here is convoluted. Each interceptor calls the PROCEED method to fetch data from top to bottom, eventually calling the CallServerInterceptor interceptor and requesting data from the network. Once the data has been retrieved, it is sent back to us, which in turn goes back to the interceptor’s calling method, and finally returns to us.

conclusion

In this way, we briefly analyzed the whole process, but of course there are still many things we haven’t seen. So let’s go back problem, can be found in the CallServerInterceptor interceptor invokes exchange. CreateRequestBody (). The buffer () the reference for the encapsulation. This explains why adding content-Type to our custom interceptor has no effect on data processing. Although here we do not have a good solution to the problem we started, but through reading the source code we still learned a lot of knowledge, it seems that this wave is not lost.