需求概述
在現今的客戶端和服務端端的網絡交互流程中,已經實現了信道重連的功能。但目前各客戶端針對重連的實現細節並不完全統一,而在某些使用場景下,也沒有達到當初設計信道重連機制的需求。因此需要重新設計整理實現方案,保證各平臺的統一和功能的完備。
功能需求
我們需要實現以下需求:
- 信道重連的請求由客戶端發起,客戶端需要定義規則來判斷何時發起重連請求;
- 服務器需要在信道重連的請求響應中返回密鑰有效期,以此來作爲信道重連的最小時間間隔;
- 所有通過信道傳輸的網絡請求,在請求過程中都需要判斷信道是否重連;
- 客戶端需要增加配置是否禁用信道重連功能。
設計
以下說明的網絡請求,未經特殊說明的,都指經過信道傳輸的網絡請求,包括但不限於:
- 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;
}