OKHttp 源码解析(二)拦截器
游戏SDK架构设计之代码实现——网络框架
OKHttp 源码解析(一)
OKHttp 源码解析(二)拦截器
前言
上一篇解读了OKHttp 的基本框架源码,其中 OKHttp 发送请求的核心是调用 getResponseWithInterceptorChain
构建拦截器链,遍历拦截器,执行请求,执行完成时返回结果。这篇看一下 OKHttp 的拦截器链。
本文查看 OKHttp 源码的版本是 3.4.2.
OkHttp 的拦截器使用了责任链设计模式,使得每个处理者都有机会处理请求,关于责任链设计模式的介绍见文章:
拦截器源码解析
无论是同步请求还是异步请求,OkHttp 都是先调用 getResponseWithInterceptorChain
方法。添加拦截器的顺序就是执行的顺序。
private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
// 添加开发者自定义的拦截器
interceptors.addAll(client.interceptors());
// 失败重连拦截器
interceptors.add(retryAndFollowUpInterceptor);
// 桥接和适配器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//缓存
interceptors.add(new CacheInterceptor(client.internalCache()));
// 链接
interceptors.add(new ConnectInterceptor(client));
if (!retryAndFollowUpInterceptor.isForWebSocket()) {
// 网络
interceptors.addAll(client.networkInterceptors());
}
// 请求服务
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
// 创建拦截器链
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
// 拦截器链执行结果
return chain.proceed(originalRequest);
}
1、开发者自定义的拦截器
OkHttp 允许开发者自定义拦截器并优先执行开发者定义的拦截器。具体做法如下
-
定义拦截器实现 OkHttp 的拦截器接口
Interceptor
public class RetryInterceptor implements Interceptor { public Response intercept(Chain chain) throws IOException { // ... } } // 官网解释:观察、修改并潜在地短路发出的请求和返回的相应响应。 // 通常,拦截器会添加、删除或转换请求或响应的标头。 public interface Interceptor { // 实现方法,处理请求 Response intercept(Chain chain) throws IOException; // 处理者串成的链:责任链模式中的一个角色,让每个处理者都有机会处理请求 interface Chain { // 获取当前的请求 Request request(); // 处理请求 Response proceed(Request request) throws IOException; // 网络链接 Connection connection(); } }
-
通过
OkHttpClient
的addInterceptor
接口添加到拦截器列表里public Builder addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); return this; }
2、RetryAndFollowUpInterceptor
重试重定向拦截器
RetryAndFollowUpInterceptor
在调用 newCall
时,返回 RealCall
对象时创建。
protected RealCall(OkHttpClient client, Request originalRequest) {
this.client = client;
this.originalRequest = originalRequest;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client);
}
/**
* This interceptor recovers from failures and follows redirects as necessary. It may throw an
* {@link IOException} if the call was canceled.
主要管理请求重试次数和重定向的
*/
public final class RetryAndFollowUpInterceptor implements Interceptor {
/**
* How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,
* curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
*/
// 定义最大重试和重定向次数
private static final int MAX_FOLLOW_UPS = 20;
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 这是一个路由、链接池和流的调用逻辑管理类,负责调度协调
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()));
int followUpCount = 0;
Response priorResponse = null;
// 无限循环
while (true) {
// 如果取消掉,释放链接池
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
// 执行拦截器链,获取结果
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
// 通过 recover 方法判断是否需要进行重试,否则中断此次循环,开始下一次循环
// 如果是一些永久故障,则不尝试。1.设置了不允许重试,2.不可再次发送请求异常, 3. 致命的异常 4.没有更多的路由可以尝试
if (!recover(e.getLastConnectException(), true, request)) throw e.getLastConnectException();
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
// 处理同上
if (!recover(e, false, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
// 不是以上异常情况,不进行重试
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// 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();
}
// 处理重定向
Request followUp = followUpRequest(response);
// 不需要重定向,此次请求结束,释放资源
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
// 重定向次数限制,超过一定次数抛出异常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(followUp.url()));
} else if (streamAllocation.stream() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
}
followUpRequest
根据结果响应码处理是否需要重定向。
当服务端数据迁移网址以后,客户端使用原地址请求数据时,服务端会将迁移后的新地址通过 header 返回给客户端,同时携带 3 开头的错误码通知客户端目标地址已修改,需要重定向地址。
客户端收到后根据响应码和新地址重新发出请求,获取目标数据。
/**
* Figures out the HTTP request to make in response to receiving {@code userResponse}. This will
* either add authentication headers, follow redirects or handle a client request timeout. If a
* follow-up is either unnecessary or not applicable, this returns null.
*/
private Request followUpRequest(Response userResponse) throws IOException {
if (userResponse == null) throw new IllegalStateException();
Connection connection = streamAllocation.connection();
Route route = connection != null
? connection.route()
: null;
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
//407 客户端使用HTTP代理服务器,需要代理身份认证
case HTTP_PROXY_AUTH:
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
}
return client.proxyAuthenticator().authenticate(route, userResponse);
// 401 身份验证,服务器需要验证者身份,
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
// 3xx :重定向
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
// "If the 307 or 308 status code is received in response to a request other than GET
// or HEAD, the user agent MUST NOT automatically redirect the request"
// 看以上注释:307、308 ,除 get 、head 外,不会重定向
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
case HTTP_MULT_CHOICE: //300
case HTTP_MOVED_PERM: //301
case HTTP_MOVED_TEMP: // 302
case HTTP_SEE_OTHER: //303
// Does the client allow redirects? 客户端是否允许重定向
if (!client.followRedirects()) return null;
String location = userResponse.header("Location");
if (location == null) return null;
// 将旧地址替换为新地址
HttpUrl url = userResponse.request().url().resolve(location);
// Don't follow redirects to unsupported protocols.
if (url == null) return null;
// If configured, don't follow redirects between SSL and non-SSL.
// http 和 https 之间的切换,不允许重定向
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
// Redirects don't include a request body.
Request.Builder requestBuilder = userResponse.request().newBuilder();
if (HttpMethod.permitsRequestBody(method)) {
// 除 PROPFIND 之外的所有请求都应重定向到 GET 请求。
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
requestBuilder.method(method, null);
}
// 将请求体中有关body的信息删除
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
// 当跨主机重定向时,删除请求头Authorization身份验证信息
if (!sameConnection(userResponse, url)) {
requestBuilder.removeHeader("Authorization");
}
return requestBuilder.url(url).build();
// 4xx 不是重定向
case HTTP_CLIENT_TIMEOUT:
// 408's are rare in practice, but some servers like HAProxy use this response code. The
// spec says that we may repeat the request without modifications. Modern browsers also
// repeat the request (even non-idempotent ones.)
// 408 很少见。但一些服务器(如 HAProxy)使用此响应代码。 规范说我们可以不加修改地重复请求。 现代浏览器也会重复请求(甚至是非幂等的)。
if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
return null;
}
return userResponse.request();
default:
return null;
}
}
重试重定向拦截器总结:
- 开始一个无限循环,调用之后的拦截器获取响应结果
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
- 请求或链接过程中抛出异常,在异常内通过
recover
方法判断是否需要重试。否则返回priorResponse
(链接之前的response
)。 - 根据响应码判断是否需要重定向
followUpRequest
- 不需要重定向结束请求,返回响应,释放资源,需要重定向执行有限次数重定向,超过一定次数抛出异常。
3、BridgeInterceptor
桥连接拦截器
桥梁拦截器是应用程序和HTTP
网络的桥梁。根据用户请求构建网络请求,发送到服务器。将服务器的响应转换为用户能够识别的响应。
public final class BridgeInterceptor implements Interceptor {
private final CookieJar cookieJar;
public BridgeInterceptor(CookieJar cookieJar) {
this.cookieJar = cookieJar;
}
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
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 {
// 请求长度不确定时,使用分块传输 chunked,减少资源消耗,提高效率
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
// 向header中添加 url 域名
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
// 默认设置长链接。长链接就是在 tcp 发送完消息后不关闭连接,而是持续连接,
// 因为TCP 连接需要三次握手四次挥手,为节省资源减少消耗,在后续传输中重用
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) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
// 获取 客户端的cookie信息
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());
}
Response networkResponse = chain.proceed(requestBuilder.build());
// 根据返回的信息是否保存到cookie中
// CookieJar.NO_COOKIES 或者没有要保存的信息时,不保存
// 否则调用 saveFromResponse ,由客户端保存
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// 如果有响应体且是gzip类型,将响应体解压为 gzip 对象
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
BridgeInterceptor
桥连接拦截器总结:
- 添加请求头,将用户请求转换为能够进行网络访问的请求。
- 执行拦截器链的下一个拦截器方法。
- 获取响应,将响应转成应用可识别的response。
4、CacheInterceptor
缓存拦截器
public final class CacheInterceptor implements Interceptor {
public CacheInterceptor(InternalCache cache) {
this.cache = cache;
}
@Override public Response intercept(Chain chain) throws IOException {
// 如果客户端实现缓存对象,则根据url获取本地缓存对象
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 获取缓存策略对象,见下方介绍
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.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
// 不需要访问网络也不需要缓存,返回 504
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(EMPTY_BODY)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// If we don't need the network, we're done.
// 网络请求是空的,返回响应
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
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());
}
}
// If we have a cache response too, then we're doing a conditional get.
// 本地缓存不为空,可以继续使用本地资源
if (cacheResponse != null) {
if (validate(cacheResponse, networkResponse)) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.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();
if (HttpHeaders.hasBody(response)) {
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
}
- 判断本地是否有缓存;
- 根据
缓存策略对象
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
// 发送请问的时间
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
// 获取响应的时间
// 记录时间是为了计算当前缓存是否有效
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
// 请求发送时间
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
// 缓存过期时间
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
// 目标资源最后修改时间
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
// 目标资源的标识,如果目标资源被修改过之后,标识会变,客户端需要用标识对比是否一致,不一致说明资源已被修改,需要重新请求
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
// 缓存的年龄
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
// 获取缓存策略对象
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
// 如果需要网络请求,且客户端只取缓存信息,条件冲突返回一个
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}
根据不同的条件返回缓存策略对象(重点)
private CacheStrategy getCandidate() {
// No cached response.
//没有缓存,请求网络
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// Drop the cached response if it's missing a required handshake.
// 缺少需要的握手,删除缓存的响应
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// If this response shouldn't have been stored, it should never be used
// as a response source. This check should be redundant as long as the
// persistence store is well-behaved and the rules are constant.
/*
凡是响应码为200, 203, 204, 300, 301, 404, 405, 410, 414, 501, 308,若请求头或响应头中不包含noStore(不允许存储缓存),返回true
凡是响应码为303,307,响应头中包含Expires(值为缓存过期时间),或Cache-Control中带有max-age,public,private其中一个,同样不包含noStore,返回true
否则,一律返回false
*/
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
/* Cache-Control:noCache,请求头信息,noCache并不是表面看起来的不缓存
数据,数据也会进行缓存,只是每次在使用本地缓存前需要先进行一次网络
请求验证缓存
If-Modified-Since:请求头中携带的存储客户端缓存最后修改的时间,服务
器将实际文件修改时间与请求头中时间进行对比,若相同返回304码表示目标
资源未更改,客户端可以使用本地缓存,若不同返回200,同时将目标资源返回
给客户端
If-None-Match:与If-Modified-Since类似作用,value为缓存资源的ETag
值(资源唯一标识),到服务端时同样会进行比较,相同返回304,不同返回目
标资源
*/
// 请求包含noCache请求头或If-Modified-Since或者有If-None-Match,访问网络
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() ||hasConditions(request)) {
return new CacheStrategy(request, null);
}
// 缓存产生到现在的时间
long ageMillis = cacheResponseAge();
// 响应缓存最小可用时间
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis,SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
//客户端设置的缓存剩余有效可用时间
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis =SECONDS.toMillis(requestCaching.minFreshSeconds());
}
// 修改过期后还可以使用的时长,未设置时表示过期多久都可以使用
long maxStaleMillis = 0;
CacheControl responseCaching = cacheResponse.cacheControl();
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis =SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
// 如果不需要访问网络,且缓存年龄+客户端认为的最小缓存有效时间<缓存实际有效时长+缓存后仍可使用时长 ————> 可以使用缓存
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
// 缓存年龄+客户端认为的最小缓存有效时间 超过响应缓存的最小有效时间
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
// 缓存年龄超过1天且没有设置过时间,抛出警告
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
// 需要请求网络,请求网络,验证头信息
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
缓存拦截器作用:是请求网络获取新数据还是使用缓存的数据,缓存拦截器的核心在于返回一个什么样的缓存策略。
5、ConnectInterceptor
链接拦截器
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
// 获取一个 StreamAllocation
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
// 创建获取一个健康的链接 findHealthyConnection
HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpStream, connection);
}
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
// 无限循环获取 connection 链接
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
// 如果是一个没有过链接记录的链接,无需检查,直接返回
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
// 检查查找到的链接是否健康,比如是否已经closed、shutdown、outputshutdown,链接是否超时等问题
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
**StreamAllocation
:**是链接、流、路由之间的桥梁。官方解释如下
连接:到远程服务器的物理套接字连接。 这些建立起来可能很慢,因此有必要能够取消当前正在连接的连接。
流:在连接上分层的逻辑 HTTP 请求/响应对。 每个连接都有自己的分配限制,它定义了该连接可以承载多少个并发流。 HTTP/1.x 连接一次可以携带 1 个流,SPDY 和 HTTP/2 通常携带多个。
调用:流的逻辑序列,通常是初始请求及其后续请求。 我们更愿意将单个呼叫的所有流保持在同一连接上,以获得更好的行为和位置。
此类的实例代表调用,在一个或多个连接上使用一个或多个流。 此类具有用于释放上述每个资源的 API:
noNewStreams() 防止连接在未来被用于新的流。 在 Connection: close 标头之后或连接可能不一致时使用它。
streamFinished() 从此分配中释放活动流。 请注意,在给定时间只有一个流可能处于活动状态,因此有必要在使用 newStream() 创建后续流之前调用 streamFinished()。
release() 删除呼叫对连接的保持。 请注意,如果仍然存在流,这不会立即释放连接。 当调用完成但其响应主体尚未完全消耗时,就会发生这种情况。
支持异步取消。
ConnectionPool
:管理 HTTP 和 SPDY 连接的重用以减少网络延迟。 共享同一地址的 HTTP 请求可能共享一个连接。 此类实现了哪些连接保持打开以供将来使用的策略。
public ConnectionPool() {
// 一个连接池最多有个 5 个空闲链接,每个链接超出 5 分钟无动作被移除
this(5, 5, TimeUnit.MINUTES);
}
// 循环遍历获取连接
RealConnection get(Address address, StreamAllocation streamAllocation) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
// 1.达到最大不可复用限制
// 2.保证和上次请求的是一个地址
// 3.链接不能添加新的流
if (connection.allocations.size() < connection.allocationLimit
&& address.equals(connection.route().address)
&& !connection.noNewStreams) {
streamAllocation.acquire(connection);
return connection;
}
}
return null;
}
// 放入链接
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
// 先判断是否有需要清理的连接
if (!cleanupRunning) {
cleanupRunning = true;
// 执行清理回收算法。
// 内部逻辑:找到最不活跃的连接,当空闲的连接超过5个后删除这个不活跃的连接。
// 内部是死循环,当所有连接都是活跃状态时,暂停执行回收机制,直到池中无连接。
executor.execute(cleanupRunnable);
}
// 将连接添加到连接池
connections.add(connection);
}
连接拦截器:
- 根据地址、证书、DNS 、最大限制流、超空闲时间等条件确定一个连接是否能复用。
- 创建一个新的连接。