OkHttp已经是非常流行的android客户端的网络请求框架,我其实在项目中使用也已经好几年了,之前一直把重心放在如何快速的搞定业务上、迭代的效率上,这一点来讲,对于一个公司优秀员工是没有毛病的。但是越到后面,你会发现,真正的高效,是你对技术的更深层次的理解,对技术更熟练的掌握。所以今天重回技术本身,搞清楚OkHttp的实现机制和部分源码分析,也提醒阅读本篇文章的同学,除了在公司加班加点赶业务进度的同时,从长远角度看,提深自我的技术技能,才是对公司和自己的双赢结果。
“OkHttp 4.x upgrades our implementation language from Java to Kotlin and keeps everything else the same. We’ve chosen Kotlin because it gives us powerful new capabilities while integrating closely with Java.”
引入官方文档的一段内容,OkHttp4.x实现语言从Java改成了Kotlin,这里面我们使用的源码是OkHttp3.10,在逻辑实现上并无太多差异。
一、OkHttp的最基础的核心类介绍
选型支撑:
摘自Http官方文档的一段介绍,翻译成了中文如下:
HTTP是现代应用常用的一种交换数据和媒体的网络方式,高效地使用HTTP能让资源加载更快,节省带宽。OkHttp是一个高效的HTTP客户端,它有以下默认特性:
支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket 连接
连接池减少请求延时
透明的GZIP压缩减少响应数据的大小
缓存响应内容,避免一些完全重复的请求
OkHttp的第一个特性,允许同一个主机地址请求共享同一个socket连接,确实OkHttp是直接对于网络分层模型中传输层socket进行封装的,内部实现涉及请求线程池和连接池线程池。
核心类介绍:
从一个简单的Demo入手:private String url = "http://www.baidu.com";
void okHttpTest() {
//核心客户端类
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
//构建请求的类
Request request = new Request.Builder().url(url).build();
//构建调用类
Call call = okHttpClient.newCall(request);
//同步调用
call.execute();
//入队列异步调用
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response/*响应核心类*/) throws IOException {
}
});
}
OkHttp的核心类主要有OkHttpClient,Dispatcher,Call,Request,Response,Interceptor,Chain。除了Dispatcher(分发器)、Intercoptor(拦截器)、Chain(链) 在上述调用代码中没有出现,这三个核心类是OkHttp的底层实现,在之后的源码流程分析中,会一一提及。
OkHttpClient是负责管理多个Call的组织者,Call是由Request和Response组成,Call入队列被执行后的响应回调提供Response对象作为响应。OkHttp执行请求的方式有同步调用和异步调用两种方式,分别是call.excute()进行同步调用,call.enqueue(callBack)进行异步调用。
二、OkHttp原理流程源码分析
我们从上面的demo代码开始,分析一下OkHttp的调用流程以及源码实现,上述代码中:
第一步:OkHttpClient okHttpClient = new OkHttpClient.Builder().build();构建一个OkHttpClient的客户端对象。
第二步:Request request = new Request.Builder().url(url).build();构建请求对象,在请求的对象的构建过程中,会通过Request对象构建者模式的api传入url。
这里OkHttpClient、Request对象的设计,实际是【构建者设计模式】的应用,本篇不对设计模式做详细介绍。
第三步:Call call = okHttpClient.newCall(request);构建了call类对象,传入Request对象,看newCall方法的源码,实际返回的RealCall对象。这里面的目的是准备一个真实的请求对象在未来的某个时间点将被执行。
第四步:异步情况下,通过 call.enqueue(new Callback() {}),完成加入请求队列的操作,等待源码层的实现发起正式的网络请求;同步情况下直接调用call.excute()。
作为应用层开发,完成上述四步流程,一个完整的网络请求所需写的代码就完成了,使用起来还是比较简单的。
接下来,我们继续看call.enqueue(new Callback(){})的内部实现,call的真实对象是RealCall的实例。
2.1OkHttp发起网络请求的两种方式
1、同步请求
同步请求直接调用RealCall.execute()方法,源码如下:@Override public Response execute() throws IOException {
...
try {
//调用分发器执行
client.dispatcher().executed(this);
//真正的执行代码在此方法中,最终会返回Response对象,这个方法的核心实现拦截器的相关逻辑
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
}
....
}
Dispatcher中的执行代码:/**Used by Call#execute to signal it is in-flight.*/
synchronized void executed(RealCall call) {
//未做任何执行操作,只是添加到运行队列,便于对请求的取消操作进行统一管理
runningSyncCalls.add(call);
}
上述代码同步执行,也不涉及线程池。
2、异步请求
看RealCall.enqueue方法的源码:@Override public void enqueue(Callback responseCallback) {
//当前call正在被执行的一个判定
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
//这里实际调用了dispatcher的enqueue方法(小细节 这里在RealCall内部创建Asyncall的对象实例)
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
接下来,看一下dispatcher类的源码:/**
* 源码中注释的解释:Policy on when async requests are executed.
* 首先Dispatcher这里被定义为分发器,处理异步请求执行时的策略
*/
public final class Dispatcher {
//最大请求数
private int maxRequests = 64;
//同一个host地址的最大请求数
private int maxRequestsPerHost = 5;
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
/** 正在执行的任务,包括刚被取消未完成的;这里Deque Double end queue*/
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Ready async calls in the order they'll be run. */
/** 准备好将被按顺序执行的call任务*/
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
....
synchronized void enqueue(AsyncCall call) {
//判定条件入不同的队列
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
//使用线程池执行call
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
....
}
runningAsyncCalls:异步任务的运行时队列,readyAsyncCalls:异步任务的等待队列。
runningAsyncCalls.size() < maxRequests 最大请求数<64;同时同一个host地址的最大请求数<5,则把当前call对象添加到运行时队列中,并使用线程池执行call,否则将包装了request的call对象添加到readyAsyncCalls异步等待队列中。这里面的队列是Deque,即Double end queue,是一个双端可插入、移除元素的队列,为什么使用这种数据结构?
之后,使用线程池executorService.execute(call)执行call,前面diam注释中提到过,这里的call是new出来的AsyncCall对象实例。AsyncCall是RealCall的内部类,定义了Request()方法和前面构建的Request对象进行关联。final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
//orginalRequest,就是在构建RealCall对象的时候,把先前构建的Request对象传入的
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@Override protected void execute() {
boolean signalledCallback = false;
try {
//真实发起请求的逻辑实现在拦截器中的逻辑中。
Response response = getResponseWithInterceptorChain();
....
} catch (IOException e) {
...
}
} finally {
//finished方法是对Dispatcher中运行时队列的移除操作
client.dispatcher().finished(this);
}
}
}
}
AsyncCall是一个runable对象,真正发起请求的是在其内部的实现execute方法中。
接下来,继续看一下client.dispatcher.finished()方法的源码实现,源码Dispatcher类中的finished方法。private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//前面分析到同步运行时任务和异步执行时任务都会添加到Dispatcher的运行时队列中,通过finished方法移除
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//这个是啥?继续看这个方法的源码了
if (promoteCalls) promoteCalls();
//异步任务和同步任务正在执行的和
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
finished(runningSyncCalls, call, false)
finished方法的第三个参数,同步执行为false,异步执行为true。参数的含义是指是否需要请求提升,什么意思呢,看源码实现:public final class Dispatcher {
private void promoteCalls() {
//请求的最大限制
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
//无等待队列
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
// 从上述英文注释,可以看出“无等待队列中的call需要提升”。
// 遍历处理等待队列
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
//添加到运行队列
runningAsyncCalls.add(call);
//线程池执行
executorService().execute(call);
//上述两句代码和我正常发起异步网络请求是一致的,只是call是从等待队列中取出放到运行队列中
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
}
到此,同步请求或是异步请求,直到分发器的流程就分析完了,但是整体流程还并未分析完成,真正的请求在这里 Response response = getResponseWithInterceptorChain();实现。再继续分析后续流程之前,我们对前面分析过的流程中设计的Dispatcher分发器做一个流程小结。
2.3Dispatcher分发器流程小结
从上述源码流程的分析过程,可以看出分发器接受RealCall调用传递进来的AsyncCall实例,Dispatcher内部设计了两个队列,分别是运行时异步请求队列、等待异步请求队列,Dispatcher控制整体请求的策略和限制,包括最大请求数不超过64、同host请求书不超过5、等待队列call请求对象的promote(从等待异步队列转移到运行时异步队列)。
Dispatcher分发器 :管理同步请求和异步请求,对于同步请求只做标记清理作用,对于异步请求同时处理等待队列和运行时队列的转移策略。
小结流程图:
2.2OkHttp中的拦截器相关源码流程分析
在上一小节的源码解析中,我们分析到无论是同步请求还是异步请求,最终的发起请求的逻辑应该在拦截器相关的代码实现中Response response = getResponseWithInterceptorChain();通过此方法的调用,最终返回响应的Response对象实例。我们看一下getResponseWithInterceptorChain()此方法的源码实现。final class RealCall implements Call {
Response getResponseWithInterceptorChain() throws IOException {
// 构建一个拦截器的集合
List<Interceptor> interceptors = new ArrayList<>();
// 向集合中添加通过Client客户端添加的拦截器
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) {
// 非webSocket添加网络拦截器
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);
}
}
真实的拦截器的执行逻辑,在chain.proceed(originalRequest)的源码实现中public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
...
//创建一个新的next chain,实际上就是将当前的拦截器从链中排除
RealInterceptorChain next = new RealInterceptorChain(this.interceptors, streamAllocation, httpCodec, connection, this.index + 1, request, this.call, this.eventListener, this.connectTimeout, this.readTimeout, this.writeTimeout);
//按照索引取拦截器
Interceptor interceptor = (Interceptor)this.interceptors.get(this.index);
//执行拦截器的intecept拦截逻辑,并持有next的引用,并继续执行,直到被处理完毕
Response response = interceptor.intercept(next);
...
return response;
}
在上述这个拦截器流程中,实际上是一种【责任链设计模式】的应用,本篇不对设计模式做详细介绍。我们继续来看拦截器的源码实现,在上述代码流程中,涉及到五大默认核心拦截器
1、retryAndFollowUpInterceptor
retryAndFollowUpInterceptor是重试和重定向拦截器,这个OkHttp框架中默认的第一个拦截器,很显然这个拦截器是直接首次接受request的拦截,最终通过责任链式调用的并返回response响应对象。public Response intercept(Chain chain) throws IOException {
...
while(!this.canceled) {
Response response;
try {
//责任链式调用
response = realChain.proceed(request, streamAllocation, (HttpCodec)null, (RealConnection)null);
releaseConnection = false;
} catch (RouteException var17) {
//路由异常
if (!this.recover(...)) {
//判定是否能够满足重试条件
throw var17.getLastConnectException();
}
releaseConnection = false;
//重试
continue;
} catch (IOException var18) {
//IO异常
boolean requestSendStarted = !(var18 instanceof ConnectionShutdownException);
if (!this.recover(...)) {
//判定是否满足重试条件
throw var18;
}
releaseConnection = false;
//重试
continue;
} finally {
.....
}
....
//重定向部分的调用
Request followUp = this.followUpRequest(response, streamAllocation.route());
if (followUp == null) {
if (!this.forWebSocket) {
streamAllocation.release();
}
return response;
}
....
}
}
retryAndFollowUpInterceptor中的intercept的逻辑,还是比较清晰的,通过while循环,然后如果出现路由异常和IO异常,就计划发起重试,在发起重试前会通过!this.recover(…)这个表达式来判定是否满足重试条件。private boolean recover(IOException e, StreamAllocation streamAllocation, boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
if (!this.client.retryOnConnectionFailure()) {
// 构建OkHttpClient的时候,设置了不允许重试,则失败就结束
return false;
} else if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) { // io异常:boolean requestSendStarted =!(var18 instanceof ConnectionShutdownException);
// 路由异常,requestSendStarted默认是false
return false;
} else if (!this.isRecoverable(e, requestSendStarted)) {
// 判断是否是可发起重试异常
return false;
} else {
// 判断是否有跟多路由
return streamAllocation.hasMoreRoutes();
}
}
经过了上面的两个条件判断后,如果走到了第三个分支条件,则需判断是否是可以发起重试的异常,说白了,就是提前预判一下,我发起重试是否有可能管用吗。isRecoverable,啥意思,就是是否是可恢复的。private boolean isRecoverable(IOException e, boolean requestSendStarted) {
//协议异常 无法重试
if (e instanceof ProtocolException) {
return false;
} else if (!(e instanceof InterruptedIOException)) {
if (e instanceof SSLHandshakeException && e.getCause() instanceof CertificateException) {
// ssl握手证书问题 false,无法重试
return false;
} else {
// ssl 未认证异常
return !(e instanceof SSLPeerUnverifiedException);
}
} else {
// socket超时 正常重试,这个也是我们平时最常见看到的重试触发点
return e instanceof SocketTimeoutException && !requestSendStarted;
}
}
重定向的逻辑实现,因为在我的日常研发中基本没有碰触过,这里也就不细纠了,也确实源码研究的文章篇幅都比较长。到此,retryAndFollowUpInterceptor拦截器的流程就分析完毕了。
小结一下: (因为流程并不复杂就不配流程图了)
retryAndFollowUpInterceptor是请求发起重试和重定向的逻辑,在责任链的调用中,是第一个接受Request对象的首个拦截器,但是其实更重要的也是最终Response响应对象,最终向上层交付的最后一道关。
当后续的责任链式调用的拦截器的结果返回到当前拦截中,如果出现了RouteException、IOException,则会尝试发起可发起的重试,比如socket超时,当然在构建OkHttpClient对象的时候,可以通过retryOnConnectionFailure设置为false,表示不启用框架的重试能力,重定向的逻辑,在重试逻辑之后,根据resoponse对象的响应码,处理相关重试逻辑。
2、BridgeInterceptor
桥拦截器,连接应用程序和服务器的桥梁,我们发出的请求将会经过它的处理才能发给服务器,比如设置请求内容长度,编码,gzip压缩,cookie等,获取响应后保存Cookie等操作。public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.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 != -1L) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
// 设置请求的主机地址
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", Util.hostHeader(userRequest.url(), false));
}
// 设置连接属性
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// 设置接收编码
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
// 设置cookie
List<Cookie> cookies = this.cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", this.cookieHeader(cookies));
}
// 设置请求头的用户代理
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
// 获取响应头
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()
// 处理是否进行gzip解压...
.....
}
return responseBuilder.build();
}
这个拦截器的就是对Http请求的头的设置拦截器,这些请求头信息补全,也无法完成和服务建立连接和请求,故被称为客户端发起请求到服务端的桥梁,被称作桥拦截器。
3、CacheInterceptor
缓存拦截器,在日常开发中,也是经常会使用到的一个拦截器,作用是从本地缓存中获取响应数据,并将服务器的数据写入缓存。核心逻辑仍然在intercept()方法中。public Response intercept(Chain chain) throws IOException {
// 读取候选的旧缓存,cacheCandidate,命名确实很好
Response cacheCandidate = this.cache != null ? this.cache.get(chain.request()) : null;
long now = System.currentTimeMillis();
// 解析请求的缓存策略
CacheStrategy strategy = (new Factory(now, chain.request(), cacheCandidate)).get();
// CacheStrategy的两个成员变量,用于确定缓存的使用策略
Request networkRequest = strategy.networkRequest;//如果是null,不使用网络请求
Response cacheResponse = strategy.cacheResponse; //如果是null,不用缓存
//上述两个成员变量,理论上能组成四种缓存策略,可以直接查看下文中的表格解析
....
//如果不使用网络请求、而缓存响应也为空,则返回504
if (networkRequest == null && cacheResponse == null) {
return (new Builder()).request(...).protocol(Protocol.HTTP_1_1)
.code(504).message("..")...build();
} else if (networkRequest == null) {
//不是用网络请求,cacheResponse非空,直接返回缓存响应
return cacheResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).build();
} else {
//networkRequest 不为null,使用网络请求
Response networkResponse = null;
try {
//推进责任链式调用继续执行
networkResponse = chain.proceed(networkRequest);
}
Response response;
if (cacheResponse != null) {
// 缓存存在
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
// 响应码为HTTP_NOT_MODIFIED就是304,304是无响应body的,表明缓存有效,直接使用缓存响应
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();
this.cache.trackConditionalCacheHit();
this.cache.update(cacheResponse, response);
//通过缓存响应构建真实的缓存返回
return response;
}
Util.closeQuietly(cacheResponse.body());
}
//无缓存匹配情况
response = networkResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).
networkResponse(stripBody(networkResponse)).build();
if (this.cache != null) {
//OKHttpClient创建的时候配置了使用缓存,一般为一个请求第一次发起会出现,配置了缓存无缓存的情况
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
//更新缓存
CacheRequest cacheRequest = this.cache.put(response);
return this.cacheWritingResponse(cacheRequest, response);
}
....
}
return response;
}
}
CacheStrategy的两个成员变量组合解析:
NetworkRequest
CacheResponse
组合结果说明
null
null
既不使用网络,有没有缓存,肯定就没数据返回了,返回504
null
not null
不使用网络,缓存非空,就使用缓存
not null
null
使用网络请求,缓存为空
not null
not null
发起网络请求,返回304(无修改),则直接用缓存的响应,返回200,更新缓存响应并返回(只缓存get请求)
缓存拦截器的主流程,都是在与对缓存策略的处理上,主要搞清楚组合情况就可以了。使用缓存技术也并非OkHttp特有的技术,而是Http的基础。要在应用开发中使用缓存,不仅能够给用户更好的体验,同时也能减小服务器的压力。
故而,在日常开发中,属于get请求属性的请求,我们就是用get请求,这样可以使用我们的Http的缓存策略,曾经的我经历的一个老项目中,所有的app接口发起全是Post请求,这是不规范的,也是性价比不高的方案。
之后,还会再写一篇关于OkHttp的缓存设置内容,相当于补全OkHttp的缓存拦截器这部分的篇幅。
4、ConnectInterceptor /** 打开一个到目标服务器的连接以及执行下一个拦截器 */
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();
StreamAllocation streamAllocation = realChain.streamAllocation();
// 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);
}
}
直接看源码,连接拦截器的源码部分很简单,但却不简单。
HttpCodec是一个编解码请求和请求和响应的接口,HttpCodec接口的实现分别对应Http1Codec、Http2Codec。httpCodec实例对象是通过streamAllocation.newStream(client, chain, doExtensiveHealthChecks);来获得的。
同时通过streamAllocation获得的是一个RealConnection的对象。
这个拦截器的作用就是打开一个和Server的连接,并HttpCodec、RealConnection实例传递给下一个拦截器CallServerInterceptor,发起执行网络请求,可以理解成ConnectionInterceptor的核心作用就是为真实的网络请求做最直接的准备工作。
关于连接的建立过程,在这一小节不详细分析,会在2.3小节中的连接池模块详细分析。
接下来,就继续看CallServerInterceptor的源码实现。
5、CallServerInterceptor
CallServerInterceptor,调用服务拦截器,正如这个类的文档注释所言,是整个OkHttp框架中的最后一个拦截器,真实发起网络请求的实现就在此拦截的的源码实现中。/** OkHttp拦截器链条中的最后一个拦截器,真实发起网络请求 */
public final class CallServerInterceptor implements Interceptor {
...
@Override public Response intercept(Chain chain) throws IOException {
...
//发送请求头
realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
//请求体的许可(内部实现是非Get、非Header方法,则许可通过))&& 请求的body非空
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// 如果请求头中是“Expect: 100-continue”,等待HTTP/1.1 响应后,向服务器传输request body请求体数据,如果没有获取服务器的100响应,则直接return 4xx,并不在传输request body请求体数据。
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
//将头部内容请求发送给服务器
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
//读取响应头,正常响应“Expect:100-continue”,即返回的响应码为100,responseBuilder获取的响应头将非空
responseBuilder = httpCodec.readResponseHeaders(true);
}
//正常响应
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()) {
// 如若 "Expect: 100-continue" 并未返回, 禁止HTTP/1的连接被复用
// 除此之外,我们仍然传输请求体给服务是的服务处于一个固定状态。()
streamAllocation.noNewStreams();
}
}
// 请求结束
httpCodec.finishRequest();
// 构建响应头
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
// 构建响应数据
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
// 如果这个响应码又是个100
int code = response.code();
if (code == 100) {
// 当我们并发送一个请求,服务端给响应100
// 就尝试在读取一遍实际的响应数据
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);
// code 101 情况
if (forWebSocket && code == 101) {
// 连接升级
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
...
return response;
}
CallServerInterceptor,主要是对Http协议本身的封装实现,需要充分的理解这块的源码实现内容,需要对Http的基础知识有所了解。
总结一下CallServerInterceptor的源码实现流程,发送请求头部–>读取非get、非head请求(Post)的响应数据–>结束请求的发送–>读取响应头部–>读取响应数据–>返回封装响应数据。
补充解释:
“Expect: 100-continue”的来龙去脉:(内容来源于腾讯云社区文档)
HTTP/1.1 协议里设计 100 (Continue) HTTP 状态码的的目的是,在客户端发送 Request Message 之前,HTTP/1.1 协议允许客户端先判定服务器是否愿意接受客户端发来的消息主体(基于 Request Headers)。
即, Client 和 Server 在Post (较大)数据之前,允许双方“握手”,如果匹配上了,Client 才开始发送(较大)数据。
这么做的原因是,如果客户端直接发送请求数据,但是服务器又将该请求拒绝的话,这种行为将带来很大的资源开销。
协议对 HTTP/1.1 clients 的要求是:
如果 client 预期等待“100-continue”的应答,那么它发的请求必须包含一个 " Expect: 100-continue" 的头域!
到此 ,整个OkHttp发起一个网络请求的拦截器流程也就分析完了。
三、总结
整个OkHttp框架的源码阅读和分析确实需要花费一些时间,但是即使到本篇文章的末尾,也并未能尽显OkHttp源码中的所有实现,比如整个框架中所涉及到的多种设计模式的应用,缓存模块的设计细节、线程池、连接池设计等。这样会使得篇幅太长,会再用两篇独立的文章继续分析OkHttp源码中的其它细节。
那其实整个OkHttp的实现网络同步请求和异步请求的流程,本篇基本分析完整,整个OkHttp发起请求的核心逻辑实现,本质上都是由拦截器完成的。五大拦截器的核心流程图如下:
发起和响应流程回顾:
真正发起请求的是最后一个拦截器,request对象从第一个拦截器开始一直交付到最后一个CallServerInterceptor,最终在CallServerInterceptor中完成真实的网络请求,对于请求的结果包装成response对象,再逆向返回给上层调用,最终交付给第一个拦截器RetryAndFollowUp拦截器返回。
期待下一篇啦。
获得更多更新,关注gongzhonghao:Hym4Android