OkHttp原理解析2(拦截器篇)(更新中...)
Hello小伙伴们,现在公司已经恢复了正常办公,但是疫情依旧还在继续。最近工作实在是有点小忙,导致更新有点缓慢,实在抱歉,本文是OkHttp原理解析的第二篇, 主要针对OkHttp中几个 默认拦截器 的具体实现逻辑进行分析。
因为OkHttp的很大一部分逻辑都在拦截器中,因此本文会比较长,同时采用连载更新的方式进行描述,每完成一个拦截器的逻辑分析都会进行更新。
如有对OkHttp的框架流程不太了解的可优先阅读网我上篇博客 OkHttp原理解析1(框架流程篇)
我又要开始表演了~~~
但为了方便后续描述,我还是简单对上文做了个总结。大概分为以下几步。
- 创建OkHttpClient(可通过OkHttpClient.Builder创建,内部设置一些基础参数)
- 创建 Request 对象设置 url,请求类型等参数
- 通过OkHttpClient.newCall()创建RealCall对象,同时创建了Transmitter对象。
- 通过RealCall.enqueue()或者RealCall.execute()触发 异步或同步 请求。
- 调用OkHttpClient.dispatcher().enqueue(new AsyncCall(responseCallback))做请求准备,循环runningAsyncCalls和readyAsyncCalls 队列找出host相同的AsyncCall进行重用,并将readyAsyncCalls中AsyncCall转移到runningAsyncCalls中,如果runningAsyncCalls超过64则终止转移,如相同主机计数器>5则终止转移本AsyncCall。
-
- 循环runningAsyncCalls调用AsyncCall.executeOn(executorService())
- 6.1. AsyncCall为Runnable,执行run()方法,调用AsyncCall.execute()连带调用AsyncCall.getResponseWithInterceptorChain()设置拦截器List,首先设置用户自定义的拦截器。最后通过RealInterceptorChain.proceed()启动拦截器。
而拦截器的启动与运行依赖 责任链 模式,大概分为以下3步。
- 首先创建RealInterceptorChain对象,通过procee()判断各种异常,并获取当前Interceptor对象。
- 然后 通过Interceptor.intercept(RealInterceptorChain)启动当前拦截器逻辑,并且触发下一个拦截器启动。
- 如果当前拦截器出现异常等错误,则终止责任链。
我们从添加拦截的的位置开始本文介绍,其实上文已经做了描述,源码如下所示。
//RealCall.getResponseWithInterceptorChain();
Response getResponseWithInterceptorChain() throws IOException {
// 建立一个完整的拦截器堆栈
List<Interceptor> interceptors = new ArrayList<>();
//将创建okhttpclient时的拦截器添加到interceptors
interceptors.addAll(client.interceptors());
//重试拦截器,负责处理失败后的重试与重定向
interceptors.add(new RetryAndFollowUpInterceptor(client));
//请求转化拦截器(用户请求转为服务器请求,服务器响应转为用户响应)
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//缓存拦截器。负责
//1.根据条件,缓存配置,有效期等返回缓存响应,也可增加到缓存。
//2.设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改)
//3.可配置自定义的缓存拦截器。
interceptors.add(new CacheInterceptor(client.internalCache()));
//网络连接拦截器,主要负责和服务器建立连接。
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//创建okhttpclient时设置的networkInterceptors
interceptors.addAll(client.networkInterceptors());
}
//数据流拦截器,主要负责像服务器发送和读取数据,请求报文封装和解析。
interceptors.add(new CallServerInterceptor(forWebSocket));
//责任链模式的创建。
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
//启动责任链
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
主要看我注释的部分,OkHttp主要涉及了以下几个默认拦截器
- client.interceptors();//用户自定义拦截器
- RetryAndFollowUpInterceptor(client)//失败重试拦截器
- BridgeInterceptor(client.cookieJar())//请求转化拦截器
- CacheInterceptor(client.internalCache())//缓存拦截器
- ConnectInterceptor(client)//网络连接拦截器
- CallServerInterceptor(forWebSocket)//数据流拦截器
OkHttp会把用户自定义的拦截器默认放到拦截器列表的头部,以方面优先执行,然后通过创建RealInterceptorChain对象,并调用RealInterceptorChain.proceed()启动第一个拦截器,然后调用拦截器的interceptor.intercept(next)执行第一个拦截器的逻辑并将下一个拦截器RealInterceptorChain对象传入依此类推。接下来我们就一个个进行分析。
1. RetryAndFollowUpInterceptor(client) 连接失败重试拦截器
首先开始的是RetryAndFollowUpInterceptor失败重试拦截器,这个拦截器是可以在OkHttpClient.Builder对象中通过retryOnConnectionFailure(boolean retryOnConnectionFailure)设置是否开启,默认构建的OkHttpClient对象是开启的。
该拦截器主要的职责官方注释解释了这么几点。
- 无法访问ip地址,如果URL的主机有多个IP地址,则无法访问任何单个IP地址不会使整个请求失败。这可以提高多宿服务的可用性。
- 过时的池连接,通过ConnectionPool重用套接字以减少请求延迟,但这些连接偶尔会超时。
- 无法访问的代理服务器,可以使用ProxySelector,最终返回到直接连接。
说实话,这描述略显抽象,要不是我用过,我真不知道他在扯啥,咱们还是通过源码看下真像吧。
//我们应该尝试多少重定向和验证挑战?Chrome遵循21个重定向;Firefox、curl和wget遵循20;Safari遵循16;HTTP/1.0建议5。
private static final int MAX_FOLLOW_UPS = 20;
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while (true) {
//通过transmitter创建ExchangeFinder,Address,RouteSelector三个对象
transmitter.prepareToConnect(request);
//判断如果当前请求结束了则抛出异常,可通过transmitter终止请求。
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
//启动下一个拦截器
response = realChain.proceed(request, transmitter, null);
//执行顺利则通过该字段退出循环。
success = true;
} catch (RouteException e) {
// 如果是路由异常。请求还没发送。
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
continue;
} catch (IOException e) {
// 尝试与服务器通信失败。请求可能已发送。
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
//网络调用引发异常。释放所有资源。
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
// 如果body不为空
if (priorResponse != null) {
//获得新的response
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
// 调用followUpRequest()查看响应是否需要重定向,不需要就返回当前请求,如果需要返回新的请求
Request followUp = followUpRequest(response, route);
// 不需要重定向或无法重定向
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
//如果重定向次数超过最大次数抛出异常
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
request = followUp;
priorResponse = response;
}
}
//RetryAndFollowUpInterceptor.recover()
//报告并尝试从与服务器通信失败中恢复。如果{@code e}可恢复,则返回true;
//如果失败是永久性的,则返回false。只有在缓冲了正文或在发送请求之前发生故障时,才可以恢复具有正文的请求。
private boolean recover(IOException e, Transmitter transmitter,
boolean requestSendStarted, Request userRequest) {
// 用户设置的禁止重试
if (!client.retryOnConnectionFailure()) return false;
// 不能再发送请求体
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;
// 异常是致命的
if (!isRecoverable(e, requestSendStarted)) return false;
// 没有更多的路线可以尝试。
if (!transmitter.canRetry()) return false;
//对于故障恢复,请对新连接使用同一路由选择器
return true;
}
/**
* 查看响应是否需要重定向,不需要就返回当前请求,如果需要返回新的请求。
* 找出接收{@code userResponse}时要发出的HTTP请求。这将添加身份验证头、遵循重定向或处理客户端请求超时。
* 如果后续操作不必要或不适用,则返回null。
*/
private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
if (userResponse == null) throw new IllegalStateException();
//获取响应码
int responseCode = userResponse.code();
//请求方法
final String method = userResponse.request().method();
//响应码分类处理
switch (responseCode) {
//407 代理需要身份认证
case HTTP_PROXY_AUTH:
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
}
return client.proxyAuthenticator().authenticate(route, userResponse);
//401 需要身份认证
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
// "If the 307 or 308 status code is received in response to a request other than GET
// or HEAD, the user agent MUST NOT automatically redirect the request"
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
// 300多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
case HTTP_MULT_CHOICE:
// 301永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。
case HTTP_MOVED_PERM:
// 302临时移动。与301类似。但资源只是临时被移动。
case HTTP_MOVED_TEMP:
// 303查看其它地址。与301类似。使用GET和POST请求查看
case HTTP_SEE_OTHER:
// Does the client allow redirects?
if (!client.followRedirects()) return null;
String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// 不要遵循重定向到不支持的协议。
if (url == null) return null;
//如果已配置,请不要遵循SSL和非SSL之间的重定向。
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
// 大多数重定向不包括请求正文。
Request.Builder requestBuilder = userResponse.request().newBuilder();
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
}
// 跨主机重定向时,删除所有身份验证头。这对应用程序层来说可能很烦人,因为它们无法保留它们。
if (!sameConnection(userResponse.request().url(), url)) {
requestBuilder.removeHeader("Authorization");
}
return requestBuilder.url(url).build();
//408 超时
case HTTP_CLIENT_TIMEOUT:
// 408's are rare in practice, but some servers like HAProxy use this response code. The
// spec says that we may repeat the request without modifications. Modern browsers also
// repeat the request (even non-idempotent ones.)
if (!client.retryOnConnectionFailure()) {
// 应用层指示我们不要重试请求
return null;
}
RequestBody requestBody = userResponse.request().body();
if (requestBody != null && requestBody.isOneShot()) {
return null;
}
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
return null;
}
if (retryAfter(userResponse, 0) > 0) {
return null;
}
return userResponse.request();
// 503 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
case HTTP_UNAVAILABLE:
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null;
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request();
}
return null;
default:
return null;
}
}
//Transmitter.prepareToConnect()
public void prepareToConnect(Request request) {
if (this.request != null) {
//判断是不是相同的连接,如果是则用之前的。
if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
return; // Already ready.
}
if (exchange != null) throw new IllegalStateException();
//exchangeFinder
if (exchangeFinder != null) {
maybeReleaseConnection(null, true);
exchangeFinder = null;
}
}
this.request = request;
this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
call, eventListener);
}
//Transmitter.createAddress()
private Address createAddress(HttpUrl url) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
if (url.isHttps()) {
//https的设置
sslSocketFactory = client.sslSocketFactory();
hostnameVerifier = client.hostnameVerifier();
certificatePinner = client.certificatePinner();
}
return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
}
//ExchangeFinder.ExchangeFinder()
ExchangeFinder(Transmitter transmitter, RealConnectionPool connectionPool,
Address address, Call call, EventListener eventListener) {
this.transmitter = transmitter;
this.connectionPool = connectionPool;
this.address = address;
this.call = call;
this.eventListener = eventListener;
//创建路由选择器
this.routeSelector = new RouteSelector(
address, connectionPool.routeDatabase, call, eventListener);
}
//RouteSelector.RouteSelector()
RouteSelector(Address address, RouteDatabase routeDatabase, Call call,
EventListener eventListener) {
this.address = address;
this.routeDatabase = routeDatabase;
this.call = call;
this.eventListener = eventListener;
resetNextProxy(address.url(), address.proxy());
}
该拦截器的逻辑很多,但其实主要做两件事,一个是重试,一个是重定向,逻辑流程大概是这样的。
- 通过realChain.transmitter获取了transmitter对象,并启用一个while死循环,
-
- 然后通过transmitter.prepareToConnect(request)transmitter创建ExchangeFinder,Address,RouteSelector三个对象,并判断了是否是相同的连接,是否需要maybeReleaseConnection(),重置ExchangeFinder。
- 2.1 . ExchangeFinder此拦截器只是创建ExchangeFinder,但ExchangeFinder中有个find()方法主要通过内部的 findHealthyConnection() 从 connectionPool 中找到一个可用的连接,这个连接可能是复用的,并 connect(),从而得到 输入/输出 流 (source/sink) ,返回一个 Exchange 给 CallServerIntercepter , 通过这个 Exchange 就可以添加请求头和请求体,并读取响应头和响应体,来交给上面的 Intercepter,层层向上传递。
- 2.2 . Address为请求参数的封装类,包含url,端口,DNS,SSL,Proxy,ProxySelector,SocketFactory,主机名验证,证书校验等逻辑。
- 2.3 . RouteSelector主要来选择路由,主要做三件事。1.收集所有的可用路由。2.选择可用路由。3.借助RouteDatabase内的Set对象来维护连接失败的路由信息,防止去连接失败路由浪费时间。
- 启动拦截器列表的下一个拦截器。
- 判断全局变量priorResponse是否为null,如果不为空则代表请求成功了。
- 执行 followUpRequest()查看响应是否需要重定向,如果不需要重定向则返回当前请求
- 判断重定向次数,如果超过最大值则退出。
- 重置request,并把当前的Response保存到priorResponse,进入下一次的while循环。
总结来说:
通过while死循环来获取response,每次循环开始都根据条件获取下一个request,如果没有request,则返回response,退出循环。而获取的request 的条件是根据上一次请求的response 状态码确定的,在循环体中同时创建了一些后续需要的对象