OkHttp是由Square创建的一个开源项目,旨在成为一个高效的HTTP和HTTP/2客户端。它可以有效地执行HTTP请求,加快请求的负载和节省带宽。它提供了几个强大的功能,如同一主机的所有HTTP/2请求共享一个套接字;HTTP/2不可用时,连接池减少请求时延;Transparent GZIP减少下载大小;响应缓存可以完全避免重复网络请求。此外,OkHttp有一个很好的机制来管理常见的连接问题。现在,它也支持WebSocket。
拦截器是OkHttp中提供一种强大机制,它可以实现网络监听、请求以及响应重写、请求失败重试等功能。恰当地使用拦截器非常重要,笔者目前正在开发维护的项目中的诸多功能都与OkHttp的拦截器相关,比如App本地缓存,移动流量免流,容灾,域名劫持等,同时,OkHttp拦截器的设计思路也非常值得开发者学习,比如开屏广告,播放器播放条件判断等。因此,我决定写一篇OkHttp拦截器的文章,通过源码分析介绍几个tips。
拦截器设计--一语道破
OkHttp的多个拦截器可以链接起来,形成一个链条。拦截器会按照在链条上的顺序依次执行。 拦截器在执行时,可以先对请求的 Request 对象进行修改;在得到响应的 Response 对象之后,可以进行修改之后再返回。见官方介绍拦截器拦截顺序图,问题来了,请问:为何这个顺序是这样的?
import java.io.IOException;
//拦截器接口
public interface Interceptor {
MyResponse intercept(Chain chain) throws IOException;
interface Chain {
MyRequest request();
MyResponse proceed(MyRequest request) throws IOException;
}
}
/**
* Created by guokun on 2018/12/4.
* Description: 模仿OkHttp Interceptor 责任链设计模式
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public final class MyInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final MyRequest myRequest;
// private int calls;
private int index;
public MyInterceptorChain(List<Interceptor> interceptors, int index, MyRequest myRequest) {
this.interceptors = interceptors;
this.index = index;
this.myRequest = myRequest;
}
public static void main(String[] args) {
OneInterceptor oneInterceptor = new OneInterceptor("one_Request", "one_response");
TwoInterceptor twoInterceptor = new TwoInterceptor("two_request", "two_response");
ThreeInterceptor threeInterceptor = new ThreeInterceptor("three_request", " three_response");
final List<Interceptor> interceptors = new ArrayList<>();
interceptors.add(oneInterceptor);
interceptors.add(twoInterceptor);
interceptors.add(threeInterceptor);
final MyRequest mainRequest = new MyRequest("main ");
MyInterceptorChain myInterceptorChain = new MyInterceptorChain(interceptors, 0, mainRequest);
try {
System.out.println(myInterceptorChain.proceed(mainRequest).getResponseDiscription());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public MyRequest request() {
return myRequest;
}
@Override
public MyResponse proceed(MyRequest request) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
// calls++;
//递归调用每个拦截器
MyInterceptorChain next = new MyInterceptorChain(interceptors, index + 1, request);
Interceptor interceptor = interceptors.get(index);
MyResponse response = interceptor.intercept(next);
return response;
}
}
public class MyRequest {
private String requestdiscription;
public MyRequest() {
System.out.println("request construct enter");
}
public MyRequest(String discription) {
this.requestdiscription = discription;
}
public String getRequestdiscription() {
return requestdiscription;
}
public void setRequestdiscription(String requestdis) {
this.requestdiscription = this.requestdiscription + " " + requestdis;
}
}
public class MyResponse {
private String responseDiscription;
public MyResponse() {
System.out.println("response construct enter");
}
public MyResponse(String discription) {
this.responseDiscription = discription;
}
public String getResponseDiscription() {
return responseDiscription;
}
public void setResponseDiscription(String responseDis) {
this.responseDiscription = this.responseDiscription + " " + responseDis;
}
}
import java.io.IOException;
public final class OneInterceptor implements Interceptor {
private String oneRequest;
private String oneResponse;
public OneInterceptor(String oneRequest, String oneResponse) {
this.oneRequest = oneRequest;
this.oneResponse = oneResponse;
}
@Override
public MyResponse intercept(Chain chain) throws IOException {
MyRequest myRequest = chain.request();
myRequest.setRequestdiscription(oneRequest);
System.out.println("one interceptor --------------request====" + myRequest.getRequestdiscription());
MyResponse myResponse = chain.proceed(myRequest);
myResponse.setResponseDiscription(oneResponse);
System.out.println("one interceptor -----------------response=======" + myResponse.getResponseDiscription());
return myResponse;
}
}
import java.io.IOException;
public final class TwoInterceptor implements Interceptor {
private String twoRequest;
private String twoResponse;
public TwoInterceptor(String oneRequest, String oneResponse) {
this.twoRequest = oneRequest;
this.twoResponse = oneResponse;
}
@Override
public MyResponse intercept(Chain chain) throws IOException {
MyRequest myRequest = chain.request();
myRequest.setRequestdiscription(twoRequest);
System.out.println("two interceptor-----------request=======" + myRequest.getRequestdiscription());
MyResponse myResponse = chain.proceed(myRequest);
// MyResponse myResponse = testFor(null, chain);
myResponse.setResponseDiscription(twoResponse);
System.out.println("two interceptor---------response===" + myResponse.getResponseDiscription());
return myResponse;
}
private MyResponse testFor(MyResponse myResponse, Chain chain) throws IOException {
MyRequest myRequest = chain.request();
MyResponse response = null;
for (int i = 0; i < 2; i++) {
MyRequest myRequest1 = new MyRequest(myRequest + "----" + i + " " + myRequest.getRequestdiscription());
response = chain.proceed(myRequest1);
}
return response;
}
}
import java.io.IOException;
public final class ThreeInterceptor implements Interceptor {
private String threeRequest;
private String threeResponse;
public ThreeInterceptor(String oneRequest, String oneResponse) {
this.threeRequest = oneRequest;
this.threeResponse = oneResponse;
}
@Override
public MyResponse intercept(Chain chain) throws IOException {
MyRequest myRequest = chain.request();
myRequest.setRequestdiscription(threeRequest);
System.out.println("three interceptor ------------request=====" + myRequest.getRequestdiscription());
MyResponse myResponse = new MyResponse("threeResponse ");
System.out.println("three interceptor ------------response ======" + myResponse.getResponseDiscription());
return myResponse;
}
}
one interceptor --------------request====main one_Request
two interceptor-----------request=======main one_Request two_request
three interceptor ------------request=====main one_Request two_request three_request
three interceptor ------------response ======threeResponse
two interceptor---------response===threeResponse two_response
one interceptor -----------------response=======threeResponse two_response one_response
threeResponse two_response one_response
理解上述代码调用逻辑就不难理解OkHttp的拦截器的调用逻辑,源码RealInterceptorChain对应MyInterceptorChain
package okhttp3.internal.http;
import java.io.IOException;
import java.util.List;
import okhttp3.Connection;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.internal.connection.StreamAllocation;
/**
* A concrete interceptor chain that carries the entire interceptor chain: all application
* interceptors, the OkHttp core, all network interceptors, and finally the network caller.
*/
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final Connection connection;
private final int index;
private final Request request;
private int calls;
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, Connection 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,
Connection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !sameConnection(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// 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");
}
//关键代码
// 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);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
return response;
}
private boolean sameConnection(HttpUrl url) {
return url.host().equals(connection.route().address().url().host())
&& url.port() == connection.route().address().url().port();
}
}
应用拦截器VS网络拦截器
OkHttp有两类拦截器--应用拦截器和网络拦截器,两者有很大的区别。
import static okhttp3.internal.platform.Platform.INFO;
final class RealCall implements Call {
......
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);
}
}
官网上有一段两者区别的解释和示例,笔者能力有限暂无法从代码给出相关的答案。
Application interceptors
- 无法操作中间的响应结果,比如当URL重定向发生以及请求重试等,只能操作客户端主动第一次请求以及最终的响应结果。
- 在任何情况下只会调用一次,即使这个响应来自于缓存。
- 可以监听观察这个请求的最原始未经改变的意图(请求头,请求体等),无法操作OkHttp为我们自动添加的额外的请求头,比如If-None-Match。
- 允许short-circuit (短路)并且允许不去调用Chain.proceed()。(编者注:这句话的意思是Chain.proceed()不需要一定要调用去服务器请求,但是必须还是需要返回Respond实例。那么实例从哪里来?答案是缓存。如果本地有缓存,可以从本地缓存中获取响应实例返回给客户端。这就是short-circuit (短路)的意思。。囧)
- 允许请求失败重试以及多次调用Chain.proceed()。
Network Interceptors
- 允许操作中间响应,比如当请求操作发生重定向或者重试等。
- 不允许调用缓存来short-circuit (短路)这个请求。(编者注:意思就是说不能从缓存池中获取缓存对象返回给客户端,必须通过请求服务的方式获取响应,也就是Chain.proceed())
- 可以监听数据的传输
- 允许Connection对象装载这个请求对象。(编者注:Connection是通过Chain.proceed()获取的非空对象)
APP本地缓存
在无网络的情况下打开App,App会加载本地缓存的数据;那么问题来了,既然缓存了本地数据,我们如何去缓存数据呢?
很简单,网络数据请求成功后立即保存一份在本地。问题来了,保存数据的逻辑写在那里?
- 加在每个请求方法里;
- 加在http请求里;
方法2显然是更好的方式,既简单又利于扩展和维护。OkHttp拦截器加一个缓存Interceptor即可。
import android.text.TextUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import tingshu.bubei.netwrapper.CacheProcessor;
import static tingshu.bubei.netwrapper.CacheStrategy.BEFORE_NET_CACHE;
import static tingshu.bubei.netwrapper.CacheStrategy.IF_NET_FAIL_CACHE;
import static tingshu.bubei.netwrapper.CacheStrategy.IF_NET_SUCCEED_FORCE_CACHE;
import static tingshu.bubei.netwrapper.CacheStrategy.IF_NET_SUCCEED_RETURN_CACHE;
import static tingshu.bubei.netwrapper.CacheStrategy.SAVE_NET_CACHE;
public class CacheInterceptor implements Interceptor {
public static final String FORCE_CACHE_WITHOUT_NO_NET_DATA = "force_cache_without_no_net_data";
private int cacheStrategy;
private CacheProcessor cacheProcessor;
public CacheInterceptor(int strategy, CacheProcessor cacheProcessor) {
this.cacheStrategy |= strategy;
this.cacheProcessor = cacheProcessor;
if(cacheProcessor==null){
throw new RuntimeException("cacheProcessor not be null");
}
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response;
String cache = null;
if((cacheStrategy&BEFORE_NET_CACHE) == BEFORE_NET_CACHE){//预先读取缓存
cache = cacheProcessor.findCache(false);
}
if(!TextUtils.isEmpty(cache)){//读取缓存成功
ResponseBody responseBody = ResponseBody.create(MediaType.parse("application/json; charset=utf-8"), cache);
response = new Response.Builder()
.message(" ")
.request(request)
.code(200)
.protocol(Protocol.HTTP_1_1)
.body(responseBody)
.build();
}else{//缓存没有,读取网络数据
ResponseBody body = null;
IOException ioException = null;
try {
response = chain.proceed(request);
body = response.newBuilder().build().body();
} catch (IOException exception) {
response = null;
ioException = exception;
}
if(body!=null&&response.code()==200){//访问网络成功
MediaType mediaType = body.contentType();
String json = null;
try{
json = response.body().string();
}catch(IllegalStateException e){
e.printStackTrace();
}
if(!TextUtils.isEmpty(json) && !"".equals(json) &&((cacheStrategy&SAVE_NET_CACHE)==SAVE_NET_CACHE)){//网络读取成功,并且需要存缓存
try {
JSONObject jsonObject = new JSONObject(json);
int status = jsonObject.optInt("status");
if(status == 0){
cacheProcessor.saveCache(json);
}
} catch (JSONException e) {
e.printStackTrace();
}
//cacheProcessor.saveCache(json);
if((cacheStrategy&IF_NET_SUCCEED_RETURN_CACHE) == IF_NET_SUCCEED_RETURN_CACHE){//请求成功还是使用缓存中的数据
json = cacheProcessor.findCache(false);
} else if ((cacheStrategy&IF_NET_SUCCEED_FORCE_CACHE) == IF_NET_SUCCEED_FORCE_CACHE) {//请求网络成功强制读缓存
json = cacheProcessor.findCache(true);
}
}else if(TextUtils.isEmpty(json)&&((cacheStrategy&IF_NET_FAIL_CACHE)==IF_NET_FAIL_CACHE)){//网络读取失败,并且需要强制使用缓存
json = cacheProcessor.findCache(true);
}
if(json==null){
json = "";
}
body = ResponseBody.create(mediaType, json);
Headers headers = response.headers().newBuilder().build();
response = response.newBuilder().headers(headers).body(body).build();
}else if((cacheStrategy&IF_NET_FAIL_CACHE)==IF_NET_FAIL_CACHE){//网络访问不成功,并且需要强制读取缓存
cache = cacheProcessor.findCache(true);
if(!TextUtils.isEmpty(cache)){
ResponseBody responseBody = ResponseBody.create(MediaType.parse("application/json; charset=utf-8"), cache);
response = new Response.Builder()
.message(" ")
.request(request)
.code(200)
.protocol(Protocol.HTTP_1_1)
.body(responseBody)
.addHeader(FORCE_CACHE_WITHOUT_NO_NET_DATA, "true")
.build();
}
}
if (response == null) {
if (ioException == null) {
throw new IOException("访问服务器失败,页没有获取到缓存");
} else {
throw ioException;
}
}
}
return response;
}
}
缓存处理器:获取数据和保存数据
//缓存处理器接口
public interface CacheProcessor {
//获取缓存数据
String findCache(boolean forceFind);
//保存缓存数据
void saveCache(String json);
}
JsonCache缓存处理器
public class JsonCacheProcessor implements CacheProcessor {
public static int DEFAULT_CACHE_TIME = 24;//默认缓存时长是24小时
private String key;
private float cacheHour;
public JsonCacheProcessor(String key) {
this.key = key;
this.cacheHour = DEFAULT_CACHE_TIME;
}
public JsonCacheProcessor(String key, float cacheHour) {
this.key = key;
this.cacheHour = cacheHour;
}
@Override
public String findCache(boolean forceFind) {
MiniDataCache miniDataCache = LocalDataBaseHelper.getInstance().queryMiniCache(key);
if (miniDataCache == null) return null;
long version = Utils.getCurrentHourVersion(cacheHour);
if (forceFind || miniDataCache.getVersion() == version) {
return miniDataCache.getJsonData();
}
return null;
}
@Override
public void saveCache(String json) {
LocalDataBaseHelper.getInstance().insertOrReplaceMiniCache(new MiniDataCache(key, json, Utils.getCurrentHourVersion(cacheHour)));
}
}
免流服务--OkHttpClient配置
- 代理请求:设置代理服务器,http请求添加头部信息。
return okHttpClient.newBuilder()
.proxy(new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(/*"101.95.47.98"*/teleHttpHostName, TingshuTypeCast.intParseInt(teleHttpPort, TELEHTTPPORT_DEFAULT))))
// .addInterceptor(new RetryInterceptor())
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
final String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
final String token = MD5(*** + *** + chain.request().url().host() + timestamp + telephone);
/* Log.i("hgk", " tele http----------------teleHttpHostName ===" + teleHttpHostName +
"------host =====" + chain.request().url().host() +
"------token = " + token);*/
Request newRequest = chain.request().newBuilder()
.addHeader("***", ***)
.addHeader("***", ***)
.addHeader("***", ***)
.addHeader("***", ***)
.build();
Log.d(DEBUG_TEL_TAG, "telecom free traffic service has open (get method), http url ==" + newRequest.url().toString());
Response response = chain.proceed(newRequest);
if (response.code() != 200) {
Log.d(DEBUG_TEL_TAG, "Error code! telecom free traffic service has open (get method), http url response code ==" + response.code());
}
return response;
}
}).build();
- 代理鉴权
final String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String url = Uri.parse(uri).getHost();
final String token = MD5(spid + spkey + url + timestamp + telephone);
final StringBuilder authenticatorStr = new StringBuilder("SPID=" + spid + "&");
authenticatorStr.append("***=" + *** + "&");
authenticatorStr.append("***=" + *** + "&");
authenticatorStr.append("***=" + *** + "&"); //免流时长有限制
authenticatorStr.append("***=" + ***);
String stringrs = authenticatorStr.toString();
// Log.i("hgk", " tele https-------------------authenticatorStr ==" + stringrs);
final String str = Base64.encodeToString(stringrs.getBytes(), Base64.NO_WRAP); //参数
Log.d(DEBUG_TEL_TAG, "telecom free traffic service has open (get method), https url ==" + uri);
Authenticator authenticator = new Authenticator() {
@Nullable
@Override
public Request authenticate(Route route, Response response) throws IOException {
return response.request().newBuilder()
.header("Proxy-Authorization", str)
.build();
}
};
return okHttpClient.newBuilder()
.proxy(new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(teleHttpsHostName, TingshuTypeCast.intParseInt(teleHttpsPort, TELEHTTPPORT_DEFAULT))))
.proxyAuthenticator(authenticator)
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if (response.code() != 200) {
Log.d(DEBUG_TEL_TAG, "Error code! telecom free traffic service has open (init method), https response code ==" + response.code()
+ " url ===" + response.request().url());
}
return response;
}
})
.build();
-替换请求: http://:/?xyz=:
参考文档
OkHttp3-拦截器(Interceptor)
Chain of Responsibility Design Pattern
OkHttp使用进阶 译自OkHttp Github官方教程
OkHttp3源码分析之拦截器Interceptor
深入理解OkHttp源码及设计思想