嘉兴网站建设的前景,wordpress导入文件,wordpress下载视频教程,html5门户网站模板拦截器责任链#xff1a;
OkHttp最核心的工作是在 getResponseWithInterceptorChain() 中进行#xff0c;在进入这个方法分析之前#xff0c;我们先来了 解什么是责任链模式#xff0c;因为此方法就是利用的责任链模式完成一步步的请求。
拦截器流程#xff1a;
OkHtt…拦截器责任链
OkHttp最核心的工作是在 getResponseWithInterceptorChain() 中进行在进入这个方法分析之前我们先来了 解什么是责任链模式因为此方法就是利用的责任链模式完成一步步的请求。
拦截器流程
OkHttp中的 getResponseWithInterceptorChain() 中经历的流程为 请求会被交给责任链中的一个个拦截器。默认情况下有五大拦截器
1. RetryAndFollowUpInterceptor 第一个接触到请求最后接触到响应重试拦截器在交出(交给下一个拦截器)之前负责判断用户是否取消了请求在获得了结果之后 会根据响应码判断是否需要重定向如果满足条件那么就会重启执行所有拦截器。
2. BridgeInterceptor 桥接拦截器在交出之前负责将HTTP协议必备的请求头加入其中(如Host)并添加一些默认的 行为(如GZIP压缩)在获得了结果后调用保存cookie接口并解析GZIP数据。
3. CacheInterceptor 缓存拦截器顾名思义交出之前读取并判断是否使用缓存获得结果后判断是否缓存。
4. ConnectInterceptor 连接拦截器在交出之前负责找到或者新建一个连接并获得对应的socket流在获得结果后 不进行额外的处理。
5. CallServerInterceptor 请求服务器拦截器进行真正的与服务器的通信向服务器发送数据解析读取的响应数据。 拦截器详情
一、重试及重定向拦截器
第一个拦截器: RetryAndFollowUpInterceptor 主要就是完成两件事情重试与重定向。
重试
请求阶段发生了 RouteException 或者 IOException会进行判断是否重新发起请求。
RouteException
catch (RouteException e) {//todo 路由异常连接未成功请求还没发出去if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {throw e.getLastConnectException();}releaseConnection false;continue;
}
IOException
catch (IOException e) {//todo 请求发出去了但是和服务器通信失败了。(socket流正在读写数据的时候断开连接)// HTTP2才会抛出ConnectionShutdownException。所以对于HTTP1 requestSendStarted一定是 trueboolean requestSendStarted !(e instanceof ConnectionShutdownException);if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;releaseConnection false;continue;
}
两个异常都是根据 recover 方法判断是否能够进行重试如果返回 true 则表示允许重试。
private boolean isRecoverable(IOException e, boolean requestSendStarted) {// 出现协议异常不能重试if (e instanceof ProtocolException) {return false;}// 如果不是超时异常不能重试if (e instanceof InterruptedIOException) {return e instanceof SocketTimeoutException !requestSendStarted;}// SSL握手异常中证书出现问题不能重试if (e instanceof SSLHandshakeException) {if (e.getCause() instanceof CertificateException) {return false;}}// SSL握手未授权异常 不能重试if (e instanceof SSLPeerUnverifiedException) {return false;}return true;
}
1、协议异常如果是那么直接判定不能重试;你的请求或者服务器的响应本身就存在问题没有按照http协议来 定义数据再重试也没用
2、超时异常可能由于网络波动造成了Socket连接的超时可以使用不同路线重试。
3、SSL证书异常/SSL验证失败异常前者是证书验证失败后者可能就是压根就没证书或者证书数据不正确 那还怎么重试
经过了异常的判定之后如果仍然允许进行重试就会再检查当前有没有可用路由路线来进行连接。简单来说比 如 DNS 对域名解析后可能会返回多个 IP在一个IP失败后尝试另一个IP进行重试。
重定向
如果请求结束后没有发生异常并不代表当前获得的响应就是最终需要交给用户的还需要进一步来判断是否需要重 定向的判断。重定向的判断位于 followUpRequest 方法
private Request followUpRequest(Response userResponse) throws IOException {//...
}
整个是否需要重定向的判断内容很多关键在于理解他们的意思。如果此方法返回空那就表 示不需要再重定向了直接返回响应但是如果返回非空那就要重新请求返回的 Request 但是需要注意的是 我们的 followup 在拦截器中定义的最大次数为20次。
总结
本拦截器是整个责任链中的第一个这意味着它会是首次接触到 Request 与最后接收到 Response 的角色在这个 拦截器中主要功能就是判断是否需要重试与重定向。
重试的前提是出现了 RouteException 或者 IOException 。一但在后续的拦截器执行过程中出现这两个异常就会 通过 recover 方法进行判断是否进行连接重试。
重定向发生在重试的判定之后如果不满足重试的条件还需要进一步调用 followUpRequest 根据 Response 的响 应码(当然如果直接请求失败 Response 都不存在就会抛出异常)。 followup 最大发生20次。
二、桥接拦截器
BridgeInterceptor 连接应用程序和服务器的桥梁我们发出的请求将会经过它的处理才能发给服务器比如设 置请求内容长度编码gzip压缩cookie等获取响应后保存Cookie等操作。这个拦截器相对比较简单。
在补全了请求头后交给下一个拦截器处理得到响应后主要干两件事情
1、保存cookie在下次请求则会读取对应的数据设置进入请求头默认的 CookieJar 不提供实现
2、如果使用gzip返回的数据则使用 GzipSource 包装便于解析。
总结
桥接拦截器的执行逻辑主要就是以下几点 对用户构建的 Request 进行添加或者删除相关头部信息以转化成能够真正进行网络请求的 Request 将符合网络 请求规范的Request交给下一个拦截器处理并获取 Response 如果响应体经过了GZIP压缩那就需要解压再构 建成用户可用的 Response 并返回。
三、缓存拦截器
CacheInterceptor 在发出请求前判断是否命中缓存。如果命中则可以不请求直接使用缓存的响应。 (只会存 在Get请求的缓存) 步骤为:
1、从缓存中获得对应请求的响应缓存
2、创建 CacheStrategy ,创建时会判断是否能够使用缓存在 CacheStrategy 中存在两个成员: networkRequest 与 cacheResponse 。
他们的组合如下: 3、交给下一个责任链继续处理
4、后续工作返回304则用缓存的响应否则使用网络响应并缓存本次响应只缓存Get请求的响应。
缓存拦截器的工作说起来比较简单但是具体的实现需要处理的内容很多。在缓存拦截器中判断是否可以使用缓 存或是请求服务器都是通过 CacheStrategy 判断。
总结
1、如果从缓存获取的 Response 是null那就需要使用网络请求获取响应
2、如果是Https请求但是又丢失了 握手信息那也不能使用缓存需要进行网络请求
3、如果判断响应码不能缓存且响应头有 no-store 标识那 就需要进行网络请求
4、如果请求头有 no-cache 标识或者有 If-Modified-Since/If-None-Match 那么需要进行 网络请求
5、如果响应头没有 no-cache 标识且缓存时间没有超过极限时间那么可以使用缓存不需要进行 网络请求
6、如果缓存过期了判断响应头是否设置 Etag/Last-Modified/Date 没有那就直接使用网络请求否 则需要考虑服务器返回304 并且只要需要进行网络请求请求头中就不能包含 only-if-cached 否则框架直接返回504
四、连接拦截器
ConnectInterceptor 打开与目标服务器的连接并执行下一个拦截器。它简短的可以直接完整贴在这里
public final class ConnectInterceptor implements Interceptor {public final OkHttpClient client;public ConnectInterceptor(OkHttpClient client) {this.client client;}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);}
}
首先我们看到的 StreamAllocation 这个对象是在第一个拦截器重定向拦截器创建的但是真正使用的地方却在 这里。
当一个请求发出需要建立连接连接建立后需要使用流用来读写数据而这个StreamAllocation就是协调请 求、连接与数据流三者之间的关系它负责为一次请求寻找连接然后获得流来实现网络通信。
这里使用的 newStream 方法实际上就是去查找或者建立一个与请求主机有效的连接返回的 HttpCodec 中包含了 输入输出流并且封装了对HTTP请求报文的编码与解码直接使用它就能够与请求主机完成HTTP通信。
StreamAllocation 中简单来说就是维护连接 RealConnection ——封装了Socket与一个Socket连接池。可复用 的 RealConnection 需要
1、 if (allocations.size() allocationLimit || noNewStreams) return false; 连接到达最大并发流或者连接不允许建立新的流如http1.x正在使用的连接不能给其他人用(最大并发流为:1)或者 连接被关闭那就不允许复用
2、DNS、代理、SSL证书、服务器域名、端口完全相同则可复用 如果上述条件都不满足在HTTP/2的某些场景下可能仍可以复用(http2先不管)。 所以综上如果在连接池中找到个连接参数一致并且未被关闭没被占用的连接则可以复用。
总结
这个拦截器中的所有实现都是为了获得一份与目标服务器的连接在这个连接上进行HTTP数据的收发。
五、请求服务器拦截器
CallServerInterceptor 利用 HttpCodec 发出请求到服务器并且解析生成 Response 。
首先调用 httpCodec.writeRequestHeaders(request); 将请求头写入到缓存中(直到调用 flushRequest() 才真正发 送给服务器)。然后马上进行第一个逻辑判断
Response.Builder responseBuilder null;
if (HttpMethod.permitsRequestBody(request.method()) request.body() ! null) {// If theres a Expect: 100-continue header on the request, wait for a HTTP/1.1 100// Continue response before transmitting the request body. If we dont get that, return// what we did get (such as a 4xx response) without ever transmitting the request body.if (100-continue.equalsIgnoreCase(request.header(Expect))) {httpCodec.flushRequest();realChain.eventListener().responseHeadersStart(realChain.call());responseBuilder httpCodec.readResponseHeaders(true);}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()) {//HTTP2多路复用不需要关闭socket不管// If the Expect: 100-continue expectation wasnt met, prevent the HTTP/1// connection// from being reused. Otherwise were still obligated to transmit the request// body to// leave the connection in a consistent state.streamAllocation.noNewStreams();}
}
httpCodec.finishRequest();
整个if都和一个请求头有关 Expect: 100-continue 。这个请求头代表了在发送请求体之前需要和服务器确定是 否愿意接受客户端发送的请求体。所以 permitsRequestBody 判断为是否会携带请求体的方式(POST)如果命中 if则会先给服务器发起一次查询是否愿意接收请求体这时候如果服务器愿意会响应100(没有响应体 responseBuilder 即为nul)。这时候才能够继续发送剩余请求数据。
但是如果服务器不同意接受请求体那么我们就需要标记该连接不能再被复用调用 noNewStreams() 关闭相关的 Socket。
后续代码为:
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();
这时 responseBuilder 的情况即为
1、POST方式请求请求头中包含 Expect 服务器允许接受请求体并且已经发出了请求体 responseBuilder 为null;
2、POST方式请求请求头中包含 Expect 服务器不允许接受请求体 responseBuilder 不为null
3、POST方式请求未包含 Expect 直接发出请求体 responseBuilder 为null;
4、POST方式请求没有请求体 responseBuilder 为null;
5、GET方式请求 responseBuilder 为null;
对应上面的5种情况读取响应头并且组成响应 Response 注意此 Response 没有响应体。同时需要注意的是 如果服务器接受 Expect: 100-continue 这是不是意味着我们发起了两次 Request 那此时的响应头是第一次查询 服务器是否支持接受请求体的而不是真正的请求对应的结果响应。
所以紧接着:
int code response.code();
if (code 100) {// server sent a 100-continue even though we did not request one.// try again to read the actual responseresponseBuilder httpCodec.readResponseHeaders(false);response responseBuilder.request(request).handshake(streamAllocation.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();code response.code();
}
如果响应是100这代表了是请求 Expect: 100-continue 成功的响应需要马上再次读取一份响应头这才是真正 的请求对应结果响应头。
最后
if (forWebSocket code 101) {// Connection is upgrading, but we need to ensure interceptors see a non-null// response body.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
forWebSocket 代表websocket的请求我们直接进入else这里就是读取响应体数据。然后判断请求和服务器是 不是都希望长连接一旦有一方指明 close 那么就需要关闭 socket 。而如果服务器返回204/205一般情况而 言不会存在这些返回码但是一旦出现这意味着没有响应体但是解析到的响应头中包含 Content-Lenght 且不为 0这表响应体的数据字节长度。此时出现了冲突直接抛出协议异常
总结
在这个拦截器中就是完成HTTP协议报文的封装与解析。
拦截器总结
整个OkHttp功能的实现就在这五个默认的拦截器中所以先理解拦截器模式的工作机制是先决条件。这五个拦截 器分别为: 重试拦截器、桥接拦截器、缓存拦截器、连接拦截器、请求服务拦截器。每一个拦截器负责的工作不一 样就好像工厂流水线最终经过这五道工序就完成了最终的产品。
但是与流水线不同的是OkHttp中的拦截器每次发起请求都会在交给下一个拦截器之前干一些事情在获得了结 果之后又干一些事情。整个过程在请求向是顺序的而响应向则是逆序。
当用户发起一个请求后会由任务分发起 Dispatcher 将请求包装并交给重试拦截器处理。
1、重试拦截器在交出(交给下一个拦截器)之前负责判断用户是否取消了请求在获得了结果之后会根据响应码 判断是否需要重定向如果满足条件那么就会重启执行所有拦截器。
2、桥接拦截器在交出之前负责将HTTP协议必备的请求头加入其中(如Host)并添加一些默认的行为(如GZIP 压缩)在获得了结果后调用保存cookie接口并解析GZIP数据。
3、缓存拦截器顾名思义交出之前读取并判断是否使用缓存获得结果后判断是否缓存。
4、连接拦截器在交出之前负责找到或者新建一个连接并获得对应的socket流在获得结果后不进行额外的处 理。
5、请求服务器拦截器进行真正的与服务器的通信向服务器发送数据解析读取的响应数据。
在经过了这一系列的流程后就完成了一次HTTP请求