OkHttp源码解析
前言
OkHttp是目前最流行的网络请求框架,现在大部分的应用都在用它,所以OkHttp源码是一个不错的学习资源。并且里面涉及的设计模式有:
- 责任链
- 策略
- 单例
- 监听者
- 享元
- 工厂
- 建造者
- 门面
今天我们就根据源码来一探究竟!(PS:本篇解析的源码是基于OkHttp 3.12.0 版本!)
一、请求总体流程
首先先来一张请求总体流程图。整个流程是,通过OkhttpClient将Request转化为RealCall,然后再根据RealCall是否异步利用Dispatcher分发执行请求。最后通过多个Interceptor发出网络Request后得到Response。
大家可以看到,整个流程可以分为四大模块:
- Create/Request:主要负责创建Client和包装Request
- Execute:主要利用Dispatcher负责分发处理RealCall请求
- Interceptor(核心模块):主要负责失败重试、请求网络相关功能
- Response:请求结果Response
二、四大模块
这里我们先来一个GET请求!
OkHttpClient client = new OkHttpClient.Builder()
.build();
Request request = new Request.Builder()
.url("http://publicobject.com/secrets/hellosecret.txt")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("异常:" + response);
System.out.println("响应:" + response.body().string());
Create/Request
1.Create
从上面代码我们可以看到OkHttpClient和Request实例都是通过建造者Builder得到的,而通过下面这张类图我们可以看到。OKHttpClient充当了整个OkHttp库的门面,OkHttp几乎所有的承担功能和设置都是通过它来负责的。
OKHttpClient类图
我们再来看一下OkHttpClient里面的参数:
final Dispatcher dispatcher; //异步请求执行管理器
final @Nullable Proxy proxy; //代理
final List<Protocol> protocols; //协议
final List<ConnectionSpec> connectionSpecs; //TLS传输层版本和连接协议
final List<Interceptor> interceptors; //拦截器
final List<Interceptor> networkInterceptors; //网络拦截器在 CallServerInterceptor 之前
final EventListener.Factory eventListenerFactory; //OkHttp请求网络监听
final ProxySelector proxySelector; //代理选择
final CookieJar cookieJar; //Cookie
final @Nullable Cache cache; //缓存
final @Nullable InternalCache internalCache; //内部缓存
final SocketFactory socketFactory; //socket 工厂
final SSLSocketFactory sslSocketFactory; //安全套接字socket工厂,Https
final CertificateChainCleaner certificateChainCleaner;// 验证确认响应证书 适用 HTTPS 请求连接的主机名。
final HostnameVerifier hostnameVerifier; //主机名验证
final CertificatePinner certificatePinner; //证书链
final Authenticator proxyAuthenticator; //代理Auth验证
final Authenticator authenticator; //服务器Auth
final ConnectionPool connectionPool; //连接池
final Dns dns; //dns解析
final boolean followSslRedirects; //是否支持http和https之间重定向
final boolean followRedirects; //是否重定向
final boolean retryOnConnectionFailure; //是否需要失败重试连接
final int callTimeout; //请求超时时间(整个流程耗费的超时时间)
final int connectTimeout; //建立TCP连接,同时设置连接超时(不包含TLS链接)
final int readTimeout; //给socket设置读取server端数据的超时
final int writeTimeout; //给socket设置写入server端数据的超时
final int pingInterval; //这个值只有http2和webSocket中有使用;定时的向服务器发送一个消息来保持长连接
通过这些参数我们可以看出,几乎所有的类都和OkHttpClient有关系!
2.Request
大家还记得上面GET请求client.newCall(request).execute()
吧,事实上真正的流程要从newCall()
说起
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
这里它创建了一个真正的请求者:RealCall
我们先来看一下它的构造方法:
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
this.timeout = new AsyncTimeout() {
@Override protected void timedOut() {
cancel();
}
};
this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}
- client:代表此前创建的OKHttpClient实例
- originRequest:代表原始的封装Request实例
- forWebSocket:是否是WebSocket,默认情况下为false
而它内部首先创建了RetryAndFollowUpInterceptor拦截器(下面Interceptor模块会重点讲解它,先放一下);另外还创建了一个定时器任务timeout
专门用来cancel超时请求,而超时时间也是由我们client进行配置的;默认为0,即不进行超时监控。
接下来我们再来看看execute()
方法,它表示同步执行请求。
Execute
1.同步
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
首先会使用executed标志判断,不能重复execute()
调用,否则抛出异常。captureCallStackTrace()
里面主要是记录一下当前response.body().close()
堆栈调用,为后期连接泄露提供信息的。后面timeout.enter()
就是请求超时监听的任务启动了。然后开启eventListener
请求任务的监听。
接下来通过Dispatcher.executed(RealCall)
执行当前RealCall,这里讲一下Dispatcher
,大家可以先回顾一下OkHttpClient类图里的Dispatcher类图。
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
上面我们可以发现executed(RealCall)
方法只是仅仅把它添加入runningSyncCalls一个队列中,就结束了…(额!汗颜),因为队列是非线程安全的,所以在方法上整体使用了synchronized
来保证线程使用安全。
我们接下来再看getResponseWithInterceptorChain
它得到一个Response,目测请求网络所有操作都是在这个里面进行了。然后直接返回Response。如果出现异常,则关闭超时定时器任务,最后从当前队列移除掉RealCall!这里我们再回过头细究一下getResponseWithInterceptorChain
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 (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
这里面代码其实比较简单,就是添加一些拦截器——Interceptor
,拦截器采用 有序 ArrayList
装载。首先添加OkHttpClient
实例的interceptors()
集合,然后下来是在RealCall
中创建的RetryAndFollowUpInterceptor
拦截器,后面是BridgeInterceptor
、CacheInterceptor
、ConnectInterceptor
和OkHttpClient
实例的networkInterceptors
集合,最后是CallServerInterceptor
。
接下来又创建了一个拦截器链,执行了它的proceed()
方法,就得到了Response。嗯,至少目前是这样的!我们先暂时不去深究它具体内部干了什么事,放到Interceptor模块再去讲。现在我们只需要明白getResponseWithInterceptorChain
里Interceptor.Chain
的帮我们请求下来了结果Response。
2.异步
上面我们一直再说的是同步,下面我们来看一下异步又是怎样的呢?
看一下发送一个异步的网络请求
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println("Response 1 response: " + response);
}
});
执行的是RealCall.enqueue()
方法
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
和同步execute
类似也会首先做一个防重复调用。然后通过Dispatcher.enqueue()
方法添加一个AsyncCall到readyAsyncCalls
队列中。
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);
}
promoteAndExecute();
}
我们先看看多出来的AsyncCall又是个啥东西?它是RealCall的非静态内部类;继承于NamedRunnable,而NamedRunnable又实现了Runnable,看到这大家可能会明白一些了,嗯!这不就是Thread实现的Runable吗!其中有两个比较重要的方法executeOn(ExecutorService)
和execute()
。
其中executeOn(ExecutorService)
会被promoteAndExecute()
调用,而execute()
会在Runnable.run()
中被调用。
final class AsyncCall extends NamedRunnable {
...
//在promoteAndExecute中调用
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);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
//线程执行Runnable.run()会调用该方法,See#NameRunnable
@Override protected void execute() {
boolean signalledCallback = false;
timeout.enter();
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
e = timeoutExit(e);
if (signalledCallback) {
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
NamedRunnable其实也没干啥事,就是比Runnable多设置一次线程名字
public abstract class NamedRunnable implements Runnable {
...
@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();
}
我们再去看看promoteAndExecute()
:它主要是将满足要求的异步请求任务readyAsyncCalls升级到runningAsyncCalls队列中,并开启线程池执行runningAsyncCalls队列中的请求任务!
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
从promoteAndExecute
方法中我们可以看到,首先遍历readyAsyncCalls
队列中的任务,如果此时同时执行的请求超过了maxRequests(默认:64)请求数量。则不添加进executableCalls
可执行集合中。如果当前请求的host有超过maxRequestsPerHost(默认:5)的请求数量,则不进行本次添加入可执行集合中。其他情况会进可执行集合和正在执行队列中,然后移除readyAsyncCalls
队列中的当前请求任务。然后遍历executableCalls集合任务,添加任务至线程池中。
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
从executorService()
方法中我们可以看到OKHttp配置的线程池策略,使用SynchronousQueue不保留任务,直接提交给线程池执行,没有线程数量的限制。但是从外部它通过maxRequest和maxRequestsPerHost很好的控制了线程数无限增长的情况,同时也保证了高并发的请求效率的诉求!
接下来我们再次回到AsyncCall.executeOn()
方法,看看它里面具体一下细节。
首先直接将当前AsyncCall添加进线程池当中,如果线程池抛出异常,则直接移除该任务。然后又执行一次promoteAndExecute
方法——将准备好的任务添加到可执行集合中执行请求任务
当线程池执行任务时,又会调用AsyncCall.execute()
,和 同步请求一样 首先它会开启timeout
超时定时器监听。然后调用再getResponseWithInterceptorChain
获得Response。
至此,整个Execute模块算是完成了!接下来我们来解析 重点模块 了—— Interceptor !
Interceptor
这个模块是OkHttp的核心,获得Response的所有操作都在这个模块!
getResponseWithInterceptorChain
将系统提供的5大拦截器添加进拦截器集合中,接着创建一个RealInterceptorChain,然后调用它的proceed(Request)
方法。
1.RealInterceptorChain
从字面意思上理解:真正的拦截器链!它确实是一条链。它为请求创建了一个拦截器对象的链。对请求和响应进行解耦。如果一个拦截器不能处理该请求,那么它会把相同的请求传给下一个拦截器,直到有拦截器能处理它为止。这不就是 责任链模式 吗!
首先看一下RealInterceptorChain的构造方法。
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
下面我们来分别看一下参数:
- interceptors:拦截器集合
- connection:它负责建立完整的HTTP或HTTPS Soket连接所需的所有工作。——目前传了个null!
- streamAllocation:协调Connections、Streams和Calls。——目前传了个null
- httpCodec:负责对请求编码和响应解码的工作——目前也传了个null
- index:当前拦截器链角标,将当前请求传给下一个拦截器就是靠它!——目前传了个0
- request:当前原始请求
- call:RealCall实例
- connectTimeout:TCP连接超时——同OKHttpClient
- readTimeout:读取超时——同OKHttpClient
- writeTimeout:写入超时——同OKHttpClient
好了看完构造方法,我们再来看一下 proceed 方法了!
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError(); //1
calls++;
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
} //2
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
} //3
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next); //4
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
} //5
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
} //6
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
} //7
return response;
}
这部分代码比较长,没关系!我们一点一点来分析!力求做到最详细的解读!
- 首先它判断了一下角标index。是否大于拦截器集合。是的话直接抛出异常结束当前请求!然后将一个calls变量自增了1,请注意不是call。
- 它这里判断了2个条件,首先是httpCodec,上面我们已经对它总结了——对请求编码和响应解码,目前它为null,实际上它只会在ConnectInterceptor拦截器中被赋值。
connection.supportsUrl(request.url())
里面主要是验证当前请求url的端口号和host证书是否匹配,目的还是为了防止上一个拦截器随意篡改url导致后续出现问题。同样它目前为null,同样也会在ConnectInterceptor拦截器中被赋值 - 增加了calls这个条件,是为了防止同一个拦截器出现多次调用
proceed()
的问题。 - 下面重点来了!!!这里它又重新创建了一个RealInterceptorChain实例,通过index+1,来引入 下一个拦截器 。然后再 调用当前拦截器 的
intercept()
方法。我们在Execute模块中说了,拦截器集合是一个有序集合!集合中第一个拦截器:RetryAndFollowUpInterceptor便是 当前拦截器。 - 下来我们先快速的过一下5/6/7标注的地方,当然目前这几个地方肯定是会被最后调用。5主要是防止下一个拦截器没有调用
RealInterceptorChain.proceed()
方法;6/7主要是对已经得到的响应做一些判空处理,最后把Response返回!
接着我们继续回到调用RetryAndFollowUpInterceptor.intercept()
方法中,看看RetryAndFollowUpInterceptor都干了些啥!
1.RetryAndFollowUpInterceptor
讲了这么久的拦截器,终于见到实体了!
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener(); //1
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation; //2
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
} //3
Response response;
boolean releaseConnection = true;
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false; //4
} catch (RouteException e) {
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
releaseConnection = false;
continue; //5
} catch (IOException e) {
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue; //6
} finally {
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
} //7
}
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp;
try {
followUp = followUpRequest(response, streamAllocation.route()); //9
} catch (IOException e) {
streamAllocation.release();
throw e;
}
if (followUp == null) {
streamAllocation.release();
return response;
} //10
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
} //11
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
} //12
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
} //13
request = followUp;
priorResponse = response; //14
}
}
额!这代码更长…
- 首先这里将参数
RealInterceptorChain
里的对象取出以备后用。注意当前RealInterceptorChain里的index已经指向下一个拦截器了——BridgeInterceptor - 它创建了一个StreamAllocation,讲解RealInterceptorChain时,我们已经说过它主要协调Connections、Streams和Calls。这里我们先不深究StreamAllocation,下面讲到ConnectInterceptor拦截器时,会重点剖析它!
- 紧接着它开启了一个死循环,首先判断一下,当前请求是否被cancel;如果是则释放Socket分配的资源,将连接断开关闭。请求结束!
- 下面它又调用了
RealInterceptorChain.proceed()
方法,上面已经解析完proceed()了,这时肯定会调用BridgeInterceptor的intercept()
方法。这里我们先不着急看BridgeInterceptor的intercept()
,先假设这时已经拿到后续拦截器的Response;看一下当前拦截器的后续操作。我们同时也要明白,后续操作会在BridgeInterceptor的intercept()
方法 弹栈完 才执行! - 这里主要获取与服务器通信故障判断是否可恢复,如果可以恢复,则尝试重新请求,如果失败则直接返回请求。
- 这块和5其实类似。
- 针对抛出的上述异常,结束请求,释放所有资源。
- 这里会把上一次的Response结果先暂存到priorResponse里。
- 到了
followUpRequest
方法,它会把本次的Response解析一下,然后将需要添加身份验证、重定向或请求超时,如果后续满足重试条件的,则重新包装一下Request,通过外部的死循环重新请求,如果不满足重试条件的,则直接返回null。由于代码篇幅比较长,我就直接把详解注释到代码里了。
private Request followUpRequest(Response userResponse, Route route) throws IOException {
if (userResponse == null) throw new IllegalStateException();
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
//响应码为必要的代理服务器身份验证,则根据OkHttpClient的设置是否重新设置Request请求
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);
//响应码为必要的服务器身份验证,则根据OkHttpClient的设置是否重新设置Request请求
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
//响应码为重定向的,则根据响应头里的新url,重新设置Request请求
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
//如果当前请求方式不是GET并且也不是HEAD,收到307或308状态代码,则用户不允许自动重定向该请求
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
// OkHttpClient是否设置允许自动重定向
if (!client.followRedirects()) return null;
//解析响应头里的重定向的url
String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// 服务器没有给重定向地址,则不满足重定向
if (url == null) return null;
//如果OkHttpClient设置了不支持HTTP和HTTPS之间的重定向操作,即不允许重定向
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
// 重定向设置新的Request Body
Request.Builder requestBuilder = userResponse.request().newBuilder();
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
}
//发生重定向时,如果重定向后的host和原始请求的host不一致,则删除所有身份验证信息。
if (!sameConnection(userResponse, url)) {
requestBuilder.removeHeader("Authorization");
}
//返回新的包装request
return requestBuilder.url(url).build();
//请求408重试
case HTTP_CLIENT_TIMEOUT:
//如果OkHttpClient不支持连接失败重试,则不允许重试
if (!client.retryOnConnectionFailure()) {
// The application layer has directed us not to retry the request.
return null;
}
//如果上次响应body为不可重复请求的响应body,也不允许重试
if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
return null;
}
// 如果上次重试了请求,但响应仍然是408,则直接放弃了。
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
return null;
}
//如果响应头设置了重试时间间隔
if (retryAfter(userResponse, 0) > 0) {
return null;
}
return userResponse.request();
//HTTP不可用
case HTTP_UNAVAILABLE:
//上次重试了请求,但响应仍然是503,则直接放弃了。
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
return null;
}
//专门收到了立即重试的指令,则直接重试请求
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
return userResponse.request();
}
return null;
default:
return null;
}
}
- 当
followUpRequest
方法返回为null时,说明当前响应不需要或者不满足重试的条件,所以直接返回当前Response。 - 关闭ResponseBody流并释放与其关联的所有系统资源。然后下面的这一行代码我们发现,虽然RetryAndFollowUpInterceptor拦截器里开启了一个死循环。但是提供了最大的循环重试次数MAX_FOLLOW_UPS(默认:21),来控制无限重试导致内存疯狂增大的问题。
- 同样请求体如果是无法重试的HTTP请求体,也是直接抛出异常,结束请求的。
- 如果当前请求url和 重试包装后 的请求url host不一致,则重新创建StreamAllocation。如果一致,还会检查一次ResponseBody流是否关闭!
- 这块是为下一次循环重试赋值!
到这里,我们的RetryAndFollowUpInterceptor拦截器算是解析完了!但是,别忘了!再次回到上面标记4代码处,正常流程它应该先调用BridgeInterceptor的intercept()
方法的,所以下面我们就一起看看BridgeInterceptor!
3.BridgeInterceptor
顾名思义:桥梁拦截器。谁的桥梁?客户端请求到网络服务端的桥梁。首先,它将用户请求构建成网络请求。然后,它继续请求网络。最后,它又将网络响应构建出用户所需的响应。
由于代码比较简单,我也只是简单的把详解注释在代码里!
@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();
//包装"Content-Type"请求head
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
包装"Content-Type"或"Transfer-Encoding"请求head
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");
}
}
//包装"Host"请求head
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
//包装"Connection"请求head
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
//OkHttp特有的编码Gzip压缩;添加“ Accept-Encoding:gzip”标头字段,负责压缩传输流
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
//添加cookie请求头
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
//User-Agent
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//开始调用下一个拦截器CacheInterceptor.intercept()
Response networkResponse = chain.proceed(requestBuilder.build()); //1
//拿到CacheInterceptor.intercept()返回的响应结果,重新更新本地缓存cookie
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");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
//返回用户响应
return responseBuilder.build();
}
整个BridgeInterceptor拦截器比较简单,也没什么复杂的逻辑。现在我们再次回到标记1处,它调用了CacheInterceptor.intercept()
方法!
4.CacheInterceptor
该类其实主要负责 2件事 :
- 1.负责响应来自缓存的请求。
- 2.将网络响应更新至缓存。
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null; //1
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); //2
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse; //3
if (cache != null) { //4
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body());
} //5
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();
} //6
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
} //7
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest); //8
} finally {
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
} //9
}
if (cacheResponse != null) {
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();
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
} //10
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build(); //11
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
} //12
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
}
} //13
}
return response; //14
}
- 首先根据当前请求url获取本地候选缓存
- 然后缓存策略工厂类根据本地缓存的响应头的缓存相关信息通过和请求头的使用缓存信息计算出网络请求策略和满足当前请求的缓存响应!(关于缓存这块的源码量不小,设计思路也非常的巧妙!考虑到当前篇幅限制!所以暂时我们可不用在意缓存模块,本人到时会专门写一篇关于OkHttp缓存源码详解文章,到时我们再好好细究一番!)
- 目前它拿到了缓存策略,通过网络请求和缓存响应来保存它。
- 然后通过Cache.trackResponse(strategy)来统计缓存命中数,缓存存在网络请求等数据。
- 根据缓存响应策略,当前候选缓存不适用,我们关闭它。
- 如果当前请求网络策略不满足使用网络请求而又没有本地缓存响应使用,那么我们直接返回 504响应 结果。
- 如果当前请求网络策略不满足使用网络请求但本地缓存响应命中,则直接返回命中的响应。
- 代码运行到这里,那么代表至少当前请求网络策略需要使用网络请求,所以会启动下一个拦截器,ConnectInterceptor开始连接目标服务器。老规矩,我们还是先把下面的代码过一遍,然后再回过头来继续看
ConnectInterceptor.intercept()
。 - 代表如果ConnectInterceptor连接服务器出现异常,则关闭命中的缓存响应,避免造成泄漏!
- 如果我们命中了一个缓存响应,那么我们先看解析一下网络的响应是否为304——HTTP未修改。不是则直接先关闭缓存响应流,避免泄漏。如果是,那我当即使用本地缓存构建一个新缓存,然后更新请求头和请求开始和响应接收时间。接着关闭网络响应流,更新缓存命中数,更新本地缓存。最后返回响应!
- 如果网络响应不为304,代码流转至此,那么使用网络响应构建一个新的响应。
- 首先检测一下网络响应是否含有body,并且当前网络响应是可缓存的!然后将网络响应更新至本地缓存,最后直接返回响应!
- 如果以上条件都无法满足,则会根据当前网络请求Method。是否属于"POST"/“PATCH”/“PUT”/“DELETE”/“MOVE”。那么将会移除本地缓存。
- 直接返回当前 网络请求 构建的响应!
至此CacheInterceptor算是完成了,下面我们再次回跳到ConnectInterceptor.intercept()
!一起来看看连接服务器的操作!
5.ConnectInterceptor
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation(); //1
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
震惊(捂脸)…!是的,你没有看错,ConnectInterceptor.intercept
就是这么点代码。甚至整个类也就是这么点代码!不过,大家听说过一句话吗——字越少,事越大!
不信我们就一起来看看吧!
首先它通过当前拦截器链获取了一个StreamAllocation。大家应该还记得吧,这个StreamAllocation在RetryAndFollowUpInterceptor拦截器中创建的。当时我们只是单纯的提了一下它主要负责协调Connections、Streams和Calls。然后又调用了StreamAllocation.newStream()
方法。最后启动了CallServerInterceptor拦截器!
这里大家要看清楚,这一次启动拦截器调用的方法是Response proceed(Request, StreamAllocation, HttpCodec, RealConnection)
这和之前启动拦截器完全不一样啊!大家对我之前提的设置httpCodec和connection还有印象吧,整个拦截链中。只有这里设置了这两参数。
所以 我们有必要在此先深究一下StreamAllocation、Connection!
在介绍StreamAllocation之前我们先介绍Connection,我们先来看RealConnection。因为RealConnection是Connection的唯一实现!
RealConnection
首先我们来看一下RealConnection的类图
RealConnection其实代表着一条socket的链路,如果拥有了一个RealConnection就代表了我们已经跟服务器有了一条通信链路,有通信链路了,就意味着会 三次握手…
我们可以看一下它的构造方法:
- connectionPool:连接复用池
- route:它主要用于连接到原始服务器的具体路由,其实内部就是保存了一条InetSocketAddress!
然后,我们直接来看它的connect()
方法,估计大家已经猜到了会在connect()里面进行三次握手。嗯,大家猜对了!
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
EventListener eventListener) {
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs); //1
if (route.address().sslSocketFactory() == null) {
if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not enabled for client"));
}
String host = route.address().url().host();
if (!Platform.get().isCleartextTrafficPermitted(host)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication to " + host + " not permitted by network security policy"));
}
} else {
if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
throw new RouteException(new UnknownServiceException(
"H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"));
}
} //2
while (true) { //3
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
break;
}
} else {
connectSocket(connectTimeout, readTimeout, call, eventListener);
} //4
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener); //5
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
break;
} catch (IOException e) {
closeQuietly(socket);
closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
http2Connection = null;
eventListener.connectFailed(call, route.socketAddress(), route.proxy(), null, e);
if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
} //6
}
if (route.requiresTunnel() && rawSocket == null) {
ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "
+ MAX_TUNNEL_ATTEMPTS);
throw new RouteException(exception);
} //7
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
} //8
}
- 首先根据当前请求Address(与服务器连接地址的规范类,保存了连接服务器的host、post、支持协议等信息),然后创建了一个ConnectionSpecSelector,ConnectionSpecSelector主要作用当连接由于握手协议问题而失败时,可以使用不同的协议重试该连接。
- 整个这块主要是根据当前如果是HTTP连接,则会对请求执行一些额外的限制。
- 我们看到这儿,发现又开启了一个循环这时连接就开始了!
- 如果要求隧道模式,建立通道连接,通常这是使用Proxy,才会进入使用该通道,一般我们不会使用这种。
通常我们使用connectSocket
,它实际上就是建立socket连接。这一块我们先放放,待会再来深究connectSocket
。 - 调用establishProtocol建立协议,TLS连接。同样这一块我们也稍后细讲。
- 当建立连接发生异常,则根据当前是否开启失败重试进行连接重试!
- 如果隧道模式建立链接失败,则抛出异常
- 如果当前协议是HTTP2,则调整一下连接可以承载的并发流的最大数量。
现在我们再回头看看 connectSocket
和establishProtocol
里的一些具体操作
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
//根据代理类型来选择socket类型
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
eventListener.connectStart(call, route.socketAddress(), proxy);
//为socket设置超时
rawSocket.setSoTimeout(readTimeout);
try {
//这里是根据各平台实现,不过实际内部调用的是Socket.connect(SocketAddress,timeout)
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
ce.initCause(e);
throw ce;
}
try {
//得到输入输出流
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
总结:
如果当前请求有代理类型为无代理或HTTP明文代理,则通过SocketFactory创建Socket;否则通过传入的proxy自己创建一个Socket。然后为Socket设置读取数据超时时间;然后开启阻塞连接,直至连接成功或出现异常。最后创建用于读取和写入的IO流。
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
//不是HTTPS,直接不进行TLS连接
if (route.address().sslSocketFactory() == null) {
//当前是H2_PRIOR_KNOWLEDGE协议,采用间隔周期ping的方式保持长链接
if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
socket = rawSocket;
protocol = Protocol.H2_PRIOR_KNOWLEDGE;
startHttp2(pingIntervalMillis);
return;
}
socket = rawSocket;
protocol = Protocol.HTTP_1_1;
return;
}
eventListener.secureConnectStart(call);
//TLS连接
connectTls(connectionSpecSelector);
eventListener.secureConnectEnd(call, handshake);
//当前是HTTP_2协议,采用间隔周期ping的方式保持长链接
if (protocol == Protocol.HTTP_2) {
startHttp2(pingIntervalMillis);
}
}
对于HTTP,则直接设置协议并返回原始Socket和应用层交互。对于HTTPS,创建TLS连接。
如果当前协议是H2_PRIOR_KNOWLEDGE或者HTTP_2,OkHttp为了保持长链接,内部采用间隔周期ping的方式向服务器发送ICMP。
下面我们再具体看一下connectTls
方法
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
Address address = route.address();
SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
boolean success = false;
SSLSocket sslSocket = null;
try {
// 在原先的Socket加上一层SSL
sslSocket = (SSLSocket) sslSocketFactory.createSocket(
rawSocket, address.url().host(), address.url().port(), true /* autoClose */);
// 配置SSLSocket的TLS的版本和扩展
ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
if (connectionSpec.supportsTlsExtensions()) {
Platform.get().configureTlsExtensions(
sslSocket, address.url().host(), address.protocols());
}
//SSL连接开始握手
sslSocket.startHandshake();
//获取证书信息
SSLSession sslSocketSession = sslSocket.getSession();
Handshake unverifiedHandshake = Handshake.get(sslSocketSession);
//验证回传回来的证书
if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
+ "\n certificate: " + CertificatePinner.pin(cert)
+ "\n DN: " + cert.getSubjectDN().getName()
+ "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
}
address.certificatePinner().check(address.url().host(),
unverifiedHandshake.peerCertificates());
//验证成功,保存握手和ALPN协议
String maybeProtocol = connectionSpec.supportsTlsExtensions()
? Platform.get().getSelectedProtocol(sslSocket)
: null;
//赋值应用层Socket
socket = sslSocket;
source = Okio.buffer(Okio.source(socket));
sink = Okio.buffer(Okio.sink(socket));
handshake = unverifiedHandshake;
protocol = maybeProtocol != null
? Protocol.get(maybeProtocol)
: Protocol.HTTP_1_1;
success = true;
} catch (AssertionError e) {
if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
} finally {
if (sslSocket != null) {
Platform.get().afterHandshake(sslSocket);
}
if (!success) {
closeQuietly(sslSocket);
}
}
}
首先使用sslSocketFactory基于原始Socket创建一个SSLSocket,然后开启SSL握手,握手成功之后;主动获取证书,并对证书进行验证。同样使用Okio创建source和sink用来作为SSLSocket读取和写入的IO操作。最后是一些失败异常的处理!
至此,整个连接算是完成了!
别急,我们再捎带看一下newCodec()
、isEligible(Address,Route)
、isHealthy(doExtensiveChecks)
方法。由于这几个方法都比较简单,我们就快速的过一遍了!
newCodec:主要是判断是否是HTTP/2,如果是HTTP/2则new一个Http2Codec。如果不是HTTP/2则new一个Http1Codec。它俩又都是HttpCodec的实现类——主要负责请求和响应的编码解码工作。
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, chain, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
isEligible(Address,Route):主要是判断给出的address和route,这个连接是否可以重用。
public boolean isEligible(Address address, @Nullable Route route) {
// 如果此连接不接受新的流,那肯定无法重用
if (allocations.size() >= allocationLimit || noNewStreams) return false;
// 如果Address不相同,无法复用
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
//如果host完全匹配,直接复用
if (address.url().host().equals(this.route().address().url().host())) {
return true;
}
//当然对于host没匹配上的,如果满足一些其他条件也可以复用
// 1. 连接必须是HTTP2
if (http2Connection == null) return false;
// 2. route必须共享一个IP地址。这要求我们为两个主机都拥有一个DNS地址,该地址仅在路由规划之后发生。
//我们无法合并使用代理的连接,因为代理不会告诉我们原始服务器的IP地址
if (route == null) return false;
if (route.proxy().type() != Proxy.Type.DIRECT) return false;
if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
if (!this.route.socketAddress().equals(route.socketAddress())) return false;
// 3. 连接的服务器证书必须包含新主机
if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
if (!supportsUrl(address.url())) return false;
// 4. 证书固定必须与主机匹配
try {
address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
} catch (SSLPeerUnverifiedException e) {
return false;
}
//以上都满足则可以复用!
return true;
}
isHealthy:主要是检查这个连接是否是健康的连接。
public boolean isHealthy(boolean doExtensiveChecks) {
if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
return false;
}
if (http2Connection != null) {
return !http2Connection.isShutdown();
}
if (doExtensiveChecks) {
try {
int readTimeout = socket.getSoTimeout();
try {
socket.setSoTimeout(1);
if (source.exhausted()) {
return false;
}
return true;
} finally {
socket.setSoTimeout(readTimeout);
}
} catch (SocketTimeoutException ignored) {
} catch (IOException e) {
return false;
}
}
return true;
}
从以上代码,我们可以总结出不健康连接的条件(满足一条即不健康):
- socket关闭
- 输入流关闭
- 输出流关闭
- 如果是HTTP2连接,HTTP2连接关闭
- 当前如果是非GET请求,如果读取流的size为0
以上基本就是我们RealConnection所有的内容了,下面我们来看StreamAllocation!
StreamAllocation
我们都知道HTTP请求网络的执行过程——请求在一个连接上建立流。而StreamAllocation就负责为一个请求寻找连接并建立流。从而完成一次网络请求!所以前面说它负责协调Connections、Streams和Calls!
同样我们还是先看一下StreamAllocation的类图
我们看到他含有一个内部类:StreamAllocationReference主要帮助连接泄露时,保存堆栈跟踪信息的,上面提过!
下来我们看一下StreamAllocation的属性:
public final Address address; //地址
private RouteSelector.Selection routeSelection; //一组选定的路由
private Route route; //路由
private final ConnectionPool connectionPool; //连接复用池
public final Call call; //请求
public final EventListener eventListener; //请求监听
private final RouteSelector routeSelector; //路由选择器
private int refusedStreamCount; //拒绝的次数
private RealConnection connection; //连接
private boolean released; //是否被释放
private boolean canceled; //是否被取消
private HttpCodec codec; //披了马甲的流
接着我们再来看一下它的构造方法:
- connectionPool:连接复用池
- address:与服务器的连接地址的规范类。这里面有服务器的主机名和端口。如果请求使用了代理,它还包括该代理信息。对于HTTPS连接,该地址还包括SSLSocket工厂,Hostname verifier和Certificate pinner。
- callStackTrace:堆栈跟踪信息
看完构造方法我们再来一起看看一个比较重要的newStream方法
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); //1
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this); //2
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec; //3
}
} catch (IOException e) {
throw new RouteException(e);
}
}
它接受的形参,我们在之前都已经介绍过,这里就不在重述了!
- 这里它通过
findHealthyConnection()
方法获取了一个连接。 - 通过resultConnection.newCodec()创建了一个流。
- 最后将创建的流返回。
上面标记2处,我们在RealConnection类中已经解析过它。
下面重点看一下findHealthyConnection()
方法
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate; //1
}
}
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
整个方法就干了一件事:findConnection()
查找连接,判断连接是否健康,健康则将其返回。如果不健康,则重复此过程,直到找到健康的连接为止!
至于1处,就是做了一个小优化,如果这是一个新建的连接,则直接返回!
下面我们再跟进 findConnection()
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled"); //1
releasedConnection = this.connection; //2
toClose = releaseIfNoNewStreams();
if (this.connection != null) {
result = this.connection;
releasedConnection = null;
} //3
if (!reportedAcquired) {
releasedConnection = null;
} //4
if (result == null) { //5
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) { //6
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
return result;
} //7
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
} //8
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
if (newRouteSelection) {
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
} //9
if (!foundPooledConnection) { //10
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
} //11
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener); //12
routeDatabase().connected(result.route()); //13
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
Internal.instance.put(connectionPool, result); //14
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
} //15
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result; //16
}
该方法返回一个连接。如果存在现有连接,则返回现有连接,如果现有连接不适用,则去复用连接池找。如果池中找到,则返回,如果没有找到,则创建一个新连接!
- 首先会进行一些异常情况判断,包括请求取消,流已创建和连接被释放了!
- 下来我们会先使用现有连接。然后会对当前现有连接进行判断是否无法创建新的流,如果是会立即释放现有连接,并置空处理。
- 现有连接没有被置空,说明是可以创建新流的连接,则将现有连接分配给result连接。
- 如果从未报告该连接已获得,则后续不要将其报告为已释放。
- 当result为null,说明现有连接不适用,则会尝试去连接复用池里寻找。由于ConnectionPool连接复用池相关代码比较多,涉及了保存连接、定时清除连接等诸多操作,这里我就不展开讨论了,后期会专门出一篇关于连接复用的文章详细讲解。
- 如果在复用池找到,则将找到的连接分配给result连接。如果没有找到,则尝试选择下一条路由。
- 如果目前从现有连接的或复用池中已经找到一个连接,那么就直接返回。
- 如果我们需要选择下一个路由的具体操作,就在这!
- 我们选择了一条新的路由,现在我们有了一组IP地址,会再次尝试从复用池中获取连接。由于连接合并,这可能会匹配上。
- 如果这时复用池仍然没有找到可用的连接,则创建一个连接,并将其立即分配给现有连接,如果这时外部调用了cancel()请求,可以以最快的速度响应,避免无效的资源浪费!
- 如果我们第二次合并匹配上了复用池的连接,那么就直接返回。
- 如果代码走到这儿,那么代表当前result使用的是新创建的连接。则会调用RealConnection的
connect()
方法进行TCP、TLS连接,这是一个阻塞操作! - routeDatabases是一个黑名单数据库,会保存连接失败的路由。因为当前路由刚连接完,所以会执行一次,从黑名单移除的操作。
- 将当前新创建的连接添加进复用池里。
- 如果这是一个HTTP2的连接并且复用池找到同一地址的多路复用连接,则会释放同一地址的多路复用连接,并使用新创建连接并添加至复用池。
- 最后会返回创建的连接!
至此,整个ConnectInterceptor拦截器算是完成!下面该启动CallServerInterceptor拦截器了!
6.CallServerInterceptor
这是拦截器链中的最后一个拦截器了,它主要负责对服务器进行发送数据和接收数据!
下面是它的拦截方法
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
//开始发送数据
realChain.eventListener().requestHeadersStart(realChain.call());
//写入请求头
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
// 检测是否有请求body
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// 在发送请求body之前,如果有一个"Expect: 100-continue"头在request里面,等待一个有"HTTP/1.1 100 Continue"的response头。
//相当于一次握手,表示与客户端和服务器开始建立连接可以发送消息
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
//构建responseBuilder对象
responseBuilder = httpCodec.readResponseHeaders(true);
}
//如果服务器允许发送请求body发送
if (responseBuilder == null) {
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
//写入请求体
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
streamAllocation.noNewStreams();
}
}
//结束请求
httpCodec.finishRequest();
//读取响应头
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
//通过responseBuilder获得response的内容
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
//如果响应码是100的话,再获取一次response
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
//如果响应码为101,则设置一个空的实体
if (forWebSocket && code == 101) {
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
//读取响应体
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
//如果设置了连接关闭,则断开
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response; //返回响应
}
我们看上面的源码注解,发现CallServerInterceptor.intercept()
虽然代码不少,但是还是比较简单的!
其实总结一下也就主要做了以下2个工作:
- 发送Http请求数据。
- 然后构建Response并返回。
好了我们整个拦截器模块都解析完了,由于该模块是OKHttp最核心的模块,所以描述篇幅比较长!
其实也没有多么复杂,我们稍微提炼总结一下,就会发现其实整个模块也没有多少内容!下面我们来回顾一下5大拦截器!
5大拦截器总结:
- RetryAndFollowUpInterceptor:负责失败/重定向/Auth重试/Proxy/Timeout等重试功能
- BridgeInterceptor:负责包装请求头Gzip/Connection/Host等响应头;Cookie的设置
- CacheInterceptor:负责服务来自缓存的请求,并将响应写入缓存。
- ConnectInterceptor:负责打开到目标服务器的连接,创建或复用连接流
- CallServerInterceptor:负责对服务器进行网络调用(IO)
最后,我们再补充一张拦截流程图,看完流程图之后,你会对整个拦截模块有了更清晰的认知!
Response
关于这个模块就更简单了! Response我就不讲了!这里我就提两个注意事项吧!
- response.body().string() 只能 调用一次
这是因为在实际开发中,响应主体 RessponseBody 持有的资源可能会很大,所以 OkHttp 并不会将其直接保存到内存中,只是持有数据流连接。只有当我们需要时,才会从服务器获取数据并返回。同时,考虑到应用重复读取数据的可能性很小,所以将其设计为 一次性流(one-shot) ,读取后即 ‘关闭并释放资源’。
- 如果当前响应数据大,应避免使用response.body().string()过早将整个响应读入内存中,容易触发OOM。
我们可以使用response.body().byteStream()/chanrSteam()等方法以 流的形式传输数据或按需读取!并且 使用完之后 务必通过Response.close() 来手动关闭响应体。避免造成资源泄露!
结束
好了,到此刻我们整个OkHttp主体流程解析全部完成了!其实还有很多东西都没有讲到,OkHttp 是个庞大的框架,其中涉及到的东西实在太多了,而且包括了很多计算机网络的基础知识。由于考虑到篇幅、时间、复杂度和本人水平有限等问题!还有很多内容都没有在该篇文章中进行深究。
比如:
- Cache相关
- ConnectionPool相关
- HTTP1和HTTP2 OKHttp不同处理
- okio
- Route相关
- 自定义拦截器
- …
后续我有时间,会慢慢补充,敬请大家期待!
另:由于时间紧迫,本人水平有限,文中某些地方难免存在不足。欢迎各位同学大佬拍砖,批评指正!!! 本人不胜感激(抱拳)!
原创作品,码字不易!转载请注明出处!(谢谢大家)