前言
前一篇文章简单介绍了OkHttp 以及创建一个请求与请求执行过程,本编继续接着向下介绍
一、责任链
private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
我们进入RealInterceptorChain 的proceed
//代码有删减
public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream,
Connection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpStream, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
//调用interceptor的intercept 方法
Response response = interceptor.intercept(next);
return response;
}
一方面RealInterceptorChain 可以 看成是一个拦截器的装饰类,被装饰的拦截器就是在拦截器列表interceptors内下标为index的拦截器。
另一方面表示拦截器链表,但是你也可以将RealInterceptorChain
我们可以看到,当调用被装饰的拦截器的时候会传入另一个RealInterceptorChain ,这个RealInterceptorChain 可以看成是下一个拦截器,因此当当前interceptor 执行完任务之后可以通过next调用下一个拦截的代码,如此逐级调用,形成了一个链表
二、RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor 是OKHttp内部第一个拦截器,主要用来重定向与网络访问失败重试。
重定向:
1 当求情的资源url发生了变换,Http协议会返回301,,302,303,307,308中的其中一种,此时需要根据返回的资源新的url求获取资源。
2 有点url可能会对应多个ip地址,当某一个ip地址不能用的时候,此时会尝试链接另一个ip,这一点跟HTTP协议没有关系。
重式:
1 当请求服务的时候缺少验证信息,此时根据HTTP协议此时会返回401,要求用户输入账号,密码之后再次请求。这一部分跟HTTP协议有关系
2 网络环境是复杂的,可能由于某些原因会导致某一瞬间连接服务器失败,此时可以在第一次失败之后再次尝试链接,这一部分与HTTP协议没有关系,是实现HTTP的软件需要考虑的情况。
了解了上面的情况之后我们再来接着阅读源码部分
@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) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
//proceed 内部会建立连接访问网络。
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
//ConnectInterceptor 以及之后的拦截器
//在连接的过程中发生的IO异常最终都会别包装成RouteException,
//最终抛到这里处理。
//当用户没有禁止重试而且有多个ip地址的时候此时revover 返回true
if (!recover(e.getLastConnectException(), true, request)) throw e.getLastConnectException();
releaseConnection = false;
continue;
} catch (IOException e) {
BridgeInterceptor以及CacheInterceptor 发生的IO 异常会走到这里
if (!recover(e, false, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//走到这里就是已经成功连接收到回复了,
//判断是不是需要重定向
Request followUp = followUpRequest(response);
// 如果不需要重定向,返回结果
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
//释放资源
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
//重定向的次数超过允许的最大次数
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
// 如果body内容只能发送一次,释放连接
if (followUp.body() instanceof UnrepeatableRequestBody) {
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
//到了这里表示因为重试或者重定向需要重新访问网络,那么需要判断
// 重试的host等是否发生改变,如果是需要重新实例化StreamAllocation
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(followUp.url()));
} else if (streamAllocation.stream() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
// 更新下一次的网络请求对象
request = followUp;
// 保存上一次的请求结果
priorResponse = response;
}
}
1 两种异常
RouteException 与IOException, 实际上RouteException 就是对IOException 的装饰,就是说RouteException 就是IOException ,区别在于发生的时机是不一样。当在底层使用sokect连接服务器的时候,可能会因为tcp的握手失败或者是https 握手失败等等情形造成IOException ,此时的IOException 会抛到ConnectInterceptor,而ConnectInterceptor 会将所有的IOException 包装成RouteException 。
在访问网络之前也即是ConnectInterceptor 之前的拦截器发生的IO异常会正常的抛到RetryAndFollowUpInterceptor。ConnectInterceptor 之前的拦截器也就是BridgeInterceptor与CacheInterceptor。
2 正常返回
如果没有发生异常,此时就会根据服务器返回的状态码做进一步的处理,判断是不是需要重定向等等,这一部分主要是followUpRequest
private Request followUpRequest(Response userResponse, Route route) throws IOException {
if (userResponse == null) throw new IllegalStateException();
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
case HTTP_PROXY_AUTH://407
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");
}
//客户端必须代理服务器上进行身份验证。
//代理服务器必须返回一个 Proxy-Authenticate 用以进行身份询问。
//客户端可以返回一个 Proxy-Authorization 信息头用以验证
return client.proxyAuthenticator().authenticate(route, userResponse);
case HTTP_UNAUTHORIZED://401
//当前请求需要用户验证。就是要求输入账号密码
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT://307
case HTTP_TEMP_REDIRECT://308
// "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"
//如果这不是一个GET 或者 HEAD 请求,那么浏览器禁止自动进行重定向
if (!method.equals("GET") && !method.equals("HEAD")) {
//post禁止直接重定向,浏览器一般要求是弹出对话框,这里直接返回null,
//intercept的处理,是直接将response返回给客户端,
//客户自己去判断状态码,获取location,决定是不是再次发起请求。
return null;
}
// 走到这一般是get与head请求返回的307,308,由于没有return与break所以
//走300,301,302,303的处理流程,也就是判断用户的是否允许重定向。
// fall-through
case HTTP_MULT_CHOICE: //300
case HTTP_MOVED_PERM: //301
case HTTP_MOVED_TEMP: //302
case HTTP_SEE_OTHER: //303
// Does the client allow redirects?
//客户端是否允许重定向
if (!client.followRedirects()) return null;
// 新的临时性的URI 应当在响应的 Location 域中返回
//location 也就是重定向的地址。
String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// Don't follow redirects to unsupported protocols.
if (url == null) return null;
// If configured, don't follow redirects between SSL and non-SSL.
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
//是否scheme相同,客户端是否遵循SSL重定向
if (!sameScheme && !client.followSslRedirects()) return null;
// Most redirects don't include a request body.
//重新构造request
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");
}
}
// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
if (!sameConnection(userResponse, url)) {
requestBuilder.removeHeader("Authorization");
}
return requestBuilder.url(url).build();
case HTTP_CLIENT_TIMEOUT: //408 超时
// 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()) {
//用户禁止超时重试
// The application layer has directed us not to retry the request.
return null;
}
if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
//内容只允许发送一次,也是禁止重试
return null;
}
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
//时间超时之后重试过一次了,不再次重试,
//也就是说时间超时允许重试一次。
return null;
}
//服务器有没有返回多久以后重试,
//返回值大于0直接放弃自动重试。
if (retryAfter(userResponse, 0) > 0) {
return null;
}
// 重试一次
return userResponse.request();
case HTTP_UNAVAILABLE://503服务不可用
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
//同样503也只允许重试一次。
return null;
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
//服务器指定我们立即重试,也就是返回的重试时间是0。
//这个时候直接重试一次。
return userResponse.request();
}
//放弃重试
return null;
default:
return null;
}
}
这里主要是根据响应码(code)和响应头(header),查看是否需要重定向,并重新设置请求,当然,如果是正常响应则直接返回Response停止循环,代码注释的已经非常清楚了,
服务器要求多久以后再次重试:
private int retryAfter(Response userResponse, int defaultDelay) {
String header = userResponse.header("Retry-After");
if (header == null) {
return defaultDelay;
}
// https://tools.ietf.org/html/rfc7231#section-7.1.3
// currently ignores a HTTP-date, and assumes any non int 0 is a delay
if (header.matches("\\d+")) {
return Integer.valueOf(header);
}
return Integer.MAX_VALUE;
}
结束