源码分析三:OkHttp(2)—拦截器简介
前一篇博客中我们介绍了OkHttp的总体架构,接下来我们以一个具体的网络请求来讲述OkHttp进行网络访问的具体过程。由于该部分与OkHttp的拦截器概念紧密联系在一起,所以将这两部分放在一起进行讲解。
并对各个拦截器进行简介:
- RetryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectInterceptor
- CallServerInterceptor
1.构造Demo
首先构造一个简单的异步网络访问Demo:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("https://github.com").build();client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {Log.d("OkHttp", "Call Failed:" + e.getMessage());}@Overridepublic void onResponse(Call call, Response response) throws IOException {Log.d("OkHttp", "Call succeeded:" + response.message());}
});
复制代码
2. 发起请求
OkHttpClient.newCall
实际是创建一个RealCall
实例:
/*** Prepares the {@code request} to be executed at some point in the future.*/@Override public Call newCall(Request request) {return new RealCall(this, request, false /* for web socket */);}复制代码
RealCall.enqueue
实际就是讲一个RealCall
放入到任务队列中,等待合适的机会执行:
@Override public void enqueue(Callback responseCallback) {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}captureCallStackTrace();client.dispatcher().enqueue(new AsyncCall(responseCallback));}复制代码
从代码中可以看到最终RealCall
被转化成一个AsyncCall
并被放入到任务队列中,任务队列中的分发逻辑这里先不说,相关实现会放在后续进行介绍。这里只需要知道AsyncCall的excute方法最终将会被执行:
[RealCall.java]
@Override protected void execute() {boolean signalledCallback = false;try {Response response = getResponseWithInterceptorChain();if (retryAndFollowUpInterceptor.isCanceled()) {signalledCallback = true;responseCallback.onFailure(RealCall.this, new IOException("Canceled"));} else {signalledCallback = true;responseCallback.onResponse(RealCall.this, response);}} catch (IOException e) {if (signalledCallback) {// Do not signal the callback twice!Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);} else {responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}}}复制代码
execute
方法的逻辑并不复杂,简单的说就是:
- 调用
getResponseWithInterceptorChain
获取服务器返回 - 通知任务分发器(
client.dispatcher
)该任务已结束
getResponseWithInterceptorChain
构建了一个拦截器链,通过依次执行该拦截器链中的每一个拦截器最终得到服务器返回。
3. 构建拦截器链
首先来看下getResponseWithInterceptorChain
的实现:
[RealCall.java]
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 (!forWebSocket) {interceptors.addAll(client.networkInterceptors());}interceptors.add(new CallServerInterceptor(forWebSocket));Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest);return chain.proceed(originalRequest);}复制代码
其逻辑大致分为两部分:
- 创建一系列拦截器,并将其放入一个拦截器数组中。这部分拦截器即包括用户自定义的拦截器也包括框架内部拦截器
- 创建一个拦截器链
RealInterceptorChain
,并执行拦截器链的proceed
方法
接下来看下RealInterceptorChain
的实现逻辑:
[RealInterceptorChain.java]
public final class RealInterceptorChain implements Interceptor.Chain {private final List<Interceptor> interceptors;private final StreamAllocation streamAllocation;private final HttpCodec httpCodec;private final RealConnection connection;private final int index;private final Request request;private int calls;public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,HttpCodec httpCodec, RealConnection connection, int index, Request request) {this.interceptors = interceptors;this.connection = connection;this.streamAllocation = streamAllocation;this.httpCodec = httpCodec;this.index = index;this.request = request;}@Override public Connection connection() {return connection;}public StreamAllocation streamAllocation() {return streamAllocation;}public HttpCodec httpStream() {return httpCodec;}@Override public Request request() {return request;}@Override public Response proceed(Request request) throws IOException {return proceed(request, streamAllocation, httpCodec, connection);}public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException {......// Call the next interceptor in the chain.RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request);Interceptor interceptor = interceptors.get(index);Response response = interceptor.intercept(next);...... return response;}
}复制代码
在proceed
方法中的核心代码可以看到,proceed实际上也做了两件事:
- 创建下一个拦截链。传入
index + 1
使得下一个拦截器链只能从下一个拦截器开始访问 - 执行索引为
index
的intercept方法,并将下一个拦截器链传入该方法
接下来再看下第一个拦截器RetryAndFollowUpInterceptor的intercept方法:
[RetryAndFollowUpInterceptor.java]public final class RetryAndFollowUpInterceptor implements Interceptor {@Override public Response intercept(Chain chain) throws IOException {Request request = chain.request();streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), callStackTrace);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.if (!recover(e.getLastConnectException(), false, request)) {throw e.getLastConnectException();}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, 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.......Request followUp = followUpRequest(response);closeQuietly(response.body());...}}
}复制代码
这段代码最关键的代码是:
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);复制代码
这行代码就是执行下一个拦截器链的proceed方法。而我们知道在下一个拦截器链中又会执行下一个拦截器的intercept方法。所以整个执行链就在拦截器与拦截器链中交替执行,最终完成所有拦截器的操作。这也是OkHttp拦截器的链式执行逻辑。而一个拦截器的intercept方法所执行的逻辑大致分为三部分:
- 在发起请求前对request进行处理
- 调用下一个拦截器,获取response
- 对response进行处理,返回给上一个拦截器
这就是OkHttp拦截器机制的核心逻辑。所以一个网络请求实际上就是一个个拦截器执行其intercept方法的过程。而这其中除了用户自定义的拦截器外还有几个核心拦截器完成了网络访问的核心逻辑,按照先后顺序依次是:
- RetryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectIntercetot
- CallServerInterceptor
4 RetryAndFollowUpInterceptor
如上文代码所示,RetryAndFollowUpInterceptor负责两部分逻辑:
- 在网络请求失败后进行重试
- 当服务器返回当前请求需要进行重定向时直接发起新的请求,并在条件允许情况下复用当前连接
5 BridgeInterceptor
public final class BridgeInterceptor implements Interceptor {private final CookieJar cookieJar;public BridgeInterceptor(CookieJar cookieJar) {this.cookieJar = cookieJar;}@Override public Response intercept(Chain chain) throws IOException {Log.e("haha", "BridgeInterceptor.intercept");Request userRequest = chain.request();Request.Builder requestBuilder = userRequest.newBuilder();RequestBody body = userRequest.body();if (body != null) {MediaType contentType = body.contentType();if (contentType != null) {requestBuilder.header("Content-Type", contentType.toString());}long contentLength = body.contentLength();if (contentLength != -1) {requestBuilder.header("Content-Length", Long.toString(contentLength));requestBuilder.removeHeader("Transfer-Encoding");} else {requestBuilder.header("Transfer-Encoding", "chunked");requestBuilder.removeHeader("Content-Length");}}if (userRequest.header("Host") == null) {requestBuilder.header("Host", hostHeader(userRequest.url(), false));}if (userRequest.header("Connection") == null) {requestBuilder.header("Connection", "Keep-Alive");}// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing// the transfer stream.boolean transparentGzip = false;if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {transparentGzip = true;requestBuilder.header("Accept-Encoding", "gzip");}List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());if (!cookies.isEmpty()) {requestBuilder.header("Cookie", cookieHeader(cookies));}if (userRequest.header("User-Agent") == null) {requestBuilder.header("User-Agent", Version.userAgent());}Response networkResponse = chain.proceed(requestBuilder.build());HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());Response.Builder responseBuilder = networkResponse.newBuilder().request(userRequest);if (transparentGzip&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))&& HttpHeaders.hasBody(networkResponse)) {GzipSource responseBody = new GzipSource(networkResponse.body().source());Headers strippedHeaders = networkResponse.headers().newBuilder().removeAll("Content-Encoding").removeAll("Content-Length").build();responseBuilder.headers(strippedHeaders);responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));}return responseBuilder.build();}
}
复制代码
BridgeInterceptor主要负责以下几部分内容:
- 设置内容长度,内容编码
- 设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦
- 添加cookie
- 设置其他报头,如
User-Agent
,Host
,Keep-alive
等。其中Keep-Alive
是实现多路复用的必要步骤
6. CacheInterceptor
[CacheInterceptor.intercept]@Override public Response intercept(Chain chain) throws IOException {Log.e("haha", "CacheInterceptor.intercept");Response cacheCandidate = cache != null? cache.get(chain.request()): null;long now = System.currentTimeMillis();CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();Request networkRequest = strategy.networkRequest;Response cacheResponse = strategy.cacheResponse;if (cache != null) {cache.trackResponse(strategy);}if (cacheCandidate != null && cacheResponse == null) {closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.}// If we're forbidden from using the network and the cache is insufficient, fail.if (networkRequest == null && cacheResponse == null) {return new Response.Builder().request(chain.request()).protocol(Protocol.HTTP_1_1).code(504).message("Unsatisfiable Request (only-if-cached)").body(Util.EMPTY_RESPONSE).sentRequestAtMillis(-1L).receivedResponseAtMillis(System.currentTimeMillis()).build();}// If we don't need the network, we're done.if (networkRequest == null) {return cacheResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).build();}Response networkResponse = null;try {networkResponse = chain.proceed(networkRequest);} finally {// If we're crashing on I/O or otherwise, don't leak the cache body.if (networkResponse == null && cacheCandidate != null) {closeQuietly(cacheCandidate.body());}}// If we have a cache response too, then we're doing a conditional get.if (cacheResponse != null) {if (networkResponse.code() == HTTP_NOT_MODIFIED) {Response response = cacheResponse.newBuilder().headers(combine(cacheResponse.headers(), networkResponse.headers())).sentRequestAtMillis(networkResponse.sentRequestAtMillis()).receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()).cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();networkResponse.body().close();// Update the cache after combining headers but before stripping the// Content-Encoding header (as performed by initContentStream()).cache.trackConditionalCacheHit();cache.update(cacheResponse, response);return response;} else {closeQuietly(cacheResponse.body());}}Response response = networkResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();if (cache != null) {if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {// Offer this request to the cache.CacheRequest cacheRequest = cache.put(response);return cacheWritingResponse(cacheRequest, response);}if (HttpMethod.invalidatesCache(networkRequest.method())) {try {cache.remove(networkRequest);} catch (IOException ignored) {// The cache cannot be written.}}}return response;}复制代码
CacheInterceptor的职责很明确,就是负责Cache的管理
- 当网络请求有符合要求的Cache时直接返回Cache
- 当服务器返回内容有改变时更新当前cache
- 如果当前cache失效,删除
7 ConnectInterceptor
[ConnectInterceptor.java]
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 {Log.e("haha", "ConnectInterceptor.intercept");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, doExtensiveHealthChecks);RealConnection connection = streamAllocation.connection();return realChain.proceed(request, streamAllocation, httpCodec, connection);}
}复制代码
ConnectInterceptor的intercept方法只有一行关键代码:
RealConnection connection = streamAllocation.connection();复制代码
即为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接,返回的连接由连接池负责决定。
8. CallServerInterceptor
[CallServerInterceptor.java]
@Override public Response intercept(Chain chain) throws IOException {RealInterceptorChain realChain = (RealInterceptorChain) chain;HttpCodec httpCodec = realChain.httpStream();StreamAllocation streamAllocation = realChain.streamAllocation();RealConnection connection = (RealConnection) realChain.connection();Request request = realChain.request();long sentRequestMillis = System.currentTimeMillis();httpCodec.writeRequestHeaders(request);Response.Builder responseBuilder = null;......httpCodec.finishRequest();if (responseBuilder == null) {responseBuilder = httpCodec.readResponseHeaders(false);}Response response = responseBuilder.request(request).handshake(streamAllocation.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();int code = response.code();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;}复制代码
CallServerInterceptor负责向服务器发起真正的访问请求,并在接收到服务器返回后读取响应返回。
8.整体流程
以上就是整个网络访问的核心步骤,总结起来如下图所示:
转载于:https://juejin.im/post/5aa1fdbb6fb9a028d374ffe8
源码分析三:OkHttp(2)—拦截器简介相关推荐
- Spring源码分析之Aop中拦截器,适配器,通知之间的关系
首先举一个例子: public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {priv ...
- 【投屏】Scrcpy源码分析三(Client篇-投屏阶段)
Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...
- Spring AOP 源码分析 - 筛选合适的通知器
1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...
- Spring源码分析(三)
Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...
- Nouveau源码分析(三):NVIDIA设备初始化之nouveau_drm_probe
Nouveau源码分析(三) 向DRM注册了Nouveau驱动之后,内核中的PCI模块就会扫描所有没有对应驱动的设备,然后和nouveau_drm_pci_table对照. 对于匹配的设备,PCI模块 ...
- 【转】ABP源码分析三十五:ABP中动态WebAPI原理解析
动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...
- 【转】ABP源码分析三:ABP Module
Abp是基于模块化设计思想进行构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modul ...
- Spring 源码分析(三) —— AOP(五)创建代理
2019独角兽企业重金招聘Python工程师标准>>> 创建代理 代理的定义其实非常简单,就是改变原来目标对象方法调用的运行轨迹.这种改变,首先会对这些方法进行拦截,从而为这些方法提 ...
- Spring 源码分析(三) —— AOP(二)Spring AOP 整体架构
2019独角兽企业重金招聘Python工程师标准>>> Spring AOP 架构 先是生成代理对象,然后是拦截器的作用,最后是编织的具体实现.这是AOP实现的三个步 ...
- 【转】ABP源码分析三十六:ABP.Web.Api
这里的内容和ABP 动态webapi没有关系.除了动态webapi,ABP必然是支持使用传统的webApi.ABP.Web.Api模块中实现了一些同意的基础功能,以方便我们创建和使用asp.net w ...
最新文章
- 有关android 应用的plugin框架调研
- 哈工大计算机学院庞,奋斗在知足与知不足之间――我与我的导师计算机学院马培军教授二三事...
- C# WebApi 返回JSON类型
- 关于protege的dot error的问题
- 大家的芝麻信用分都是多少?
- flash xml+textArea组件+CSS
- 利用java的jsoup实现:短视频无水印下载
- 跳槽遇到背景调查,你可以这样做!
- Ubuntu: Firefox 的profile missing解决
- 关于Windows 7与Ubuntu启动的一些注记(win7屏蔽ubuntu的启动项)
- c语言中如何将字体弄大,CFree怎样调大字体
- 成功鲜有偶然:一览IT名人的教育成长经历
- 基于JAVAOTET交通在线查询购票系统计算机毕业设计源码+系统+lw文档+部署
- QtCreator 下使用glut.lib glut.dll
- 我的世界(12)-服务器领地(Residence插件)
- 《中学物理教学参考》期刊简介及投稿须知
- 面试时应该采取什么方法才能克服紧张的情绪?
- 23、合并果子( fruit.cpp )
- Javascript入门学习笔记
- 【三步搞定】3DMAX不锈钢材质制作方法!