1. 拦截器简介
在 Web 开发中拦截器是一种非常有用的模式,它允许开发者在请求发送到服务器之前或响应返回给客户端之前执行一些预处理或后处理操作。这种机制特别适用于需要对所有网络请求或响应进行统一处理的情况,比如添加全局错误处理、请求头的修改、响应数据的格式化等,本示例将使用 RCP 模块提供的拦截器功能,实现对 HTTP 请求的性能监控,为简单起见,本示例只记录每个 HTTP 请求和响应的时间以及相关的状态信息,读者可以根据需要记录更多的信息并在此基础上进行深入的统计分析。
在 RCP 模块的 API 中,拦截器是以接口的形式提供的,接口名称为 Interceptor,包括名称为 intercept 的一个方法:
intercept(context: RequestContext, next: RequestHandler): Promise<Response>
复制代码
该方法第一个参数 context 为请求上下文,第二参数 next 为下一个请求处理器,可以返回 Response 的 Promise 对象,或者返回 next 调用 handle 方法的值。
2. 拦截器性能监控演示
本示例运行后的界面如图所示:
这里列出了 5 个可以请求的 web 地址,输入要请求的次数,然后单击“随机请求”按钮,应用会随机请求地址列表中的 web 地址,拦截器会在下方的日志区域实时显示请求性能信息,如图所示:
3. 拦截器性能监控示例编写
下面详细介绍创建该示例的步骤。
步骤 1:创建 Empty Ability 项目。
步骤 2:在 module.json5 配置文件加上对权限的声明:
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
复制代码
这里添加了访问互联网的权限。
步骤 3:在 Index.ets 文件里添加如下的代码:
import { rcp } from '@kit.RemoteCommunicationKit';
@ObservedV2
//拦截日志类
class RequestPerfRecord {
@Trace public id: string = ""
@Trace public method: string = ""
@Trace public begin: Date = new Date()
@Trace public end: Date = new Date()
@Trace public requestUrl: string = ""
@Trace public stateCode: number = -1
constructor(request: rcp.Request) {
this.id = request.id
this.method = request.method
this.begin = new Date()
this.requestUrl = request.url.toString()
}
//请求耗费的秒数
public spendTime(): number {
return (this.end.valueOf() - this.begin.valueOf()) / 1000
}
public toString(): string {
if (this.stateCode == -1) {
return `请求地址:${this.requestUrl}\r\n请求方法:${this.method}\r\n` +
`请求开始时间:${this.begin.toLocaleString()} \r\n`
} else {
return `请求地址:${this.requestUrl}\r\n请求方法:${this.method}\r\n` +
`请求开始时间:${this.begin.toLocaleString()}\r\n响应时间:${this.end.toLocaleString()} \r\n` +
`请求响应耗时:${this.spendTime()}秒\r\n` +
`响应状态码:${this.stateCode}\r\n`
}
}
}
@Entry
@ComponentV2
struct Index {
@Local title: string = '基于拦截器的HTTP请求性能监控';
//请求地址列表
@Local requestUrlList: Array<string> =
["https://www.baidu.com", "https://www.baidu.com/nopage.html", "https://www.aliyun.com/"
, "https://developer.huawei.com/consumer/cn/forum/", "https://www.zhihu.com/"]
//请求次数
@Local requestTimes: number = 5
//拦截日志列表
@Local requestPerRecordList: Array<RequestPerfRecord> = new Array()
scroller: Scroller = new Scroller()
build() {
Row() {
Column() {
Text(this.title)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.width('100%')
.textAlign(TextAlign.Center)
.padding(5)
Text("请求地址列表:")
.fontSize(14)
.width('100%')
.padding(5)
List({ space: 5, initialIndex: 0 }) {
ForEach(this.requestUrlList, (item: string) => {
ListItem() {
Text(item)
.fontSize(13)
}
}, (item: string) => item)
}.width('100%')
.padding(5)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("请求次数:")
.fontSize(14)
.width(80)
Counter() {
Text(this.requestTimes.toString())
}
.onInc(() => {
this.requestTimes++
})
.onDec(() => {
this.requestTimes--
})
.width(140)
.padding(10)
.height(50)
Button("随机请求")
.onClick(() => {
this.requestTest()
})
.width(100)
.fontSize(14)
}
.width('100%')
.padding(5)
Scroll(this.scroller) {
List({ space: 5, initialIndex: 0 }) {
ForEach(this.requestPerRecordList, (item: RequestPerfRecord) => {
ListItem() {
Text(item.toString())
.fontSize(13)
}
}, (item: string) => item)
}.width('100%')
.padding(5)
}
.align(Alignment.Top)
.backgroundColor(0xeeeeee)
.height(300)
.flexGrow(1)
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.On)
.scrollBarWidth(20)
}
.width('100%')
.justifyContent(FlexAlign.Start)
.height('100%')
}
.height('100%')
}
async requestTest() {
let cfg: rcp.SessionConfiguration = {
interceptors: [new PerfInterceptor(this.requestPerRecordList)]
}
const session = rcp.createSession(cfg);
for (let i = 0; i < this.requestTimes; i++) {
let index = Math.floor(Math.random() * this.requestUrlList.length)
await session.get(this.requestUrlList[index])
let sleepTime = Math.random() * 5 + 1
await sleep(sleepTime)
}
}
}
//休眠指定的毫秒数
function sleep(time: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, time));
}
//性能监控拦截器
class PerfInterceptor implements rcp.Interceptor {
requestPerList: Array<RequestPerfRecord>
constructor(requestPerList: Array<RequestPerfRecord>) {
this.requestPerList = requestPerList
}
//拦截方法
async intercept(context: rcp.RequestContext, next: rcp.RequestHandler): Promise<rcp.Response> {
let record = new RequestPerfRecord(context.request)
this.requestPerList.push(record)
const promise = next.handle(context);
promise.then((resp) => {
record.stateCode = resp.statusCode;
record.end = new Date()
});
return promise;
}
}
复制代码
步骤 4:编译运行,可以使用模拟器或者真机。
步骤 5:按照本节第 1 部分“拦截器性能监控演示”操作即可。
4. 代码分析
本示例的关键点在于记录 HTTP 请求及响应的时间,这是通过拦截器的 intercept 方法实现的,该方法首先记录当前的时间,也就是请求发起的时间,然后调用 next 的 handle 方法,该方法会发起 HTTP 请求并返回响应,在该方法的响应处理回调函数中,再次记录当时的时间,也就是响应返回的时间,这样就拿到了最关键的两个时间信息,具体代码如下所示:
async intercept(context: rcp.RequestContext, next: rcp.RequestHandler): Promise<rcp.Response> {
let record = new RequestPerfRecord(context.request)
this.requestPerList.push(record)
const promise = next.handle(context);
promise.then((resp) => {
record.stateCode = resp.statusCode;
record.end = new Date()
});
return promise;
}
复制代码
另外,因为需要把记录的信息在应用界面上展示,所以把状态变量 requestPerRecordList 传递到了拦截器实例中,这样拦截器生成的 HTTP 请求日志信息也就保存到了 requestPerRecordList 变量中,从而可以在界面上随时看到拦截日志。
(本文作者原创,除非明确授权禁止转载)
本文源码地址:
https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/rcp/HttpRequestMonitor
本系列源码地址:
https://gitee.com/zl3624/harmonyos_network_samples
评论