如果研究过okhttp源码,应该知道okhttp的核心是拦截器,而拦截器所采用的设计模式是责任链设计,即每个拦截器只处理与自己相关的业务逻辑。
今天彻底分析Okhttp的核心拦截器RetryAndFollowUpInterceptor的原理解析:
这里先贴出RetryAndFollowUpInterceptor的核心伪代码,可以大体的看一遍,待下文一步一步带你解析。
拦截器的核心代码都在intercept(Chain chain )方法中,所以有必要彻底研究该方法是如何处理即可理解RetryAndFollowUpInterceptor的奥妙。
@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); //...省略部分代码 StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace); //...省略部分代码 while (true) { Response response; boolean releaseConnection = true; try { response = realChain.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. if (!recover(e.getLastConnectException(), streamAllocation, false, request)) { throw e.getFirstConnectException(); } releaseConnection = false; continue; } catch (IOException e) { // An attempt to communicate with a server failed. The request may have been sent. boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, streamAllocation, requestSendStarted, 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; try { //判断是否进行重新请求 followUp = followUpRequest(response, streamAllocation.route()); } catch (IOException e) { streamAllocation.release(); throw e; } if (followUp == null) { //如果为空,则释放资源,不空则继续下一次请求 streamAllocation.release(); return response; } request = followUp; priorResponse = response; } }
1、先分析正常的一次网络请求的业务逻辑
1.1 实例化StreamAllocation,初始化一个Socket连接对象,获取到输入/输出流,从线程池中获取线程及拼接请求地址得到StreamAllocation对象
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace);
2、通过创建好的request和streamAllocation对象,去网络获取到response并传递给下一级拦截器
response = realChain.proceed(request, streamAllocation, null, null);
如果是正常的网络请求,RetryAndFollowUpInterceptor起作用的代码就这些。
2、回到最初的起点,看它的"真正"的作用
RetryAndFollowUpInterceptor的定义为:This interceptor recovers from failures and follows redirects as necessary 即网络请求失败后,在一些必要的条件下,会重新进行网络请求。
接下来,看看到底是如何重新进行网络请求?
2.1 首先可以看见它本身维护这一个死循环
while (true) { try { response = realChain.proceed(request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { throw e; }catch(IOException e){ //.....省略部分代码 continue; } }
会进行try..catch 如果出现了异常信息,可以看到,它本身会进行处理 RouteException、IOException 它会重新根据recover(...)方法,然后continue掉本次循环,然后继续下一次请求,即:
response = realChain.proceed(request, streamAllocation, null, null);
那么问题来了,假如说出现了异常,它岂不是一直重新进行请求网络,它什么时候跳出循环?
2.2 这里有两种情况进行跳出死循环
第一种:当发生RouteException时候,并且 recover为false的时候,这时候会跳出循环,然后抛出去异常,并回调callback.onFailure(Exception e),返回给UI层进行处理 ,假如说没有开数据流量的情况下,去请求网络,则会抛出该异常。
第二种:当发生IOException时候,recover为false的时候,则throw exception,中断死循环的操作,
3、以网络重定向进行分析,看它是如何进行重新请求网络
如果你对网络知识了解的话,正常一次请求会返回一个响应,但是重定向比较特殊,其实是客户端请求了2次网络,服务器返回了2次数据。
3.1 以访问百度为例,重定向实例,请求两次网络
百度的真正的地址为:https://www.baidu.com ,但是在地址栏中输入 http://www.baidu.com 也可以看到百度主页,但是只要看到百度主页,说明浏览器里面请求了https://www.baidu.com,这里就说明发生了重定向,通过打开开发者模式可以看见。如下图所示:
image.png
当按下回车的时候,地址栏变为自动变为https://www.baidu.com
image.png
3.2 在开发者模式中看到访问了两次www.baidu.com
第一次 则status code 为307 即表示是重定向,而且在返回的response Headers中Location字段中是重定向的地址,即真正的百度的地址。
第二次 则status code 为200 ,真正的网络请求,如下图所示:
第一次请求www.baidu.com
重定向请求网络.png
看到上面重定向以后,再来看看okhttp是如何进行处理这种重定向的请求
4、okhttp是如何实现重定向功能
知道了重定向返回的status code 为307,来研究okhttp是怎么实现,注意重定向并不会发生异常,而是执行如下方法 followUpRequest()判断是否要重新进行网络请求。
try { followUp = followUpRequest(response, streamAllocation.route()); } catch (IOException e) { streamAllocation.release(); throw e; }
既然这里执行了followUpRequest(),那看看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_PERM_REDIRECT: case HTTP_TEMP_REDIRECT: if (!method.equals("GET") && !method.equals("HEAD")) { return null; } case HTTP_MULT_CHOICE: case HTTP_MOVED_PERM: case HTTP_MOVED_TEMP: case HTTP_SEE_OTHER: if (!client.followRedirects()) return null; String location = userResponse.header("Location"); if (location == null) return null; HttpUrl url = userResponse.request().url().resolve(location); boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme()); 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"); } return requestBuilder.url(url).build(); default: return null; } }
作者:OneXzgj
链接:https://www.jianshu.com/p/bd1ed7b5dc9b
共同学习,写下你的评论
评论加载中...
作者其他优质文章