OKHTTP源码分析(一):主流程和巧妙的责任链
终于下定决心写OkHttp了,我们使用它也已经2年多了,一直没有好好研究过,最近正在看《HTTP权威指南》,借着这个契机我们一起来从实现的角度来学习一次HTTP,所以这会是一个系列的文章。这个系列基于最新的OkHttp大版本3.9.0,GitHub库地址。
作为开篇,肯定有些废话需要絮叨。Android程序员都知道,Android开发中做HTTP请求从最开始的HttpClient,到HttpURLConnection,到现在OkHttp,一路走来OkHttp终于在Android 4.4 由小三熬成了正室了,你可以看到在新的Android SDK中,HttpURLConnection都是使用OkHttp来实现的,下图是URL这个类中产生URLStreamHandler的方法:
所以让我们开始撸源码的旅程吧,这一篇我们会简单过一下OkHttp做Http请求的主流程,以及实现这个主流程所使用的一个设计模式:责任链模式。
HttpURLConnection 其实是JDK提供的API,相当于Java语言默认提供了一个HTTP协议的实现给开发者,只不过在Android SDK里面把这个类重写了;HttpClient是Apache公司提供的Http套件中的一部分,在Android 6.0之后的SDK中已经把这个类去掉了,所以如果你的compileSdkVersion>=23时,如果需要使用HttpClient,那么需要这么配置:
android { useLibrary 'org.apache.http.legacy' }
使用
OkHttp上手还是非常简单的,你只需要极少的代码就可以完成一个Http请求,我们以获取百度首页为例:
-
第一步,创建一个Call.Factory,它是Call的工厂接口,OkHttpClient实现了这个接口,所以我们创建一个默认的OkHttpClient。
OkHttpClient okHttpClient = new OkHttpClient();
Call是OkHttp中对于一个请求和响应的控制API,它主要提供了三个API:同步开始请求、异步开始请求和取消请求。
-
第二步,设置请求参数
Request request = new Request.Builder() .url("https://www.baidu.com/index.html") .method("get", null).build();
-
第三步,通过Call.Factory创建一个Call,开始执行。
//我们可以同步执行这个请求: Response response = okHttpClient.newCall(request).execute(); //也可以异步执行 okHttpClient.newCall(request) .enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) {} @Override public void onResponse(Call call, final Response response) throws IOException { final String responseStr = response.body().string(); //搞事情,注意,这里是异步线程 } });
通过这三步,我们可以很方便的构建一个Http请求,并接受到后台的响应,同时,由于Call有异步的方法,所以我们可以知道OkHttpClient内部肯定有自己的异步框架,这点和HttpURLConnection只提供了同步的方法不同。
主流程分析
由于OkHttpClient实现了Call.Factory,所以我们看看它的实现:
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
这里一看,把所有逻辑都放到了RealCall了,注意第三个参数为false,写着for web socket,也就是true的时候,这里会把Http请求升级成WebSocket,这里看出来OKHTTP也已经开始干一些不属于HTTP协议的事情了。
敲黑板了,WebSocket是对于Http协议的补充,为什么需要它呢?我们知道虽然在HTTP 1.1 版本之后,保持长链已经被写到协议里面,但是HTTP协议还是有一个缺陷无法避免,那就是只能客户端往服务器发Request,服务器响应Response,尽管底下的TCP是全双工的,但是HTTP协议不支持服务器主动向客户端发送数据(PUSH)。为了解决这个问题,一群前端同学开始造轮子,一个崭新的协议WebSocket来到你的面前,这个协议的几个特点:
1、基于HTTP握手,所以默认都会运行在 80 或者 443,这样设计的好处是可以使得这次连接能通过各种防火墙和代理设备(这两个端口这么出名,一般大家都会放行的)
2、HTTP握手成功之后,相当于之后的通信就已经没有HTTP的限制了,完全是全双工的通信。
关于WebSockete更多内容戳这里
收回来,我们看到创建Call被放到了RealCall.newRealCall这个静态方法中了,它其实就是简单的创建了一个RealCall的实例,那么看起来,应该所有请求的逻辑都在RealCall中了,你可能以为RealCall应该怎么着也要几千行代码吧,结果里面带注释加起来200行...这个略屌啊!
由于我们今天主要是说明HTTP请求和响应的主流程,很明显以同步执行为栗子能更清晰地说明OkHttp对于请求和响应的处理流程:
@Override
public Response execute() {
return getResponseWithInterceptorChain();
}
删掉杂七杂八的代码之后,这就调用了getResponseWithInterceptorChain()一个逻辑,这个方法也是OkHttp中最重要的一个设计了,它通过不到15行代码就把一个请求响应返回回来了,这也是为什么RealCall这个类能够这么精简的原因了。
Response getResponseWithInterceptorChain() throws IOException {
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);
}
这就是传说中的责任链模式了。
我们来举个通俗的栗子来说明责任链:
比如说我们提交一个报销的单子,公司的报销制度是这样的, <500元,你们主管批准就可以了; <1000元,需要主管和经理审批;<5000元,需要主管 经理 总监 审批; >5000,需要 主管 经理 总监 CEO审批;
那么在审批这个流程中,你作为责任链的调用方,发起调用,和你对接的人肯定是你的主管,所以对于你来说你每次都是向主管发起申请就可以了。(责任链的第一个好处:调用方不需要关心整个流程,它有唯一的调用入口);
主管 经理 总监 CEO 就是你责任链中的四个角色,每个角色只关心自己需要处理的事情和如果我处理不了需要甩锅给谁,比如主管的甩锅对象是经理..(责任链的第二个好处:功能分层,每个角色只处理自己相关的东西);
我们就重点来看看OkHttp中对于责任链的实现吧。首先在OkHttp中抽象了一个拦截器Interceptor
,它比较简单,只有一个方法,接受一个调用链,返回一个结果:
Response intercept(Chain chain) throws IOException;
Interceptor就是责任链中的一个角色,但是在OkHttp中有一个Chain的概念,就是调用链,我们自己定义一个拦截器的时候,可以调用chain.process(chain.request()) 就可以获得一个Response,我们来看看Chain是怎么实现的吧,在OkHttp中Chain的实现类是RealInterceptorChain,我们看到在getResponseWithInterceptorChain中就是直接new了一个RealInterceptorChain实例,并调用了它的process方法,那我们就直接来看这个方法吧:
public Response proceed() {
//可以看到拦截器中的Chain其实并不是一个对象,每一个拦截器就对应了一个Chain;
//每个Chain会保存两个属性,一个拦截器数组,以及当前是那个拦截器(index)
//生成下一个拦截器的Chain
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
//直接调用当前拦截器,传入下一个拦截器的Chain,可以想象一下,一般我们都会在拦截器中调用chain.process,这样,整个调用链就成立了;当然,我们也可以不在拦截器中调用process,这样所有下游的拦截器就都不会走了。
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
通过对源码分析,我们知道OkHttp对于拦截器的执行是有严格的顺序的;对于拦截器,OKHttp提供了两个API给我们添加,分别是:应用拦截器,拦截器会加到最开始;网络拦截器,会加到最后一个发生网络交互的拦截器
CallServerInterceptor
之前,官方wiki对于这两个拦截器的区别其实都是由于这个添加顺序导致的。
最后,一图胜千言,从网络上copy了一张图:
image.png
后面我们会去解析各个拦截器的实现,敬请期待。