何为rcp?
rcp 即 Remote Communication Protocol(远场通信协议)。在HarmonyOS中,它的实现集成在Remote Communication Kit(远场通信服务)中,是华为提供的HTTP发起数据请求的NAPI封装。
应用通过Remote Communication Kit可以便捷快速地向服务器发起数据请求,rcp支持HTTP的GET、POST、OPTIONS等请求,提供了丰富的业务场景。
我们知道,在鸿蒙SDK的系统服务中,网络请求相关服务已经有了NetWork Kit了,为什么还需要使用rcp呢?根据官网FAQ说明: http原生库与rcp模块区别可以知道:
http原生库能力还在维护中,ohos.net.http暂不会再演进或新增其他功能,当前推荐使用hms.collaboration.rcp能力替代,在接口易用性、性能、功耗方面比http网络库好。
因此官方推荐使用rcp,我们也需要紧跟官方的步伐,赶快用起来~
使用Remote Communication Kit的主要业务流程如下:
- 应用客户端创建会话。
- 应用客户端发起请求。
- 应用客户端接收请求结果,处理相应业务。
如何使用
首先,需要申请网络权限,在entry/src/main路径下的module.json5中配置如下:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.GET_NETWORK_INFO"
}
]
}
}
然后,在需要使用该能力的.ets文件中导入rcp:
import { rcp } from "@kit.RemoteCommunicationKit";
这样,我们就可以使用rcp的能力了。
封装
rcp的使用场景很多,一般场景有:
- 获取服务器资源
- 发送数据到服务器
- 断点续传
- 双向证书校验
- 拦截器 ......
我们可以对一些主要场景进行封装,达到在项目中方便易用的效果。
这里,我们创建一个工具类HTTPRequestUtil
,并声明一个rcp.Configuration
和一个rcp.RequestHeaders
供全局调用:
const configuration: rcp.Configuration = {
transfer: {
autoRedirect: true,
timeout: {
// 允许建立连接的最长时间
connectMs: HTTP_TIMEOUT,
// 允许传输数据的最长时间
transferMs: HTTP_TIMEOUT,
}
}
};
const header: rcp.RequestHeaders = {
'content-type': 'application/json'
}
接着,再创建一个session
:
// Use the configuration in the session creation
private static session = rcp.createSession({ requestConfiguration: configuration });
这个session
是创建rcp网络请求或连接的基础,由此发起相关事务。
我们可以使用session
的fetch
能力发起一个HTTP请求,并获取返回结果:
private static sendRequest(url: string, method: rcp.HttpMethod, params: Record<string, string>, succeedCallback: Callback<rcp.Response>, failedCallback: Callback<BusinessError>) {
let req = new rcp.Request(url);
if (params) req.content = JSON.stringify(params);
req.method = method.toUpperCase();
req.headers = header;
HTTPRequestUtil.session.fetch(req).then((response) => {
LogUtil.info("RCP request succeed: " + JSON.stringify(response));
succeedCallback(response);
HTTPRequestUtil.session.close();
}).catch((err: BusinessError) => {
LogUtil.error("RCP request failed: " + err);
HTTPRequestUtil.session.close();
failedCallback(err);
});
}
可以看出,以上方法基于参数回调,返回结果和错误都会通过回调返回。
我们也可以通过Promise
的方式异步返回结果:
private static async sendRequestP(url: string, method: rcp.HttpMethod, params: Record<string, string>): Promise<rcp.Response | BusinessError> {
let req = new rcp.Request(url);
if (params) req.content = JSON.stringify(params);
req.method = method.toUpperCase();
req.headers = header;
req.configuration = configuration;
try {
let res = await HTTPRequestUtil.session.fetch(req);
LogUtil.info("RCP request succeed: " + JSON.stringify(res));
return res;
} catch (err) {
LogUtil.error("RCP request failed: " + err);
return err as BusinessError;
} finally {
HTTPRequestUtil.session.close();
}
}
这样,我们就可以通过传入参数的method变化实现一般的GET、POST、PUT等HTTP请求。
通过回调返回的可以实现如下:
/**
* 发送 GET 请求
* @param url: URL
* @param params: 参数
* @param succeedCallback: 成功回调
* @param failedCallback: 失败回调
*
* @returns 通过回调返回
*/
static GET(url: string, params: Record<string, string>, succeedCallback: Callback<rcp.Response>, failedCallback: Callback<BusinessError>) {
HTTPRequestUtil.sendRequest(url, "GET", params, (response: rcp.Response) => {
succeedCallback(response);
}, (err: BusinessError) => {
failedCallback(err);
});
}
/**
* 发送 POST 请求
* @param url: URL
* @param params: 参数
* @param succeedCallback: 成功回调
* @param failedCallback: 失败回调
*
* @returns 通过回调返回
*/
static POST(url: string, params: Record<string, string>, succeedCallback: Callback<rcp.Response>, failedCallback: Callback<BusinessError>) {
HTTPRequestUtil.sendRequest(url, "POST", params, (response: rcp.Response) => {
succeedCallback(response);
}, (err: BusinessError) => {
failedCallback(err);
});
}
/**
* 发送 PUT 请求
* @param url: URL
* @param params: 参数
* @param succeedCallback: 成功回调
* @param failedCallback: 失败回调
*
* @returns 通过回调返回
*/
static PUT(url: string, params: Record<string, string>, succeedCallback: Callback<rcp.Response>, failedCallback: Callback<BusinessError>) {
HTTPRequestUtil.sendRequest(url, "PUT", params, (response: rcp.Response) => {
succeedCallback(response);
}, (err: BusinessError) => {
failedCallback(err);
});
}
/**
* 发送 DELETE 请求
* @param url: URL
* @param params: 参数
* @param succeedCallback: 成功回调
* @param failedCallback: 失败回调
*
* @returns 通过回调返回
*/
static DELETE(url: string, params: Record<string, string>, succeedCallback: Callback<rcp.Response>, failedCallback: Callback<BusinessError>) {
HTTPRequestUtil.sendRequest(url, "DELETE", params, (response: rcp.Response) => {
succeedCallback(response);
}, (err: BusinessError) => {
failedCallback(err);
});
}
其使用十分简便:
HTTPRequestUtil.GET(url, {}, (res) => {
// 返回结果
}, (err) => {
// 错误处理
});
如果你希望通过Promise
返回,同样可以实现如下:
/**
* 发送 GET 请求(通过 Promise 返回)
* @param url: URL
* @param params: 参数
*
* @returns Promise 返回结果
*/
static async get(url: string, params: Record<string, string>): Promise<rcp.Response | BusinessError> {
try {
const response = await HTTPRequestUtil.sendRequestP(url, "GET", params);
return response;
} catch (error) {
return error;
}
}
/**
* 发送 POST 请求(通过 Promise 返回)
* @param url: URL
* @param params: 参数
*
* @returns Promise 返回结果
*/
static async post(url: string, params: Record<string, string>): Promise<rcp.Response | BusinessError> {
try {
const response = await HTTPRequestUtil.sendRequestP(url, "POST", params);
return response;
} catch (error) {
return error;
}
}
/**
* 发送 PUT 请求(通过 Promise 返回)
* @param url: URL
* @param params: 参数
*
* @returns Promise 返回结果
*/
static async put(url: string, params: Record<string, string>): Promise<rcp.Response | BusinessError> {
try {
const response = await HTTPRequestUtil.sendRequestP(url, "PUT", params);
return response;
} catch (error) {
return error;
}
}
/**
* 发送 DELETE 请求(通过 Promise 返回)
* @param url: URL
* @param params: 参数
*
* @returns Promise 返回结果
*/
static async delete(url: string, params: Record<string, string>): Promise<rcp.Response | BusinessError> {
try {
const response = await HTTPRequestUtil.sendRequestP(url, "DELETE", params);
return response;
} catch (error) {
return error;
}
}
同样,我们可以通过一行代码进行调用:
let res = await HTTPRequestUtil.get(url, "");
LogUtil.info(`HTTPRequestUtil succeed: ${res}`);
我们还可以实现下载文件流程:
/**
* 下载文件
* @param url: URL
* @param toPath: 文件保存路径
*
* @returns 通过回调返回
*/
async downloadFile(url: string, filename: string, progressCallback: (progress: number) => void, succeedCallback: (filepath: string) => void, failedCallback:(err: BusinessError) => void) {
// Define a custom response handler
const customHttpEventsHandler: rcp.HttpEventsHandler = {
onDownloadProgress: (totalSize: number, transferredSize: number) => {
// Custom logic for handling download progress
console.info("Download progress:", transferredSize, "of", totalSize);
let proc = Math.ceil(transferredSize / totalSize * 100);
progressCallback(proc);
}
};
// Configure tracing settings
const tracingConfig: rcp.TracingConfiguration = {
verbose: true,
infoToCollect: {
textual: true,
incomingHeader: true,
outgoingHeader: true,
incomingData: true,
outgoingData: true,
incomingSslData: true,
outgoingSslData: true,
},
collectTimeInfo: true,
httpEventsHandler: customHttpEventsHandler,
};
// Use the configuration in the session creation
const session = rcp.createSession({ requestConfiguration: { tracing: tracingConfig } });
try {
// 获取保存全路径
let finalPath = FileUtil.getFilesDirPath('', filename);
let fileDir = FileUtil.getFilesDirPath('');
let fn = FileUtil.getFileName(finalPath);
let path = finalPath.replace(fn, "");
let isExists = fs.accessSync(finalPath);
if (isExists) {
fs.unlinkSync(finalPath);
} else {
if (path != fileDir + '/') {
FileUtil.mkdirSync(path, true);
}
}
let downloadToFile: rcp.DownloadToFile = {
kind: "file",
file: finalPath
} as rcp.DownloadToFile
await session.downloadToFile(url, downloadToFile);
LogUtil.info("File path: " + finalPath);
succeedCallback(finalPath);
} catch (err) {
LogUtil.error("HTTPRequestUtil download file error:" + err);
failedCallback(err);
}
}
}
这里有个关键点在于获取下载进度,这一监听在rcp.HttpEventsHandler
的onDownloadProgress
中,我们可以在这里实时返回当前进度,如果需要返回一个百分比的数值,可以使用Math.ceil
进行转换:let proc = Math.ceil(transferredSize / totalSize * 100);
。
如需取消下载,可调用session.cancel()
。
总结
这里,我们对rcp的部分功能进行了封装尝试,当然,这只是其中很少的一部分。rcp的能力正如我在前文中列举的那样很强大很全面,如果你需要在项目中用到其他能力,可以尝试按照官方文档进行封装与优化。
当然,你也可以直接使用OpenHarmony中心仓的一些优秀的开源库,如eftool就提供了rcp的良好封装。
最后,值得一提的是,官方说明中表示rcp模块有关底层能力与原生HTTP不同主要由内部开发闭源封装,所以我们无法完全拿到rcp的源码去研究,只能通过其提供的API来理解,这也是很多开发者犹豫和纠结的原因之一。
我是郑知鱼🐳,欢迎大家讨论与指教。
如果你觉得有所收获,也请点赞👍🏻收藏⭐️关注🔍我吧~~
本文正在参加华为鸿蒙有奖征文征文活动