1. Introduction
First of all why I want to write this blog, mainly because the network is now using okHTTP3, so in the interview, will ask about the principle of OKHTTP, and the principle of the Internet, also looked at the next, either too short, the core of a brush, or long, looking at the circle. So I wanted to see if I could explain okhttp3 in the simplest way possible. Of course, if you are not familiar with this framework, you can click here to learn all about OKHttp3.
Look at the picture first
This is a simple get request. Let’s start with 1,2,3,4. Let’s start with point 1.
2. Create okhttp
The first thing you need to do to use a network request is initialize it and see what properties it has.
File location: okhttpClient.java
final Dispatcher dispatcher;/ / scheduler
final Proxy proxy;/ / agent
final List<Protocol> protocols;/ / agreement
final List<ConnectionSpec> connectionSpecs;// Transport layer version and connection protocol
final List<Interceptor> interceptors;/ / the interceptor
final List<Interceptor> networkInterceptors;// Network interceptor
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector;// Proxy selector
final CookieJar cookieJar;//cookie
final Cache cache;/ / cache cache
final InternalCache internalCache;// Internal cache
final SocketFactory socketFactory;/ / socket factory
final SSLSocketFactory sslSocketFactory;// The socket factory is used for HTTPS
final CertificateChainCleaner certificateChainCleaner;// Verify the acknowledgement reply, which applies to the host name of the HTTPS request connection
final HostnameVerifier hostnameVerifier;// Confirm the host name
final CertificatePinner certificatePinner;/ / certificate chain
final Authenticator proxyAuthenticator;// Proxy authentication
final Authenticator authenticator;// Local province authentication
final ConnectionPool connectionPool;// Link pool overuses connections
final Dns dns; / / domain name
final boolean followSslRedirects;// SSL redirection
final boolean followRedirects;// Local redirect
final boolean retryOnConnectionFailure;// Retry connection failed
final int connectTimeout;// Connection timed out
final int readTimeout;// Read times out
final int writeTimeout;// Write timeout
Copy the code
Okay, so that’s it, that’s all, that’s basically enough, but how to assign is a problem, you can’t write it all in the constructor, right? What are some good design patterns? The kind that separates usage from complex builds? Congratulations, that’s the builder pattern.
Sure enough, okHttp3 uses it too. Look at the source code
public static final class Builder {...public Builder(a) {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
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;
connectTimeout = 10 _000;
readTimeout = 10 _000;
writeTimeout = 10 _000;
pingInterval = 0;
}
public OkHttpClient build(a) {
return new OkHttpClient(this); }... }Copy the code
That’s easy, with the default values set, so when we call it from outside it’s easy, just write it like this.
OkHttpClient mClient = new OkHttpClient.Builder() // Builder pattern, create instance
.build();
Copy the code
OK, step 1 is done
To summarize
The first step is to create an instance of okHttp3 using the Builder pattern, which encapsulates the necessary properties for use, such as timeout, interceptor, and so on
3. Create a Request
This is step 2, so step 1 is to create an instance of okHttp3, so step 2 is to create the request information. So without further ado, let’s see what properties it has.
File location: Request
final HttpUrl url; // Interface address
final String method; // Post or get
final Headers headers; // Header field of the Http message
final RequestBody body; If the request is a GET request, the body object passes null. If the request is a POST request, it needs to be set.
final Object tag;
Copy the code
Ok, so if you want to make it easy and easy to automatically set the defaults, let’s continue with our Builder mode
public static class Builder {
HttpUrl url;
String method;
Headers.Builder headers;
RequestBody body;
Object tag;
public Builder(a) {
this.method = "GET";
this.headers = new Headers.Builder();
}
public Request build(a) {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this); }}Copy the code
When you use it directly, it gives you a default Get. But now that it’s wrapped, we can call it this way
Request mRequest = new Request.Builder() // Builder pattern to create request information
.url("https://www.baidu.com")
.build();
Copy the code
OK, step 2 is done
To summarize
The second step is to create an instance of the request information using the Builder pattern, which encapsulates the necessary attributes for use, such as request mode, request header information, interface address, and so on
4. Create the Call object
Ok, moving on to step 3, first of all, from the okHttpClient and the Request object, we can build the Call object that actually makes the Request. Looking at the source code, we know that call is an interface, and it is the RealCall that actually makes the request, and we create it
File location: okhttpClient.java
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
Copy the code
Look what it did
RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
final EventListener.Factory eventListenerFactory = client.eventListenerFactory();
this.client = client; // The okHttpClient instance created in step 1
this.originalRequest = originalRequest; // Request instance created in step 2
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); // Redirect interceptor, it will be said later
// TODO(jwilson): this is unsafe publication and not threadsafe.
this.eventListener = eventListenerFactory.create(this);
}
Copy the code
Okay, step 3 is done
To summarize
Step 3 is to create the RealCall object. The real request is handed to the RealCall class, which implements the Call method and is the real core code.
5. Realcall asynchronous request (part 1)
Start analyzing the last step and this is the key, so let’s do it step by step. Let’s start with our call
call.enqueue(new Callback(){ ... });
Copy the code
Then look at the realCall.enqueue () method
@Override
public void enqueue(Callback responseCallback) {
1.synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
2.client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
Copy the code
- Synchronized (this) ensures that each call can only be executed once and cannot be repeated. If you want the exact same call, you can use the following methods
- Dispatcher (). Enqueue (new AsyncCall(responseCallback))
So let’s look at new AsyncCall(responseCallback)
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback; }}Copy the code
Encapsulate our callback and inherit the Runnable interface. Ok, let’s move on to client.dispatcher().enqueue().
File location: dispatcher.java
synchronized void enqueue(AsyncCall call) {
1.if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
2.runningAsyncCalls.add(call);
3.executorService().execute(call);
} else {
4.readyAsyncCalls.add(call); }}Copy the code
- RunningAsyncCalls. Size () < maxRequests for a maximum of 64 requests. RunningCallsForHost (call) < maxRequestsPerHost)
- If the condition is met, the runnable being requested is added to the asynchronous request queue being executed.
- This AsyncCall is then executed through the thread pool
- AsyncCall is added to the readyAsyncCalls queue if the number of tasks in progress exceeds the set maximum or if the number of hosts currently requested by the network exceeds the set maximum.
To summarize
In the call. The enqueue (new Callback () {… }) after execution, the first thing to do is
Call realcall.call.enqueue () to check whether the current call has been executed and raise an exception. If not, wrap callback as AsyncCall. Then call the dispatcher.enqueue() method (the Dispatcher dispatcher created in okHttpClient).
2. In the dispatcher.enqueue() method, check whether the number of ongoing requests and the number of hosts requesting network requests exceeds the maximum value. If the maximum value is exceeded, the request is placed on a waiting queue; if not, it is placed on an executing queue and the thread pool is called to execute it.
6. Realcall asynchronous request (part 2)
6.1 the executorService. Execute
After lunch, we will continue to analyze the source code, and we will use the thread pool to execute it if the condition is met
executorService().execute(call);
Copy the code
Take a look at our thread pool
public synchronized ExecutorService executorService(a) {
if (executorService == null) {
1.executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher".false));
}
return executorService;
}
Copy the code
If the maximum value is set to integer.max_value, it will affect the performance of the core thread. The answer is no, because in the dispatcher.enqueue() method, there is a limit on the number of requests. If the number of requests exceeds the specified maximum, they will be placed in a waiting queue.
Executorservice.execute (Call), which executes the run() method of the call method, the AsyncCall run method, which is actually in its parent NamedRunnable. In the namedrunable.run () method, we actually call execute(), which is implemented by subclasses, which calls asynccall.execute ().
Executorservice.execute (call) -> namedrunnable.run () -> asynccall.execute () So asynccall.execute ()
6.2 AsyncCall. The execute ()
Look at the source file location: realcall.java
@Override protected void execute(a) {
boolean signalledCallback = false;
try {
1.Response response = getResponseWithInterceptorChain();
2.if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this.new IOException("Canceled"));
} else {
signalledCallback = true;
3.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); }}finally {
4.client.dispatcher().finished(this); }}Copy the code
- Execute the interceptor chain and return Response
- Whether blocker redirection interceptor in the chain has been canceled, if cancelled, is executed responseCallback. OnFailure (), this is our outside in step 3, pass the Callback method Callback the onFailure () () method.
- If not, go onResponse, which is the onResponse() method in Callback(). Returns the result. Of course, this is all in the child thread.
- This is actually calling the Finished method in the Dispatcher method. So let’s see
6.3 the dispatcher. Finished ()
Look at the source code
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
1.if(! calls.remove(call))throw new AssertionError("Call wasn't in-flight!");
2.if (promoteCalls) promoteCalls();
3.runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0&& idleCallback ! =null) { idleCallback.run(); }}Copy the code
- Remove the request from the executing task queue
- Call promoteCalls() to adjust the request queue
- Recalculate the number of requests
6.4 the dispatcher. Finished ()
private void promoteCalls(a) {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.}}Copy the code
It is simply a matter of traversing the waiting queue of requests and joining the queue of executing requests until the number of concurrent requests and the number of hosts currently requesting network requests reaches the upper limit.
At this point, okHTTP asynchrony analysis is complete
1. What is dispatcher? The Dispatcher is used to maintain the status of requests and maintain a thread pool. Used to execute requests.
Isn’t that easy, young man? Is it over? You think too much. Let’s analyze the core part
7. getResponseWithInterceptorChain()
Look at the source
Response getResponseWithInterceptorChain(a) throws IOException {
// Add interceptor, responsibility chain mode
List<Interceptor> interceptors = new ArrayList<>();
// The intercept set when configuring okhttpClient is set by the user
interceptors.addAll(client.interceptors());
// Handles retries and redirects after failures
interceptors.add(retryAndFollowUpInterceptor);
/** Is responsible for converting user-constructed requests into requests sent to the server, and converting responses returned by the server into user-friendly response handling configuration requests and other information. Bridge from application code to network code. First, it builds network requests based on user requests. Then it continues to call the network. Finally, it builds the user response from the network response. * /
interceptors.add(new BridgeInterceptor(client.cookieJar()));
The processing cache configuration returns cached responses based on conditions (response caching exists and is set to invariant, or the response is within an expiration date)
// Set request headers (if-none-match, if-modified-since, etc.) the server may return 304(unmodified)
// The user can configure the cache interceptor
interceptors.add(new CacheInterceptor(client.internalCache()));
// The connection server is responsible for establishing the connection with the server. This is the real request network
interceptors.add(new ConnectInterceptor(client));
if(! forWebSocket) { interceptors.addAll(client.networkInterceptors()); }// Perform the flow operations (write out the request body, get the response data) to send the request data to the server and read the response data from the server
// Encapsulates and parses HTTP request packets
interceptors.add(new CallServerInterceptor(forWebSocket));
// Responsibility chain, add the above interceptor to the responsibility chain
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null.null.null.0, originalRequest);
return chain.proceed(originalRequest);
}
Copy the code
Add interceptors to a collection, use the interceptor collection as a construction parameter, create an object (RealInterceptorChain), and call its proceed method. And then you keep going down.
I was going to write the logic of interceptors again, but, ni, my husband pinched the numbers and realized that you are easy to confuse, so let’s write our own
1. Define interfaces
public interface Interceptor {
interface Chain{
String request(a);
String proceed(String request); }}Copy the code
2. To define a RetryAndFollowInterceptor (redirect interceptor)
public class RetryAndFollowInterceptor implements Interceptor{
@Override
public String interceptor(Chain chain) {
System.out.println("RetryAndFollowInterceptor_start");
String response = chain.proceed(chain.request());
System.out.println("RetryAndFollowInterceptor_start");
returnresponse; }}Copy the code
2. Define another BridgeInterceptor
public class BridgeInterceptor implements Interceptor{
@Override
public String interceptor(Chain chain) {
System.out.println("BridgeInterceptor_start");
String response = chain.proceed(chain.request());
System.out.println("BridgeInterceptor_end");
returnresponse; }}Copy the code
3. The last interceptor, CallServerInterceptor
public class CallServerInterceptor implements Interceptor {
@Override
public String interceptor(Chain chain) {
System.out.println("CallServerInterceptor_start");
System.out.println("---------- send data to server: data is"+ chain.request());
System.out.println("CallServerInterceptor_end");
return "Successful landing."; }}Copy the code
4. Define a RealInterceptorChain object
public class RealInterceptorChain implements Interceptor.Chain{
private List<Interceptor> interceptors;
private int index;
private String request;
public RealInterceptorChain(List<Interceptor> interceptors, int index, String request) {
this.interceptors = interceptors;
this.index = index;
this.request = request;
}
@Override
public String request(a) {
return request;
}
@Override
public String proceed(String request) {
if(index >= interceptors.size()) {
return null;
}
// This is the responsibility chain mode, which adds its index+1 and creates a RealInterceptorChain object
RealInterceptorChain next = new RealInterceptorChain(interceptors, index + 1, request);
Interceptor Interceptor = interceptors.get(index);
returnInterceptor.interceptor(next); }}Copy the code
5. Call it
public static void main(String[] args) {
List<Interceptor> interceptors = new ArrayList<>();
interceptors.add(new RetryAndFollowInterceptor());
interceptors.add(new BridgeInterceptor());
interceptors.add(new CallServerInterceptor());
RealInterceptorChain chain = new RealInterceptorChain(interceptors, 0."");
String result = chain.proceed("xiaoming, 123");
System.out.println("---------- server returns:"+result);
}
Copy the code
Results 6.
Does that make sense? If you don’t, this diagram should make sense.
Does okHTTP feel easy? I haven’t introduced the interceptor yet, so let’s finally introduce the interceptor.
8. Introduction to interceptors
1. User-defined interceptors are used to intercept the server before establishing a link
interceptors.addAll(client.interceptors());
Copy the code
2, RetryAndFollowUpInterceptor retry or failure to redirect the interceptor
interceptors.add(retryAndFollowUpInterceptor);
Copy the code
3. The BridgeInterceptor interceptor calibrates and ADAPTS interceptors that complement the content-type header of user-created requests
interceptors.add(new BridgeInterceptor(client.cookieJar()));
Copy the code
4. CacheInterceptor deals primarily with caching
interceptors.add(new CacheInterceptor(client.internalCache()));
Copy the code
5. The ConnectInterceptor creates a link with the server to create a usable RealConnection(encapsulating java.io and java.nio).
interceptors.add(new ConnectInterceptor(client));
Copy the code
6. User-defined interceptors are used to intercept after a link is established with the server. Only non-sockets can be set
if(! forWebSocket) { interceptors.addAll(client.networkInterceptors()); }Copy the code
7. The CallServerInterceptor sends requests and receives data from the server. The request is written to the IO stream and the response data is read from the IO stream
interceptors.add(new CallServerInterceptor(forWebSocket));
Copy the code
Here is just the interview shorthand, if you need details, please leave a message below.