【拥抱鸿蒙】基于rcp的封装与应用

844 阅读6分钟

题图.png

何为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的主要业务流程如下:

  1. 应用客户端创建会话。
  2. 应用客户端发起请求。
  3. 应用客户端接收请求结果,处理相应业务。

如何使用

首先,需要申请网络权限,在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网络请求或连接的基础,由此发起相关事务。 我们可以使用sessionfetch能力发起一个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.HttpEventsHandleronDownloadProgress中,我们可以在这里实时返回当前进度,如果需要返回一个百分比的数值,可以使用Math.ceil进行转换:let proc = Math.ceil(transferredSize / totalSize * 100);

如需取消下载,可调用session.cancel()

总结

这里,我们对rcp的部分功能进行了封装尝试,当然,这只是其中很少的一部分。rcp的能力正如我在前文中列举的那样很强大很全面,如果你需要在项目中用到其他能力,可以尝试按照官方文档进行封装与优化。

当然,你也可以直接使用OpenHarmony中心仓的一些优秀的开源库,如eftool就提供了rcp的良好封装。

最后,值得一提的是,官方说明中表示rcp模块有关底层能力与原生HTTP不同主要由内部开发闭源封装,所以我们无法完全拿到rcp的源码去研究,只能通过其提供的API来理解,这也是很多开发者犹豫和纠结的原因之一。

0FBF5A01-A384-4659-9389-2663BEA17216.png

我是郑知鱼🐳,欢迎大家讨论与指教。

如果你觉得有所收获,也请点赞👍🏻收藏⭐️关注🔍我吧~~

本文正在参加华为鸿蒙有奖征文征文活动