![]() |
聪明伶俐的围巾 · 数据结构-王道-树和二叉树 - ...· 1 年前 · |
![]() |
严肃的电影票 · android 获取ssid ...· 1 年前 · |
![]() |
冷静的猴子 · UITextField 类 (UIKit) ...· 2 年前 · |
![]() |
瘦瘦的橙子 · ffmpeg/ffprobe常用命令 - 知乎· 2 年前 · |
synchronized response |
https://hanpfei.github.io/2016/11/15/OkHttp3-HTTP%E8%AF%B7%E6%B1%82%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/ |
![]() |
留胡子的柑橘
1 年前 |
通过一段示例代码来看一下具体要如何操作:
|
|
Call是一个接口,其定义如下:
|
|
Call中还定义了一个Factory接口。
那在OkHttp中,我们调用的Call方法的实际执行过程是怎样的呢?这就需要扒出来在OkHttp中实际使用的Call实现了。OkHttpClient实现了Call.Factory接口,通过接口方法OkHttpClient.newCall()可以看到具体使用的Call实现是哪个类:
|
|
在OkHttp中使用了RealCall来执行整个Http请求。
|
|
通过调用RealCall.execute()同步执行Http请求的过程大体为:
通过调用RealCall.enqueue()异步执行Http请求的过程则为,创建AsyncCall并将之丢给client的dispatcher。而在RealCall.AsyncCall的execute()中执行Http请求的过程与RealCall.execute()中的过程有些类似:
这里再通过Dispatcher的定义来看一下在OkHttp中,请求的执行管理及异步执行是怎么做的:
|
|
在Call的同步执行过程中,调用client.dispatcher().executed(this)向client的dispatcher注册当前Call,Dispatcher仅仅是将Call放进了runningSyncCalls,其它便什么也没做,目测同步执行Call时向Dispatcher注册的主要目的是方便全局性的cancel所有的Call。
Dispatcher中异步的AsyncCall是被放在一个ExecutorService中执行的。默认情况下,这是一个不限容量的线程池。但Dispatcher会限制每个host同时执行的最大请求数量,默认为5,同时也会限制同时执行的总的最大请求数量。runningAsyncCalls中保存所有正在被ExecutorService执行的AsyncCall,而readyAsyncCalls则用于存放由于对单个host同时执行的最大请求数量的限制,或总的同时执行最大请求数量的限制,而暂时得不到执行的AsyncCall。
finished()中,除了会将执行结束的AsyncCall从runningAsyncCalls移除之外,还会检查是否存在由于 单host同时进行的最大请求数量限制 或 总的同时执行最大请求数量限制,而暂时得不到执行的AsyncCall,若存在则满足限制条件的请求会被执行。
所有的Call,不管是异步的AsyncCall还是同步的Call在执行结束后都会检查是否没有正在进行的Http请求了。若没有了,则存在idle 回调时,该回调会被调用。
用户可以通过Dispatcher的构造函数来定制ExecutorService,这需要通过OkHttpClient.Builder在OkHttpClient的构建过程中间接的做到。
回到RealCall,继续来追Call的网络请求及响应处理。来看一下RealCall.getResponseWithInterceptorChain():
|
|
这里主要是创建了一个Interceptor的列表,继而创建了一个Interceptor.Chain对象来处理请求并获得响应。我们继续追踪一下RealInterceptorChain:
|
|
在RealInterceptorChain.proceed()中,除了对状态及获取的reponse做检查之外,最主要的事情即是构造新的RealInterceptorChain对象,获取对应Interceptor,并调用Interceptor的intercept(next)了。在这里,index充当迭代器或指示器的角色,用于指出当前正在处理的Interceptor。
RealInterceptorChain + Interceptor实现了装饰器模式,实现了请求/响应的串式或流式处理。只不过内层装饰器不是外层装饰器的成员变量,而是接口方法中创建的临时变量。
但Interceptor链中具体都有哪些Interceptor呢?我们就在RealCall.getResponseWithInterceptorChain()中打个断点来看一下:
|
|
由此可见OkHttp中,Http请求的实际处理流程将大致如下图这样:
具体这些Interceptor中每一个都会做些什么事情呢?我们后面再来详细地做分析。
首先来看RetryAndFollowUpInterceptor:
|
|
RetryAndFollowUpInterceptor
在
intercept()
中首先从client取得connection pool,用所请求的URL创建Address对象,并以此创建StreamAllocation对象。
Address描述某一个特定的服务器地址。StreamAllocation对象则用于分配一个到特定的服务器地址的流HttpStream,这个HttpStream可能是从connection pool中取得的之前没有释放的连接,也可能是重新分配的。RetryAndFollowUpInterceptor这里算是为后面的操作准备执行条件StreamAllocation。
随后
RetryAndFollowUpInterceptor.intercept()
利用Interceptor链中后面的Interceptor来获取网络响应。并检查是否为重定向响应。若不是就将响应返回,若是则做进一步处理。
对于重定向的响应,
RetryAndFollowUpInterceptor.intercept()
会利用响应的信息创建一个新的请求。并检查新请求的服务器地址与老地址是否相同,若不相同则会根据新的地址创建Address对象及StreamAllocation对象。
RetryAndFollowUpInterceptor
对重定向的响应也不会无休止的处理下去,它处理的最多的重定向级数为20次,超过20次时,它会抛异常出来。
RetryAndFollowUpInterceptor
通过followUpRequest()从响应的信息中提取出重定向的信息,并构造新的网络请求:
|
|
我们知道OkHttp提供了非常好用的容错功能,它可以从某些类型的网络错误中恢复,即出错重试机制。这种出错重试机制主要由recover()来实现:
|
|
主要是对某些类型IOException的恢复,恢复的次数会由StreamAllocation控制。
总结一下
RetryAndFollowUpInterceptor
做的事情:
|
|
这个Interceptor做的事情比较简单。可以分为发送请求和收到响应两个阶段来看。在发送请求阶段,BridgeInterceptor补全一些http header,这主要包括
Content-Type
、
Content-Length
、
Transfer-Encoding
、
Host
、
Connection
、
Accept-Encoding
、
User-Agent
,还加载
Cookie
,随后创建新的Request,并交给后续的Interceptor处理,以获取响应。
而在从后续的Interceptor获取响应之后,会首先保存
Cookie
。如果服务器返回的响应的content是以gzip压缩过的,则会先进行解压缩,移除响应中的header
Content-Encoding
和
Content-Length
,构造新的响应并返回;否则直接返回响应。
CookieJar
来自于
OkHttpClient
,它是OkHttp的
Cookie
管理器,负责
Cookie
的存取:
|
|
由
OkHttpClient
默认的构造过程可以看到,OkHttp中默认是没有提供Cookie管理功能的。由这里的代码,我们大概也能知道要支持Cookie的话,需要做些什么事情。
CacheInterceptor紧接于BridgeInterceptor之后,它主要用来处理缓存:
|
|
对于
CacheInterceptor.intercept(Chain chain)
的分析同样可以分为两个阶段,即请求发送阶段和响应获取之后的阶段。这两个阶段由
chain.proceed(networkRequest)
来分割。
在请求发送阶段,主要是尝试从cache中获取响应,获取成功的话,且响应可用未过期,则响应会被直接返回;否则通过后续的Interceptor来从网络获取,获取到响应之后,若需要缓存的,则缓存起来。
关于HTTP具体的缓存策略这里暂时不再详述。
由
RealCall.getResponseWithInterceptorChain()
可见CacheInterceptor的cache同样来自于OkHttpClient。OkHttp已经有实现Cache的整套策略,在Cache类,但默认情况下不会被用起来,需要自己在创建OkHttpClient时,手动创建并传给OkHttpClient.Builder。
CacheInterceptor接下来是ConnectInterceptor:
|
|
这个类的定义看上去倒是蛮简洁的。ConnectInterceptor的主要职责是建立与服务器之间的连接,但这个事情它主要是委托给StreamAllocation来完成的。如我们前面看到的,StreamAllocation对象是在RetryAndFollowUpInterceptor中分配的。
ConnectInterceptor通过StreamAllocation创建了HttpStream对象和RealConnection对象,随后便调用了realChain.proceed(),向连接中写入HTTP请求,并从服务器读回响应。
连接建立过程的更多细节我们这里先不详述。
ConnectInterceptor之后是CallServerInterceptor,这也是这个链中的最后一个Interceptor,它的主要职责是处理IO:
|
|
CallServerInterceptor
首先将http请求头部发给服务器,如果http请求有body的话,会再将body发送给服务器,继而通过
httpStream.finishRequest()
结束http请求的发送。
随后便是从连接中读取服务器返回的http响应,并构造Response。
如果请求的header或服务器响应的header中,
Connection
值为
close
,
CallServerInterceptor
还会关闭连接。
最后便是返回Response。
总结一下这几个Interceptor的职责:
RetryAndFollowUpInterceptor —>创建StreamAllocation对象,处理http的redirect,出错重试。对后续Interceptor的执行的影响:修改request及StreamAllocation。
BridgeInterceptor————–>补全缺失的一些http header。对后续Interceptor的执行的影响:修改request。
CacheInterceptor————–>处理http缓存。对后续Interceptor的执行的影响:若缓存中有所需请求的响应,则后续Interceptor不再执行。
ConnectInterceptor————>借助于前面分配的StreamAllocation对象建立与服务器之间的连接,并选定交互所用的协议是HTTP 1.1还是HTTP 2。对后续Interceptor的执行的影响:创建了httpStream和connection。
CallServerInterceptor———–>处理IO,与服务器进行数据交换。对后续Interceptor的执行的影响:为Interceptor链中的最后一个Interceptor,没有后续Interceptor。
![]() |
瘦瘦的橙子 · ffmpeg/ffprobe常用命令 - 知乎 2 年前 |