前言
在 Android 开发中,需要网络请求的时候我们一般都选择使用 OkHttp。它的功能特别强大,比如拦截器,自动重连,请求缓存策略,socket 连接池,响应数据 gzip 压缩,HTTP2,文件上传和下载,Cookie 持久化,websocket 等等。下面就从拦截器角度出发学习下它。
拦截器
为什么 OkHttp 要使用拦截器呢?我们想想,一个网络请求是从发起 Request 到得到 Response 的过程,这个过程里面要考虑到重定向,Header 字段处理,请求缓存问题,连接池复用,TCP/TLS,Response 的读写。这个过程任何一个步骤出现问题了,都要尝试自动重连。这是不是特别适合责任链模式呢?那在 OkHttp 里面这些角色分别对应着:控制权的对象是实现 Interceptor 接口的对象,它们要承担起处理各个流程的责任,然后请求者就是 Chain 接口的实现类。那实现的关系图就是:
如果按自己实现步骤代码简化下会是
interface Chain {
fun request():Request
}
class RealInterceptorChain(private var originRequest: Request) : Chain {
override fun request(): Request {
return originRequest
}
}
abstract class Interceptor(private var next: Interceptor?) {
abstract fun intercept(chain: Chain): Response?
fun setNext(next: Interceptor?) {
this.next = next
}
protected fun getNext(): Interceptor? {
return next
}
}
class RetryAndFollowUpInterceptor(next: Interceptor? = null) : Interceptor(next) {
override fun intercept(chain: Chain): Response? {
val request = chain.request()
println("RetryAndFollowUpInterceptor 处理")
if (request.type == 1) {
return Response("RetryAndFollowUpInterceptor say ok")
}
return getNext()?.intercept(chain)
}
}
//..其他 interceptor 类似,省略..
执行测试代码:
fun main() {
val chain = RealInterceptorChain(Request(5))
val list = listOf(
RetryAndFollowUpInterceptor(),
BridgeInterceptor(), CacheInterceptor(), ConnectInterceptor(), CallServerInterceptor()
)
list.forEachIndexed { index, interceptor ->
interceptor.setNext(list.getOrNull(index + 1))
}
println("得到响应:${list.first().intercept(chain)?.info}")
}
得到执行结果:
RetryAndFollowUpInterceptor 处理
BridgeInterceptor 处理
CacheInterceptor 处理
ConnectInterceptor 处理
CallServerInterceptor 处理
得到响应:CallServerInterceptor say ok
通过上面实现步骤会发现,每个控制对象要指明下个询问的控制对象是谁,不太好,怎么解决呢?OkHttp 是在 Chain 里面加了一个方法来调用下个控制对象的 intercept 方法,这个方法就是 proceed 方法
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
}
那上面代码就可以换成
interface Chain {
fun request(): Request
fun proceed(request: Request): Response?
}
class RealInterceptorChain(
private var originRequest: Request,
private var interceptors: List<Interceptor>,
private var index: Int
) : Chain {
override fun request(): Request {
return originRequest
}
override fun proceed(request: Request): Response? {
val next = RealInterceptorChain(originRequest, interceptors, index + 1)
return interceptors.getOrNull(index)?.intercept(next)
}
}
interface Interceptor {
fun intercept(chain: Chain): Response?
}
class RetryAndFollowUpInterceptor : Interceptor {
override fun intercept(chain: Chain): Response? {
val request = chain.request()
println("RetryAndFollowUpInterceptor 处理")
if (request.type == 1) {
return Response("RetryAndFollowUpInterceptor say ok")
}
return chain.proceed(request)
}
}
fun main() {
val list = listOf(
RetryAndFollowUpInterceptor(),
BridgeInterceptor(), CacheInterceptor(), ConnectInterceptor(), CallServerInterceptor()
)
val originRequest = Request(5)
val chain = RealInterceptorChain(originRequest,list,0)
println("得到相应:${chain.proceed(originRequest)?.info}")
}
那有个问题 proceed 里面可不可以不 new 一个 RealInterceptorChain,而是直接通过 index++ 来实现呢?如果每个链调用 proceed 一次是没问题的,但一个链多调用几遍就对应不上了了,所以这里给每个链配置一个对应 index 的链。拦截器的责任链模式大概就说到这里。
接下来就来看看 OkHttp 的拦截器在哪里以及它们分别干了什么工作?在拦截器之前,OkHttp还做了大量工作,这里拿一次异步请求来说:
val mOkHttpClient: OkHttpClient = OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS).build()
val request =
Request.Builder().url("https://www.wanandroid.com/article/list/0/json").build()
mOkHttpClient.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val result = response.body()?.string()
Log.d("goach", "result========$result")
}
})
我们一般会在 OkHttpClient.Builder 配置请求读写超时时间,自己的应用拦截器和网络拦截器和同时连接的个数和时间等待,在 Request.Builder 配置请求的基本信息,比如请求 url,请求头信息等等。再加两个队列 runningAsyncCalls 和 readAsyncCalls 分别缓存执行中的任务和等待中的任务,然后用线程池执行任务,接着就来到了拦截器部分。大概流程如下:
前期工作准备好后,就来到了拦截器模块了。首先第一个就是 RetryAndFollowUpInterceptor 拦截器,它是干嘛的呢?
public class RetryAndFollowUpInterceptor implements Interceptor {
@Nullable
@Override
public Response intercept(@NotNull Chain chain) {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//这个 transmitter 在 newRealCall 时候创建的,它是应用层和网络层的桥梁,保存着 client 和 RealCall,connection,
//连接,请求 <------> 响应,响应体流的读写都要经过它
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while (true) {//通过 while(true) + try catch 方式尝试自动重连
//这里面判断了请求 url 是不是和上一个请求 url 一样,如果一样并且还有路由可以尝试,也就是请求连接还存在,就不用再去创建了,否则创建一个
//ExchangeFinder,它持有了 Transmitter(桥梁),连接池,
// Address(请求url的信息),RealCall,eventListener 事件监听(默认空实现)
transmitter.prepareToConnect(request);
//当外部已经调用了 RealCall 的 cancel 方法,那就抛出异常取消
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
//这里走下一个拦截器,后面任何一个拦截器抛出异常,都会在这里捕获到,然后判断是否尝试重新请求
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
// 路由异常,尝试再与服务器通信失败时候恢复,这时候还没发出去请求,所以直接传false,
// 想要恢复需要满足几个条件,在 OkHttpClient.Builder 里面配置 OkHttpClient 时候没禁用重试,请求还没发出去,
// 不是协议异常,IO流异常,SSL 证书问题,并且还有其他路由可以重试情况下,那就可以恢复过来
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
continue;
} catch (IOException e) {
// 尝试与服务器通信失败。出现了IO异常,请求可能已发送出去了。
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
// 网络调用引发了异常。释放任何资源。
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
// 这里重定向过来时候才有值,设置上个响应体。并且它是不会有 body 体的。
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//这个 Exchange 在 ConnectInterceptor 的 intercept 里面会通过 transmitter 的 newExchange 创建出来,
//然后赋值给 RealInterceptorChain,传给了后面的拦截器,这样后面的拦截器 exchange 就有值了
//在 CallServerInterceptor.readResponseHeaders 通过 initExchange 赋值给了 response
//这里拿的就是 response 的 exchange,它是把请求 Request 转换成 Response 的作用,
// 里面持有的 ExchangeCodec 处理了响应体的 IO 流操作
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();
//如果有重定向,又只让请求一次,这个 isOneShot 可以自定义 RequestBody 体里面重写这个方法设置,设置为true,就代表着 body 体只能让请求一次。
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
//默认支持 20 次的重定向
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//赋值,重定向进入下一轮轮询进行请求
request = followUp;
priorResponse = response;
}
}
//https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Redirections
private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
if (userResponse == null) throw new IllegalStateException();
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
case HTTP_PROXY_AUTH://407 客户端必须在代理服务器上进行身份验证(与之对应的是401)。
// 代理服务器必须返回一个 Proxy-Authenticate 用以进行身份询问。客户端可以返回一个 Proxy-Authorization 信息头给服务器用以验证
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);
case HTTP_UNAUTHORIZED://401 未授权,或认证失败。对于需要登录的网页,服务器可能返回此响应
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT://308,永久性重定向,方法和消息主体都不发生变化(与之对应的是 301),不能用于 GET 和 HEAD 方法重定向,
case HTTP_TEMP_REDIRECT://307,临时重定向,方法和消息主体都不发生变化(与之对应的是302),通常不用于 GET 和 HEAD 方法。
// "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"
//非 GET 请求,和非 HEAD(只请求头部信息,比如使用在获取下载文件大小的情况) 请求
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
case HTTP_MULT_CHOICE://300 特殊重定向,手工设置重定向,包含了一个可能的重定向链接的列表,用户可以从中进行选择。
case HTTP_MOVED_PERM://301,永久性重定向,GET方法不会被改变,但是其他方法(如POST)可以改变成GET方法
case HTTP_MOVED_TEMP://302 临时性重定向,GET方法不会被改变,但是其他方法(如POST)可以改变成GET方法
case HTTP_SEE_OTHER://303 临时性重定向,GET方法不会被改变,但是其他方法(如POST)会改变成GET方法
// 配置是否允许重定向?
if (!client.followRedirects()) return null;
//Location 首部指定的是需要将页面重新定向至的地址
String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// 不支持重定向的协议.
if (url == null) return null;
// 如果协议不一样, 从 SSL(https) 到 non-SSL(http),不进行重定向
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
// Most redirects don't include a request body.
Request.Builder requestBuilder = userResponse.request().newBuilder();
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
if (HttpMethod.redirectsToGet(method)) {//重定向到 GET 方法
requestBuilder.method("GET", null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {//GET 请求移除这些字段
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.
//跨主机重定向时,删除所有身份验证标头
if (!sameConnection(userResponse.request().url(), url)) {
requestBuilder.removeHeader("Authorization");
}
return requestBuilder.url(url).build();
case HTTP_CLIENT_TIMEOUT://408,请求超时。客户端没有在服务器预备等待的时间内完成一个请求的发送。
// 客户端可以随时再次提交这一请求而无需进行任何更改。
// 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.)
if (!client.retryOnConnectionFailure()) {
// The application layer has directed us not to retry the request.
return null;
}
RequestBody requestBody = userResponse.request().body();
if (requestBody != null && requestBody.isOneShot()) {
return null;
}
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
return null;
}
if (retryAfter(userResponse, 0) > 0) {
return null;
}
return userResponse.request();
case HTTP_UNAVAILABLE://503,由于临时的服务器维护或者过载,服务器当前无法处理请求,如果能够预计延迟时间,那么响应中可以包含一个 Retry-After
// 头用以标明这个延迟时间。如果没有给出这个 Retry-After 信息,那么客户端应当以处理500响应的方式处理它。500 一般是服务代码导致的
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null;
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request();
}
return null;
default:
return null;
}
}
}
这里面有个类特别重要 Transmitter ,它是从 OkHttp 配置的请求,连接到响应的一个桥梁,它里面持有了
- OkHttpClient,用来拿到应用层请求的配置;
- RealConnectionPool(连接池),用来为请求找到或创建一个连接,具体操作在 ExchangeFinder 类里面处理;
- Exchange,获取响应体的一个桥梁,通过 HTTP1ExchangeCodec 或者 HTTP2ExchangeCodec 拿到响应体给 Response
通过上面代码注释总结下 RetryAndFollowUpInterceptor 做的事情
- 将请求的地址信息保存到 ExchangeFinder 里面,为后面连接做准备
- 通过一个 while(true) 加 try catch 方式尝试对未发出去的请求出现异常进行重连
- 在 followUpRequest 方法里面处理重定向的响应码,默认支持 20 次重定向
接着就是 BridgeInteceptor 拦截器:
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) {
//告诉服务器实际发送的数据类型,如application/json,application/x-www-form-urlencoded,multipart/form-data 等等
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
//HTTP消息长度,如果gzip压缩, 那 Content-Length 首部指的就是压缩后的大小而不是原始大小
requestBuilder.header("Content-Length", Long.toString(contentLength));
//不是 chunked 模式,即服务器一边产生数据,一边发送给客户端,移除 Transfer-Encoding
requestBuilder.removeHeader("Transfer-Encoding");
} else {
//否则就是 chunked 模式
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
//host ,拼接域名+端口号
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; //header 的编码格式,这里默认使用 gzip 压缩
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
//请求配置的 cookie
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
//访问网站提供你说使用的浏览器类型和版本,操作系统和版本 ,这里给的是 OkHttp 版本
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
//把头部信息回传给 cookieJar
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)) {
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");
//对请求数据进行 gzip 压缩后给 response
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
/** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */
private String cookieHeader(List<Cookie> cookies) {
StringBuilder cookieHeader = new StringBuilder();
for (int i = 0, size = cookies.size(); i < size; i++) {
if (i > 0) {
cookieHeader.append("; ");
}
Cookie cookie = cookies.get(i);
cookieHeader.append(cookie.name()).append('=').append(cookie.value());
}
return cookieHeader.toString();
}
}
它干的事情主要有:
- 处理请求 header 字段,如 Content-Type,Content-Length,Connection 等等
- header 编码格式采用 gzip 压缩,并且会对请求回来的数据进行 gzip 压缩
- 通过 CookieJar 里面做的 cookie 操作就是在这个拦截器里面处理的,通过 loadForRequest 添加到头部 Cookie 字段,然后再 receiveHeaders 回调出去进行存储
再就是 CacheInteceptor :
public final class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
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.
if (networkRequest == null && cacheResponse == null) {
// only-if-cached 模式,如果缓存为空,就返回 504 。
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();
}
// 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) {
// 如果有缓存,并且 304,也就是请求数据没有发生改变,对缓存和网络请求进行合并
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();
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);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
}
- 它只针对 GET 请求进行缓存,如果 only-If-cached 模式,没有缓存就会返回 504,
- 有缓存,就会直接返回缓存的 Response,没有缓存则会进行网络请求,
- 请求返回 304,那就请求数据没有发生改变,那会对请求的数据和缓存进行合并返回 Response
- 请求返回的不是 304,那就更新缓存,对无效的缓存进行删除,并且返回 Response
再就是 ConnectInteceptor 拦截器:
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@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");
//这里就需要用到桥了,并且会在 RetryAndFollowUpInterceptor 拦截器里面创建的 ExchangeFinder 里面在连接池里面找到一个连接,或者创建一个
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
}
这个地方关键点在 newExchange 这里,这里会为请求在连接池里面找到或者创建一个连接,经过一系列方法调用,会来到 ExchangeFinder 里面的 findConnection 方法里面。
public class ExchangeFinder {
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
RealConnection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
hasStreamFailure = false; // This is a fresh attempt.
// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new exchanges.
releasedConnection = transmitter.connection;
//transmitter.connection 同个路由上次使用的连接,noNewExchanges为 true,代表连接创建了,但是没用到,需要从连接池里面移除
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
//noNewExchanges 为 false 时候,这里 transmitter.connection 才不为空
if (transmitter.connection != null) {
//也就是这个连接还可以用,那就直接用这个连接
result = transmitter.connection;
releasedConnection = null;
}
if (result == null) {
//第一次进来,result 为空,那就从线程池里面找
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true;
result = transmitter.connection;//找到了
} else if (nextRouteToTry != null) {
//连接池没找到就赋值当前请求路由,为创建一个连接做准备,nextRouterToTry 在 RetryAndFollowUpInterceptor 拦截器里面的
// prepareToConnect 里面调用的 hasRouteToTry 赋值
selectedRoute = nextRouteToTry;
nextRouteToTry = null;
} else if (retryCurrentRoute()) {
//走到这里noNewExchanges肯定为true,因为 transmitter.connection 不为空了,那只有为 true,result 才为 null
// 而且连接池没有并且 nextRouteToTry 为 null (也就是没有路由可试的时候),
// 拿到上次创建但没用到的连接的路由
selectedRoute = transmitter.connection.route();
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {//当前上次创建的连接可用或者连接池里面找到了的就直接返回
// If we found an already-allocated or pooled connection, we're done.
return result;
}
// If we need a route selection, make one. This is a blocking operation.
boolean newRouteSelection = false;
//如果上面 selectedRoute 有赋值了,那就不用再次拿所有路由去连接池里面查找了,直接创建连接就好了。
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
List<Route> routes = null;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
if (newRouteSelection) {
// 这里已经拿到了请求的所有路由,尝试通过路由的方式去匹配连接
routes = routeSelection.getAll();
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
//创建一个连接
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// If we found a pooled connection on the 2nd time around, we're done.
//如果从路由的方式在连接池里面找到了,那就直接返回
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
//为创建的连接做TCP 三次和 TLS 四次握手
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
// Last attempt at connection coalescing, which only occurs if we attempted multiple
// concurrent connections to the same host.
//再一次拿IP地址去连接池找,多个连接并发的时候这个时候可能会成功找到
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
// We lost the race! Close the connection we created and return the pooled connection.
//找到了,把创建的连接关闭了,复用连接池的
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
// It's possible for us to obtain a coalesced connection that is immediately unhealthy. In
// that case we will retry the route we just successfully connected with.
nextRouteToTry = selectedRoute;
} else {
//还是找不到,创建的连接加入到连接池,用起来
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
}
所以总结下来,连接复用有以下几个步骤
- 如果当前连接正常,并且 noNewExchange 为 false,那就直接复用
- 通过 address 的方式在连接池里面查找是否有匹配的连接
- 拿到请求的所有路由 routers,在连接池里面查找是否有匹配的连接
- 再创建一个连接后,并且TCP,TLS 握手后,再一次拿 address 和路由 routers 去连接池里面查找,由于多个连接并发问题,可能可以匹配上。如果匹配上了,那这个连接 noNewExchange 就会赋值为 true.
- 如果以上方式都找不到,那就把连接加到连接池里面,给下一次做复用
连接创建好后,也就代表着 TCP,TLS 握手已经完成,也就代表着请求连接已经创建好了,这时候就会执行自定义的 networkInterceptor ,所以从这里我们也能发现 networkInterceptor 和 applicationInterceptor 的区别了,applicationInterceptor是在请求还没开始执行,也不参与到开始请求到连接的过程里面,而 networkInterceptor 正好相反。走完 networkInterceptor 拦截器后,接着就会来到 CallServiceInterceptor 拦截器了。下面来看看具体源码
public class CallServerInterceptor {
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
//发送请求的头部信息,这里分别对应着 Http1ExchangeCodec(HTTP/1.1) 和 Http2ExchangeCodec(Http/2)
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Response.Builder responseBuilder = null;
// 不是 Get 请求,需要发送 body 体
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.
//如果请求头有 Expect :"100-continue",也就是问下服务器是否愿意接收发送的消息主体,刷新下,看是否在传输请求正文之前响应,
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));
//发送 body 体
request.body().writeTo(bufferedRequestBody);
} else {
//"Expect: 100-continue" 问题,编写请求正文,并且发送出去
// 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 {//"Expect: 100-continue" 问题,被阻止了,并且拿去下次复用
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();
}
if (request.body() == null || !request.body().isDuplex()) {
exchange.finishRequest();
}
//到这里,请求信息就发送出去了,下面就拿响应体了
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
if (responseBuilder == null) {
//拿 header 部分响应
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);
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 {
//读取 body 体
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;
}
}
这个拦截器干嘛用呢,简而言之,就是把 Request 请求信息发出去,然后为 Response 赋值请求回来的 header 和 body 信息。到此,一个请求Request到得到响应体Response 就完成了。
最后,一路学习下来会发现,OkHttp 涵盖的网络知识真是太多太多了,以上只是自己学习的一些理解,应该也有一些理解错误的地方,需要后面不断学习,不断矫正。