概述
Remote Communication Kit中的@hms.collaboration.rcp(后续简称RCP)指的是远程通信平台(remote communication platform),RCP提供了网络数据请求功能,相较于Network Kit中HTTP请求能力,RCP更具易用性,且拥有更多的功能。在开发过程中,如果有些场景使用Network Kit中HTTP请求能力达不到预期或无法实现,那么就可以尝试使用RCP中的数据请求功能来实现。
接下来,本文将先介绍RCP与HTTP的区别,然后从使用RCP实现基础的网络请求、多表单提交、双向证书校验、DNS的相关设置、请求与响应拦截和捕获有关HTTP请求/响应流的详细信息等几个场景来介绍RCP拥有的能力。
RCP与HTTP的区别
为了方便了解RCP与HTTP的区别,可以从功能分类、功能名称和功能描述这三个方面进行对比,主要区别如下:
功能分类 | 功能名称 | 功能描述 | HTTP | RCP |
---|---|---|---|---|
基础功能 | 发送PATCH类型请求 | 以PATCH的方式请求 | 不支持 | 支持 |
基础功能 | 设置会话中URL的基地址 | 会话中URL的基地址将自动加在URL前面,除非URL是一个绝对的URL | 不支持 | 支持 |
基础功能 | 取消自动重定向 | HTTP请求不会自动重定向 | 不支持 | 支持 |
基础功能 | 拦截请求和响应 | 在请求后或响应前进行拦截 | 不支持 | 支持 |
基础功能 | 取消请求 | 发送请求前取消、发送请求过程中取消、请求接收后取消 | 不支持 | 支持 |
基础功能 | 响应缓存 | 是否使用缓存,请求时优先读取缓存。缓存跟随当前进程生效,新缓存会替换旧缓存 | 不支持 | 支持 |
基础功能 | 设置响应数据的类型 | 设置数据以何种方式返回,将要响应的数据类型可设置为string、object、arraybuffer等类型 | 支持 | 不支持 |
基础功能 | 定义允许的HTTP响应内容的最大字节数 | 服务器成功响应时,在获取数据前校验响应内容的最大字节数 | 支持 | 不支持 |
证书验证 | 自定义证书校验 | 自定义逻辑校验客户端和服务端的证书,判断是否可以连接 | 不支持 | 支持 |
证书验证 | 忽略SSL校验 | 在建立SSL连接时不验证服务器端的SSL证书 | 不支持 | 支持 |
DNS | 自定义DNS解析 | 包括自定义DNS服务器或静态DNS规则 | 不支持 | 支持 |
rcp特有 | 捕获详细的跟踪信息 | 在会话中的HTTP请求期间捕获详细的跟踪信息。跟踪有助于调试、性能分析和深入了解通信过程中的数据流 | 不支持 | 支持 |
rcp特有 | 数据打点,获取HTTP请求的具体数据 | HTTP请求各阶段的定时信息 | 不支持 | 支持 |
实现基础的网络请求
发送请求
通过RCP模块能够发起基础的网络请求,如GET、POST、HEAD、PUT、DELETE、PATCH、OPTIONS等请求。以PATCH请求为例,开发过程中经常会遇到发送请求修改资源的场景,假设有一个UserInfo,里面有userId、userName、 userGender等10个字段。可编辑功能因为需求,在某个特别的页面里只能修改userName,这时就可以用PATCH请求,来更新局部资源。
实现思路
在创建[session] 会话后,通过创建请求对象并传入第二个参数且指定为PATCH,然后通过session.fetch()发起请求即可。
- 导入rcp模块。
- 创建headers,设置可接受的数据内容的类型为json字符串;创建modifiedContent,传入想要修改的内容。
- 调用rcp.createSession()创建通信会话对象session。
- 使用new rcp.Request()方法创建请求对象req。
- 调用session.fetch()方法发起请求。
- 获取响应结果。
核心代码
// 定义请求头
let headers: rcp.RequestHeaders = {
'accept': 'application/json'
};
// 定义要修改的内容
let modifiedContent: UserInfo = {
'userName': 'xxxxxx'
};
const securityConfig: rcp.SecurityConfiguration = {
tlsOptions: {
tlsVersion: 'TlsV1.3'
}
};
// 创建通信会话对象
const session = rcp.createSession({ requestConfiguration: { security: securityConfig } });
// 定义请求对象rep
let req = new rcp.Request('http://example.com/fetch', 'PATCH', headers, modifiedContent);
// 发起请求
session.fetch(req).then((response) => {
Logger.info(`Request succeeded, message is ${JSON.stringify(response)}`);
}).catch((err: BusinessError) => {
Logger.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
});
设置会话中URL的基地址
会话中URL的基地址是在发起请求时用作请求地址的前缀。 当我们向服务器发起请求时,该请求的最终的请求地址由会话中URL的基地址与请求路径来构建的。 这使得我们可以将服务器的主机地址与公共路径隔离开来,方便管理和维护。
实现思路
会话中URL的基地址可通过RCP模块中的[SessionConfiguration] 来进行设置,在sessionConfig对象中设置“baseAddress:‘http://api.example.com’ ”即可。
- 导入rcp模块。
- 设置sessionConfig对象中的baseAddress为http://api.example.com。
- 调用rcp.createSession()传入sessionConfig,创建通信会话对象session。
核心代码
// 定义sessionConfig对象
const sessionConfig: rcp.SessionConfiguration = {
baseAddress: 'http://api.example.com',
headers: {
'authorization': 'Bearer YOUR_ACCESS_TOKEN',
'content-type': 'application/json'
},
requestConfiguration:{
security:{
tlsOptions: {
tlsVersion: 'TlsV1.3'
}
}
}
};
// 创建通信会话对象,并传入sessionConfig
const session = rcp.createSession(sessionConfig);
实现多表单提交
开发过程中时常会遇到多表单提交的场景,例如在同页面下tab栏可切换多个Form表单组件,但只有一个提交按钮,各组件下的表单数据需要被一起提交。此时可以使用RCP模块中的[MultipartForm] 来实现多表单提交的场景。
实现思路
在创建session会话后,通过new rcp.Request()的第四个参数传入MultipartForm,然后通过rcp.fetch()发起POST请求将多个表单数据携带上传至服务端。
- 导入rcp模块。
- 设置请求头类型、配置HTTP请求的超时值、HTTP请求中包含的cookie和设置传输数据范围。
- 调用rcp.createSession()创建通信会话对象。
- 通过new rcp.MultipartForm()设置多表单数据。
- 使用new rcp.Request()创建请求对象,调用session.fetch()方法发起请求。
- 处理响应结果。
核心代码
// 定义请求头类型
let headers: rcp.RequestHeaders = {
'accept': 'application/json'
};
// 配置HTTP请求的超时值
let configuration: rcp.Configuration = {
transfer: {
timeout: {
connectMs: 60000,
transferMs: 60000
}
}
};
// HTTP请求中的Cookie
let cookies: rcp.RequestCookies = {
'name1': 'value1',
'name2': 'value2',
};
// 设置数据传输范围
let transferRange: rcp.TransferRange = {
from: 100,
to: 200
};
// 设置multipartFrom数据
const multiForm = new rcp.MultipartForm({
'Form1': this.name, // string
'Form2': this.hobbies, // string
'Form3': {
contentType: 'text/plain',
remoteFileName: 'RemoteFileName',
contentOrPath: '/file/to/Path'
} // object
});
const securityConfig: rcp.SecurityConfiguration = {
tlsOptions: {
tlsVersion: 'TlsV1.3'
}
};
// 创建通信会话对象
const session = rcp.createSession({ requestConfiguration: { security: securityConfig } });
// 定义请求对象req
let req =
new rcp.Request('https://www.example.com', 'POST', headers, multiForm, cookies, transferRange, configuration);
req.content = multiForm;
// 发起请求
session.fetch(req).then((response) => {
Logger.info(`Request succeeded, message is ${JSON.stringify(response)}`);
}).catch((err: BusinessError) => {
Logger.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
});
实现双向证书校验
为验证服务端和客户端之间的身份和数据完整性,确保通信的安全性,可使用RCP接口能力实现双向证书校验能力。
实现对DNS的定制设置
通过RCP模块,能够实现对DNS的定制设置。开发人员可以按自己的需要配置DNS,包括自定义DNS服务器、自定义静态DNS规则和配置HTTPS上的DNS,都可以通过[DnsConfiguration] 设置。DnsConfiguration中可设置dnsRules、dnsOverHttps。
-
dnsRules(配置DNS规则)
自定义DNS服务器([DnsServers] ):可指定自定义的DNS服务器提供解析服务。
自定义静态DNS([StaticDnsRules] ):有些时候,默认的DNS不能正常解析部分域名,就需要手动添加静态DNS。添加静态DNS后,如果hostname匹配,则优先使用指定的地址。
自定义动态DNS([DynamicDnsRules] ):除了添加静态DNS外,还可以添加动态DNS。动态DNS可看作一个可以根据hostname和port直接返回IP地址的函数,如果设置,则优先使用函数中返回的地址。
-
dnsOverHttps
DNS over HTTPS配置([DnsOverHttpsConfiguration] ):配置HTTPS上的DNS(DOH)设置,以加密的HTTPS协议进行DNS解析请求,避免原始DNS协议中用户的DNS解析请求被窃听或者修改的问题,来达到保护用户隐私的目的。如果设置,则优先使用DNS服务器解析的地址。
自定义DNS服务器
实现思路
先配置自定义的DNS服务器customDnsServers,在创建session会话时,通过requestConfiguration传入dns对象,指定dns对象中的dnsRules为customDnsServers。
- 导入rcp模块。
- 配置自定义的DNS服务器。
- 调用rcp.createSession()创建通信会话对象时,传入自定义的DNS服务器。
核心代码
// 配置自定义DNS服务器
const customDnsServers: rcp.DnsServers = [
{ ip: '8.8.8.8' },
{ ip: '8.8.4.4', port: 53 }
];
// 创建通信会话对象
const sessionWithCustomDns = rcp.createSession({
requestConfiguration: {
dns: {
dnsRules: customDnsServers
},
security: {
tlsOptions: {
tlsVersion: 'TlsV1.3'
}
}
}
});
// 发起请求
sessionWithCustomDns.get('http://www.example.com').then((response) => {
Logger.info(`Request succeeded, message is ${JSON.stringify(response)}`);
}).catch((err: BusinessError) => {
Logger.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
});
自定义静态DNS
实现思路
先配置静态DNS规则staticDnsRules,在创建session会话时,通过requestConfiguration传入dns对象,指定dns对象中的dnsRules为staticDnsRules 。
- 导入rcp模块。
- 配置静态DNS规则。
- 调用rcp.createSession()创建通信会话对象时,传入静态DNS规则。
// 当匹配到hostname时,优先使用指定的地址
const staticDnsRules: rcp.StaticDnsRules = [
{
host: 'example.com',
port: 80,
ipAddresses: ['192.168.1.1', '192.168.1.2']
}
];
// 创建通信会话对象
const sessionWithCustomDns = rcp.createSession({
requestConfiguration: {
dns: {
dnsRules: staticDnsRules
},
security: {
tlsOptions: {
tlsVersion: 'TlsV1.3'
}
}
}
});
// 发起请求
sessionWithCustomDns.get('http://www.example.com').then((response) => {
Logger.info(`Request succeeded, message is ${JSON.stringify(response)}`);
}).catch((err: BusinessError) => {
Logger.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
});
配置HTTPS上的DNS
实现思路
先创建HTTPS上的DNS对象dohConfig,在创建session会话时,通过requestConfiguration传入dns对象,指定dns对象中的dnsOverHttps为dohConfig 。
- 导入rcp模块。
- 创建HTTPS上的DNS对象dohConfig。
- 调用rcp.createSession()创建通信会话对象时,传入dohConfig。
// DNS over HTTPS配置
const dohConfig: rcp.DnsOverHttpsConfiguration = {
url: 'https://dns.example.com/dns-query',
skipCertificatesValidation: true
};
// 创建通信会话对象
const sessionWithCustomDns = rcp.createSession({
requestConfiguration: {
dns: {
dnsOverHttps: dohConfig
},
security: {
tlsOptions: {
tlsVersion: 'TlsV1.3'
}
}
}
});
// 发起请求
sessionWithCustomDns.get('http://www.example.com').then((response) => {
Logger.info(`Request succeeded, message is ${JSON.stringify(response)}`);
}).catch((err: BusinessError) => {
Logger.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
});
实现请求与响应拦截
使用拦截器可以方便的对HTTP的请求与响应进行修改,您可以创建拦截器链,按需定制一组拦截器对您的网络请求/响应进行修改。RCP模块提供了拦截器能力,在[SessionConfiguration] 中添加[Interceptors] 参数,传入自定义的拦截器,即可在HTTP请求和响应的过程中添加拦截器功能。
拦截器工作原理
客户端发送HTTP请求,到达目标服务器之前,可以使用拦截器对HTTP的请求进行修改。如下图,定义了链式拦截器,RequestUrlChangeInterceptor拦截器(下文以拦截器1代替)和ResponseHeaderRemoveInterceptor拦截器(下文以拦截器2代替)。请求先被拦截器1拦截,该拦截器可以实现当网络质量差时,通过修改HTTP请求中的URL,来调整请求资源的大小。然后经过拦截器2,最后到达Internet。当请求到达目标服务器,服务器返回请求响应的结果给客户端之前,可以使用拦截器对HTTP的响应进行修改。如下图,响应先被拦截器2拦截,在响应返回给应用前检查和修改服务器的请求头。然后经过拦截器1,最后客户端接收响应结果。
说明
RequestUrlChangeInterceptor拦截器和ResponseHeaderRemoveInterceptor拦截器都是自定义拦截器,需要开发者通过代码去实现内部逻辑。
拦截器的定义和使用
下面将介绍如何自定义拦截器,定义RequestUrlChangeInterceptor拦截器和ResponseHeaderRemoveInterceptor拦截器实现[rcp.Interceptor] ,可在[intercept()] 方法中根据业务需求自定义处理逻辑,实现对请求/响应的修改。
- 导入rcp模块。
- 定义RequestUrlChangeInterceptor拦截器和ResponseHeaderRemoveInterceptor拦截器。
- 在intercept()方法中实现对请求/响应的修改逻辑。
核心代码
import { rcp } from '@kit.RemoteCommunicationKit';
import { url } from '@kit.ArkTS';
import Logger from '../common/Logger';
import { NetworkQualityProvider } from './NetworkStateSimulator';
// 定义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()) {
Logger.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];
Logger.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 {
Logger.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
};
Logger.info('[ResponseHeaderRemoveInterceptor]: Response was modified');
return toReturn;
}
}
说明
NetworkQualityProvider中定义了isNetWorkFast(),isNetWorkFast用于在示例代码中模拟网络质量的好坏,这里仅作为场景模拟,需要开发者自行评估实现。
RequestUrlChangeInterceptor拦截器中,当网络质量较差的时候,修改请求中的URL路径,请求获取分辨率较小的图片,可提升用户体验。
下面将介绍如何使用拦截器,可通过RCP模块中的[SessionConfiguration] 来进行设置,在sessionConfig对象中设置interceptors,即可在请求/响应中添加拦截器。
- 导入rcp模块、自定义的拦截器。
- 设置sessionConfig对象中的interceptors(传入自定义拦截器)。
- 调用rcp.createSession()传入sessionConfig,创建通信会话对象session。
核心代码
const sessionConfig: rcp.SessionConfiguration = {
interceptors: [
new RequestUrlChangeInterceptor(networkStateSimulator),
new ResponseHeaderRemoveInterceptor()
],
requestConfiguration:{
security:{
tlsOptions: {
tlsVersion: 'TlsV1.3'
}
}
}
};
const session = rcp.createSession(sessionConfig);
捕获有关HTTP请求/响应流的详细信息
当需要采集应用中HTTP请求的详细跟踪信息时,可以使用[TracingConfiguration] 进行相关配置。TracingConfiguration中可以设置verbose(启用详细跟踪)、[infoToCollect] (配置要收集的特定类型的信息事件)、collectTimeInfo(在跟踪过程中是否应收集与时间相关的信息)、[httpEventsHandler] (为HTTP请求/响应过程中的特定操作定义响应处理程序的回调)四个参数。
下面将以获取HTTP请求/响应时的数据接收时、请求头接收时、数据传输完成时等详细信息为例,进行介绍。
实现思路
通过配置[TracingConfiguration] 中的参数,来捕获HTTP请求/响应时的详细信息。
- 导入rcp模块。
- 设置tracingConfig对象中的verbose为true,表示启用详细跟踪。
- 设置tracingConfig对象中的infoToCollect对象中的incomingData为true(收集传入的数据信息事件)、outgoingData为true(收集传出的数据信息事件)、incomingHeader为true(收集传入的header信息事件)、outgoingHeader为true(收集传出的header信息事件)。
- 设置tracingConfig对象中collectTimeInfo为true,表示在跟踪过程中收集与时间相关的信息。
- 在[HttpEventsHandler] 中设置onDataReceive(当接收到HTTP响应正文的一部分时调用的回调)、onHeaderReceive(用于在响应期间处理接收到的headers的回调)、onDataEnd(数据传输完成时触发的回调)。
- 调用rcp.createSession()传入tracingConfig ,创建通信会话对象session。
核心代码
// 定义自定义响应处理程序
const customHttpEventsHandler: rcp.HttpEventsHandler = {
onDataReceive: (incomingData: ArrayBuffer) => {
// 用于处理传入数据的自定义逻辑
Logger.info('Received data:', JSON.stringify(incomingData));
return incomingData.byteLength;
},
onHeaderReceive: (headers: rcp.RequestHeaders) => {
// 处理响应头的自定义逻辑
Logger.info('Received headers:', JSON.stringify(headers));
},
onDataEnd: () => {
// 用于处理数据传输完成的自定义逻辑
Logger.info('Data transfer complete');
}
};
// 配置跟踪设置
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'
}
};
// 创建通信会话对象,并传入相关配置
const session = rcp.createSession({ requestConfiguration: { tracing: tracingConfig, security: securityConfig } });
session.get('http://developer.huawei.com').then((response) => {
Logger.info(`Request succeeded, message is ${JSON.stringify(response)}`);
}).catch((err: BusinessError) => {
Logger.error(`err: err code is ${err.code}, err message is ${JSON.stringify(err)}`);
});
本文主要介绍了基于RCP实现基础的网络请求、多表单提交、双向证书校验、DNS相关设置、请求与响应拦截、HTTP请求期间捕获详细的跟踪信息等。
- 调用rcp.createSession()创建通信会话对象,实现基础的网络请求能力。
- 对标HTTP原生能力,通过new rcp.MultipartForm()可实现多表单提交的场景。
- 双向证书校验:用于验证服务端和客户端之间的身份和数据完整性,确保通信的安全性。
- 给HTTP请求配置域名系统(DNS),包括自定义DNS服务器、自定义静态DNS规则和配置HTTPS上的DNS。
- 定义拦截器对象实现高性能网络资源加载。
- 使用rcp.TracingConfiguration进行相关配置,在会话中的HTTP请求期间捕获详细的跟踪信息。