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/maste...

本系列源码地址:
https://gitee.com/zl3624/harmonyos_network_samples


长弓三石
1 声望0 粉丝