OkHttp拦截器
在之前的文章【OkHttp源码分析】中我已经对OkHttp的整体流程以及它的分发器分发流程进行详细分析,感兴趣的大佬可以观摩观摩,并给小弟提点意见~
今天的重点是把OkHttp的拦截器部分的内容单独拎出来讲讲,因为它是OkHttp的核心部分!
读过OkHttp源码的大佬应该都知道,无论是使用OkHttp进行同步请求还是异步请求,它们都会通过一个方法getResponseWithInterceptorChain()
来获取Response。而这个方法就是通过责任链执行拦截器的入口,所以我们下面就从这个方法开始讲起。
拦截器的执行流程
OkHttp的内部就用了典型的责任链设计模式,它将多个拦截器对象连接起来形成一条链,每个拦截器各司其职。网络请求request就放在这条链上有上至下传递,直到链上的某一个拦截器对象决定对这个网络请求进行处理,处理完后就将处理得到的结果response再从链又下至上返回,这样,每个拦截器都能拿到request和response了。
好了,那么我们就开始进入getResponseWithInterceptorChain()
方法的源码看看:
RealCall.java:
Response response = getResponseWithInterceptorChain();
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));
//构建一个chain对象
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//执行chain.proceed方法,并把originalRequest传入
return chain.proceed(originalRequest);
}
在getResponseWithInterceptorChain()
方法中,创建一个Interceptor.Chain
对象,注意创建的时候将参数interceptors(拦截器集合)、index=0(拦截器所在集合的索引)、originalRequest、call、等参数传了进去。最后调用chain.proceed(originalRequest)
方法得到响应并return。
这里,chain对象实际上是一个RealInterceptorChain,所以我们进入RealInterceptorChain.java看看它是怎么实现的:
RealInterceptorChain.java:
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;
// 主要是对成员变量进行赋值
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) {
// 外部传进来的拦截器集合赋值给变量interceptors
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
// 外部传进来的request赋值给变量request
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
// .....................................................................................
// 可以看到proceed返回的是一个Response对象
@Override
public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
// ...........................................................................................
// 调用责任链的下一个拦截器
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);
// ..............................................................................................
return response;
}
}
注意到这里使用责任链进行处理的时候,
1、新建责任链对象next,并且第5个参数传入了index + 1
2、通过index从拦截器集合interceptors中取出一个拦截器对象
3、执行该拦截器的intercept(Chain chain)
方法,并将next对象传入
那么拦截器是怎样将请求传递到下一级的呢?又是怎样将响应往上一级回传呢?这就得看看拦截器的intercept(Chain chain)
方法又做了什么事情了,我们以桥接拦截器为例,看看里面的核心代码:
BridgeInterceptor.java:
@Override public Response intercept(Chain chain) throws IOException {
// 从chain对象中获取请求userRequest
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
// ..................................................................................
// 执行chain.proceed(Request request)方法,得到响应
Response networkResponse = chain.proceed(requestBuilder.build());
// ..................................................................................
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// ..................................................................................
// 将响应return
return responseBuilder.build();
}
在BridgeInterceptor的intercept(Chain chain)
中,核心的流程就是
1、先是从chain对象中获取请求userRequest
2、然后执行chain.proceed(Request request)
方法,得到响应
3、最后将响应return出去
我们细想,在第2点,执行chain.proceed(Request request)
方法,是不是又去执行了责任链的proceed方法。
也就是说,在执行getResponseWithInterceptorChain()
方法的最后,调用了proceed方法,而proceed方法内部,又执行了proceed方法,这很显然就是递归调用嘛!
当我们执行getResponseWithInterceptorChain()
方法,会得到一个拦截器集合,并且它们的顺序如下:
重试重定向拦截器 -> 桥接拦截器 -> 缓存拦截器 -> 连接拦截器 -> 请求服务器拦截器
将它们构建成一个Interceptor.Chain
对象,然后执行chain.proceed(originalRequest)
方法,那么后面的流程将会是这样的:
-
index加1,并创建一个RealInterceptorChain对象next,执行
重试重定向拦截器的intercept(next)方法
,并把next传入。重试重定向拦截器的intercept(next)方法
内部会继续执行next.proceed(next.request())
方法,将请求传向下一级拦截器(桥接拦截器) -
index加1,并创建一个RealInterceptorChain对象next,执行
桥接拦截器的intercept(next)方法
,并把next传入。桥接拦截器的intercept(next)方法
内部会继续执行next.proceed(next.request())
方法,将请求传向下一级拦截器(缓存拦截器) -
index加1,并创建一个RealInterceptorChain对象next,执行
缓存拦截器的intercept(next)方法
,并把next传入。缓存拦截器的intercept(next)方法
内部会继续执行next.proceed(next.request())
方法,将请求传向下一级拦截器(连接拦截器) -
index加1,并创建一个RealInterceptorChain对象next,执行
连接拦截器的intercept(next)方法
,并把next传入。连接拦截器的intercept(next)方法
内部会继续执行next.proceed(next.request())
方法,将请求传向下一级拦截器(请求服务器拦截器) -
index加1,并创建一个RealInterceptorChain对象next,执行
请求服务器拦截器的intercept(next)方法
,并把next传入。请求服务器拦截器的intercept(next)方法
内部会继续执行next.proceed(next.request())
方法,由于请求服务器拦截器没有下一级拦截器了,因此,它会直接将response返回。返回到哪呢?那肯定是上一次拦截器呀,也就是连接拦截器。 -
连接拦截器将response返回到缓存拦截器
-
缓存拦截器将response返回到桥接拦截器
-
桥接拦截器将response返回到重试重定向拦截器
-
重试重定向拦截器将response返回到
getResponseWithInterceptorChain()
方法
至此,拦截器的执行流程已经解释完毕了,是不是很简单,实际上就是一个递归的流程嘛。
重试重定向拦截器
重试重定向拦截器将请request交给下一级拦截器之前,会判断用户是否取消了请求,在获得了结果之后,会根据响应码判断是否需要重定向或者重试,如果满足条件那么会新建或者复用之前的连接在下一次循环中进行请求重试,重试或重定向会重启执行所有拦截器。
那么怎么判断当前链接是否需要重定向呢?我们知道Http请求的响应是包含响应头和响应体的,响应头中有一个参数Location,如果Location的值为重定向的值,则说明该请求链接需要重定向到另一个请求链接。
重试重定向拦截器的重定向次数最多为20次,如果超过这个次数,将抛出一个异常。
RetryAndFollowUpInterceptor.java:
@Override public Response intercept(Chain chain) throws IOException {
// 通过chain拿到request
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
// 通过chain拿到call对象
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
// 记录重定向的次数
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
// 这里从当前的责任链开始执行一遍责任链,是一种重试的逻辑
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// 调用recover方法从失败中进行恢复,如果可以恢复就返回true,否则返回false
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// 重试与服务器进行连接
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// 如果 releaseConnection 为 true 则表明中间出现了异常,需要释放资源
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// 使用之前的响应 priorResponse 构建一个响应,这种响应的响应体 body 为空
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
// 根据得到的响应进行处理,可能会增加一些认证信息、重定向或者处理超时请求
// 如果该请求无法继续被处理或者出现的错误不需要继续处理,将会返回 null
Request followUp = followUpRequest(response, streamAllocation.route());
// 无法重定向,直接返回之前的响应
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
// 关闭资源
closeQuietly(response.body());
// 达到了重定向的最大次数(20次),就抛出一个异常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
// 这里判断新的请求是否能够复用之前的连接,如果无法复用,则创建一个新的连接
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), 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?");
}
request = followUp;
priorResponse = response;
}
}
基本的流程在注释里都写得比较清楚了,这里还有个变量要留意一下:StreamAllocation
,它相当于一个管理类,维护了服务器连接、并发流和请求之间的关系,该类还会初始化一个Socket
连接对象,获取输入和输出流对象。
在重试和重定向拦截器中,是通过 client.connectionPool()
传入了一个连接池对象 ConnectionPool
来创建出一个StreamAllocation对象。而这里只是初始化了这个对象,并没有在当前方法中有真正用到这个类,而是把它们传递到下一级的拦截器里来从服务器中获取请求的响应。
桥接拦截器
桥接拦截器的逻辑比较简单,在将请求交给下一级拦截器之前,它负责将HTTP协议必备的请求头加入其中,比如Host、Content-Type、Accept-Encoding等等的请求头,并添加一些默认的行为,比如GZIP压缩,在获得结果后,调用保存cookie接口并解析GZIP数据。
@Override public Response intercept(Chain chain) throws IOException {
// 通过chain拿到request
Request userRequest = chain.request();
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 != -1) {
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", 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");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", 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());
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();
}
缓存拦截器
缓存拦截器会根据请求的信息和缓存的响应的信息来判断是否存在缓存可用,如果有可以使用的缓存,就将缓存返回给用户,否则就从服务器中获取响应。当获取到响应的时候,会读取并判断是否使用缓存,是的话就会把响应缓存到磁盘上。
@Override public Response intercept(Chain chain) throws IOException {
// 从缓存中获得对应请求的响应缓存,并赋值给cacheCandidate变量
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 获取CacheStrategy对象
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
// 请求网络(如果该请求没有使用网络,则strategy.networkRequest为null)
Request networkRequest = strategy.networkRequest;
// 请求缓存(如果该请求没有使用缓存,则strategy.cacheResponse为null)
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 如果不使用网络进行请求也不使用缓存进行请求,那就没必要交给下一级拦截器执行了,直接响应码返回504
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1) // 网络协议1.1
.code(504)
.message("Unsatisfiable Request (only-if-cached)") // 错误信息
.body(Util.EMPTY_RESPONSE) // 一个空的响应体
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// 如果使用了缓存进行请求 且 没有使用网络进行请求,从缓存中拿结果,并return
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
// 能走到这里,说明肯定使用了网络进行请求。那么将会把请求交给下一级拦截器来处理
Response networkResponse = null;
try {
// 执行chain.proceed,将请求下一级拦截器来处理,并拿到Response
networkResponse = chain.proceed(networkRequest);
} finally {
// 如果下级的拦截器返回了响应回来,且 cacheCandidate 不为null,
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 能走到这里,则说明既使用了缓存进行请求也了使用网络进行请求
if (cacheResponse != null) {
// 那么这里会判断服务器返回的code,服务器返回的结果是304,则代表缓存没有被修改,那么更新缓存的时效并返回response
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());
}
}
// 如果能走到这里,则说明只使用了网络进行请求,没有使用缓存进行请求,那么将它的下一级拦截器拿到的response回传给上一级拦截器
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
// 如果cache != null,就会把它的下一级拦截器拿到的response放进缓存里
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
1、从缓存中获得对应请求的响应缓存
1、获取CacheStrategy对象并判断CacheStrategy对象的两个成员变量:
- Request networkRequest:表示是否需要向服务器发起请求(非null即为是)
- Response cacheResponse:表示是否可以使用缓存进行请求(非null即为是)
2、如果不使用网络进行请求也不使用缓存进行请求,那就没必要交给下一级拦截器执行了,直接响应码返回504
3、如果使用了缓存进行请求 且 没有使用网络进行请求,从缓存中拿结果,并return
4、如果能继续往下走,说明肯定使用了网络进行请求。那么将会把请求交给下一级拦截器来处理
5、如果既使用了缓存进行请求也了使用网络进行请求,那么会判断服务器返回的code,服务器返回的结果是304,则代表缓存没有被修改,那么更新缓存的时效并返回response
6、如果能继续往下走,则说明只使用了网络进行请求,没有使用缓存进行请求,那么将它的下一级拦截器拿到的response回传给上一级拦截器
7、如果cache不为null,就会把它的下一级拦截器拿到的response放进缓存里
可以看到,只有cache != null
时才会对response数据进行缓存,cache
这个变量怎么来的呢?其实我们可以通过构造OKHttpClient的时候传入:
val mOkHttpClient = OkHttpClient.Builder()
.cache(Cache(File(Environment.DIRECTORY_DOCUMENTS), 1024 * 1024 * 10))
.build()
这里的Cache这类,内部是用DiskLruCache的来进行缓存的,也就是将缓存的数据存到磁盘上。DiskLruCache
是 Android 上比较常见的缓存策略,它的核心思想就是Least Recently Used,即最近最少使用算法。
另外,上面我们根据请求和缓存的响应中的信息来判断是否存在缓存可用的时候用到了 CacheStrategy
的两个字段networkRequest和cacheResponse,得到这两个字段的时候使用了非常多的判断,涉及到Http缓存相关的知识,这里篇幅有限,就不详细深究了,感兴趣的话可以自己参考源码。
下面我简单的列举几个涉及到缓存相关的常见请求头与响应头:
请求头 | 说明 | 举例子 |
---|---|---|
Cache-Control | 请求控制 | no-cache |
If-Modified-Since | 服务器没有在指定的时间后修改请求对应资源,则返回304(无修改) | If-Modifified-Since: Fri, 22 Jul 2016 02:57:17 GMT |
If-None-Match | 服务器将其与请求对应资源的Etag值进行比较,匹配返回304 | If-None-Match: "16df0-5383097a03d40 |
响应头 | 说明 | 举例 |
---|---|---|
Cache-Control | 请求控制 | no-cache |
Data | 消息发送时间 | Date: Sat, 18 Nov 2028 06:17:41 GMT |
Expires | 资源过期的时间 | Expires: Sat, 18 Nov 2028 06:17:41 GMT |
Last-Modifified | 资源最后修改时间 | Last-Modifified: Fri, 22 Jul 2016 02:57:17 GMT |
ETag | 资源在服务器的唯一标识 | ETag: “16df0-5383097a03d40” |
Age | 服务器用缓存响应请求,该缓存从产生到现在经过多长时间(秒) | Age: 3825683 |
其中Cache-Control既可以在请求头存在,也能在响应头存在,对应的value可以设置多种组合:
-
max-age=[秒] :资源最大有效时间;
-
public :表明该资源可以被任何用户缓存,比如客户端,代理服务器等都可以缓存资源;
-
private :表明该资源只能被单个用户缓存,默认是private。
-
no-store :资源不允许被缓存
-
no-cache :(请求)不使用缓存
-
immutable :(响应)资源不会改变
-
min-fresh=[秒] :(请求)缓存最小新鲜度(用户认为这个缓存有效的时长)
-
must-revalidate :(响应)不允许使用过期缓存
-
max-stale=[秒] :(请求)缓存过期后多久内仍然有效
连接拦截器
连接拦截器在将请求交给下一级拦截器之前,负责找到或新建一个连接,获得对应的socket流。
@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);
}
这个里的代码比较少,我们继续跟进里面的newStream方法:
StreamAllocation.java:
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);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
这里通过findHealthyConnection方法拿到一个RealConnection对象,也就是连接对象。那么我们继续进入findHealthyConnection:
StreamAllocation.java:
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;
}
}
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
又通过findConnection方法来返回一个RealConnection对象,因此,获取连接的逻辑应该就在findConnection方法,事实上确实如此,findConnection
方法内部的逻辑比较多也比较长,这里就不详细说,感兴趣的可以去深究源码,这里我只讲它的大概流程,
findConnection
方法总体上就是判断了连接是否可复用,如果可以复用直接使用即可,否则就通过new RealConnection(connectionPool, selectedRoute);
创建一个新的连接result,然后调用result.connect()
方法进行TCP + TLS握手连接(这是个阻塞操作)。
连接拦截器的目的主要就是为了获得一个与目标服务器的连接,并交给下一个拦截器处理,这里我们只打开了一个网络连接,但是并没有发送请求到服务器。
从服务器获取数据的逻辑交给下一级的拦截器来执行。在获取连接对象的时候,使用了连接池 ConnectionPool
来复用连接。
连接池是对网络连接的一种优化,当需要与服务进行连接的时候,就会从连接池里面查找有没有闲置的连接,如果有那么就直接使用,否则就重新创建一个连接并放入连接池。
请求服务器拦截器
这是OkHttp最后处理的一个拦截器。请求服务器拦截器是真正进行服务器通信的拦截器,向服务器发送数据,读取并解析响应服务器响应回来数据。
CallServerInterceptor.java:
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
// 获取拦截拦截器中创建的HttpCodec,这个类是与服务器进行io操作的,内部是通过Okio与远端进行读写的
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);
// 判断请求头中的参数Expect: 100-continue
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// 如果请求中有一个“Expect: 100-continue”报头,则等待一个“HTTP/1.1 100”,(如果服务器愿意接收请求体就会响应100)
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
// 读取响应头数据
responseBuilder = httpCodec.readResponseHeaders(true);
}
// responseBuilder即为null
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
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);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
// // 如果响应是100,这代表了是请求Expect: 100-continue成功的响应,需要马上再次读取一份响应头,这才是真正的请求对应结果响应头。
if (code == 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);
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;
}
判断请求头中的参数Expect: 100-continue,如果有,则等待服务器响应,(如果服务器愿意接收请求体就会响应100)
http 100-continue这个请求头是什么意思呢?客户端在发送post数据给服务器前,需要征询服务器,如果服务器不愿意处理,那么客户端就不会上传post数据,如果愿意处理,则上传post数据。所以只有在post请求时,才会使用到100-continue这个请求头。
下面我就简单的总结一下这个拦截器的流程吧:
1、获取到ConnectInterceptor拦截器中的httpCodec
2、判断请求头中是否有100-continue,有的话就等待服务器返回响应头,然后再继续。如果服务器接收了请求体,responseBuilder将会为null,另外,如果请求头没有100-continue,responseBuilder也会为null
3、如果RequestBuilder为null,就开始向流中写入RequestBody
4、再次读取响应头信息 ,并构建Response,写入原请求、握手情况、请求时间、得到结果的时间等
5、针对204/205状态码处理
6、返回Response
总结就是,CallServerInterceptor这个拦截器就是完成HTTP协议报文的封装与解析,并返回Response。
自定义拦截器
以上几个OkHttp自带的拦截器会被默认添加到拦截器集合中并在责任链中一一被执行,事实上,我们也可以自定义拦截器,并添加到拦截器集合中,我们回看getResponseWithInterceptorChain方法:
RealCall.java:
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) {
// 添加自定义的network拦截器
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);
}
可以看到,这两行代码是添加自定义拦截器的:
interceptors.addAll(client.interceptors());
// 添加自定义的普通拦截器interceptors.addAll(client.networkInterceptors());
// 添加自定义的network拦截器
普通拦截器和network拦截器拦截器有什么区别呢?
一个很明显的区别就是它们的执行顺序是不一样的,自定义的普通拦截器是会最先被执行的拦截器,而自定义的network拦截器被放在CallServerInterceptor拦截器的前一个。
另一个区别就是network拦截器只有在forWebSocket这个变量为false的时候才会被执行,而我们通过OkHttpClient.newCall(request)
的方式创建RealCall对象,forWebSocket就会为false。通过newWebSocketCall
的方法创建RealCall则forWebSocket为true。
那么,怎样创建一个自定义的拦截器呢?
下面我们就以创建普通拦截器为例,向大家演示如创建一个自定义的拦截器,我们的需求是通过自定义拦截器处理网络返回的Response,根据Response的。
定义一个类HandleResponseInterceptor并实现Interceptor接口,重写intercept方法:
class HandleResponseInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
// 通过chain拿到request
val request = chain.request()
// 对request处理并返回一个新的request(具体实现根据自己的业务需求来实现)
val newRequest = dealRequest(request)
// 执行chain.proceed方法,得到response
val response = chain.proceed(request)
// 对response处理并返回一个新的response(具体实现根据自己的业务需求来实现)
val newResponse = dealResponse(response, request.url().toString())
// 最后将response进行返回
newResponse?.let {
return it
}
throw IOException("$TAG, newResponse is null.")
}
// 这个方法是对Response进行处理,并返回一个新的的Response的方法
@Throws(IOException::class)
private fun dealResponse(response: Response, url: String): Response? {
try {
var body: ResponseBody? = response.body()
body?.contentType()?.let {
val jsonStr: String? = body?.string()
var responseBean: MyBaseResponse<*>? = null
runCatching {
GsonUtils.fromJson(jsonStr, MyBaseResponse::class.java)
}.onFailure {
throw IOException("$TAG, json解析异常!, url=$url")
}.onSuccess { bean ->
responseBean = bean
}
val resultCode = responseBean?.resultCode
val resultMessage = responseBean?.resultMessage
val resultData = responseBean?.resultData
Lg.d(TAG, "response, resultCode=$resultCode, resultMessage=$resultMessage, resultDataIsNullOrEmpty=${
NetworkResultCode.resultDataIsNullOrEmpty(
resultData
)
}, url=$url"
)
if (NetworkResultCode.isNetworkRequestSuccess(resultCode, resultMessage, resultData)) {
// 请求成功,构建新的response继续向下传递
body = ResponseBody.create(it, jsonStr ?: "")
return response.newBuilder()
.body(body)
.build()
} else {
// 请求不成功,抛出IO异常
throw IOException(
"$TAG, network request failed, resultCode=$resultCode, resultMessage=$resultMessage, resultDataIsNullOrEmpty=${
NetworkResultCode.resultDataIsNullOrEmpty(
resultData
)
}, url=$url"
)
}
}
} catch (e: IOException) {
throw e
}
return response
}
}
以上就是我们定义好的拦截器,主体流程非常简单,我们主要看intercept方法,它做了以下几件事:
1、通过chain拿到request
2、对request处理并返回一个新的request(具体实现根据自己的业务需求来实现)
3、执行chain.proceed方法,得到response
4、对response处理并返回一个新的response(具体实现根据自己的业务需求来实现)
5、最后将response进行返回
好了,既然我们已经完成了自定义的拦截器的编写,怎么用起来呢?
其实也很简单,在构建OkHttpClient实例的时候,通过OkHttpClient的Builder对象来构建即可,如下:
val mOkHttpClient: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(HandleResponseInterceptor())
.build()
通过这样添加,在执行网络请求的时候,HandleResponseInterceptor拦截器将会在其他所有拦截器之前拿到request,并对request进行处理后将request传递到下一级拦截器,当其他所有拦截器都处理完request并返回response,HandleResponseInterceptor才会拿到response,换句话说就是HandleResponseInterceptor将第一个拿到request,最后一个拿到response。