欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Okhttp3源码解析(5)-拦截器RetryAndFollowUpInterceptor

程序员文章站 2022-08-08 20:01:17
### 前言 回顾: [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析](https://www.jianshu.com/p/bf1d01b79ce7) [Okhttp3源码解析( ......
### 前言 回顾: [okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [okhttp3源码解析(1)-okhttpclient分析](https://www.jianshu.com/p/bf1d01b79ce7) [okhttp3源码解析(2)-request分析](https://www.jianshu.com/p/5a85345c8ea7) [okhttp3源码解析(3)-call分析(整体流程)](https://www.jianshu.com/p/4ed79472797a) [okhttp3源码解析(4)-拦截器与设计模式](https://www.jianshu.com/p/b8817597f269) 上节讲了拦截器与设计模式,今天讲`retryandfollowupinterceptor`,如果我们没有去自定义拦截器, 那`retryandfollowupinterceptor`是第一个拦截器。 ### 初始化 首先先看`retryandfollowupinterceptor`被添加的位置: ![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190829084744888-554996670.png) 初始化位置: call实例化方法中: ``` private realcall(okhttpclient client, request originalrequest, boolean forwebsocket) { this.client = client; this.originalrequest = originalrequest; this.forwebsocket = forwebsocket; this.retryandfollowupinterceptor = new retryandfollowupinterceptor(client, forwebsocket); } ``` 找到了初始化的位置, 下面去`retryandfollowupinterceptor`种分析! ### retryandfollowupinterceptor解析 从上节我们就知道拦截器中的**intercept()是核心!** 这里贴出代码: ``` @override public response intercept(chain chain) throws ioexception { request request = chain.request(); realinterceptorchain realchain = (realinterceptorchain) chain; 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) { // 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) { 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); } 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; } } ``` 我先贴出一个`while循环`的流程图: ![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190829084745296-805720763.png) 根据流程图和源码可以分析`retryandfollowupinterceptor`主要做了以下内容,**后两点都是发生在`while循环`中**: - **初始化streamallocation 对象** - **网络请求-chain.proceed() ,对在请求时发生的异常进行捕获以及对应的重连机制** - **followuprequest 对响应码进行处理** 下面可以逐块代码分析: ###### 1.初始化streamallocation 对象 `streamallocation`类是**协调三个实体之间的关系** 三个实体是:`connections`,`streams`,`calls` 我们请求网络时需要传递它 ![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190829084745679-501952792.png) `streamallocation`在这大家简单了解一下就可以了. ###### 2.网络请求时异常捕获-以及重连机制 网络请求如下: ``` response = realchain.proceed(request, streamallocation, null, null); ``` 如果请求发现异常,我们通过try/catch捕获 ``` 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; } ``` -`routeexception` 路由异常 - `ioexception` io异常 捕获后都做了`recover()`重连判断,具体代码如下,就不细说了: ``` private boolean recover(ioexception e, streamallocation streamallocation, boolean requestsendstarted, request userrequest) { streamallocation.streamfailed(e); // the application layer has forbidden retries. if (!client.retryonconnectionfailure()) return false; // we can't send the request body again. if (requestsendstarted && userrequest.body() instanceof unrepeatablerequestbody) return false; // this exception is fatal. if (!isrecoverable(e, requestsendstarted)) return false; // no more routes to attempt. if (!streamallocation.hasmoreroutes()) return false; // for failure recovery, use the same route selector with a new connection. return true; } ``` 这里需要注意的是如果可以重连,执行 continue; `continue`含义: 继续循环,(不执行 循环体内`continue` 后面的语句,直接进行下一循环) ###### 3.` 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_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); 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 case http_mult_choice: case http_moved_perm: case http_moved_temp: 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); // 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()); if (!samescheme && !client.followsslredirects()) return null; // most redirects don't include a request body. 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'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; } if (retryafter(userresponse, 0) > 0) { return null; } return userresponse.request(); 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; } } ``` 不难看出, 是根据响应码进行判断的。 - http_proxy_auth 407 代理身份验证 - http_unauthorized 401 未授权 - http_perm_redirect 308 重定向 - http_temp_redirect 307 重定向 - http_mult_choice 300 multiple choices - http_moved_perm 301 moved permanently - http_moved_temp 302 temporary redirect - http_see_other 303 see other - http_client_timeout 408 request time-out - http_unavailable 503 service unavailable 对于这些响应码都做了处理: 1.返回null ``` if (followup == null) { if (!forwebsocket) { streamallocation.release(); } return response; } ``` 2.其他异常情况直接抛异常了 强调: `max_follow_ups`字段, 表示最大的重定向次数 ``` /** * how many redirects and auth challenges should we attempt? chrome follows 21 redirects; firefox, * curl, and wget follow 20; safari follows 16; and http/1.0 recommends 5. */ private static final int max_follow_ups = 20; ``` ``` if (++followupcount > max_follow_ups) { streamallocation.release(); throw new protocolexception("too many follow-up requests: " + followupcount); } ``` 这节就说到这,希望对大家有所帮助..... ![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190829084746645-654895211.png) 大家可以关注我的微信公众号:「秦子帅」一个有质量、有态度的公众号! ![公众号](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190829084746832-730035128.jpg)