Explorer le cadre open source Android - 1. Okhttp Source Analysis

Jin Yang 2021-09-15 05:52:37
explorer le cadre open source


Utiliser

1. Ajouter une dépendance

implementation 'com.squareup.okhttp3:okhttp:3.14.9'
Copier le Code

2. Méthode de demande commune

1. SynchroniserGETDemande

  • L'opération demandée est bloquée,Jusqu'àhttpRetour de la réponse
1. CréationOkHttpClientObjet
  1. Création directe
val client = OkHttpClient()
Copier le Code
  1. AdoptionBuilderCréation de modèles
val client = OkHttpClient.Builder()
.build()
Copier le Code
2. CréationRequestObjet
val request = Request.Builder()
.url("https://www.baidu.com")
.get()
.build()
Copier le Code
3. Oui.requestEncapsulé danscallObjet
val call = client.newCall(request)
Copier le Code
4. Appelezcall.executeEnvoyer une demande de synchronisation
val response = call.execute()
if (response.isSuccessful) {
log(response.body()?.string())
} else {
log(IOException("Unexpected code $response").message)
}
Copier le Code
  • Attention!: Doit être appelé dans le Sous - thread ,Après l'envoi de la demande,Le thread actuel est bloqué, Jusqu'à ce qu'une réponse soit reçue
lifecycleScope.launch {
withContext(Dispatchers.IO) {
getSync()
}
}
Copier le Code
  • N'oubliez pas d'ajouter les permissions de demande de réseau
<uses-permission android:name="android.permission.INTERNET" />
Copier le Code
  • Si oui nonhttpsDemande,Peut signaler une erreur:java.net.UnknownServiceException: CLEARTEXT communication to...
  • CLEARTEXT, C'est le sens du texte. ,InAndroid P Sur l'équipement du système ,Si l'application utilise du trafic texte clair non chiffréhttpDemande de réseau,L'application ne peut pas faire de demande réseau,

https Ne sera pas affecté ,De même, Si la nidification est appliquée webView,webView Ne peut être utilisé que httpsDemande;

  • La résolution de cette exception doit être changée enhttpsDemande,Ou AndroidManifest.xmlDocumentApplicationAjouterandroid:usesCleartextTraffic="true"

2. AsynchronegetDemande

  • L'opération demandée n'est pas bloquée,Les résultats de l'exécution sont communiqués à l'appelant au moyen d'un rappel d'interface
  • Les trois premières étapes sont les mêmes ,La quatrième étape appelle la méthode asynchrone call.enqueue
val client = OkHttpClient()
val request = Request.Builder()
.url("https://www.baidu.com")
.get()
.build()
val call = client.newCall(request)
// Appeler une méthode asynchrone enqueue
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
log("onFailure:${e.message}")
runOnUiThread { tv.text = e.message }
}
override fun onResponse(call: Call, response: Response) {
val result = response.body()?.string()
log("onResponse:${result}")
runOnUiThread { tv.text = "onResponse${result}" }
}
})
Copier le Code
  • Attention!:Méthode de rappelonResponse,onFailureOui. Sous - thread/Fils de travail Exécuté dans, Alors...onResponseUtilisé dansrunOnUiThreadPour mettre à jourUI;

3. AsynchronePOST Demande de soumission de paires de valeurs clés

  • Une étape de plus pour créer FormBody,PourPOSTParamètres demandés
val client = OkHttpClient()
//CréationFormBody
val formBody = FormBody.Builder()
.add("k", "wanAndroid")
.build()
val request = Request.Builder()
.url("https://www.wanandroid.com/article/query/0/json")
.post(formBody)
.build()
val call = client.newCall(request)
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
log("onFailure:${e.message}")
runOnUiThread { tv.text = e.message }
}
override fun onResponse(call: Call, response: Response) {
val result = response.body()?.string()
log("onResponse:${result}")
runOnUiThread { tv.text = "onResponse${result}" }
}
})
Copier le Code

4. Post Mode de communication (Télécharger des fichiers)

private fun postFile() {
val client = OkHttpClient()
// Obtenir le fichier à télécharger
val file=File(externalCacheDir,"ljy.txt")
//CréationRequestBody:
val requestBody=RequestBody.create(
MediaType.parse("text/x-markdown; charset=utf-8"),
file
)
val request=Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(requestBody)
.build()
client.newCall(request).enqueue(object : Callback{
override fun onFailure(call: Call, e: IOException) {
log("onFailure:${e.message}")
}
override fun onResponse(call: Call, response: Response) {
log("onResponse:${ response.body()?.string()}")
}
})
}
Copier le Code
  • Besoin deAndroidManifest.xml Ajouter des permissions de lecture et d'écriture , Et demande de permission d'exécution
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
) {
this@MainActivity.requestPermissions(arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE), 10001)
} else {
...
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 10001) {
...
}
}
Copier le Code

5. Téléchargement asynchrone de fichiers

private fun downloadFile() {
val client = OkHttpClient()
val url = "https://pic3.zhimg.com/v2-dc32dcddfd7e78e56cc4b6f689a24979_xl.jpg"
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
log("onFailure:${e.message}")
}
override fun onResponse(call: Call, response: Response) {
val inputStream = response.body()?.byteStream()
val fileOutputStream = FileOutputStream(File(externalCacheDir, "ljy.jpg"))
val buffer = ByteArray(2048)
var len: Int
while (inputStream?.read(buffer).also { len = it ?: -1 } != -1) {
fileOutputStream.write(buffer, 0, len)
}
fileOutputStream.flush()
log("Téléchargement de fichiers réussi")
}
})
}
Copier le Code

6. PostSoumettre le formulaire

  • Parfois, les fichiers sont téléchargés en même temps que d'autres types de champs
private fun sendMultipart() {
val client = OkHttpClient()
val file = File(externalCacheDir, "ljy.jpg")
val requestBody: RequestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("name", "ljy")
.addFormDataPart("age", "18")
.addFormDataPart(
"image", "header.jpg",
RequestBody.create(MediaType.parse("image/png"), file)
)
.build()
val request: Request = Request.Builder()
.header("Authorization", "Client-ID " + "...")
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
log("onFailure:${e.message}")
}
override fun onResponse(call: Call, response: Response) {
log("onResponse:${response.body()?.string()}")
}
})
}
Copier le Code

Paramètres communs

1. Réglage du temps d'arrêt

val client = OkHttpClient.Builder()
.connectTimeout(30,TimeUnit.SECONDS)
.readTimeout(60,TimeUnit.SECONDS)
.writeTimeout(90,TimeUnit.SECONDS)
.build()
Copier le Code

2. Définir le cache

//Définir le chemin et la taille du cache, Et l'intercepteur de cache
val client = OkHttpClient.Builder()
.addNetworkInterceptor(CacheInterceptor())
.cache(
Cache(
File(cacheDir, "httpCache2"),
100 * 1024 * 1024L
)
).build()
//Intercepteur de cache
class CacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request: Request = chain.request()
val var10000: Response
val response: Response
if (NetUtil.isNetworkAvailable(this@OkHttpDemoActivity)) {
//S'il y a un filet,Retour à un30 Réponse interne efficace ,Et30La même requête sera lue directement à partir du cache en quelques secondes
response = chain.proceed(request)
//ConstruiremaxAge = 30SecondCacheControl
val cacheControl = CacheControl.Builder()
.maxAge(30, TimeUnit.SECONDS)
.build()
.toString()
var10000 = response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control") //Remplissage30SecondCacheControl
.header("Cache-Control", cacheControl)
.build()
} else {
// Sans filet ,Reconstruire une requête qui force à lire à partir du cache avec la requête originale
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build()
var10000 = chain.proceed(request)
}
return var10000
}
}
Copier le Code
  • OkHttpClient.cacheLe paramètre d'entréeCacheLes constructeurs sont les suivants:
public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
Copier le Code
  • Ça marche aussi. DiskLruCache;

3. Configuration Échec retry

val client = OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.build()
Copier le Code

4. Persistancecookie

// Ajouter une dépendance de bibliothèque tripartite
implementation 'com.zhy:okhttputils:2.6.2'
//Persistancecookie,Tiens bon.sessionSession:
val cookieJar = new CookieJarImpl(new PersistentCookieStore(CommonModule.getAppContext()))
val client = OkHttpClient.Builder()
.cookieJar(cookieJar)
.build()
Copier le Code

Analyse du code source

Request

  1. Request.Builder()La méthode de construction est la suivante:,methodPar défautGET
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
//RequestMéthode de construction
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tags = Util.immutableMap(builder.tags);
}
Copier le Code
  1. Request.BUilderDepostLa méthode est la suivante::
public Builder post(RequestBody body) {
return method("POST", body);
}
public Builder method(String method, @Nullable RequestBody body) {
if (method == null) throw new NullPointerException("method == null");
if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
if (body != null && !HttpMethod.permitsRequestBody(method)) {
throw new IllegalArgumentException("method " + method + " must not have a request body.");
}
if (body == null && HttpMethod.requiresRequestBody(method)) {
throw new IllegalArgumentException("method " + method + " must have a request body.");
}
this.method = method;
this.body = body;
return this;
}
Copier le Code

OkHttpClient

  1. OkHttpClient La méthode de construction est la suivante: :
public OkHttpClient() {
this(new Builder());
}
// builderLes valeurs par défaut sont fournies dans la méthode de construction pour:
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
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;
}
public OkHttpClient build() {
return new OkHttpClient(this);
}
Copier le Code
  1. OkHttpClient.newCall
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
// Appelé en interne RealCall.newRealCall:
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;
}
//RealCallLa méthode de construction est la suivante::
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
}
Copier le Code

Call

  1. Call.execute Sync request method source
// Call Ça dépend. RealCallMise en œuvre,dispatcherResponsable principalement de l'enregistrement et de la suppression des demandes de synchronisation
@Override public Response execute() throws IOException {
//Jugementexecuted, Assurez - vous que le même HTTP La demande n'est exécutée qu'une seule fois
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart();
try {
//AppelezdispatcherDeexecutedAjouter une demande à la file d'attente des demandes de synchronisation
client.dispatcher().executed(this);
// Obtenu par la chaîne d'interception response
return getResponseWithInterceptorChain();
} finally {
// Récupérer la demande de synchronisation
client.dispatcher().finished(this);
}
}
Copier le Code
  1. Call.enqueue Source de la méthode de demande asynchrone
//RealCallMise en œuvre:
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
//Assurez - vous quecallUne seule fois
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
Copier le Code
  • Vous pouvez voir qu'ils ont tous appelédispatcherMéthode
DispatcherCalendrier des tâches
  • Demandes de contrôle de la concurrence,Les variables suivantes sont principalement maintenues
/**
Nombre maximum de demandes concurrentes
*/
private int maxRequests = 64;
/**
Nombre maximum de demandes par hôte
*/
private int maxRequestsPerHost = 5;
/**
Pool de fils de consommation
*/
private ExecutorService executorService;
/**
File d'attente de requêtes asynchrones à exécuter
*/
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**
Exécution de la file d'attente de requêtes asynchrones
*/
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/**
Exécution de la file d'attente des demandes de synchronisation
*/
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
Copier le Code
  • dispatcherDeexecutedLa méthode est la suivante::
synchronized void executed(RealCall call) {
//Ajouter une demande à la file d'attente des demandes de synchronisation
runningSyncCalls.add(call);
}
Copier le Code
  • dispatcher().finished Pour récupérer les demandes de synchronisation ,La réalisation est la suivante:
void finished(RealCall call) {
finished(runningSyncCalls, call);
}
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
// Supprimer la demande de synchronisation
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
boolean isRunning = promoteAndExecute();
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
Copier le Code
  • dispatcherDeenqueueLa méthode est la suivante::
void enqueue(AsyncCall call) {
synchronized (this) {
//Ajouter une demande à une file d'attente de demandes asynchrones préparée
readyAsyncCalls.add(call);
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!call.get().forWebSocket) {
//Adoptionhost Trouver ce qui existe déjà Call
AsyncCall existingCall = findExistingCallWithHost(call.host());
// Réutiliser s'il existe callsPerHost
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
promoteAndExecute();
}
Copier le Code
  • Ses paramètres d'entréeAsyncCall- Oui.RealCallClasse interne,Les paramètres entrants du constructeur sont ceux que nous avons passéscallback,Et dansexecuteAppelé dans la méthodecallback,EtNamedRunnableDerunAppelé dansexecuteMéthodes
final class AsyncCall extends NamedRunnable {
...
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!
}
}
}
@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);
}
}
}
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();
}
Copier le Code
  • Là - hautAsyncCallDeexecuteMoyenne,À la finfinally Aussi appelé finished Pour récupérer les demandes asynchrones
void finished(AsyncCall call) {
call.callsPerHost().decrementAndGet();
finished(runningAsyncCalls, call);
}
Copier le Code
  • finished Et asynchrone promoteAndExecuteMéthodes,Les résultats sont les suivants:
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
//Traverser une file d'attente de demandes asynchrones préparée, Mise en œuvre listEt dans la file d'attente:
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);
}
//Recalculer le nombre de demandes asynchrones synchrones à exécuter
isRunning = runningCallsCount() > 0;
}
// Traverser exécutable AsyncCall list,AppelezexecuteOnMéthode d'exécution du pool de Threads entrants
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
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;
}
public synchronized int runningCallsCount() {
return runningAsyncCalls.size() + runningSyncCalls.size();
}
Copier le Code
Ordre d'invocation des requêtes asynchrones:
  1. Appel du consommateurCall.enqueue(Callback);
  2. Call.enqueueAppelé dansclient.dispatcher().enqueue(new AsyncCall(responseCallback));
  3. dispatcher().enqueueAppelezpromoteAndExecute;
  4. promoteAndExecute Traversée moyenne readyAsyncCalls,Mets - le.executableCallsEtrunningAsyncCallsMoyenne,Et appellerunningCallsCountRecalculer le nombre de demandes asynchrones synchrones à exécuter,Et traverserexecutableCalls,Appelez asyncCall.executeOn(executorService());
  5. asyncCall.executeOnAppel moyenexecutorService.execute(this),Parmi euxthisPourrunnableTypeasyncCall, Il est appelé à la fin runMéthodes;
  6. NamedRunnableDerunAppelé dans la méthodeexecuteMéthodes,asyncCallDans la réalisation deexecuteMéthodes;
  7. asyncCall.executeAppelé dans Response response = getResponseWithInterceptorChain(),Et appellecallback,Appel finaldispatcher().finished;
  8. dispatcher().finished Encore une fois. promoteAndExecuteMéthodes,Jusqu'à ce que toutes les demandes dans la file d'attente soient exécutées;

Chaîne d'interception

  • L'intercepteur estokhttp Un mécanisme puissant , Surveillance du réseau possible , Demande et réponse ,Demande d'échec retry et autres fonctions;
  • Synchrone request asynchrone request above has calls in the sourcegetResponseWithInterceptorChainMéthodes,Les codes sont les suivants:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
// Créer une série d'intercepteurs ,Et mettrelistMoyenne
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
//1. Retry and Failure redirection Interceptor
interceptors.add(new RetryAndFollowUpInterceptor(client));
//2. Intercepteur d'adaptateur de pont ( En - tête de demande supplémentaire ,Mode de codage,Mode de compression)
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//3. Intercepteur de cache
interceptors.add(new CacheInterceptor(client.internalCache()));
//4. Connectez L'intercepteur
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
//5. Réseauio Intercepteur de flux
interceptors.add(new CallServerInterceptor(forWebSocket));
// Créer une chaîne d'intercepteurs chain,Et la mise en œuvrechain.proceedMéthodes
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);
}
}
}
Copier le Code
  • La méthode ci - dessus crée une série d'intercepteurs,Et mettrelistMoyenne, Recréer la chaîne d'interception RealInterceptorChain,Et la mise en œuvrechain.proceedMéthodes
  • proceedLa méthode est la suivante::
@Override public Response proceed(Request request) throws IOException {
return proceed(request, transmitter, exchange);
}
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
...
// Call the next interceptor in the chain.
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;
}
Copier le Code
  • Son Code de base, neuf lignes au - dessus du cœur,Créer la prochaine chaîne d'intercepteurs,Appelezinterceptors.get(index) Obtenir l'intercepteur actuel ,Et la mise en œuvreinterceptor.interceptMéthoderesponseRetour;
  • getResponseWithInterceptorChainEntrée dansindexPour0, L'intercepteur actuel est RetryAndFollowUpInterceptor, Alors Regardons - le. interceptComment la méthode est - elle mise en œuvre?
RetryAndFollowUpInterceptor
  • RetryAndFollowUpInterceptorDeintercept Les codes de méthode sont les suivants:
 @Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//Tu te souviens?interceptor.intercept(next)- Oui.,Donc icirealChain C'est la prochaine chaîne d'interception.
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while (true) {
transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
//Qui appelle la prochaine chaîne d'intercepteursproceedMéthodes
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
continue;
//Quand ça arriveIOExceptionOuRouteExceptionSera exécutérecoverMéthodes
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
// The network call threw an exception. Release any resources.
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
Request followUp = followUpRequest(response, route);
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
// Jugement des temps de retry
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
request = followUp;
priorResponse = response;
}
}
Copier le Code
  • recover Les codes de méthode sont les suivants:
private boolean recover(IOException e, Transmitter transmitter,
boolean requestSendStarted, Request userRequest) {
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
// We can't send the request body again.
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;
// This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false;
// No more routes to attempt.
if (!transmitter.canRetry()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
Copier le Code
  • RetryAndFollowUpInterceptorDeinterceptQui appelle la chaîne d'interception suivante dans la méthodeproceedMéthode d'acquisitionresponse, Et danswhile (true) Déterminer si une nouvelle demande doit être faite en boucle en fonction des résultats anormaux ou des résultats de la réponse, Si cela se produit IOExceptionOuRouteExceptionSera exécutérecoverMéthodes, Et à travers++followUpCount > MAX_FOLLOW_UPS Déterminer le nombre maximal de retraits , Hors de la boucle ;
  • ParRealInterceptorChain.proceedOn sait qu'il va continuer à appeler le prochain intercepteurinterceptMéthodes,PargetResponseWithInterceptorChainL'ordre du milieu indique que le prochain intercepteur estBridgeInterceptor
  • Continue de regarder. BridgeInterceptorDeinterceptMéthodes
BridgeInterceptor
  • BridgeInterceptorDeinterceptLa méthode est la suivante:
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
//SupplémentRequestBodyEn - tête de la demande
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//Qui appelle la prochaine chaîne d'intercepteursproceedMéthodes
Response networkResponse = chain.proceed(requestBuilder.build());
// En - tête de réponse supplémentaire
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
//Response.body Flux d'entrée pour GzipSource,Lire les données de flux de manière décompressée
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
Copier le Code
  • BridgeInterceptorDeinterceptMoyenne, Tout d'abord. RequestBody En - tête de la demande ,Convertir en une demande d'accès au réseau,Puis appelez la prochaine chaîne d'intercepteursproceedMéthode d'acquisitionresponse,Encore une fois.respone En - tête de réponse pour compléter ,Comme définicookieJar,gzipDécompresser, Réponse à la demande de retour response Convertir en disponible pour l'utilisateur response;
  • Qui appelle la prochaine chaîne d'intercepteursproceed,Le prochain intercepteur sera appeléinterceptMéthodes, L'intercepteur suivant est CacheInterceptor
CacheInterceptor
  • CacheInterceptorDeinterceptLa méthode est la suivante:
@Override public Response intercept(Chain chain) throws IOException {
// Essayer d'obtenir le cache Response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//CacheStrategyPolitique de mise en cache,EntretiennetworkRequest Et cacheResponse
//Obtenir la politique de cache en fonction du temps,Il renvoie le test de cache correspondant en combinaison avec des conditions telles que le temps
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
//Si l'accès au réseau est interdit et qu'il n'y a pas de cache,Directement.new Un échec Response
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
//Si l'accès au réseau n'est pas nécessaire,Renvoie directement le cacheresponse
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
// Accès au réseau requis ,Appelle la chaîne d'interception suivanteproceedAccèsresponse
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
//Si on avait un cache localResponse
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
//Le serveur retourne304,Renvoie directement le cache localresponse
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//S'il y a un cache, Mettre à jour le cache
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//Si ce n'est pas le cas,get Supprimer le cache sur demande
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
Copier le Code
  • CacheInterceptorDeinterceptDivers jugements sont faits sur l'utilisation du cache et si le cache est mis à jour,La prochaine chaîne d'intercepteurs sera également appelée si une demande réseau est utiliséeproceedMéthode d'acquisitionresponse,
  • Alors le prochain intercepteur estConnectInterceptor
ConnectInterceptor
  • ConnectInterceptorDeinterceptLa méthode est la suivante:, Officiellement ouvertokhttpDemande de réseau pour
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
Transmitter transmitter = realChain.transmitter();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
Copier le Code
  • Appelé ci - dessustransmitter.newExchangeAccèsExchange,Et appelle la prochaine chaîne d'intercepteursproceed Passer à l'intercepteur suivant ,Accèsresponse,newExchangeLa méthode est la suivante:
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
...
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
...
}
Copier le Code
  • C'est appelé ci - dessus.exchangeFinder.findAccèsExchangeCodec, Dont adoptionfindHealthyConnectionJe l'ai.RealConnection,Encore.return RealConnection.newCode
 public ExchangeCodec find(OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
...
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
return resultConnection.newCodec(client, chain);
...
}
Copier le Code
  • findHealthyConnectionEncore appeléfindConnectionMéthodes, findConnection Les codes de méthode sont les suivants: , Où le pool de connexion ou new RealConnectionAccèsRealConnection, Et l'a appelé connectMéthodes
  • Le code source est long,Voici une liste des étapes clés
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
...
// D'abord à partir du pool de connexion
if (result == null) {
// Attempt to get a connection from the pool.
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
...
}
...
//Il n'y en a pas dans le pool de connexion pour continuer
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
return result;
}
...
// Le pool de connexion n'est pas juste newUn,Et appelleconnectMéthodes
result = new RealConnection(connectionPool, selectedRoute);
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
// Ajouter au pool de connexion
connectionPool.routeDatabase.connected(result.route());
...
return result;
}
Copier le Code
CallServerInterceptor
  • Voyons enfin. CallServerInterceptorDeintercept
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
//Verssocket Écrire les informations de l'en - tête de la requête dans
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
responseBuilder = exchange.readResponseHeaders(true);
}
if (responseBuilder == null) {
if (request.body().isDuplex()) {
// Prepare a duplex body so that the application can send a request body later.
exchange.flushRequest();
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, true));
//ÉcrirebodyInformation
request.body().writeTo(bufferedRequestBody);
} else {
// Write the request body if the "Expect: 100-continue" expectation was met.
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
} else {
exchange.noRequestBody();
if (!exchange.connection().isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
exchange.noNewExchangesOnConnection();
}
}
} else {
exchange.noRequestBody();
}
//Fin de la demande
if (request.body() == null || !request.body().isDuplex()) {
exchange.finishRequest();
}
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
// Lire l'en - tête de réponse
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
response = exchange.readResponseHeaders(false)
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
exchange.responseHeadersEnd(response);
//Lire la réponsebody
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(exchange.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
exchange.noNewExchangesOnConnection();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
Copier le Code

Je suis Jin Yang,Si vous souhaitez progresser et en savoir plus sur les produits secs,Bienvenue à Wechat public “Le soleil dit” Recevoir mes derniers articles

版权声明
本文为[Jin Yang]所创,转载请带上原文链接,感谢
https://qdmana.com/2021/09/20210915055132980w.html

  1. Mid Autumn Festival special! Use the simplest animation animation to make the most local and trendy holiday blessing greeting card. This romantic male and female tears of Xiao Chen.
  2. Wang Ou went back to the hotel with the man at night. It was suspected that his relationship was open. The netizen replied mercilessly: is the man single
  3. 借助HTML ping属性实现数据上报
  4. APNG在线制作、兼容、播放和暂停
  5. Apng production, compatibilité, lecture et pause en ligne
  6. Mise en œuvre de l'escalade des données avec l'attribut de Ping HTML
  7. Comment envoyer 100 000 requêtes http le plus rapidement possible
  8. JQuery Basics
  9. Front and back end data interaction (V) -- what is Axios?
  10. Serverless is a model architecture invented driven by economic benefits- Grady
  11. Les questions d'entrevue pour les ingénieurs Java d'Internet, les intervieweurs rencontrés sont tous de niveau architecte,
  12. Cinq ans d'entrevue d'expérience en développement Java, découvrez les questions que vous devez poser lors de l'entrevue d'embauche du printemps Java de cette année.
  13. La dernière collection de questions d'entrevue Java haute fréquence organisée cette année, 2021 Java Universal Popular Framework
  14. Intel selected Weilai es8 to promote driverless taxis in Europe
  15. JavaScript operator (1), Web Development Engineer
  16. Trier les questions d'entrevue Javascript, trier les points de connaissance des itinéraires d'apprentissage
  17. Song Mengjun's "sleepless night" triggered an upsurge of dance storm after 00
  18. Module management of "free and open source" front-end spa project crudapi background management system based on Vue and Quasar (14)
  19. Encapsulated PHP sends HTTP requests with curl. Get and post are very easy to use
  20. Front and back end data interaction (V) -- what is Axios?
  21. Flutter: résoudre le futur blocage en utilisant Isolate
  22. Résumé des opérations courantes pour les données de structure de l'arbre frontal
  23. Ant Design Transfer Twin Tree Shuttle box "make Wheels"
  24. De la carte de pensée à la base et à l'approfondissement, prenez note de l'expérience d'entrevue d'un octet sautant le poste de recherche et développement Java.
  25. Apprenez les composants d'implémentation de vue et Publiez - les à NPM
  26. [Questions d'entrevue à haute fréquence] À vous de choisir
  27. Une faible connaissance de beginpath () provoque une superposition de style lors de la peinture d'un dessin en toile
  28. React Hooks, laisse - moi t'emmener étudier.
  29. Comment la copie profonde résout - elle les références circulaires?
  30. JavaScript Advanced Programming (3rd Edition) Reading note 6
  31. Analyse de l'URL
  32. Discussion préliminaire sur xss
  33. Solution: développement de la page Web Wechat, obtenir la fosse Piétinée par le flux d'entrée de la caméra via navigator.mediadevice.getusermedia ()
  34. Des milliers de questions d'entrevue sélectionnées n'ont pas encore ét é effacées.
  35. Les questions d'entrevue de niveau intermédiaire et avancé d'Android au fil des ans sont entièrement incluses, et l'algorithme est distribué microservice
  36. J'ai résumé toutes les questions d'entrevue.
  37. Compréhension de la réactivité des données de vue
  38. Note de service CSS (vi): Flex, page mobile et mise en page réactive
  39. Non-ASCII character ‘\xe5‘ in file kf1.py on line 4, but no encoding declared; see http://python.or
  40. 手把手教你搭建微信小程序服务器(HTTPS)
  41. JavaScript Review sketch - 1
  42. Analyse du bootstrap webpack
  43. sqli-labs-less-18 http头user agent+报错注入
  44. Génération de code nest pour l'outil CLI de nestjs
  45. JS | This
  46. Augmentation des variables
  47. The sinking gs8 raises its flag again. GAC motor's sales are falling endlessly. Is it the car or the people?
  48. Ren Hao's lunch at work today is president Hao wearing a sleeveless coat! Clean and handsome!
  49. Summary of basic knowledge points of JavaScript language (mind map)
  50. The new front-end lady asked: there was a 404 problem refreshing the page in Vue routing history mode
  51. Sqli Labs - less - 18 http header user agent + Error Reporting Injection
  52. Vous apprendrez à construire un serveur d'applet Wechat (https) à la main
  53. Non - ASCII character 'xe5' in file kf1.py on Line 4, but no Encoding declared;Voirhttp://python.or
  54. The new front-end lady asked: there was a 404 problem refreshing the page in Vue routing history mode
  55. En tant que programmeur, quelle est la plus grande tristesse que vous ressentez? L'entrevue d'emploi Java de 2021 dans une grande usine vous demandera:
  56. En tant que programmeur, je n'oublie pas le dernier résumé de mon expérience d'entrevue de stage en Java.
  57. Experts suggested that performers work with certificates, which triggered a collective heated debate. It is meaningless to be accused of repeating the mistakes
  58. The new front-end lady asked: there was a 404 problem refreshing the page in Vue routing history mode
  59. The appearance value of 200000 "Odyssey" is less than 100000, and has become the "sales champion" of household MPV
  60. Les programmeurs Java qui sont entrés dans l'entreprise pendant trois mois ont dû faire face à une correction d'échelle, et les octets ont sauté dans le traitement des questions d'entrevue de JD 360 Netease.