Source Analysis okHttp Basic Workflow

Posted by robindean on Sat, 05 Feb 2022 18:27:57 +0100

Basic usage of okHttp

Let's briefly mention the basic usage of okHttp.
okHttp can use both synchronous and asynchronous requests, although synchronous requests cannot be done in the UI thread, which can cause app crashes.

Synchronization Request

//Construct OkHttpClient
final OkHttpClient client=new OkHttpClient.Builder().build();
//Construct Requestor
final Request request=new Request.Builder().url("www.baidu.com").build();
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            //Request Responded
            Response response=client.newCall(request).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}).start();

Asynchronous request

//Construct OkHttpClient
final OkHttpClient client=new OkHttpClient.Builder().build();
//Construct Requestor
final Request request=new Request.Builder().url("www.baidu.com").build();
//Asynchronous request
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        //Request Failure Response
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        //Request Successful Response
    }
});

Basic workflow of okHttp


okHttp has three queues:

/** Ready async calls in the order they'll be run. */
//Wait queue for all tasks to be executed
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
//Executing asynchronous request queue holding all executing asynchronous request tasks
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
//Executing synchronization request queue, holding all executing synchronization request tasks
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

enqueue method
This method passes in an AsyncCall object, each encapsulating all the information required for the request. Also, add the current request to the queue to be executed

void enqueue(AsyncCall call) {
   synchronized (this) {
   	 //Join the waiting queue
     readyAsyncCalls.add(call);
   }
   promoteAndExecute();
 }

PromAndExecute method
1. Traverse the task queue to be executed
2. Judge to skip if the number of tasks being performed exceeds the maximum number of requests specified
3. Decide that if the number of requests for the current domain name exceeds the specified maximum number of requests for a single domain name, the current request will not be processed
4. Remove the current request from the list to be executed and add it to the executing queue and executable list
5. Traverse the executable list and throw all the tasks you just performed into the thread pool to execute them

private boolean promoteAndExecute() {
  assert (!Thread.holdsLock(this));
  
  List<AsyncCall> executableCalls = new ArrayList<>();
  boolean isRunning;
  synchronized (this) {
    //Traversing the task queue to be executed
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall asyncCall = i.next();
	  //Judge to skip if the number of tasks being performed exceeds the specified maximum number of requests
      if (runningAsyncCalls.size() >= maxRequests) break; 
      //Determine that if the number of requests for the current domain name exceeds the specified maximum number of single domain name requests, the current request will not be processed
      if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; 
	  //Remove the current request from the list to execute and add it to the executing queue and executable list
      i.remove();
      executableCalls.add(asyncCall);
      runningAsyncCalls.add(asyncCall);
    }
    isRunning = runningCallsCount() > 0;
  }
  //Traverse the executable list, throw all the tasks you just left to be executed into the thread pool to execute
  for (int i = 0, size = executableCalls.size(); i < size; i++) {
    AsyncCall asyncCall = executableCalls.get(i);
    asyncCall.executeOn(executorService());
  }

  return isRunning;
}

When dropped to the thread pool, the execute method for each request to AsyncCall is executed
The execute() method has two main functions:
1. Call the getResponseWithInterceptorChain method to construct an interceptor chain to process the request
2. Call dispatcher at the end of the task. Finish method, which moves the current request out of the executing queue, traverses the queue to be executed again, and pushes the task to be executed into the thread pool for execution

protected void execute() {
    .....
    try {
      //Construct interceptor chains to respond to various interceptors
      Response response = getResponseWithInterceptorChain();
      signalledCallback = true;
      responseCallback.onResponse(RealCall.this, response);
    } catch (IOException e) {
     ...
    } catch (Throwable t) {
      ...
    } finally {
      //End of task, call finished method
      client.dispatcher().finished(this);
    }
  }
}

Let's first look at the finished method, followed by getResponseWithInterceptorChain.
The finished method calls the promoteAndExecute() method again. Remember that the function of this method is to traverse the queue to be executed and drop the task to be executed into the thread pool, because when the request is executed, the thread pool is free, and if there were any requests to be executed before, the execution is called again.

private <T> void finished(Deque<T> calls, T call) {
  Runnable idleCallback;
  synchronized (this) {
  	//Remove the current task from the executing queue
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    idleCallback = this.idleCallback;
  }
  //Mainly here, the promoteAndExecute() method is called again
  boolean isRunning = promoteAndExecute();

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

So here we have a look at how okHttp pushes requests. Here's what each request task does in the thread pool, starting with the figure above

The thread pool executes the execute() method for each requested task.
Call getResponseWithInterceptorChain in execute to construct interceptor chain to process request

protected void execute() {
    .....
    try {
      //Construct interceptor chains to respond to various interceptors
      Response response = getResponseWithInterceptorChain();
      signalledCallback = true;
      responseCallback.onResponse(RealCall.this, response);
    } catch (IOException e) {
     ...
    } catch (Throwable t) {
      ...
    } finally {
      //End of task, call finished method
      client.dispatcher().finished(this);
    }
  }
}

getResponseWithInterceptorChain() method
This method constructs an interceptor chain object and calls its proceed method

Response getResponseWithInterceptorChain() throws IOException {
    List<Interceptor> interceptors = new ArrayList<>();
    //Add user-defined interceptors
    interceptors.addAll(client.interceptors());
    //Adding reconnection and redirection interceptors
    interceptors.add(retryAndFollowUpInterceptor);
    //Add request headers, cookie s, compression processing interceptors
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //Add Cache Processing Interceptor
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //Add Connection Interceptor
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //Add Send Request Interceptor
    interceptors.add(new CallServerInterceptor(forWebSocket));
	//Construct interceptor chain object, index passes in 0
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
    	originalRequest, this, eventListener, client.connectTimeoutMillis(),
    client.readTimeoutMillis(), client.writeTimeoutMillis());
	//Call proceed method of chain object
    Response response = chain.proceed(originalRequest);
    if (retryAndFollowUpInterceptor.isCanceled()) {
      closeQuietly(response);
      throw new IOException("Canceled");
    }
    return response;
  }

chain.proceed method

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
  .....
  //Construct a new intercept chain object using the original interceptor list, index+1
  RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
      connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
      writeTimeout);
  //Interceptor to get current index
  Interceptor interceptor = interceptors.get(index);
  //Call the intercept method of the current interceptor and pass in a new intercept chain object (index already + 1)
  Response response = interceptor.intercept(next);
  .....
  return response;
}

interceptor.intercept method
All interceptors except CallServerInterceptor. Intercept method, call chain.proceed() method,

public Response intercept(Chain chain) throws IOException {
  .....
  1,Do your own interceptor duties
  2,call chain.proceed()Method, because of the incoming chain For newly constructed, index Add 1 and re-execute the above chain.proceed When using the method,
  The acquired interceptor is the next one and will be constructed index+2 New intercept chain object, passed in index+1 Interceptors for
}

Chain will not be called until the last interceptor, CallServerInterceptor. Proceed() method, instead of requesting the network, constructs the return Response object

Topics: Java http https