一、背景
鸿蒙项目立项后,为了快速对齐网络能力,我司直接基于Axios二次封装了一套基础可用的网络能力。
但随着业务不断完善,对取消网络请求的需求越发紧迫,而由于系统ohos.net.http api未提供取消相关api, 因此Axios暂未实现。
二、现状
Axios 受限于底层网络引擎,无法支持取消:
Axios 开源库 上对此问题有一些反馈(维护者推荐使用rcp,但****暂无明确适配排期 )
鸿蒙官方现状 & 未来趋势 (官方****http暂时不会支持取消 ,长期主要维护rcp网络库)
基于上述背景, 我司考虑基于Axios支持取消能力。
三、方案选型
方案一:修改axios 将ohos.net.http 改为 rcp (直接废弃http引擎)
缺点:与开源版本割裂, 可持续维护性差。
方案二 : HNet基于rcp重新开发(直接废弃axios)
缺点:业务迁移成本高。
方案三:基于axios再封装一套rcp引擎
(支持切换引擎,使用rcp引擎时支持真实取消、使用ohos引擎支持上层"伪"取消)
缺点:短期维护内部版本,长期期望统一。已给官方开源库提pr
方案三可行性分析:
-
基于Axios源码分析可知,axios是通过Adapter模式来适配****不同平台 , 默认ohos(即 ohos.net .http) 因此可以快速实现一个****rcp适配器 ,随时切换。
不同平台可等价于不同的网络引擎,我们直接抹除平台概念,adapter : 对应不同网络引擎实现。
-
Axios是一套api封装, 可简单类比retrofit,扩展引擎的关键在于梳理reques、response映射关系.
request映射关系:
response 映射关系:
基于上述分析,方案三技术上完全可行
四、方案实践
4.1 工程架构
4.2 Api 设计
与现有Axios Api完全保持一致,业务可低成本切换。
1.一行代码切换引擎
let axiosInstance: HNet = HNet.createNewInstance({
timeout: 30 * 1000,
baseURL: Config.getApiUrl(),
adapter: rcpAdapter, //axios 默认是"ohos"
});
2.复用axios cancelToken 实现取消
getCommonConfig(): Promise<Response<string>> {
let map = new Map<string, Object>();
const source = hNet.createCancelTokenSource()
setTimeout(() => {
source.cancel("")
}, 100)
return axiosClient.get({
url: "?_m=get_common_config",
argParams: map,
cancelToken: source.token,
adapter:rcpAdapter // "rcp"
})
}
4.3 编码
1.关键代码一:新增文件说明
新增rcp 引擎 -> rcp.js
实现request&response convert -> convert.js
实现基本http请求(get、post、put、delete等) -> http.js
实现上传请求 -> upload.js
实现下载请求 -> download.js
2.关键代码二:新增rcpAdapter
//adapter.js 文件
const knownAdapters = {
ohos: ohosAdapter,
rcp:rcpAdapter,
}
// defaults.js 文件中切换默认引擎,非必需
adapter: ['rcp'] // 'ohos'
3.关键代码三:rcp实现取消
let session = rcp.createSession(sessionConfig)
if (config.cancelToken) {
config.cancelToken.promise.then(cancel => {
if (session) {
session.cancel();
reject(new AxiosError('Request canceled', AxiosError.ECONNABORTED, config, null));
}
});
}
4.关键代码四:放开cancelToken参数
// index.d.ts
export interface AxiosRequestConfig<D = any> {
cancelToken?: CancelToken
}
5.关键代码五:ohos上层"伪"取消
// 内部网络库会统一封装requsetError, 因此统一在此方法内处理, 伪代码
this . axiosInstance . get <T, AxiosResponse <T>, D>(url, axiosConfig) . then <( HNetResponse <T>)>( response => this . handleRequestSuccess (response)) . catch ( error => { throw this . handleRequestError (error); });
private handleRequestError<T = any>(err: AxiosError): Promise<HNetResponse<T> | void> {
if (!err) throw new Error('Error is null');
// error返回undefined
if (err && (err.message == null || err.message === '' || err.message === 'canceled')) {
return;
}
const hNetError: HNetError = {
config: err.config as HNetRequestConfig,
code: err.code,
request: err.request,
response: err.response as HNetResponse,
isAxiosError: err.isAxiosError,
toJSON: err.toJSON,
status: err.status,
cause: err.cause,
name: err.name,
message: err.message,
stack: err.stack,
};
throw xxxError;
}
4.4 过程中遇到的一些问题
-
js中无法正常导入官方文档中的依赖
官方文档导入姿势:import rcp from '@kit.RemoteCommunicationKit';
. js中文件中导入姿势: import rcp from '@hms.collaboration.rcp' ;
2. #### rcp设置 httpEventsHandler:customHttpEventsHandler, 会导致response.body 返回undifined
export interface HttpEventsHandler {
/**
* Callback called when a part of the HTTP response body is received.
* If the callback is registered, then returned {@link Response|response object}
* has undefined {@link Response.body|body} field.
* @type {?OnDataReceive}
* @syscap SystemCapability.Collaboration.RemoteCommunication
* @since 4.1.0(11)
*/
onDataReceive?: OnDataReceive;
-
axios HttpServer 无法正常启动,可删除zlib依赖 & 更新相关依赖版本解决
// 调整后的依赖
"dependencies": {
"body-parser": "^1.20.2",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"element-ui": "^2.10.1",
"express": "^4.19.2",
"http-errors": "~1.6.3",
"jade": "^0.29.0",
"morgan": "~1.9.1",
"multer": "^1.4.5-lts.1"
}
4. #### HttpServer下载有bug,实际下载成功,服务返回500 & HttpServer上传服务异常
解决手段:使用免费文件测试上传和下载 -> Filebin
// upload 测试
axios.post<UploadModel, AxiosResponse<UploadModel>, FormData>('https://filebin.net/'/*this.uploadUrl*/, formData, {
headers: { 'Content-Type': 'application/octet-stream' ,
'accept': 'application/json',
'bin': 'abcdefghajkmlnopqlshksakjdhsajghdjsa',
'filename': 'bule'},
//download 测试
axios<string, AxiosResponse<string>, null>({
url: 'https://filebin.net/abcdefghajkmlnopqlshksakjdhsajghdjsa/bule'/*this.downloadUrl*/,
method: 'get',
5. #### HttpServe无法直接测试代理,可使用**免费代理**测试
// 1.通过crul 测试 ,快速验证代理是否可用 (遇到失败的,多尝试几个)
curl --proxy http://153.101.67.170:9002 http://jsonplaceholder.typicode.com/posts
// 2.代理确认可用后,代码验证测试
axios<string, AxiosResponse<string>, null>({
url: 'http://jsonplaceholder.typicode.com/posts',
method: 'get',
adapter:'rcp',
proxy: {
host: '153.101.67.170' ,
port: 9002,
exclusionList: []
},
connectTimeout: this.connectTimeout,
readTimeout: this.readTimeout,
maxBodyLength: this.maxBodyLength,
maxContentLength: this.maxContentLength
})
-
rcp session 需要池化,每次request结束都close 会导致性能恶化
测试业务中核心链路接口 平均耗时增加 125ms
解决方案池化 rcp session (见Hll-Axios源码 - HllRcpSessionManager.js文件) - 池化后rcp更快(110ms)
五、 使用姿势
-
修改并强制指定工程 axios版本号 & HNet版本号
// 备注HNet为内部网络库(基于axios封装)
"dependencies": {
"@hll-wp/hnet": "1.0.0-alpha.3", // 内部仓库,外网不可访问
"@ohos/axios": "1.0.0-hll.5", // 内部仓库,外网不可访问
}
"overrides": {
"@hll-wp/hnet": "1.0.0-alpha.3", // 内部仓库,外网不可访问
"@ohos/axios": "1.0.0-hll.5", // 内部仓库,外网不可访问
}
2. ##### 初始化网络库时统一切换引擎
// 1.使用rcp引擎 (注意ohosAdapter 底层无法真实取消 - 只是上层取消) let axiosInstance : HNet = HNet . createNewInstance ({ timeout : 30 * 1000 , baseURL : Config . getApiUrl (), adapter : rcpAdapter });
3. ##### 切换引擎(当前支持ohos、rcp)
axios.get<string, AxiosResponse<string>, null>(this.getUrl, {
connectTimeout: this.connectTimeout,
adapter: ohosAdapter
})
4. ##### 取消请求
// 1.获取tokenSource const source = hNet. createCancelTokenSource () // 2.按需求执行取消 setTimeout ( () => { console . error ( "HNet" , "getCommonConfig() cancel do not need cancel result" ) /** * 2.1 cancel("") 方法参数传 null、undefined、"canceled" 、"" -> error 返回undefined * 2.2 cancel("abc") 方法参数传非2.1 内容,需要感知取消结果,Promise.catch **/
source. cancel ( "need cancel result" ) }, 100 ) // 3.请求设置取消token axiosInstance. get ({ url : "?_m=get_common_config" , argParams : map, cancelToken : source. token })
六、效果展示
七、相关资料&特别鸣谢
- Hll-Axios **-**扩展rcp源码(需要的可自取)
- axios源码
- rcp文档
- ohos.net.http文档