@Awille
2019-01-05T10:43:48.000000Z
字数 10293
阅读 103
okhttp android 网络请求
在项目开发当中,涉及到对http的request请求加签以及对返回的reponse进行验签的需求,项目中这两项的实现都是通过在okhttpClient的builder建造者中添加拦截器实现的,所以希望对okhttp的拦截器机制有了解。
okhttp的拦截链设计采用的是责任链模式。
在责任链模式当中,很多对象由 每个对象对其下家的引用连接起来而形成一条处理请求的链。当客户端请求到达时,并不知道在该条链路上是哪一个对象处理了请求,这样的好处是可以对客户端过来的请求做动态的处理。
a.纯责任链模式:当请求到达一个类时要么处理完,要么扔给下一个对象
b.不纯的责任链模式:当请求到达时,该类处理一部分(可以配置一些参数,适配一下请求),然后扔给下一个。
首先我们在源码中可以看到:
RealCall#execute()方法当中,获取最终的返回结果是通过该行函数来实现的:
Response result = getResponseWithInterceptorChain();
从函数名就可以知道,这是拦截器链路的开始:
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, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
从上面的函数可以看到,首先创建了一个数组存储了以下的拦截链:
a、interceptors.addAll(client.interceptors());这里是添加设置okhttpclient时设置的interceptors,我们做的一些自定义个拦截器也是通过这里添加上去的(比如我们需求要我们做的设置的加签验签的拦截器)
b、retryAndFollowUpInterceptor 负责失败重试以及重定向的拦截器
c、BridgeInterceptor 负责把用户构造的请求转换为发送到服务器的请求 以及把 服务器的reponse转换成为用户友好的response
d、CacheInterceptor 负责读取缓存,更新缓存
e、ConnectInterceptor 负责和服务器构建链接
f、client.networkInterceptors() 添加构建okhttpclient时构建的networkInterceptors
g、CallServerInterceptor 负责向服务器发送请求数据、从服务器读取响应数据的
最后的语句:
return chain.proceed(originalRequest);
这里就是开启链式调用的。
上面所有的拦截链都是继承Interceptor接口来实现的,我们可以来看下这个接口:
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
}
}
Interceptor接口中的intercept方法是实际处理请求的具体实现,在拦截链当中,每一个实现该接口的对象负责处理reponse的一部分
Chain接口中的接口负责执行intercept方法,request()方法简单明了,及是返回当前的request请求。
而proceed方法的解析,我们可以看一下框架中使用的最多的Chain接口实现类RealInterceptorChain都干了些什么事情。
首先看到重写的proceed方法:
@Override
public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
proceed方法的实现:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
//interceptors是一个拦截器组成的list
if (index >= interceptors.size()) throw new AssertionError();
//这个int参数是用来做标记的,确保拦截器链上的每个拦截器只被调用一次
calls++;
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
//调用拦截器链的下一个拦截器,可以观察到(index + 1)这个参数,这个参数是用来推动拦截器往下移动的
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
//这里index的作用可以看到,就是获取拦截器链上的相应拦截器的
Interceptor interceptor = interceptors.get(index);
//调用intercept方法获得reponse,传入的参数为指向下一个节点的chain对象
Response response = interceptor.intercept(next);
//下面都是省略一些对返回的reponse的参数做合法性判断的
...
return response;
}
可以看到,这个proceedfang方法主要职责就是逐个调用拦截器的拦截链,Chain接口的两个方法一个是返回request对象,一个是调用Interceptor接口中的intercept,这个intercept方法吧Chain对象当做参数传递,实现request跟reponse对象在拦截器链中的层层传递。
@Override
public Response intercept(Chain chain) throws IOException {
//根据chain获得传递的request
Request request = chain.request();
//chain对象转换
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//call 对象获取
Call call = realChain.call();
int followUpCount = 0;
Response priorResponse = null;
while (true) {
//... 省略判断是否取消的相关操作
Response response;
boolean releaseConnection = true;
try {
//调用拦截器的方法
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
//情况一:路由链接失败 request请求未发送
//判断是否可以修复
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
releaseConnection = false;
//重试
continue;
} catch (IOException e) {
// 情况二:服务器链接失败,
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
//recover尝试修复该连接,如果是不可修复的连接,直接抛出异常,由于后面没有再
//捕获异常的catch了,那么函数直接停止运行
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
//重连 判断为可修复
continue;
} finally {
// 在finalliy中判断是否链接已经释放,如果释放了就释放资源
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release(true);
}
}
// 如果上次尝试连的reponse,这一类的reponse是没有body的
// priorResponse是记录上次尝试的reponse,在后面的closeQuietly函数中把body设为null
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp;
//followUpRequest该函数的作用
//这个函数如判断根据reponse返回重新构造request,如果是正常reponse会构造request为null,
//如果是异常的reponse,比如需要重定向,会自动构建重定向的request
//当然如果是404等错误,也是直接构造request为null。
try {
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release(true);
throw e;
}
//followUp为空,要么是成功的返回,
//要么是404等不可通过修改request参数来修复的错误
if (followUp == null) {
streamAllocation.release(true);
return response;
}
//这里就是priorResponse的body为null的原因
closeQuietly(response.body());
//超过最大尝试重连次数,直接就抛出异常退出
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release(true);
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//... 这里省略了一些异常情况的判断
request = followUp;
priorResponse = response;
}
}
可以看到这个RetryAndFollowUpInterceptor在某些网络请求失败的情况下帮我们做尝试重连操作,甚至完善到了根据返回的reponse的状态码以及返回信息重新构建request请求去进行网络请求(比如重定向这种操作)。
同样是查看intercept方法:
@Override
public Response intercept(Chain chain) throws IOException {
//这是我们应用上构建的request
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
//类型转换 一些我们没有赋值的字段,比如Host、Connection中的字段
//另外header中Connection字段的值如我们没有设置,这里默认会使用Keep-Alive这个值
//省略header字段的补充
//Accept-Encoding字段如果没设置,会设为为gzip
//添加cookie
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());
}
//获得reponse,这里跟之前一样,这种调用方法可以理解为递归调用,
//这里得到的是调用以后各个拦截器得到的返回
Response networkResponse = chain.proceed(requestBuilder.build());
//network reponse到 applicaition reponse的转换
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//... 省略帮我们做解压等操作,还有剔除一些我们应用上不需要的header字段
return responseBuilder.build();
}
通过这个拦截器的实现,我们可以看到一个拦截器大概做的事情:
a、首先通过传入的chain得到request
b、对request进行我们需要的操作(比如加签)
c、通过chain调用下一个拦截器的intercept方法(递归调用)得到返回的reponse
d、对reponse记性我们需要的操作(比如验签名)
e、直接把reponse返回回去
@Override
public Response intercept(Chain chain) throws IOException {
//判断是否有上一个传入request请求的缓存
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 (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();
}
// 如果没有构造出networkrequest,说明不要网络,直接返回缓存结果
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//继续拦截链的调用
networkResponse = chain.proceed(networkRequest);
} finally {
...
}
// 根据返回的reponse的相关字段对已有缓存进行处理
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());
}
}
...
return response;
}
主要是做以下事情:
首先,根据request来判断cache中是否有缓存的response,如果有,得到这个response,然后进行判断当前response是否有效,没有将cacheCandate赋值为空。根据request判断缓存的策略,是否要使用了网络,缓存 或两者都使用调用下一个拦截器,决定从网络上来得到response如果本地已经存在cacheResponse,那么让它和网络得到的networkResponse做比较,决定是否来更新缓存的cacheResponse
@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);
}
通过创建HttpCodec对象进行连接
这个拦截器首先向服务器发起连接,然后根据返回的reponse的返回码进行判断,然后直接返回。
这个拦截器应该是okhttp拦截器链的最后一个拦截器,没有看到其调用传入的chain对象的proceed方法。它返回的reponse是在从服务器获得的reponse基础上做了返回码判断二次构造后返回回来的。
public class MyInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//加签 对request处理
Response response = chain.proceed(request);
//验签 对reponse操作 打点也可以放在这里
return reponse;
}
}
在okhttpclient.builder创建时,将拦截器加上去
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new MyInterceptor());
如果使用retrofit,可以创建时把整个OkHttpClient加进去:
mRetrofit = new Retrofit.Builder()
.baseUrl(url)
.addCallAdapterFactory(new HttpCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.client(builder.build())
.build();