This is the 11th day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021
The basic use
OkHttpClient client =new OkHttpClient(); Request request = new Request.Builder().url("").build(); Call call = client.newCall(request); call.execute(); // Synchronize the requestCopy the code
Request process
Source code analysis
OkHttpClient create
There are two ways to create OkHttpClient: new and Builder.
public OkHttpClient() { this(new Builder()); } public Builder() {dispatcher = new dispatcher (); // Default supported HTTP protocol version protocols = DEFAULT_PROTOCOLS; //OKHttp Connection (Connection) set connectionSpecs = DEFAULT_CONNECTION_SPECS; eventListenerFactory = EventListener.factory(EventListener.NONE); proxySelector = ProxySelector.getDefault(); if (proxySelector == null) { proxySelector = new NullProxySelector(); } cookieJar = CookieJar.NO_COOKIES; socketFactory = SocketFactory.getDefault(); hostnameVerifier = OkHostnameVerifier.INSTANCE; certificatePinner = CertificatePinner.DEFAULT; proxyAuthenticator = Authenticator.NONE; authenticator = Authenticator.NONE; connectionPool = new ConnectionPool(); dns = Dns.SYSTEM; followSslRedirects = true; followRedirects = true; retryOnConnectionFailure = true; callTimeout = 0; connectTimeout = 10_000; readTimeout = 10_000; writeTimeout = 10_000; pingInterval = 0; }Copy the code
As you can see with our simple new OkHttpClient(), OkHttp has done a lot of work for us, and many of the parameters we need are given default values here
- Dispatcher: The main function of the dispatcher is to store Calls (synchronous & asynchronous Calls) through a dual-end queue while executing asynchronous Calls in the thread pool.
- Protocols: default supported Http Protocol version protocol. HTTP_2, protocol. HTTP_1_1.
- ConnectionSpecs: OKHttp Connection (Connection) configuration – connectionSpec. MODERN_TLS, connectionspec.cleartext
- EventListenerFactory: A Call status listener. Note that this is a new feature added to OKHTTP. It is not yet final and will change in a future release.
- ProxySelector: use the default proxySelector;
- CookieJar: There are no cookies by default;
- SocketFactory: generates the Socket using the default Socket factory.
- HostnameVerifier, certificatePinner, proxyAuthenticator, authenticator: security-related Settings;
- ConnectionPool: indicates the connectionPool
- Domain name -> IP address;
- PingInterval: This one has to do with websockets. To maintain a long connection, we must send a ping command at intervals.
Look at the ConnectionSpec MODERN_TLS
Public static final ConnectionSpec MODERN_TLS = new Builder(true).ciphersuites (APPROVED_CIPHER_SUITES) .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2) .supportsTlsExtensions(true) .build(); Public static Final ConnectionSpec CLEARTEXT = new Builder(false).build();Copy the code
OkHttpClient is strongly recommended for global singletons because each OkHttpClient has its own separate connection pool and thread pool, which can be reused to reduce latency and save memory.
RealCall creates (generates calls)
Public Call newCall(Request Request) {return RealCall. NewRealCall (this, Request, false /* for web socket */); } static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket); call.transmitter = new Transmitter(client, call); return call; } private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; }Copy the code
- The RealCall object represents a request that is ready to be executed. It can be canceled.
- A RealCall object represents a Request/Response pair (Stream)
- Also, a RealCall can only be executed once
performcall.execute()
Method (synchronous request)
@override public Response execute() throws IOException {synchronized (this) {// Executed equals true. If (executed) throw new IllegalStateException("Already executed "); executed = true; } transmitter.timeoutEnter(); transmitter.callStart(); try { client.dispatcher().executed(this); return getResponseWithInterceptorChain(); } finally { client.dispatcher().finished(this); }}Copy the code
performcall.enqueue
Method (asynchronous request)
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
Copy the code
You can see that a synchronous request generates a RealCall object and an asynchronous request generates an AsyncCall object.
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
private volatile AtomicInteger callsPerHost = new AtomicInteger(0);
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
}
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
Copy the code
AsyncCall is a subclass of Runnable.
When an asynchronous request is made, the Call object is placed in the Dispatcher, and the interceptors in the chain of interceptors process the request and return the final Response
Dispatcher (Dispatcher)
The Dispatcher is where synchronous and asynchronous calls are kept and is responsible for performing asynchronous AsyncCall.
Public final class Dispatcher {// maxRequests = 64; Private int maxRequestsPerHost = 5; // Thread pool private @nullable ExecutorService; private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); Private Final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); public Dispatcher(ExecutorService executorService) { this.executorService = executorService; } public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; }}Copy the code
- For synchronization requests, the Dispatcher uses a Deque to hold the synchronization task;
- For asynchronous requests, the Dispatcher uses two DeQues, one to hold requests ready for execution and one to hold requests in progress. Why use two deQues? Since the Dispatcher default supports a maximum of 64 concurrent requests, a single Host can execute up to 5 concurrent requests. If this is exceeded, the Call will be placed in readyAsyncCall first. The thread from readyAsyncCall is then moved into runningAsynCalls to execute the request.
Execute the process
To synchronize a request to a request, you can see the following code executed: client.dispatcher().executed(this);
Synchronized void executed(RealCall call) {runningSynccalls.add (call); synchronized void executed(RealCall call) {runningSynccalls.add (call); }Copy the code
After processing by the interceptor, the Response is received and the finally block is executed
void finished(RealCall call) { finished(runningSyncCalls, call); } private <T> void finished(Deque<T> calls, T call) { Runnable idleCallback; synchronized (this) { if (! calls.remove(call)) throw new AssertionError("Call wasn't in-flight!" ); idleCallback = this.idleCallback; } boolean isRunning = promoteAndExecute(); if (! isRunning && idleCallback ! = null) { idleCallback.run(); }}Copy the code
private boolean promoteAndExecute() { assert (! Thread.holdsLock(this)); List<AsyncCall> executableCalls = new ArrayList<>(); boolean isRunning; synchronized (this) { for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall asyncCall = i.next(); if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity. if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity. i.remove(); asyncCall.callsPerHost().incrementAndGet(); executableCalls.add(asyncCall); runningAsyncCalls.add(asyncCall); } isRunning = runningCallsCount() > 0; } for (int i = 0, size = executableCalls.size(); i < size; i++) { AsyncCall asyncCall = executableCalls.get(i); asyncCall.executeOn(executorService()); } return isRunning; }Copy the code
Take a look at the logic of asynchronous processing
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
Copy the code
Enter the enqueue method
void enqueue(AsyncCall call) { synchronized (this) { readyAsyncCalls.add(call); if (! call.get().forWebSocket) { AsyncCall existingCall = findExistingCallWithHost(call.host()); if (existingCall ! = null) call.reuseCallsPerHostFrom(existingCall); } } promoteAndExecute(); }Copy the code
private boolean promoteAndExecute() { assert (! Thread.holdsLock(this)); List<AsyncCall> executableCalls = new ArrayList<>(); boolean isRunning; synchronized (this) { for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall asyncCall = i.next(); if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity. if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity. i.remove(); asyncCall.callsPerHost().incrementAndGet(); executableCalls.add(asyncCall); runningAsyncCalls.add(asyncCall); } isRunning = runningCallsCount() > 0; } for (int i = 0, size = executableCalls.size(); i < size; i++) { AsyncCall asyncCall = executableCalls.get(i); asyncCall.executeOn(executorService()); } return isRunning; }Copy the code
If the total number of requests being executed <= 64&& the number of requests being executed by a single Host <=5, the request is added to the runningAsyncCalls collection, followed by execution using the thread pool, otherwise it is put into the readyAsyncCalls collection.
AsyncCall is a subclass of Runnable (indirect), so AsyncCall’s asynccall.executeon () method is eventually called in the thread pool to execute an asynchronous request:
void executeOn(ExecutorService executorService) { assert (! Thread.holdsLock(client.dispatcher())); boolean success = false; try { executorService.execute(this); success = true; } catch (RejectedExecutionException e) { InterruptedIOException ioException = new InterruptedIOException("executor rejected"); ioException.initCause(e); transmitter.noMoreExchanges(ioException); responseCallback.onFailure(RealCall.this, ioException); } finally { if (! success) { client.dispatcher().finished(this); // This call is no longer running! }}Copy the code
The execution logic and synchronization execution logic are basically the same.
Interceptor processing
@Override protected void execute() {
boolean signalledCallback = false;
transmitter.timeoutEnter();
try {
Response response = getResponseWithInterceptorChain();
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} catch (Throwable t) {
cancel();
if (!signalledCallback) {
IOException canceledException = new IOException("canceled due to " + t);
canceledException.addSuppressed(t);
responseCallback.onFailure(RealCall.this, canceledException);
}
throw t;
} finally {
client.dispatcher().finished(this);
}
}
Copy the code
Enter the getResponseWithInterceptorChain this method
Response getResponseWithInterceptorChain() throws IOException { List<Interceptor> interceptors = new ArrayList<>(); // Custom interceptors.addall (client.interceptors()); interceptors.add(new RetryAndFollowUpInterceptor(client)); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); if (! forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); boolean calledNoMoreExchanges = false; try { Response response = chain.proceed(originalRequest); if (transmitter.isCanceled()) { closeQuietly(response); throw new IOException("Canceled"); } return response; } catch (IOException e) { calledNoMoreExchanges = true; throw transmitter.noMoreExchanges(e); } finally { if (! calledNoMoreExchanges) { transmitter.noMoreExchanges(null); }}}Copy the code
In this method, We, in turn, add the user-defined interceptor, retryAndFollowUpInterceptor, BridgeInterceptor, CacheInterceptor, ConnectInterceptor, NetworkInterceptors, CallServerInterceptor, and pass these interceptors to the RealInterceptorChain. The interceptors’ ability to call sequentially, and eventually return a Response from the previous, depends on the Method proceed of the RealInterceptorChain.
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange) throws IOException { ... RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange, index + 1, request, call, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); . return response; }Copy the code
The core of this method is the middle line, which executes the current interceptor’s Intercept method and calls the next (index+1) interceptor. The next call to the (index+1) interceptor depends on the call to the Proceed method of the RealInterceptorChain in the Intercept method of the current interceptor.
The Response of the current interceptor depends on the Response of the Intercept of the next interceptor. Therefore, each interceptor will be called in turn along the chain of interceptors, and when the last interceptor is executed, Response will be returned in the opposite direction, and finally we get the “ultimate version” of Response we need.