目录
3.2 TransferConfiguration:定制数据传输
3.4 SecurityConfiguration:定制安全传输行为
3.5 ProcessingConfiguration:定制处理行为
6.1 基于TracingConfiguration实现性能维测
1 概述
Remote Communication Kit提供了网络数据请求功能,相较于Network Kit中HTTP请求能力,更具易用性,且拥有更多的功能。在开发过程中,如果有些场景使用Network Kit中HTTP请求能力达不到预期或无法实现,那么就可以尝试使用Remote Communication Kit中的数据请求功能来实现。
Remote Communication Kit还提供了URPC(Unified Remote Procedure Call)高性能rpc通信库,拥有构筑远程函数调用能力,具有抗弱网传输、多径传输(5G和Wifi)等特征。应用可以通过URPC完成简单方便的远程过程调用。
1.1 基本概念
HTTP(Hypertext Transfer Protocol)是一种用于传输数据的协议,它是基于客户端-服务器模型的,客户端向服务器发出请求,服务器返回响应。
HTTP请求是客户端向服务器发送请求的过程,包括以下基本概念:
- 请求方法(Request Method):客户端向服务器发送请求的方式,如GET、POST、PUT、DELETE、OPTIONS、HEAD、PATCH等。
- URL(Uniform Resource Locator):统一资源定位符,用于指定要访问的资源的地址。
- 请求头(Request Header):包含一些附加的信息,如请求的来源、客户端的浏览器类型、语言、字符集等。
- 请求体(Request Body):包含客户端向服务器发送的数据,如表单数据等。
- HTTP版本(HTTP Version):HTTP协议的版本号,如HTTP/1.1、HTTP/2等。
- 状态码(Status Code):服务器响应请求后返回的状态码,如200表示请求成功;404表示请求的资源不存在等。
- 响应头(Response Header):包含一些附加的信息,如服务器的类型、响应的日期、内容类型等。
- 响应体(Response Body):包含服务器返回给客户端的数据,如HTML、JSON等格式的数据。
请求类型 | 说明 |
---|---|
GET | 获取资源,用于请求特定资源的表示形式。 常用场景:用于获取资源,如网页、图片、视频等。 |
POST | 提交资源,用于提交实体,通常用于提交表单数据。 常用场景:用于提交数据,如表单数据、上传文件等。 |
OPTIONS | 获取资源支持的HTTP方法,用于请求有关目标资源的通信选项。 常用场景:用于获取资源支持的HTTP方法,如获取某个API支持的请求方式。 |
HEAD | 获取资源的元数据,与GET方法类似,但不返回资源的主体部分,只返回资源的元数据,如响应头。 常用场景:用于获取资源的头部信息,如文件大小、修改时间等。 |
PUT | 更新资源,用于修改已经存在服务器上的资源。对指定URL路径上的资源进行完全替换。 常用场景:用于更新资源,如更新文件、修改数据库记录等。 |
DELETE | 删除资源,用于删除目标资源。 常用场景:用于删除资源的操作,比如删除用户、删除文章等。 |
PATCH | 更新资源的一部分,用于对资源进行局部修改。 常用场景:更新服务器局部资源,比如资源的某些属性和字段,因此不需要替换整个资源。 |
1.2 与相关Kit的关系
为了方便了解Remote Communication Kit与NetWork Kit的区别,可以从功能分类、功能名称和功能描述这三个方面进行对比,主要区别如下:
功能分类 | 功能名称 | 功能描述 | NetWork Kit | Remote Communication Kit |
---|---|---|---|---|
基础功能 | 发送PATCH类型请求 | 以PATCH的方式请求。 | 不支持 | 支持 |
基础功能 | 设置会话中URL的基地址 | 会话中URL的基地址将自动加在URL前面,除非URL是一个绝对的URL。 | 不支持 | 支持 |
基础功能 | 取消自动重定向 | HTTP请求自动重定向。 | 不支持 | 支持 |
基础功能 | 拦截请求和响应 | 在请求后或响应前进行拦截。 | 不支持 | 支持 |
基础功能 | 取消请求 | 发送请求前取消、发送请求过程中取消、请求接收后取消。 | 不支持 | 支持 |
基础功能 | 响应缓存 | 是否使用缓存,请求时优先读取缓存。缓存跟随当前进程生效,新缓存会替换旧缓存 | 不支持 | 支持 |
基础功能 | 设置响应数据的类型 | 设置数据以何种方式返回,将要响应的数据类型可设置为string、object、arraybuffer等类型。 | 支持 | 不支持 |
基础功能 | 定义允许的HTTP响应内容的最大字节数 | 服务器成功响应时,在获取数据前校验响应内容的最大字节数。 | 支持 | 不支持 |
证书验证 | 自定义证书校验 | 自定义逻辑校验客户端和服务端的证书,判断是否可以连接。 | 不支持 | 支持 |
证书验证 | 忽略SSL校验 | 在建立SSL连接时不验证服务器端的SSL证书。 | 不支持 | 支持 |
DNS | 自定义DNS解析 | 包括自定义DNS服务器或静态DNS规则 | 不支持 | 支持 |
Remote Communication Kit特有 | 捕获详细的跟踪信息 | 在会话中的HTTP请求期间捕获详细的跟踪信息。跟踪有助于调试、性能分析和深入了解通信过程中的数据流。 | 不支持 | 支持 |
Remote Communication Kit特有 | 数据打点,获取HTTP请求的具体数据 | HTTP请求各阶段的定时信息。 | 不支持 | 支持 |
1.3 申请权限
应用在使用Remote Communication Kit能力前,需要检查是否已经获取对应权限。如未获得授权,需要声明对应权限。
除取消网络请求,关闭会话,其余请求都需要权限Remote Communication kit所需权限有:
- ohos.permission.INTERNET:用于应用的权限,决定是否允许应用访问互联网。
- ohos.permission.GET_NETWORK_INFO:用于获取设备网络信息的 API 。
必须手动配置上述权限后才能使用,详细配置参见申请权限步骤。
1.4 申请权限步骤
需要在entry/src/main路径下的module.json5中配置所需申请的权限。示例代码如下所示:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.GET_NETWORK_INFO" // 如果使用PathPreference的'cellular'模式,则需要额外申请此权限
}
]
}
}
2 使用HTTP协议进行网络通信
2.1 发送网络请求(ArkTS)
2.1.1 如何使用FETCH发起网络请求
发送一个HTTP请求,也可以设置请求头和请求体等参数,并返回来自服务器的HTTP响应。使用Promise异步回调。常用于获取资源,支持流处理和通过拦截器来处理请求和响应。
1、接口说明
接口名 | 描述 |
---|---|
fetch(request: Request): Promise<Response> | 发送一个HTTP请求,并返回来自服务器的HTTP响应。使用Promise异步回调。 |
2、使用示例
1. 导入模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建Request对象。"https://www.example.com"请根据实际情况替换为想要请求的URL地址。
const kHttpServerAddress = "https://www.example.com/fetch";
const request = new rcp.Request(kHttpServerAddress, 'GET');
3. 创建会话。
const session = rcp.createSession();
4. 发起请求,并处理返回结果。
session.fetch(request).then((rep: rcp.Response) => {
console.info(`Response succeeded: ${rep}`);
}).catch((err: BusinessError) => {
// 错误处理,通过catch块,捕获error,并对error进行处理,本示例中会将错误信息展现到打印台上。
console.error(`Response err: Code is ${JSON.stringify(err.code)}, message is ${JSON.stringify(err)}`);
});
2.1.2 如何发起GET网络请求
HTTP GET请求是常用的通信方式之一。为了有效地实现这一目标,RemoteCommunicationKit采用了Promise和异步回调的组合策略,不仅可以高效地从服务器获取数据,还可以提高代码的可读性和可维护性。
1、接口说明
接口名 | 描述 |
---|---|
get(url: URLOrString, destination?: ResponseBodyDestination): Promise<Response> | 发送一个带有默认HTTP参数的HTTP GET请求,参数为开发者需要请求的地址及响应的目标,并返回来自服务器的HTTP响应。使用Promise异步回调。 |
2、使用示例
1. 导入模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建会话,会话发起get请求。
// 1、定义请求URL,此处只给出示例,还请根据实际情况将其替换为需要请求的URL
const getURL = "http://www.example.com/get";
// 2、创建session
const session = rcp.createSession();
// 3、使用session.get发起请求,以getURL为入参,使用Promise进行异步回调。
session.get(getURL).then((response) => {
console.info(`Response succeeded: ${response}`);
}).catch((err: BusinessError) => {
// 4、错误处理,通过catch块,捕获error,并对error进行处理,本示例中会将错误信息展现到打印台上。
console.error(`Response err: Code is ${JSON.stringify(err.code)}, message is ${JSON.stringify(err)}`);
});
2.1.3 如何发起POST网络请求
发送一个带有默认HTTP参数的HTTP POST请求,并返回来自服务器的HTTP响应。使用Promise异步回调。常用于向服务器提交数据。与GET请求不同,POST请求将参数包含在请求主体中,适用于创建新资源、提交表单数据或执行某些操作。
1、接口说明
接口名 | 描述 |
---|---|
post(url: URLOrString, content?: RequestContent, destination?: ResponseBodyDestination): Promise<Response> | 发送一个带有默认HTTP参数的HTTP POST请求,并返回来自服务器的HTTP响应。使用Promise异步回调。 |
2、使用示例
1. 导入模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建会话,使用会话发起post请求。
// 定义URL此处给出示例,请根据实际情况选择正确地址
const postURL = "https://www.example.com";
// 定义content,请根据实际情况选择
const postContent: rcp.RequestContent = {
fields: {
'key1': 'value1',
'key2': 'value2',
'key3': 'value3'
}
}
// 创建session
const session = rcp.createSession();
// 使用post发起请求,使用Promise进行异步回调;其中content以及destination为可选参数,可根据实际情况选择
session.post(postURL, postContent)
.then((response) => {
console.info(`Response succeeded: ${JSON.stringify(response.headers)}`);
console.info(`Response succeeded: ${JSON.stringify(response.statusCode)}`);
console.info(`Response succeeded: ${JSON.stringify(postContent)}`);
})
.catch((err: BusinessError) => {
console.error(`Response err: Code is ${JSON.stringify(err.code)}, message is ${JSON.stringify(err)}`);
})
2.1.4 如何发起PUT网络请求
在远场通信服务的框架中,当需要对服务器上存储的特定资源进行更新操作时,通常采用HTTP PUT请求。作为一种幂等操作,确保了每次请求都不会对除目标资源以外的任何状态产生影响。在实现这一功能时,我们通常利用Promise这一异步编程模型,以确保请求的执行流程能够被有效地管理和控制,它提供了一种结构化的方式来处理异步操作,无论请求成功还是失败,都能确保适当的反馈被正确地处理。
1、接口说明
接口名 | 描述 |
---|---|
put(url: URLOrString, content?: RequestContent, destination?: ResponseBodyDestination): Promise<Response> | 发送一个带有默认HTTP参数的HTTP PUT请求,并返回来自服务器的HTTP响应。使用Promise异步回调。 |
2、使用示例
1. 导入模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建会话,会话发起put请求。
// 创建会话
const session = rcp.createSession();
// 定义content,请根据实际情况选择
const postContent: rcp.RequestContent = {
fields: {
'key1': 'value1',
'key2': 'value2',
'key3': 'value3'
}
}
// 会话发起PUT请求,"http://www.example.com"请根据实际情况替换为想要请求的URL地址。
session.put("http://www.example.com/put", postContent).then((response) => {
// 对响应的处理,此处为示例,只做打印处理
console.info(`Response succeeded: ${response}`);
}).catch((err: BusinessError) => {
// 请求错误处理
console.error(`Response err: Code is ${JSON.stringify(err.code)}, message is ${JSON.stringify(err)}`);
});
2.1.5 如何发起HEAD网络请求
涉及到对服务器资源的高效访问时,HTTP HEAD请求是一种非常有用的操作。它与GET请求类似,但主要的区别在于,HEAD请求只返回响应头,而不返回实体内容,这使得其在获取资源的元信息,如文件大小、修改日期等,以及检查资源是否已更改等方面更加有效。
1、接口说明
接口名 | 描述 |
---|---|
head(url: URLOrString): Promise<Response> | 发送一个带有默认HTTP参数的HTTP HEAD请求,并返回来自服务器的HTTP响应。使用Promise异步回调。 |
2、使用示例
1. 导入模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建会话,会话发起head请求。
// 1、创建会话
const session = rcp.createSession();
// 2、会话发起HEAD请求,"http://www.example.com"请根据实际情况替换为想要请求的URL地址。
session.head("http://www.example.com/head").then((response) => {
// 3、对响应的处理,此处为示例,只做打印处理
console.info(`Response succeeded: ${response}`);
}).catch((err: BusinessError) => {
// 4、请求错误处理
console.error(`Response err: Code is ${JSON.stringify(err.code)}, message is ${JSON.stringify(err)}`);
});
2.1.6 如何发起DELETE网络请求
在远场通信服务的框架中,Remote Communication Kit采用了一种结合发起 HTTP DELETE 请求与 Promise 异步处理的方法。具体操作如下:通过向预定义的 URL 发送一个包含默认 HTTP 参数的 HTTP DELETE 请求,即可实现对目标 URL 上相关资源的有效删除。这种机制不仅简化了请求的发送过程,还增强了异步处理的效率,确保了资源管理的灵活性和响应速度。
1、接口说明
接口名 | 描述 |
---|---|
delete(url: URLOrString): Promise<Response> | 发送一个带有默认HTTP参数的HTTP DELETE请求,并返回来自服务器的HTTP响应。使用Promise异步回调。 |
2、使用示例
1. 导入模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建会话,会话发起delete请求。"http://www.example.com"请根据实际情况替换为想要请求的URL地址。
const session = rcp.createSession();
session.delete("http://www.example.com/delete").then((response) => {
console.info(`Response succeeded: ${response}`);
}).catch((err: BusinessError) => {
console.error(`Response err: Code is ${JSON.stringify(err.code)}, message is ${JSON.stringify(err)}`);
});
2.2 取消网络请求(ArkTS)
在远场通信服务的框架中,没有明确指定任何 request 的情况下,通过调用 session.cancel,可以取消所有正在进行的网络请求。根据具体需求,灵活地管理和控制网络请求的执行。
2.2.1 接口说明
接口名 | 描述 |
---|---|
|
2.2.2 使用示例
1. 导入模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建会话并创建两个Request,分别发起请求,在请求完成后进行cancel操作。
- 单独取消某个请求
// 创建会话
const session = rcp.createSession();
// 创建request1、request2
let request1 = new rcp.Request("https://www.example.com");
let request2 = new rcp.Request("https://www.example.com");
// 分别发起请求
session.fetch(request1).then((response: rcp.Response) => {
console.info(`The response1 code is ${response.statusCode}`);
}).catch((err: BusinessError) => {
console.error(`Request1 error code is ${JSON.stringify(err.code)}, error message is ${JSON.stringify(err)}`);
})
session.fetch(request2).then((response: rcp.Response) => {
console.info(`The response2 code is ${response.statusCode}`);
}).catch((err: BusinessError) => {
console.error(`Request2 error code is ${JSON.stringify(err.code)}, error message is ${JSON.stringify(err)}`);
})
// 单独取消Request1、request2
session.cancel(request1);
session.cancel(request2);
- 取消全部请求
// 创建会话
const session = rcp.createSession();
// 创建request1、request2
let request1 = new rcp.Request("https://www.example.com");
let request2 = new rcp.Request("https://www.example.com");
// 分别发起请求
session.fetch(request1).then((response: rcp.Response) => {
console.info(`The response1 code is ${response.statusCode}`);
}).catch((err: BusinessError) => {
console.error(`Request1 error code is ${JSON.stringify(err.code)}, error message is ${JSON.stringify(err)}`);
})
session.fetch(request2).then((response: rcp.Response) => {
console.info(`The response2 code is ${response.statusCode}`);
}).catch((err: BusinessError) => {
console.error(`Request2 error code is ${JSON.stringify(err.code)}, error message is ${JSON.stringify(err)}`);
})
// 取消全部request
session.cancel();
2.3 关闭会话(ArkTS)
当一个HTTP请求完成,即数据已经成功发送并收到确认,或者在某些情况下,由于超时或其他错误原因,通信尝试失败,此时应立即调用相应的“关闭会话”或“释放资源”方法。这一操作的主要目的是:
- 释放资源:在通信过程中,系统会分配各种资源,包括内存、网络带宽、处理器时间等,以支持数据的发送和接收。一旦通信结束,这些资源应被及时释放。
- 清理状态:关闭会话还涉及清理与特定会话相关的所有内部状态信息,如缓存、连接状态标志等。这有助于保持系统的清晰性和可预测性,避免潜在的资源泄漏或状态冲突。
- 优化性能:及时释放资源有助于提高系统的整体性能。例如,通过快速释放网络带宽,可以减少延迟,提高后续通信的效率。
- 错误恢复:在遇到通信错误时,正确的关闭会话操作可以帮助系统更快地从错误状态中恢复,避免资源锁定或死锁情况的发生。
在请求结束后,及时关闭会话并释放相关资源是保持系统健康和高效运行的关键步骤。这不仅有助于优化资源利用,还能提高系统的稳定性和可靠性。
2.3.1 接口说明
接口名 | 描述 |
---|---|
close(): void | 关闭会话。返回为空。 |
2.3.2 使用示例
1. 导入模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建会话,会话发起请求后关闭会话。
// 1、创建会话
const session = rcp.createSession();
// 2、创建Request,"http://www.example.com"请根据实际情况替换为想要请求的URL地址。
let req = new rcp.Request("http://www.example.com/fetch", "GET");
// 3、利用fetch发起网络请求
session.fetch(req).then((response) => {
// 4、对响应的处理,此处为示例,只做打印处理
console.info(`Response succeeded: ${response}`);
}).catch((err: BusinessError) => {
// 5、请求错误处理
console.error(`Response err: Code is ${JSON.stringify(err.code)}, message is ${JSON.stringify(err)}`);
});
session.close();
3 实现HTTP请求定制
3.1 DnsConfiguration:定制DNS
3.1.1 场景介绍
在远程通信服务框架中,Remote Communication Kit提供了一套高度可定制的 DNS(Domain Name System)请求规则服务。功能如下:
- 根据自身需求调整 DNS 查询行为
- 支持用户自定义 DNS 服务器设置
- 根据具体的网络环境或安全需求,选择最适合的 DNS 服务器进行配置。
- 不仅能够实现网络管理的高效性,还能增强网络的安全性,确保数据传输的稳定与安全。
DnsConfiguration中可设置dnsRules、dnsOverHttps。
- dnsRules(配置DNS规则)
- 自定义DNS服务器(DnsServers):可指定自定义的DNS服务器提供解析服务。
- 自定义静态DNS(StaticDnsRules):当默认的DNS不能正常解析部分域名,就需要手动添加静态DNS。添加静态DNS后,如果hostname匹配,则优先使用指定的地址。
- 自定义动态DNS(DynamicDnsRule):除了添加静态DNS外,还可以添加动态DNS。动态DNS可看作一个可以根据hostname和port直接返回IP地址的函数,如果设置,则优先使用函数中返回的地址。
- dnsOverHttps
DNS over HTTPS配置(DnsOverHttpsConfiguration):配置HTTPS上的DNS(DOH)设置,以加密的HTTPS协议进行DNS解析请求,避免原始DNS协议中用户的DNS解析请求被窃听或者修改的问题,实现保护用户隐私的目的。如果设置,则优先使用DNS服务器解析的地址。
3.1.2 使用示例
下面以定制DNS服务器、重写DNS解析函数两个示例来说明如何进行DNS的定制,从而获取最佳的DNS性能体验。
1、定制DNS服务器
1. 导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建一个新的会话对象和请求,会话用于管理后续的网络请求。同时需要定义一个具体的请求,请求中指定了需要请求的URL。
const session = rcp.createSession();
// 其中的URL需要根据自身业务需要调整
const request = new rcp.Request('https://example.com');
3. 设置请求的DNS规则,可根据自身业务的需要对DNS的ip和port进行设置。
request.configuration = {
dns: {
dnsRules: [
{
ip: 'x.xxx.x.xx', // DNS服务器的IP地址
port: 53, // DNS服务器的端口号
},
]
}
};
4. 利用fetch发起网络请求并在成功或失败时进行响应的处理,此处只给出示例,对成功或失败的处理请根据实际业务来实现。
session.fetch(request).then((response: rcp.Response) => {
console.info(`The response is ${JSON.stringify(response)}`); // 处理成功响应
}).catch((err: BusinessError) => {
console.info(`The error is ${JSON.stringify(err)}`); // 处理错误
})
// 关闭会话
session.close();
2、定制DNS解析函数
1. 导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建一个新的会话对象和请求,会话用于管理后续的网络请求。同时需要定义一个具体的请求,请求中指定了需要请求的URL。
const session = rcp.createSession();
// 定义请求的URL(请根据实际需求调整)
const requestURL = 'https://example.com';
const request = new rcp.Request(requestURL);
3. 设置DNS解析规则,此处请根据自身业务进行逻辑处理,本例只给出简单示例。
request.configuration = {
dns: {
dnsRules: (host: string, port: number): IpAddress[] => {
if (host === 'example.com') {
return ['x.xxx.x.xx', 'x.xxx.x.xx']; // 此处请根据实际情况填写
}
return [];
}
}
};
4. 利用fetch发起网络请求并在成功或失败时进行响应的处理,此处只给出示例,对成功或失败的处理请根据实际业务来实现。
session.fetch(request).then((response: rcp.Response) => {
// 处理成功响应
console.info(`The response is ${JSON.stringify(response)}`);
}).catch((err: BusinessError) => {
// 处理错误
console.info(`The error is ${JSON.stringify(err)}`);
})
// 关闭会话
session.close();
3.2 TransferConfiguration:定制数据传输
在远场通信框架中,开发者们利用 TransferConfiguration,可以对 HTTP请求期间的数据传输行为进行精细化管理和定制化调整。 TransferConfiguration提供了自动重定向策略、超时时间设定以及链接层 HTTP/3支持等关键功能的配置选项。通过理解和灵活运用这些属性,开发者可以根据项目需求,实现数据传输策略的个性化定制,从而获得更高效、更可靠的数据传输体验。
3.2.1 使用示例
下面会介绍超时重试场景下TransferConfiguration如何去使用。
1、超时重试
1. 导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 定义会话配置,创建会话,以及定义日志函数。
// 定义日志函数
const logI = console.info
const logE = console.error
// 定义会话配置
const sessionConfig: rcp.SessionConfiguration = {
requestConfiguration: {
transfer: {
timeout: {
connectMs: 3000,
transferMs: 6000
}
}
}
};
// 创建会话
const session = rcp.createSession(sessionConfig);
3. 定义异步函数,利用递归实现重试,如果请求失败,会在指定的重试次数内进行重试,在最后一次重试时,会等待3秒后再发送请求(根据实际情况进行调整)。
async function retryRequest(url: string, retryCount: number, attempt: number): Promise<rcp.Response | undefined> {
return new Promise((resolve, reject) => {
// 在最后一次重试时,等待一段时间后再发送请求
const delay = attempt === retryCount - 1 ? 3000 : 0; // 如果是最后一次重试,延迟3秒,否则不延迟
setTimeout(() => {
session.get(url)
.then(response => {
if (response.statusCode === 200) {
logI(`Request successful on attempt ${attempt}.`); // 记录请求成功信息
resolve(response); // 请求成功,Promise resolve
} else {
logE(`Request failed on attempt ${attempt}, statusCode: ${response.statusCode}`); // 记录请求失败信息
if (attempt < retryCount) { // 如果还未达到重试次数
retryRequest(url, retryCount, attempt + 1); // 进行下一次重试
} else { // 如果已经达到重试次数
logE(`All retries failed.`); // 记录所有重试失败信息
reject(new Error('All retries failed')); // 所有重试失败,Promise reject
}
}
})
.catch((err: BusinessError) => { // 请求的catch块,处理请求过程中抛出的错误
logE(`Request error on attempt ${attempt}, error: ${JSON.stringify(err)}`); // 记录请求错误信息
if (attempt < retryCount) { // 如果还未达到重试次数
retryRequest(url, retryCount, attempt + 1); // 进行下一次重试
} else { // 如果已经达到重试次数
logE(`All retries failed.`); // 记录所有重试失败信息
reject(new Error('All retries failed')); // 所有重试失败,Promise reject
}
});
}, delay); // 延迟指定时间后再发送请求
});
}
4. 调用retryRequest方法,实现网络请求的重试逻辑
// 定义URL
const URL = 'https://www.example.com'
// 定义重试次数,值为3
const reTryCount = 3
// 定义当前尝试次数,初始值为1
const attempt = 1
// 调用retryRequest函数进行网络请求,参数为URL、重试次数和当前尝试次数,将返回的结果存储在response变量中
const response = retryRequest(URL, reTryCount, attempt);
// 使用then方法处理response的成功返回情况
response.then((res) => {
// 如果返回的状态码不是200,表示请求未成功
if (res?.statusCode != 200) {
// 打印日志,表示超时重试失败
logI(`Timeout retry failed`);
return;
}
// 打印日志,如果返回的状态码是200,表示请求成功
logI(`Timeout retry succeeded, print result: ${res}`);
}).catch((err: BusinessError) => {
// 打印日志,表示响应出错,并打印错误信息
logE(`Response error, the error message is: ${JSON.stringify(err)}`);
})
3.3 ProxyConfiguration:定制代理
3.3.1 场景介绍
在远场通信框架中,ProxyConfiguration配置会话代理设置,提供system、no-proxy和WebProxy三种选项。
- system使用系统代理,适合快速部署;
- no-proxy不使用代理,适用于需直接访问特定资源或有代理限制的环境;
- WebProxy允许开发者自定义代理设置,解决特定网络问题,优化代理路径,提升性能和用户体验。
3.3.2 使用示例
下面将对框架提供的三种选项('system','no-proxy','WebProxy')以示例代码的方式进行说明。
1、'no-proxy'
1. 导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建一个新的会话对象和请求,会话用于管理后续的网络请求。
const session = rcp.createSession();
// 定义请求的URL(请根据实际需求调整)
const requestURL = 'https://example.com';
3. 定义发起请求时需要的代理配置,在定义proxyConfiguration时选择'no-proxy'即可将代理方式选择为不使用代理方式。
// 配置请求的proxy方式为'no-proxy'
const configuration: rcp.Configuration = {
proxy: 'no-proxy'
}
// 定义request并将请求configuration添加到request中
const request = new rcp.Request(requestURL, "GET");
request.configuration = configuration;
4. 利用fetch发起网络请求并在成功或失败时进行响应的处理,此处只给出示例,对成功或失败的处理请根据实际业务来实现。
session.fetch(request).then((response: rcp.Response) => {
// 处理请求成功响应
console.info(`Response success, ${response}`);
// 关闭会话
session.close();
}).catch((err: BusinessError) => {
// 处理请求失败响应
console.error(`The error code is ${err.code}, error message is ${JSON.stringify(err)}`);
// 关闭会话
session.close();
})
2、'system'
1. 导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建一个新的会话对象和请求,会话用于管理后续的网络请求。
const session = rcp.createSession();
// 定义请求的URL(请根据实际需求调整)
const requestURL = 'https://example.com';
3. 定义发起请求时需要的代理配置,在定义proxyConfiguration时选择'system'即可将代理方式选择为使用系统代理方式。
// 配置请求的proxy方式为'system'
const configuration: rcp.Configuration = {
proxy: 'system'
}
// 定义request并将请求configuration添加到request中
const request = new rcp.Request(requestURL, "GET");
request.configuration = configuration;
4. 利用fetch发起网络请求并在成功或失败时进行响应的处理,此处只给出示例,对成功或失败的处理请根据实际业务来实现。
session.fetch(request).then((response: rcp.Response) => {
// 处理请求成功响应
console.info(`Response success, ${response}`);
// 关闭会话
session.close();
}).catch((err: BusinessError) => {
// 处理请求失败响应
console.error(`The error code is ${err.code}, error message is ${JSON.stringify(err)}`);
// 关闭会话
session.close();
})
3、webProxy(自定义代理设置)
1. 导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建一个新的会话对象和请求,会话用于管理后续的网络请求。
const session = rcp.createSession();
// 定义请求的URL(请根据实际需求调整)
const requestURL = 'https://example.com';
3. 通过webProxy自定义代理配置,通过声明式方式。
// 自定义proxy
const configuration: rcp.Configuration = {
proxy: {
url: 'https://www.example.com',
createTunnel: 'always',
exclusions: [
'https://www.example1.com',
'https://www.example2.com'
]
}
}
// 定义request并将请求configuration添加到request中
const request = new rcp.Request(requestURL, "GET");
request.configuration = configuration;
3.4 SecurityConfiguration:定制安全传输行为
3.4.1 场景介绍
在软件开发中,安全性是一个非常重要的方面。 Remote Communication Kit 提供的SecurityConfiguration是一个用于定制安全传输行为的工具,可以帮助更好地保护其开发的应用程序。通过合理的配置和使用,可以大大降低应用程序受到攻击的风险。
3.4.2 使用示例
1、自定义证书校验
Remote Communication Kit会将服务器证书传递给调用方,调用方可以根据自身业务需要,对证书进行校验。例如:证书过期,但客户端不关注日期,此时可以只校验证书的内容,不校验日期。
1. 导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 自定义证书逻辑,模拟校验证书的版本是否符合预期。
// 自定义证书校验器
const selfDefinedRemoteValidation = (context: rcp.ValidationContext) => {
// 判断context是否为空或者undefined
if (context === null) {
return false;
}
// 循环遍历context中x509证书并最后返回证书版本是否符合预期
for (let i = 0; i <= context.x509Certs.length; i++) {
let cert = context.x509Certs[i];
if (cert === null) {
console.info(`Cert error: ${cert}}`);
continue;
}
console.info(`Cert success: ${cert}}`);
// 注意,此处只是模拟证书版本,实际请根据业务进行判断
return cert.getVersion() === 0
}
return true;
}
3. 将定义好的自定义证书校验器配置到configuration中,并利用fetch发起请求。
const request = new rcp.Request('https://www.example.com');
// 将证书校验器配置到session中并选择TLS版本
const session =
rcp.createSession({
requestConfiguration: {
security: { remoteValidation: selfDefinedRemoteValidation, tlsOptions: { tlsVersion: 'TlsV1.3' } }
}
});
session.fetch(request).then((response: rcp.Response) => {
console.info(`Http response: ${response}}`);
}).catch((err: BusinessError) => {
console.error(`Response error: ${JSON.stringify(err)}`)
})
2、双向证书校验
是一种加密通信机制,用于在客户端和服务器之间建立可信的连接。通过交换证书和公钥来验证双方的身份,并确保数据的完整性和隐私。
1. 导入需要的模块
import { rcp } from '@kit.RemoteCommunicationKit'
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
2. 定义readSyn函数,用于异步读取文件中的证书内容。首先定义文件路径,然后打开文件并读取文件内容。
let readSyn = async () => {
// 定义文件路径,请根据业务自行定义
let pathDir = "/path/files/";
// 定义文件路径,请根据业务自行定义
let filePath = pathDir + "/client.crt";
// 使用 fileIo.open 方法打开文件,返回一个 Promise
fileIo.open(filePath, fileIo.OpenMode.READ_WRITE).then((file: fileIo.File) => {
// 打印成功打开的文件描述符
console.info(`readxx the file is: ${file.fd}`);
// 创建一个 4096 字节的缓冲区用于存储读取的数据
let buf = new ArrayBuffer(4096);
// 使用 fileIo.read 方法读取文件内容,返回一个 Promise
fileIo.read(file.fd, buf, (err: BusinessError, readLen: number) => {
// 如果读取过程中发生错误
if (err) {
// 打印错误信息
console.error(`readxx failed with error message: Code is ${err.code}, message is ${err.message}`);
} else {
// 如果读取成功
// 打印成功信息
console.info(`readxx file data succeed`);
// 打印读取的数据
console.info(`readxx: ${String.fromCharCode(...new Uint8Array(buf.slice(0, readLen)))}`);
// 将读取的数据转换为字符串
let content = String.fromCharCode(...new Uint8Array(buf.slice(0, readLen)));
// 打印证书内容
console.info(`this.cert.data: ${content}`);
// 同步关闭文件
fileIo.closeSync(file);
}
});
}).catch((err: BusinessError) => {
// 如果打开文件过程中发生错误
// 打印错误信息
console.error(`readxx Open failed errInfo: Code is ${err.code}, message is ${err.message}`);
})
}
3. 定义getCertSyn函数,用于异步使用读取到的证书内容进行HTTPS请求的验证。使用 rcp.createSession 创建一个网络请求会话,然后创建一个GET请求,并设置请求的证书配置,包括证书内容、类型、密钥和密钥密码。最后,使用 session.fetch发送请求,并处理响应或错误。
let getCertSyn = async (content: string) => {
// 定义服务器地址:
let kHttpServerAddress = "https://www.example.com/fetch";
// 创建会话并发送请求:
try {
const session = rcp.createSession();
const request = new rcp.Request(kHttpServerAddress, "GET");
request.configuration = {
security: {
certificate: {
content: content,
type: "PEM",
key: " ",
keyPassword: " "
}
}
}
const response = await session.fetch(request);
console.info(`${kHttpServerAddress} certificate verification succeeded, message is ${response}`);
} catch (err) {
console.error(`${kHttpServerAddress} certificate verification failed: Code is ${err.code}, message is ${err.message}`);
}
}
3.5 ProcessingConfiguration:定制处理行为
3.5.1 场景介绍
ProcessingConfiguration 是 Remote Communication Kit 中用于定制响应处理行为的一个重要组件。它允许你在消息被分发到不同的处理器之前或之后执行一些自定义的逻辑。场景如检验响应状态是否为成功即响应码是否为200。
3.5.2 使用示例
1. 导入需要的模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建会话、请求以及定义相关处理配置。
const session = rcp.createSession();
const request = new rcp.Request("https://www.example.com");
// 定义处理配置,用于验证响应状态码是否为200
const processing: rcp.ProcessingConfiguration = {
validateResponse: (response: rcp.Response): boolean => {
return response.statusCode === 200;
},
};
// 将处理配置应用到请求中
request.configuration = {
processing: processing,
};
3. 发送请求并等待响应,使用 then 方法处理成功的响应,使用 catch 方法处理可能出现的错误。
- 处理成功的响应:
- if (response):检查是否收到响应。
- const isValidStatusCode = processing.validateResponse:调用响应验证回调函数。
- if (isValidStatusCode):检查响应状态码是否为 200,并打印相应的信息。
- else:如果响应状态码不是 200,打印验证失败的响应状态码。
- else:如果没有收到响应,打印没有响应的信息。
- session.close():关闭会话。
- 处理错误:
- catch((err: BusinessError) => {...}):捕获可能出现的错误。
- console.error(The error is: ${JSON.stringify(err)}):打印错误信息。
- session.close():关闭会话。
session.fetch(request).then((response: rcp.Response) => {
if (response) {
const isValidStatusCode = processing.validateResponse;
if (isValidStatusCode) {
console.info(`Response received with status code 200:, ${response.statusCode}`);
} else {
console.error(`Validation failed with status code:, ${response.statusCode}`);
}
} else {
console.error('No response received');
}
session.close();
}).catch((err: BusinessError) => {
console.error(`The error is: ${JSON.stringify(err)}`);
session.close();
});
3.6 拦截器:更丰富、更高阶的定制能力
使用拦截器可以方便的对HTTP的请求与响应进行修改,您可以创建拦截器链,按需定制一组拦截器对您的网络请求/响应进行修改。Remote Communication Kit模块提供了拦截器能力,在SessionConfiguration中添加Interceptor参数,传入自定义的拦截器,即可在HTTP请求和响应的过程中添加拦截器功能。
3.6.1 拦截器工作原理
客户端发送HTTP请求,到达目标服务器之前,可以使用拦截器对HTTP的请求进行修改。如下图,定义了链式拦截器,RequestUrlChangeInterceptor拦截器(下文以拦截器1代替)和ResponseHeaderRemoveInterceptor拦截器(下文以拦截器2代替)。拦截器1会将请求先拦截,该拦截器可以实现当网络质量差时,通过修改HTTP请求中的URL,来调整请求资源的大小。然后经过拦截器2,最后到达Internet。当请求到达目标服务器,服务器返回请求响应的结果给客户端之前,可以使用拦截器对HTTP的响应进行修改。响应先被拦截器2拦截,在响应返回给应用前检查和修改服务器的请求头。然后经过拦截器1,最后客户端接收响应结果。
说明
RequestUrlChangeInterceptor拦截器和ResponseHeaderRemoveInterceptor拦截器都是自定义拦截器,需要开发者通过代码去实现内部逻辑。
3.6.2 拦截器的定义和使用
下面将介绍如何自定义拦截器,定义RequestUrlChangeInterceptor拦截器和ResponseHeaderRemoveInterceptor拦截器实现Interceptors,可在intercept方法中根据业务需求自定义处理逻辑,实现对请求/响应的修改。以下示例模拟网络质量不佳的情况。
1. 导入需要的模块,示例中包含了利用远场通信框架发起网络请求以及请求后的响应和错误处理,所以需导入以下模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { url } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
2. 定义两种拦截器,RequestUrlChangeInterceptor拦截器中,当网络质量较差的时候,修改请求中的URL路径,请求获取分辨率较小的图片,可提升用户体验;NetworkQualityProvider中的isNetWorkFast用于在示例代码中模拟网络质量的好坏
// 模拟网络质量不佳的情况
export class NetworkQualityProvider {
isNetworkFast: boolean = true
public constructor(isNetworkFast: boolean) {
this.isNetworkFast = isNetworkFast
}
}
// 定义RequestUrlChangeInterceptor拦截器
export class RequestUrlChangeInterceptor implements rcp.Interceptor {
private readonly networkQualityProvider: NetworkQualityProvider;
constructor(networkQualityProvider: NetworkQualityProvider) {
this.networkQualityProvider = networkQualityProvider;
}
// 自定义请求处理逻辑
async intercept(context: rcp.RequestContext, next: rcp.RequestHandler): Promise<rcp.Response> {
if (context.request.method === 'GET' && !this.networkQualityProvider.isNetworkFast) {
console.info('[RequestUrlChangeInterceptor]: Slow network is detected');
const parts = context.request.url.pathname.split('.');
if (parts.length === 2) {
const changed = url.URL.parseURL(context.request.url.href);
changed.pathname = parts[0] + '_small.' + parts[1];
console.info(`[RequestUrlChangeInterceptor]: Replace URL from "${context.request.url.href}" to "${changed}"`);
AppStorage.setOrCreate('ReplacedInfo',`[RequestUrlChangeInterceptor]: Replace URL from "${context.request.url.href}" to "${changed}"`);
context.request.url = changed;
}
} else {
console.info('[RequestUrlChangeInterceptor]: Network is fast');
}
return next.handle(context);
}
}
// 定义ResponseHeaderRemoveInterceptor拦截器
export class ResponseHeaderRemoveInterceptor implements rcp.Interceptor {
// 自定义响应处理逻辑
async intercept(context: rcp.RequestContext, next: rcp.RequestHandler): Promise<rcp.Response> {
const response = await next.handle(context);
const toReturn: rcp.Response = {
request: response.request,
statusCode: response.statusCode,
httpVersion: response.httpVersion,
headers: {
'content-range': response.headers['content-range']
},
effectiveUrl: response.effectiveUrl,
timeInfo: response.timeInfo,
toJSON: () => null
};
console.info('[ResponseHeaderRemoveInterceptor]: Response was modified');
return toReturn;
}
}
3. 使用拦截器,通过Remote Communication Kit模块中的SessionConfiguration对象来设置interceptors,即可在请求/响应中添加拦截器。
function httpRequest(networkStateSimulator: NetworkQualityProvider) {
const sessionConfig: rcp.SessionConfiguration = {
interceptors: [
new RequestUrlChangeInterceptor(networkStateSimulator),
new ResponseHeaderRemoveInterceptor()
],
requestConfiguration:{
security:{
tlsOptions: {
tlsVersion: 'TlsV1.3'
}
}
}
};
const session = rcp.createSession(sessionConfig);
}
4 文件上传下载
Remote Communication Kit 结合 Core File Kit 可以实现基于文件、目录、对象的快速上传和下载功能。 Remote Communication Kit 提供了远程通信的功能,包括远程连接、数据传输等 API 接口。而 Core File Kit 则提供了文件管理的功能,包括文件读取、写入等 API 接口。结合起来使用,可以通过 Remote Communication Kit 实现文件、目录、对象的远程传输,并且由于 Core File Kit 的高效文件处理能力,可以实现快速的上传和下载功能。
4.1 下载功能实现
使用实例
1. 导入需要的模块,示例中除去发起请求以及响应错误处理,还需用到Core File Kit中的fileIo,所以需导入以下模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
2. 定义下载路径,并创建相关配置。
const DOWNLOAD_TO_PATH = getContext().filesDir
// 创建了一个安全配置对象,其中remoteValidation设置为'skip',表示将跳过远程验证。
const securityConfig: rcp.SecurityConfiguration = {
remoteValidation: 'skip'
}
// 创建了一个下载配置对象,其中kind设置为'folder',表示下载的目标是文件夹,path设置为之前定义的DOWNLOAD_TO_PATH。
let downloadToFile: rcp.DownloadToFile = {
kind: 'folder',
path: DOWNLOAD_TO_PATH
}
// 创建一个HTTP会话,其中请求配置包括传输超时设置和安全配置(配置可自定义)
const session = rcp.createSession({
requestConfiguration: {
transfer: { timeout: { connectMs: 6000, transferMs: 6000, inactivityMs: 6000 } },
security: securityConfig
}
})
3. 检查目标路径是否存在,如果存在,则先删除该路径,以确保下载的文件不会覆盖已存在的文件;最后发起请求,使用创建的会话执行下载操作,将https://example.com.png这个URL的内容下载到指定的本地路径。如果下载成功,会输出成功信息;如果失败,会输出错误信息。
// 检查目标路径是否存在
if (fileIo.accessSync(DOWNLOAD_TO_PATH)) {
fileIo.unlinkSync(DOWNLOAD_TO_PATH);
}
// 发起请求,执行下载操作,这里的https://example.com.png网址为示例图片网址,模拟下载图片场景
session.downloadToFile('https://example.com.png', downloadToFile)
.then((response: rcp.Response) => {
console.info(`Succeeded in getting the response ${response}`)
}).catch((err: BusinessError) => {
console.error(`Failed, the error message is ${JSON.stringify(err)}`)
})
4.2 上传功能实现
使用实例
1. 导入需要的模块,示例中除去发起请求以及响应错误处理,还需用到CoreFileKit中的fileIo,需导入以下模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
2. 定义Session配置,定义一个名为SESSION_CONFIG的对象,用于配置请求会话。配置包括传输超时和安全设置,如远程验证和TLS版本。
let SESSION_CONFIG: rcp.SessionConfiguration = {
requestConfiguration: {
transfer: {
timeout: {
connectMs: 6000
}
},
security: {
remoteValidation: 'skip',
tlsOptions: {
tlsVersion: 'TlsV1.3'
}
}
}
}
3. 定义FdReadFile类,用于读取文件描述符(File Descriptor)指向的文件。read方法异步读取指定的ArrayBuffer缓冲区,并返回实际读取的字节数。
class FdReadFile {
readonly fd: number;
constructor(fd: number) {
this.fd = fd;
}
async read(buffer: ArrayBuffer): Promise<number> {
return fileIo.read(this.fd, buffer);
}
}
4. 创建会话并打开文件,以只读模式打开一个文件。fileIo.openSync方法返回一个文件描述符,如果打开失败,程序会打印错误信息并返回。
const session = rcp.createSession(SESSION_CONFIG);
const file = fileIo.openSync('/data/storage/el1/bundle/entry_test/resources/resfile/upload_file.txt',
fileIo.OpenMode.READ_ONLY);
if (!file) {
console.error('fileIo.openSync failed');
return;
}
5. 读取文件,创建一个FdReadFile实例,并分配一个缓冲区来读取文件。read方法异步执行,等待文件读取完成。
const fdReadFile = new FdReadFile(file.fd);
const buffer = new ArrayBuffer(1024 * 1024); // 假设文件大小为1MB
await fdReadFile.read(buffer);
6. 上传文件,使用会话的uploadFromFile方法将文件上传到指定的URL。UploadFromFile构造函数接受一个文件描述符读取文件。上传成功或失败时,会分别打印相应的信息。
await session.uploadFromFile('https://httpbin.org/anything', new rcp.UploadFromFile(fdReadFile))
.then((response: rcp.Response) => {
console.info(`Upload succeeded: ${response}`)
})
.catch((err: BusinessError) => {
console.error(`Upload failed: ${JSON.stringify(err)}`)
});
7. 关闭文件和会话,释放资源。
fileIo.closeSync(file.fd);
session.close();
4.3 请求暂停、恢复
使用实例
1. 导入需要的模块。
import { BusinessError } from '@kit.BasicServicesKit';
import { rcp } from '@kit.RemoteCommunicationKit';
import { util } from '@kit.ArkTS';
2. 定义调试信息接口、调试信息源类型以及调试信息序列化函数,用于将调试信息序列化为StringifiedDebugInfo数组。函数首先根据infoSource的类型获取调试信息,然后使用TextDecoder将调试信息的data字段解码为字符串,并返回一个包含解码后的调试信息的数组。
const HTTP_SERVER_POST: string = "https://example.org/anything";
// 定义调试信息接口
interface StringifiedDebugInfo {
type: rcp.DebugEvent;
data: string;
};
// 定义调试信息源类型
type DebugInfoSource = undefined | rcp.DebugInfo[] | rcp.Response;
// 定义调试信息序列化函数
function debugInfoStringify(infoSource: DebugInfoSource): StringifiedDebugInfo[] {
const debugInfo = Array.isArray(infoSource)
? (infoSource as rcp.DebugInfo[])
: (infoSource as rcp.Response).debugInfo;
if (!debugInfo) {
return [];
}
const decoder = util.TextDecoder.create('utf-8');
return debugInfo.map((i: rcp.DebugInfo): StringifiedDebugInfo => {
return {
type: i.type,
data: decoder.decodeWithStream(new Uint8Array(i.data)).trim(),
};
});
}
3. 获取发送暂停和恢复事件,用于从调试信息中筛选出发送暂停和恢复事件。
function getSendPausedEvents(debugInfo: DebugInfoSource) { return debugInfoStringify(debugInfo).filter((i) => i.data.startsWith('[[RCP]]: Pause sending')); }
function getSendResumedEvents(debugInfo: DebugInfoSource) { return debugInfoStringify(debugInfo).filter((i) => i.data.startsWith('[[RCP]]: Resume sending')); }
4. 编写发起请求的函数。
const SendingPauseByTimeout = async (done: Function): Promise<void> => {
const session = rcp.createSession();
const request = new rcp.Request(HTTP_SERVER_POST);
// 定义发送暂停策略,kind为'timeout',timeoutMs为1ms
const sendPolicy: rcp.SendingPausePolicy = {
kind: 'timeout',
timeoutMs: 1,
};
// 定义暂停策略,sending字段引用了上述定义的发送暂停策略
const pausePolicy: rcp.PausePolicy = {
sending: sendPolicy,
};
// 设置请求的配置,包括传输策略和跟踪信息
request.configuration = {
transfer: {
pausePolicy: pausePolicy,
},
tracing: {
infoToCollect: {
textual: true,
},
},
};
// 定义请求体数据
const data = 'TestData';
// 设置请求头,'Content-Length'字段表示请求体的长度
request.headers = {
'Content-Length': data.length.toString(),
};
// 初始化一个标志位,用于控制请求体的生成
let read = false;
// 设置请求方法为POST
request.method = 'POST';
// 定义请求体内容生成函数,如果read为true,则返回空的ArrayBuffer,否则生成包含请求体数据的ArrayBuffer
request.content = (maxSize) => {
if (read) {
return new ArrayBuffer(0);
}
read = true;
const buffer = new ArrayBuffer(data.length);
util.TextEncoder.create('utf-8').encodeIntoUint8Array(data, new Uint8Array(buffer));
return buffer;
};
// 发送请求并等待响应
const response = await session.fetch(request)
// 从响应的调试信息中获取发送暂停和恢复事件
const pausedEvents = getSendPausedEvents(response);
const resumedEvents = getSendResumedEvents(response);
// 关闭会话
session.close();
// 调用完成回调函数
done();
}
4.4 实现断点续传
使用示例
1. 导入模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建session,定义请求url及对request进行配置。
// 创建会话
const session = rcp.createSession();
// 定义服务器地址
const kHttpServerAddress = "http://www.example.com/fetch";
// 创建请求
const request = new rcp.Request(kHttpServerAddress, "GET");
3. 定义HTTP请求函数。
function httpRequest() {
// 假设我们有一个存储上次传输位置的变量
let lastTransferPosition = 100; // 上次传输的位置
// 设置请求的范围,基于上次传输位置
request.transferRange = { from: lastTransferPosition, to: lastTransferPosition + 100 };
// 发起请求
session.fetch(request).then((rep: rcp.Response) => {
if (rep.body) {
// 处理响应
console.info(`Response succeeded: ${rep}`);
// 检查是否需要继续传输,如果Content-Range字段存在,说明文件还未传输完成,需要继续传输
if (rep.headers['Content-Range']) {
// 更新上次传输位置
lastTransferPosition += rep.body?.byteLength;
// 递归调用,继续传输
continueTransfer(lastTransferPosition);
} else {
console.info("文件传输完成");
}
} else {
return;
}
}).catch((err: BusinessError) => {
console.error(`Response err: Code is ${err.code}, message is ${JSON.stringify(err)}`);
});
}
4. 定义递归调用函数,
// 递归调用函数,用于继续传输
function continueTransfer(position: number) {
request.transferRange = { from: position, to: position + 100 };
session.fetch(request).then((rep) => {
if (rep.body) {
// 处理响应
console.info(`Response succeeded: ${rep}`);
// 更新上次传输位置
position += rep.body.byteLength;
// 再次调用继续传输
continueTransfer(position);
} else {
return;
}
}).catch((err: BusinessError) => {
console.error(`Continue transfer error: Code is ${err.code}, message is ${err.message}`);
});
}
5 实现流式请求
5.1 场景介绍
HTTP流式传输(Streaming)允许客户端与服务器之间以流的形式进行数据交互,而无需等待所有数据准备完毕,能显著提升用户体验。流式传输适用于大文件的上传下载、直播、实时数据更新等场景。
5.2 接口说明
接口名 | 描述 |
---|---|
write(buffer: string | ArrayBuffer): void | 将一段数据写入队列当中,框架的IO线程会在合适的时候把该数据发送出去。 |
read(buffer: ArrayBuffer): Promise<number> | 从文件中读取数据。 |
5.3 使用示例
1. 导入模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { expect } from '@ohos/hypium';
2. 利用rcp.NetworkInputQueue创建同步写队列对象实现同步写功能。
function testNetworkInputQueue() {
// 创建同步写队列对象
const NetworkInputQueue = rcp.NetworkInputQueue;
const networkInputQueue = new NetworkInputQueue();
// 模拟文件分批上传场景
let counter = 0;
const interval = setInterval(() => {
// 添加数据到同步写队列
networkInputQueue.write('a counter ' + counter++);
if (counter === 10) {
clearInterval(interval);
// 关闭同步写队列
networkInputQueue.close();
}
}, 100);
// 创建session 开发者根据需要实际情况设置远程校验方式
const session = rcp.createSession({
requestConfiguration: {
security: {
remoteValidation: 'skip',
},
},
});
// 发起请求
session.post('https://example.org/post', networkInputQueue).then((resp: rcp.Response) => {
console.info(`Response success, ${resp}`);
}).catch((err: BusinessError) => {
console.info(`Response failed, the error code is ${err.code}, error message is ${JSON.stringify(err)}`);
}).finally(() => {
// 关闭session
session.close();
});
}
3. 利用rcp.NetworkOutputQueue创建同步读队列对象实现同步读功能。
function testNetworkOutputQueue() {
// 创建同步读队列对象
const NetworkOutputQueue = rcp.NetworkOutputQueue;
const networkOutputQueue = new NetworkOutputQueue();
// 创建session
const session = rcp.createSession();
// 配置请求流数据size
const numOfChunks = 10;
const chunkLength = 1000;
const totalBytes = numOfChunks * chunkLength;
// 发起同步读请求
session.get('https://httpbin.org/bytes/' + totalBytes.toString(), networkOutputQueue).then((resp: rcp.Response) => {
// 分段读取请求到的数据
for (let i = 0; i < numOfChunks; i++) {
// 开发者需要根据实际场景处理后续业务
const chunk = networkOutputQueue.read(chunkLength);
console.info(`The chunk is ${JSON.stringify(chunk)}`)
}
console.info(`Response succeeded, ${resp}`)
}).catch((err: BusinessError) => {
console.error(`Response failed, error code is ${err.code}, error message is ${JSON.stringify(err)}`)
}).finally(() => {
// 关闭session
session.close();
});
}
6 提升HTTP传输性能
6.1 基于TracingConfiguration实现性能维测
6.1.1 捕获有关HTTP请求/响应流的详细信息
当需要进行性能维测时,可以采集应用中HTTP请求的详细跟踪信息时,利用TracingConfiguration可以设置四个参数:
- verbose(启用详细跟踪)
- infoToCollect(InfoToCollect类型,配置要收集的特定类型的信息事件)
- collectTimeInfo(在跟踪过程中是否应收集与时间相关的信息)
- httpEventsHandler(HttpEventsHandler类型,为HTTP请求/响应过程中的特定操作定义响应处理程序的回调)
下面将以获取HTTP请求/响应时的数据接收时、请求头接收时、数据传输完成时等详细信息为例,进行介绍。
1. 导入需要的模块,示例中包含了利用远场通信框架发起网络请求以及请求后的响应和错误处理,所以需导入以下模块。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
2. 创建自定义响应处理程序,在HttpEventsHandler中设置onDataReceive(当接收到HTTP响应正文的一部分时调用的回调)、onHeaderReceive(用于在响应期间处理接收到的headers的回调)、onDataEnd(数据传输完成时触发的回调)。
// 定义自定义响应处理程序
const customHttpEventsHandler: rcp.HttpEventsHandler = {
onDataReceive: (incomingData: ArrayBuffer) => {
// 用于处理传入数据的自定义逻辑
console.info('Received data:', JSON.stringify(incomingData));
return incomingData.byteLength;
},
onHeaderReceive: (headers: rcp.RequestHeaders) => {
// 处理响应头的自定义逻辑
console.info('Received headers:', JSON.stringify(headers));
},
onDataEnd: () => {
// 用于处理数据传输完成的自定义逻辑
console.info('Data transfer complete');
}
};
3. 设置tracingConfig对象中的verbose为true,表示启用详细跟踪,设置tracingConfig对象中的infoToCollect对象中的incomingData为true(收集传入的数据信息事件)、outgoingData为true(收集传出的数据信息事件)、incomingHeader为true(收集传入的header信息事件)、outgoingHeader为true(收集传出的header信息事件)。
// 配置跟踪设置
const tracingConfig: rcp.TracingConfiguration = {
verbose: true,
infoToCollect: {
incomingHeader: true, // 收集传入的header信息事件
outgoingHeader: true, // 收集传入的header信息事件
incomingData: true, // 收集传入数据信息事件
outgoingData: true // 收集传出数据信息事件
},
collectTimeInfo: true,
httpEventsHandler: customHttpEventsHandler
};
const securityConfig: rcp.SecurityConfiguration = {
tlsOptions: {
tlsVersion: 'TlsV1.3'
}
};
4. 调用rcp.createSession()传入tracingConfig ,创建通信会话对象session。
// 创建通信会话对象,并传入相关配置
const session = rcp.createSession({ requestConfiguration: { tracing: tracingConfig, security: securityConfig } });
session.get('http://developer.huawei.com').then((response) => {
console.info(`Request succeeded, message is ${JSON.stringify(response)}`);
}).catch((err: BusinessError) => {
console.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
});
6.1.2 HTTP请求过程中各时间点详解
在实施性能维测的过程中,HTTP请求的各个时间点至关重要。借助TimeInfo提供的详细字段,可以精准控制请求过程,无论是直接利用这些字段,还是通过它们之间的运算,都能准确获取所需的时间点,从而提升测试效率。
下面,我们将通过图片、时间线及一段示例代码,详细解析请求过程中的关键时间点。
从图中可以看到HTTP请求过程的基本过程,并且有一些关键的时间点,下面将以时间线的方式对其进行说明:
1、TimeInfo时间线
请求开始 (0时刻) -> nameLookupTimeMs(DNS解析)-> connectTimeMs(建立连接)-> tlsHandshakeTimeMs(TLS握手)-> preTransferTimeMs(请求业务数据发送到服务器的时间点) -> startTransferTimeMs(从服务器接收到首包数据的时间点)。
说明
各时间节点所显示的时间均相对于0时刻,即从0时刻开始计时的时间。例如tlsHandshakeTimeMs为150.1ms,指从发起请求时间0开始,直到TLS握手结束所花费的时间为150.1ms。
网络请求过程中关键节点时间计算方法:
- 首包耗时:startTransferTimeMs - preTransferTimeMs
- TLS握手(不包含建连时间)耗时:tlsHandshakeTimeMs - connectTimeMs
- 接收剩余数据的耗时:totalTimeMs - startTransferTimeMs
2、示例代码
这段代码在使用过程中会将上述说明中三个比较关键的时间点打印出来,可以根据获取到的时间对应用性能实现动态调整,获取最佳体验。
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 1、创建session、requestURL
const session = rcp.createSession();
const requestURL = "https://www.example.com";
// 2、在需要跟踪分析请求过程中各个时间段消耗的时间,请将此开关打开
const configuration: rcp.Configuration = {
tracing: {
collectTimeInfo: true
}
}
// 3、创建请求
const request = new rcp.Request(requestURL, "GET");
request.configuration = configuration;
// 4、使用fetch发起网络请求,request中携带上面配置好的configuration
session.fetch(request).then((response: rcp.Response) => {
// 由于timeInfo中各个参数有可能为undefined,所以需要在两个时间段做运算前添加判空操作
if (!response.timeInfo) {
console.error(`timeInfo is undefined ${response.timeInfo}`);
return;
}
let remainderDataTime = response.timeInfo?.totalTimeMs - response.timeInfo?.startTransferTimeMs;
let firstPackageTime = response.timeInfo?.startTransferTimeMs - response.timeInfo?.preTransferTimeMs;
let TLSTime = response.timeInfo?.tlsHandshakeTimeMs - response.timeInfo?.connectTimeMs;
console.info(`首包耗时${firstPackageTime}`);
console.info(`TLS握手(不包含建连时间)耗时${TLSTime}`);
console.info(`接收剩余数据的耗时${remainderDataTime}`);
}).catch((err: BusinessError) => {
console.error(`Response err, the err is ${JSON.stringify(err)}`);
})
6.2 通过修改Configuration优化传输性能
6.2.1 请求预处理阶段
- 基于Session抽象的高并发网络框架
- 支持创建多个Session
- 支持请求动态取消
- 支持关闭Session
- Session中的资源互相独立、互不影响
- 应用可以通过使用Session来获取最佳的网络性能体验
const session1 = rcp.createSession({
requestConfiguration: {
transfer: {
timeout: {
connectMs: 5000,
transferMs: 5000
}
}
}
});
const request1 = new rcp.Request('example1.com');
const request2 = new rcp.Request('example2.com');
session1.fetch(request1);
session1.fetch(request2);
session1.cancel(request1); // 取消request1请求
session1.close();
6.2.2 DNS阶段
应用可定制DNS请求规则,如定制DNS服务器、重写DNS解析函数,从而获取最佳的DNS性能体验。
// 定制DNS解析函数
const session = rcp.createSession();
const request = new rcp.Request('https://example.com');
request.configuration = {
dns: {
dnsRules: (host: string, port: number): IpAddress[] => {
if (host === 'example.com') {
return ['7.128.8.45', '7.128.8.46'];
}
return [];
}
}
};
session.fetch(request);
// 定制DNS服务器
const session = rcp.createSession();
const request = new rcp.Request('https://example.com');
request.configuration = {
dns: {
dnsRules: [
{
ip: '7.128.8.45',
port: 53,
},
]
}
};
session.fetch(request);
6.2.3 连接阶段
根据资源特征动态调整连接池大小
const session = rcp.createSession({
connectionConfiguration: {
maxConnectionsPerHost: 16,
maxTotalConnections: 1024,
}
});
for (let i = 0; i < 1024; ++i) {
session.get('https://example' + i.toString() + '.com/image.png');
}
6.2.4 HTTP请求阶段
- 支持响应体分段返回,以节省内存
- 支持直接将响应写入文件,以节省内存
- 支持请求体分段上传,以节省内存
// 使用响应体直接写入文件
const session = rcp.createSession();
const response = await session.get('https://example.com/video.mp4', {
kind: 'file',
file: './video.mp4',
});
// 分段上传数据
const session = rcp.createSession();
const response = await session.post('https://example.com/video.mp4', (maxSize: number) => {
return new ArrayBuffer(maxSize);
});
6.2.5 HTTP响应阶段
获取响应各阶段耗时动态判断网络质量,动态调整请求(请求不同质量的资源、降低资源缓存数量)。更详细的示例请移步HTTP请求过程中各时间点详解。
// 获取各个阶段的耗时信息
const
session = rcp.createSession();
const
response = await session.get('https://example.com');
console.info(response.timeInfo?.nameLookupTimeMs.toString());
console.info(response.timeInfo?.connectTimeMs.toString());
console.info(response.timeInfo?.tlsHandshakeTimeMs.toString());
console.info(response.timeInfo?.preTransferTimeMs.toString());
console.info(response.timeInfo?.startTransferTimeMs.toString());
console.info(response.timeInfo?.totalTimeMs.toString());
console.info(response.timeInfo?.redirectTimeMs.toString());
7 使用URPC进行远程程序调用
7.1 场景介绍
发送一个URPC请求,也可以设置优先级等参数,并返回来自远程服务器的URPC响应。当发起请求后,可以选择取消指定或正在进行的URPC请求。当完成请求后,需要关闭请求来释放与此URPC关联的资源。
7.2 接口说明
接口名 | 描述 |
---|---|
call: (funcName: string, request: object, returnValue: object, config?: CallingOption) => UrpcPromise | 发送一个URPC请求,并返回来自服务器的HTTP响应。使用Promise异步回调。 |
cancel: (callingId?: number | number[]) => void | 取消指定或所有正在进行的URPC请求,返回值为空。 |
destroy: () => void | 销毁UrpcStub实例 |
7.3 使用示例
7.3.1 创建urpcStub
1. 导入模块
import { hilog } from "@kit.PerformanceAnalysisKit";
import { urpc } from "@kit.RemoteCommunicationKit";
2. 定义远程调用的类,作为调用方法的入参和返回值,示例如下:
// 定义调用方法的入参类示例
export class MediaTaskRequestMessage {
RequestMessage: urpc.FlowbufElement<string>;
constructor() {
this.RequestMessage = {type: 'STRING', value: "", name: ""};
}
setRequestMessage(RequestMessage: string) {
this.RequestMessage.value = RequestMessage;
}
getRequestMessage(): string {
return this.RequestMessage.value;
}
}
// 定义用于接收调用方法返回值的类示例
export class MediaTaskResponseMessage {
ResponseMessage: urpc.FlowbufElement<string>;
constructor() {
this.ResponseMessage = {type: 'STRING', value: "", name: ""};
}
setResponseMessage(ResponseMessage: string) {
this.ResponseMessage.value = ResponseMessage;
}
getResponseMessage(): string {
return this.ResponseMessage.value;
}
}
3. 创建Request对象和Response接收对象。
let request = new MediaTaskRequestMessage();
let response = new MediaTaskResponseMessage();
4. 配置连接信息,创建发起URPC调用的UrpcStub。
let node: urpc.IpAndPort = {
ip: '127.0.0.1',
port: 8000
}
let connect: urpc.UrpcConnectConfiguration = {
node: node,
protocol: 'eat',
}
let config: urpc.UrpcInitConfiguration = {
timeout: 3000,
mode: 'client',
connect: connect
}
const funcList:string[] = ["uploadFile"];
let urpcStub = urpc.urpcStubCreate(config, funcList);
7.3.2 使用call收发网络请求
urpcStub.then(async (stub: urpc.UrpcStub) =>{
let upload_config: urpc.CallingOption = {
priority: 0
};
let urpcPromise = stub.call("uploadFile", request, response, upload_config);
urpcPromise.promise.then((resp: object) => {
hilog.info(0x000, "urpc", "resp: %{public}s", resp);
}).catch((err: Error) => {
hilog.error(0x000, "urpc", "err.name: %s, err.message: %s, err.stack: %s", err.name, err.message, err.stack);
})
})
7.3.3 (可选)使用cancel取消网络请求
当调用call发起一次urpc收发请求后,根据业务需要,不用接收响应时,可调用cancel取消指定callingId的请求;若不指定callingId,则取消UrpcStub发起的全部请求。
urpcStub.then(async (stub: urpc.UrpcStub) =>{
let upload_config: urpc.CallingOption = {
priority: 0
};
let urpcPromise = stub.call("uploadFile", request, response, upload_config);
stub.cancel(urpcPromise.callingId);
})
7.3.4 使用destroy关闭URPC
当完成所有urpc收发网络请求后,需调用destroy释放并销毁UrpcStub相关的资源。
urpcStub.then(async (stub: urpc.UrpcStub) =>{
let upload_config: urpc.CallingOption = {
priority: 0
};
let urpcPromise = stub.call("uploadFile", request, response, upload_config);
urpcPromise.promise.then((resp: object) => {
hilog.info(0x000, "urpc", "resp: %{public}s", resp);
}).catch((err: Error) => {
hilog.error(0x000, "urpc", "err.name: %s, err.message: %s, err.stack: %s", err.name, err.message, err.stack);
})
stub.destroy();
})