解析OKHttp首先走一遍正常的流程,然后将比较有意思的点拿出来说明
正常流程分析
1.OkHttpClient初始化
OkHttpClient mOkHttpClient = new OkHttpClient();
通过代码查看,可以看到调用了内部的Builder构造
如下
public Builder() {
//调度器
dispatcher = new Dispatcher();
//默认支持的协议列表
protocols = DEFAULT_PROTOCOLS;
//默认的连接规范
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
//默认的代理选择器(直连)
proxySelector = ProxySelector.getDefault();
//默认不管理cookie
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
//主机验证
hostnameVerifier = OkHostnameVerifier.INSTANCE;
//证书锁,默认不开启
certificatePinner = CertificatePinner.DEFAULT;
//默认不进行授权
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
//初始化连接池
connectionPool = new ConnectionPool();
//DNS
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
//超时时间
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
接下来介绍下关于上面的OkHttpClient配置需要用到的类
Dispatcher
调度器,里面包含了线程池和三个队列(readyAsyncCalls:保存等待执行的异步请求;runningAsyncCalls:保存正在运行的异步请求;runningSyncCalls:保存正在执行的同步请求)
//保存准备运行的异步请求(当运行请求超过限制数时会保存在此队列)
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//保存正在运行的异步请求
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//保存正在运行的同步请求
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
对于异步请求,调用Dispatcher的enqueue方法,在这个方法会将相关请求提交到线程池中操作,从而异步执行
synchronized void enqueue(AsyncCall call) {
//检查容量大小
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);//加入队列
executorService().execute(call);//执行
} else {
//超过容量大小后,加入准备队列中
readyAsyncCalls.add(call);
}
}
对于同步请求,不需要提交到线程池执行,通过Dispatcher的executed方法调用即可
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
当请求执行完毕后,调用finished将请求从runningAsyncCalls队列中移除,并且检查readyAsyncCalls以继续提交在队列中准备的请求。
//移除执行完毕的请求
synchronized void finished(AsyncCall call) {
if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
promoteCalls();//推进请求队列
}
//推进请求
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; //容量已满,不提交新请求
if (readyAsyncCalls.isEmpty()) return; // 没有正在准备的请求,返回
//从readyAsyncCalls中循环取出AsyncCall直到达到容量上限
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // 达到上限后返回
}
}
Protocal
协议类,用来表示使用的协议版本,比如http/1.0,http/1.1,spdy/3.1,h2等
ConnectionSpecs
连接规范,用于配置Socket连接层,对于HTTPS,还能配置安全传输层协议(TLS)版本与密码套件(CipherSuite)
Proxy与ProxySelector
Proxy代理类,默认有三种代理模式DIRECT(直连),HTTP(Http代理),SOCKS(socks代理)
ProxySelector代理选择类,默认不使用代理,即使用直连方式,当然,我们可以自定义配置,以指定URI使用某种代理,类似代理软件的PAC功能。
CookieJar
用来管理cookie,可以根据url保存cookie,也可以通过url取出相应cookie。默认的不做cookie管理。该接口中有两个抽象方法,用户可以自己实现该接口以对cookie进行管理。
//保存cookie
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
//根据Url导入保存的Cookie
List<Cookie> loadForRequest(HttpUrl url);
SocketFactory
Socket工厂,通过createSocket来创建Socket
HostnameVerifier
主机名验证器,与HTTPS中的SSL相关,当握手时如果URL的主机名不是可识别的主机,就会要求进行主机名验证
public interface HostnameVerifier {
//通过session验证指定的主机名是否被允许
boolean verify(String hostname, SSLSession session);
}
CertificatePinner
证书锁,HTTPS相关,用于约束哪些证书可以被信任,可以防止一些已知或未知的中间证书机构带来的攻击行为。如果所有证书都不被信任将抛出SSLPeerUnverifiedException异常。
其中用于检查证书是否被信任的源码如下:
//检查证书是否被信任
public void check(String hostname, List<Certificate> peerCertificates)
throws SSLPeerUnverifiedException {
List<Pin> pins = findMatchingPins(hostname);//获取Pin(网址,hash算法,hash值)
if (pins.isEmpty()) return;
if (certificateChainCleaner != null) {
//通过清洁器获取信任的证书
peerCertificates = certificateChainCleaner.clean(peerCertificates, hostname);
}
for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
//对证书进行比对hash值,如果配对失败就抛出SSLPeerUnverifiedException异常
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
// Lazily compute the hashes for each certificate.
ByteString sha1 = null;
ByteString sha256 = null;
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
Pin pin = pins.get(p);
if (pin.hashAlgorithm.equals("sha256/")) {
if (sha256 == null) sha256 = sha256(x509Certificate);
if (pin.hash.equals(sha256)) return; // Success!
} else if (pin.hashAlgorithm.equals("sha1/")) {
if (sha1 == null) sha1 = sha1(x509Certificate);
if (pin.hash.equals(sha1)) return; // Success!
} else {
throw new AssertionError();
}
}
}
// ...
}
Authenticator
身份认证器,当连接提示未授权时,可以通过重新设置请求头来响应一个新的Request。状态码401表示远程服务器请求授权,407表示代理服务器请求授权。该认证器在需要时会被RetryAndFollowUpInterceptor触发。
public interface Authenticator {
Authenticator NONE = new Authenticator() {
@Override public Request authenticate(Route route, Response response) {
return null;
}
};
Request authenticate(Route route, Response response) throws IOException;
}
关于授权的源码实现如下:
class MyAuthenticator implements Authenticator {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(...)
Request.Builder builder=response.request().newBuilder();
if(response.code()==401){
builder .header("Authorization", credential);
}else if(response.code()==407){
builder .header("Proxy-Authorization", credential);
}
return builder.build();
}
}
ConnectionPool
连接池,用于管理HTTP和SPDY连接的复用以减少网络延迟,HTTP请求相同的Address时可以共享同一个连接。
DNS
DNS这里就不用介绍了,用于根据主机名来查询对应的IP。
2.发起请求
使用OKHttp发送请求一般有两种方式,一种是同步方式,一种是异步方式,如下
//异步方式
Request request = new Request.Builder()
.url("").build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
//同步方式
try {
Request request = new Request.Builder()
.url("").build();
Call call = mOkHttpClient.newCall(request);
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
接下来我们分别从源码角度分析下这两种方式
首先是同步方式
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
这里收先调用了Dispatcher的executed方法,将这个请求加入runningAsyncCalls队列中,然后调用getResponseWithInterceptorChain方法获取Respone,这个就是我们请求后得到的回复,获取后返回这个Respone,最后在finally调用了Dispatcher的finished方法,将请求从runningAsyncCalls队列中移除
接下来是异步方式
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
可以看到调用Dispatcher的enqueue方法传递了一个AsyncCall对象,注意这个AsyncCall对象继承Runnable接口,所以在当在线程池中运行会调用AsyncCall中的execute方法,接下来我们看下AsyncCall的execute方法,如下
@Override protected void execute() {
boolean signalledCallback = false;
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) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
可以看到这里调用getResponseWithInterceptorChain方法获取Respone,接下来通过回调传递出去,最后在finally调用了Dispatcher的finished方法,将请求从runningAsyncCalls队列中移除
通过这两个代码分析,可以知道获取Respone都是通过getResponseWithInterceptorChain方法,唯一的区别是一个是在主线程中,另外一个在线程池中的线程,接下来看一下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);
return chain.proceed(originalRequest);
}
这里是真正发出网络请求的地方,可以看到这里有很多个Interceptor,Interceptor是OkHttp中最核心的一个东西,它把实际的网络请求、缓存、透明压缩等功能都统一了起来,每一个功能都只是一个 Interceptor,它们再连接成一个 Interceptor.Chain,环环相扣,最终圆满完成一次网络请求。
从 getResponseWithInterceptorChain 函数我们可以看到,Interceptor.Chain 的分布依次是:
![](http://i4.buimg.com/519918/6d74ff7c4d531915.png =350x434)
流程如下:
1.在配置 OkHttpClient 时设置的 interceptors;
2.负责失败重试以及重定向的 RetryAndFollowUpInterceptor;
3.负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor;
4.负责读取缓存直接返回、更新缓存的 CacheInterceptor;
5.负责和服务器建立连接的 ConnectInterceptor;
6.配置 OkHttpClient 时设置的 networkInterceptors;
7.负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。
这里很明显的是使用了责任链模式,接下来就是分析一下每一个Interceptor究竟是干了什么事情
3.分析Interceptor
1.RetryAndFollowUpInterceptor 重试与重定向拦截器
这个拦截器主要用来实现重试与重定向的功能,核心代码如下
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//初始化流分配器
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()));
int followUpCount = 0;
Response priorResponse = null;
while (true) {//死循环
//..
//省略了部分源码
Response response = null;
boolean releaseConnection = true;
try {
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (Exception e) {
//..
//省略了部分源码
releaseConnection = false;
continue;
} finally {
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
//将上次的请求放入priorResponse中
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//检查是否触发重定向重试等条件,并返回Request
Request followUp = followUpRequest(response);
if (followUp == null) {//null表示无需重试
if (!forWebSocket) {
streamAllocation.release();
}
return response;//返回response
}
//..
//省略了部分源码
request = followUp;
priorResponse = response;
//while循环进行下次请求
}
}
通过代码可以发现RetryAndFollowUpInterceptor内部通过while(true)死循环来进行重试获取Response(有重试上限,超过会抛出异常)。followUpRequest主要用来根据响应码来判断属于哪种行为触发的重试和重定向(比如未授权,超时,重定向等),然后构建响应的Request进行下一次请求。当然,如果没有触发重新请求就会直接返回Response。
2.BridgeInterceptor 桥接拦截器
桥接拦截器,用于完善请求头,比如Content-Type、Content-Length、Host、Connection、Accept-Encoding、User-Agent等等,这些请求头不用用户一一设置,如果用户没有设置该库会检查并自动完善。此外,这里会进行加载和回调cookie。
核心代码如下:
@Override
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
//将用户没有写入请求头的内容自动补充进去,比如Content-Type、Content-Length、Host、Connection、Accept-Encoding、User-Agent等等
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
//..
}
//获取cookie添加到请求头中
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
//...
Response networkResponse = chain.proceed(requestBuilder.build());
//将响应cookie回调出去供用户保存
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//...
//省略了部分源码
responseBuilder.headers(strippedHeaders);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
return responseBuilder.build();
}
3.CacheInterceptor 缓存拦截器
缓存拦截器首先根据Request中获取缓存的Response,然后根据用于设置的缓存策略来进一步判断缓存的Response是否可用以及是否发送网络请求(CacheControl.FORCE_CACHE因为不会发送网络请求,所以networkRequest一定为空)。如果从网络中读取,此时再次根据缓存策略来决定是否缓存响应。
核心代码如下:
@Override
public Response intercept(Chain chain) throws IOException {
//通过Request从缓存中获取Response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//根据请求头获取用户指定的缓存策略,并根据缓存策略来获取networkRequest,cacheResponse。cacheResponse为null表示当前策略就算有缓存也不读缓存
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;//表示发往网络的request,不请求网络应为null
Response cacheResponse = strategy.cacheResponse;//返回从缓存中读取的response
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
//cacheResponse表示不读缓存,那么cacheCandidate不可用,关闭它
closeQuietly(cacheCandidate.body());
}
//..
//省略了部分源码
//返回从缓存中读取的Response
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
//..
//省略了部分源码
//获取网络Response
networkResponse = chain.proceed(networkRequest);
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (HttpHeaders.hasBody(response)) {
//如果可以缓存(用户允许,响应也允许)就进行缓存到本地
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
配置缓存策略的方法如下:
Request request = new Request.Builder()
.cacheControl(CacheControl.FORCE_NETWORK)
.url("")
.build();
4.ConnectInterceptor 连接拦截器
连接拦截器,用于打开一个连接到远程服务器。
核心代码如下
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//获取HttpStream
HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
//获取RealConnection
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpStream, connection);
}
实际上建立连接就是创建了一个HttpCodec对象,它将在后面的步骤中被使用,那它又是何方神圣呢?它是对HTTP协议操作的抽象,有两个实现:Http1Codec和Http2Codec,顾名思义,它们分别对应HTTP/1.1和HTTP/2版本的实现。
在Http1Codec中,它利用Okio对Socket的读写操作进行封装,我们对它们保持一个简单地认识:它对java.io和java.nio进行了封装,让我们更便捷高效的进行IO操作。
5.CallServerInterceptor 调用服务拦截器
调用服务拦截器是拦截链中的最后一个拦截器,通过网络与调用服务器。通过HttpStream依次次进行写请求头、请求头(可选)、读响应头、读响应体。
@Override
public Response intercept(Chain chain) throws IOException {
HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
//写请求头
httpStream.writeRequestHeaders(request);
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
//写请求体
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpStream.finishRequest();
//获取Response。
Response response = httpStream.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
//写入Response的body
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpStream.openResponseBody(response))
.build();
}
//...
return response;
}
这里主要做的事情:
1.向服务器发送request header;
2.如果有request body,就向服务器发送;
3.读取response header,先构造一个Response对象;
4.如果有response body,就在3的基础上加上body构造一个新的Response对象;
这里我们可以看到,核心工作都由HttpCodec对象完成,而HttpCodec实际上利用的是Okio,而Okio实际上还是用的Socket。
到这里,一个请求的流程就基本走完了。接下来说一下OKHttp中比较有意思的点。
网友评论