HarmonyOS NEXT网络状态监听HTTP和RCP请求网络
当我们在HarmonyOS NEXT中开发的应用,基本上都会使用网络请求,从服务端获取数据在客户端显示或者供用户交互,有时候网络发生变化时,我们需要做一些相应的操作,接下来我们一起来了解下在HarmonyOS NEXT下如何监听网络状态,如何向服务端请求网络
网络监听
网络监听需要声明接口调用所需要的权限:ohos.permission.GET_NETWORK_INFO
1.创建网络对象
从@kit.NetworkKit中导入connection命名空间,创建NetConnection对象
import { connection } from "@kit.NetworkKit";
netConnection= connection.createNetConnection(this.netSpecifier, this.timeout)
2.开启网络状态订阅
订阅默认网络状态发生变化的通知,订阅成功后进行后续步骤
openRegister(){
this.netConnection.register((error:BusinessError)=>{
hilog.info(0x0000, this.TAG,JSON.stringify(error))
})
}
3.订阅具体网络变化
订阅具体网络变化事件,如网络可用,网络丢失等
this.netConnection.on('netAvailable',(data:connection.NetHandle)=>{
promptAction.showToast({message:'当前可用网络:'+JSON.stringify(data),duration:2000});
});
this.netConnection.on('netLost',()=>{
promptAction.showToast({message:'网络丢失了',duration:2000});
});
具体的网络时间主要分为:
参数 | 说明 | 触发时机 |
---|---|---|
netAvailable | 网络可用事件 | 当设备连接到可用网络时触发 |
netBlockStatusChange | 网络阻塞状态事件 | 当建立网络连接超时、传输数据包丢失或网络带宽不足、负载过高时触发 |
netCapabilitiesChange | 网络能力变化事件 | 当网络的能力(如类型、带宽等)发生变化时触发 |
netConnectionPropertiesChange | 网络连接信息变化事件 | 当网络连接的信息(如IP地址、网关等)发生变化时触发 |
netLost | 网络丢失事件 | 当设备失去网络连接时触发 |
netUnavailable | 网络不可用事件 | 当设备处于无网络状态或网络不可用时触发 |
他们的触发条件关系如下图:
4.取消网络状态订阅
应用关闭时取消网络状态订阅,不在接受通知
unRegister(){
this.netConnection.unregister((error:BusinessError)=>{
hilog.info(0x0000, this.TAG,'移除网络状态订阅:'+JSON.stringify(error))
})
}
使用HTTP访问网络
场景介绍
应用通过HTTP发起一个数据请求,支持常见的GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT方法。
HTTP数据请求功能主要由http模块提供。
使用该功能需要申请ohos.permission.INTERNET权限
request接口开发步骤
- 从@kit.NetworkKit中导入http命名空间。
- 调用createHttp()方法,创建一个HttpRequest对象。
- 调用该对象的on()方法,订阅http响应头事件,此接口会比request请求先返回。可以根据业务需要订阅此消息。
- 调用该对象的request()方法,传入http请求的url地址和可选参数,发起网络请求。
- 按照实际业务需要,解析返回结果。
- 调用该对象的off()方法,取消订阅http响应头事件。
- 当该请求使用完毕时,调用destroy()方法主动销毁。
// 引入包名
import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 每一个httpRequest对应一个HTTP请求任务,不可复用
let httpRequest = http.createHttp();
// 用于订阅HTTP响应头,此接口会比request请求先返回。可以根据业务需要订阅此消息
// 从API 8开始,使用on('headersReceive', Callback)替代on('headerReceive', AsyncCallback)。 8+
httpRequest.on('headersReceive', (header) => {
console.info('header: ' + JSON.stringify(header));
});
httpRequest.request(
// 填写HTTP请求的URL地址,可以带参数也可以不带参数。URL地址需要开发者自定义。请求的参数可以在extraData中指定
"EXAMPLE_URL",
{
method: http.RequestMethod.POST, // 可选,默认为http.RequestMethod.GET
// 开发者根据自身业务需要添加header字段
header: {
'Content-Type': 'application/json'
},
// 当使用POST请求时此字段用于传递请求体内容,具体格式与服务端协商确定
extraData: "data to send",
expectDataType: http.HttpDataType.STRING, // 可选,指定返回数据的类型
usingCache: true, // 可选,默认为true
priority: 1, // 可选,默认为1
connectTimeout: 60000, // 可选,默认为60000ms
readTimeout: 60000, // 可选,默认为60000ms
usingProtocol: http.HttpProtocol.HTTP1_1, // 可选,协议类型默认值由系统自动指定
usingProxy: false, // 可选,默认不使用网络代理,自API 10开始支持该属性
caPath:'/path/to/cacert.pem', // 可选,默认使用系统预制证书,自API 10开始支持该属性
clientCert: { // 可选,默认不使用客户端证书,自API 11开始支持该属性
certPath: '/path/to/client.pem', // 默认不使用客户端证书,自API 11开始支持该属性
keyPath: '/path/to/client.key', // 若证书包含Key信息,传入空字符串,自API 11开始支持该属性
certType: http.CertType.PEM, // 可选,默认使用PEM,自API 11开始支持该属性
keyPassword: "passwordToKey" // 可选,输入key文件的密码,自API 11开始支持该属性
},
multiFormDataList: [ // 可选,仅当Header中,'content-Type'为'multipart/form-data'时生效,自API 11开始支持该属性
{
name: "Part1", // 数据名,自API 11开始支持该属性
contentType: 'text/plain', // 数据类型,自API 11开始支持该属性
data: 'Example data', // 可选,数据内容,自API 11开始支持该属性
remoteFileName: 'example.txt' // 可选,自API 11开始支持该属性
}, {
name: "Part2", // 数据名,自API 11开始支持该属性
contentType: 'text/plain', // 数据类型,自API 11开始支持该属性
// data/app/el2/100/base/com.example.myapplication/haps/entry/files/fileName.txt
filePath: `${getContext(this).filesDir}/fileName.txt`, // 可选,传入文件路径,自API 11开始支持该属性
remoteFileName: 'fileName.txt' // 可选,自API 11开始支持该属性
}
]
}, (err: BusinessError, data: http.HttpResponse) => {
if (!err) {
// data.result为HTTP响应内容,可根据业务需要进行解析
console.info('Result:' + JSON.stringify(data.result));
console.info('code:' + JSON.stringify(data.responseCode));
// data.header为HTTP响应头,可根据业务需要进行解析
console.info('header:' + JSON.stringify(data.header));
console.info('cookies:' + JSON.stringify(data.cookies)); // 8+
// 当该请求使用完毕时,调用destroy方法主动销毁
httpRequest.destroy();
} else {
console.error('error:' + JSON.stringify(err));
// 取消订阅HTTP响应头事件
httpRequest.off('headersReceive');
// 当该请求使用完毕时,调用destroy方法主动销毁
httpRequest.destroy();
}
}
);
做过移动开发的同学应该都清楚,网络请求一般官方都可能会对其再封装,让开发者能够更方便快捷的使用,例如Android原生开发中的OkHttp对HttpClient和Retrofit对OKHTTP
RCP的网络请求开发实践
概述
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模块。
- 创建通信会话对象调用rcp.createSession()创建通信会话对象session。
- 使用new rcp.Request()方法创建请求对象req。
- 调用session.fetch()方法发起请求。
- 获取响应结果。
- 销毁对象,释放资源。
核心代码
import rcp from '@hms.collaboration.rcp';
/**
* @FileName : RCPUtils
* @Author : kirk.wang
* @Time : 2025/2/14 16:52
* @Description : 文件描述
*/
export class RCPUtils{
rcpSession:rcp.Session;
constructor() {
//1创建Session对象
this.rcpSession = rcp.createSession();
}
//2发起请求
async getRCPRequest(exterData:string):Promise<ResponseDataBean>{
let respData :ResponseDataBean = new ResponseDataBean() ;
await this.rcpSession.get('this.url').then((response)=>{
//3处理响应
respData = response.toJSON() as ResponseDataBean;
});
await this.rcpSession.post('this.url',exterData).then((response)=>{
//3处理响应
respData = response.toJSON() as ResponseDataBean;
});
return respData;
}
//4销毁对象 ,当该请求使用完毕时,必须调用close方法主动释放与此会话关联的资源
destroySession(){
this.rcpSession.close()
}
}
export class ResponseDataBean{
}
我们可以将RCP理解成Retrofit基于OKHttp的封装。
实现请求与响应拦截
使用拦截器可以方便的对HTTP的请求与响应进行修改,您可以创建拦截器链,按需定制一组拦截器对您的网络请求/响应进行修改。RCP模块提供了拦截器能力,在SessionConfiguration中添加Interceptors参数,传入自定义的拦截器,即可在HTTP请求和响应的过程中添加拦截器功能
实现步骤
- 导入rcp模块。
- 定义RequestUrlChangeInterceptor拦截器和ResponseHeaderRemoveInterceptor拦截器。
- 在intercept()方法中实现对请求/响应的修改逻辑
核心代码
import { rcp } from "@kit.RemoteCommunicationKit";
import { url } from "@kit.ArkTS";
import { hilog } from "@kit.PerformanceAnalysisKit";
import { ConnectionUtils } from "./ConnectionUtils";
/**
* @FileName : RequestUrlChangeInterceptor
* @Author : kirk.wang
* @Time : 2025/2/16 17:34
* @Description : 定义RequestUrlChangeInterceptor拦截器
*/
export class RequestUrlChangeInterceptor implements rcp.Interceptor {
TAG = "RequestUrlChangeInterceptor"
// 自定义请求处理逻辑
async intercept(context: rcp.RequestContext, next: rcp.RequestHandler): Promise<rcp.Response> {
if (context.request.method === 'GET' && ConnectionUtils.isNetworkFast()) {
hilog.info(0x0000, this.TAG,'[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];
hilog.info(0x0000, this.TAG,`[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 {
hilog.info(0x0000, this.TAG,'[RequestUrlChangeInterceptor]: Network is fast');
}
return next.handle(context);
}
}
// 定义ResponseHeaderRemoveInterceptor拦截器
export class ResponseHeaderRemoveInterceptor implements rcp.Interceptor {
TAG = "ResponseHeaderRemoveInterceptor"
// 自定义响应处理逻辑
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
};
hilog.info(0x0000, this.TAG,'[ResponseHeaderRemoveInterceptor]: Response was modified');
return toReturn;
}
}
拦截器的使用
可通过RCP模块中的SessionConfiguration来进行设置,在sessionConfig对象中设置interceptors,即可在请求/响应中添加拦截器
const sessionConfig: rcp.SessionConfiguration = {
interceptors: [
new RequestUrlChangeInterceptor(),
new ResponseHeaderRemoveInterceptor()
],
requestConfiguration:{ security: this.securityConfig }
}
};
const session = rcp.createSession(sessionConfig);
和安卓的拦截器使用方法很相似,我们可以在拦截器里做加解密或者自定义header的处理。