需求概述
在现今的客户端和服务端端的网络交互流程中,已经实现了信道重连的功能。但目前各客户端针对重连的实现细节并不完全统一,而在某些使用场景下,也没有达到当初设计信道重连机制的需求。因此需要重新设计整理实现方案,保证各平台的统一和功能的完备。
功能需求
我们需要实现以下需求:
- 信道重连的请求由客户端发起,客户端需要定义规则来判断何时发起重连请求;
- 服务器需要在信道重连的请求响应中返回密钥有效期,以此来作为信道重连的最小时间间隔;
- 所有通过信道传输的网络请求,在请求过程中都需要判断信道是否重连;
- 客户端需要增加配置是否禁用信道重连功能。
设计
以下说明的网络请求,未经特殊说明的,都指经过信道传输的网络请求,包括但不限于:
- luahttp接口发出的请求;
- js http接口发出的请求;
- 离线更新及通过信道下载发出的请求;
- 开发模式请求ewp服务器资源的请求;
- 控件属性或样式中需要去ewp服务器请求资源的请求。
信道重连逻辑
- 在信道握手请求中,若EWP端有配置信道重连时间,则将其值(单位秒)作为信道重连的时间间隔,以X-Emp-CipherExpiry header的形式,返回给客户端;
- 客户端在接收到EWP端的网络响应时,解析header,如遇X-Emp-CipherExpiry字段,将其值保存,后续以此计算信道重连的时间戳;
- 客户端在所有通过信道传输的网络请求发起前,先判断当前系统时间是否大于信道重连的时间戳。如需要信道重连,则重新走握手流程,并在请求中加上X-Emp-Rehandshake header,值为1;
- EWP端在握手流程中需获取X-Emp-Rehandshake header,若值为1,则在握手成功后返回ok,否则与正常流程一致,返回app初始化页面;
- 为保证安全,客户端在信道重连的请求响应中判断是否重连成功,若重连失败,则取消后续请求,并提示网络错误。其中网络错误信息可配置。
- 客户端增加配置项是否禁用信道重连功能,该配置关闭时,不走信道重连流程。
信道重连时间戳的计算方法
- 客户端拿到服务器返回的X-Emp-CipherExpiry字段值后,先将其与当前系统时间相加,计算得出一个信道超时时间戳。
- 在后续的网络请求响应成功后,取当前系统时间与本地存储的X-Emp-CipherExpiry字段值相加,得出新的信道超时时间戳;若请求响应失败,无须更新时间戳。
关于并发请求的说明
由于产品的网络请求大多为异步请求,存在并发访问ewp的情况。那么很可能出现这种场景:
- 请求A发现信道超时,请求ewp服务器重连信道;
- 在A重连信道请求尚未返回时,出现并发请求B;
- 请求B发现信道超时,也请求ewp服务器重连信道。
这样就造成冗余重连信道的现象发生。
为此规定,当某个网络请求开启信道重连请求时,后续触发的网络请求必须等待此信道重连结束后,方可继续执行后续操作。若信道重连成功,等待的网络请求继续发出;若信道重连失败,取消所有等待的网络请求。
简单的实现
/** 信道超时时间,非0情况下,当前时间大于此时间时,信道超时重连 */
private static long mRehandshakeTime = 0L;
/** 服务器返回超时时间 */
private static long mInterval = 0L;
/** 信道是否重连的标记 */
private static String mRehandshake = "false";
/** 信道重连是否成功的的标记 */
private static String mRehandshakeSuccess = "true";
/** 1.4 信道是否为密文传输 默认true */
private boolean mIsEncryptedTrans = true;
/** 存放CryptoHttpManager的队列 */
private static ArrayList<CryptoHttpManager> mList = new ArrayList<CryptoHttpManager>();
/** 标记是否取消后续请求。 */
public boolean mCancel = false;
/** 是否重连的标记 */
private boolean mIsRehandshake = false;
if(!mIsRehandshake){
synchronized(mList){
if( EMPConfig.newInstance().isTlsReconnectAble() && "true".equals(mRehandshake)){
mList.add(this);
mList.wait();
}
}
//取消后续请求返回null
if(mCancel){
return resultObj;
}
synchronized (mRehandshake) {
// 请求之前首先判断信道是否需要重建
if (EMPConfig.newInstance().isTlsReconnectAble() && "false".equals(mRehandshake)
&& mRehandshakeTime != 0L && System.currentTimeMillis() > mRehandshakeTime) {
mRehandshake = "true";
try{
//信道重连
new ClientHello();
// 信道重连结束后更新成功标记
mRehandshakeSuccess = "true";
}catch (HttpResponseException ex) {
//信道重连失败后更新标记
mRehandshakeSuccess = "false";
//信道重连失败,则依次取消请求
Iterator<CryptoHttpManager> iterator = mList.iterator();
while(iterator.hasNext()){
iterator.next().setCancel(true);
}
throw ex;
}finally{
//信道重连失败后更新标记
mRehandshake = "false";
synchronized(mList){
mList.notifyAll();
mList.clear();
}
}
}
}
}
if("false".equals(mRehandshakeSuccess)){
return resultObj;
}