鸿蒙应用开发——http数据请求

514 阅读5分钟

1. 运行官网提供的案例

网络接口请求必不可少,是前端对接后端的必经途径。在华为官网HarmonyOS第一课从网络获取数据中列出的案例之《新闻加载数据》中,下载源码解压后,可看到如下所示的目录:

image.png

其中HttpServerOfNews 明显不是属于HarmonyOS开发包中的内容。将这个文件夹用其他编辑器打开(VScode、webstorm、Cursor等都可以),看下这个文件夹内的目录结构。

image.png

这个打开后,结构很了然。按照顺手的习惯,打开终端,按顺序执行以下步骤:

npm install
npm run start
image.png

执行成功后,在回到DevEco Studio关于新闻加载数据的项目上,将请求的service改为本地启动服务连接。

在src/main/ets/common/constant/CommonConstant.ets中找到

static readonly SERVER: string = 'http://**.**.**.**:9588';

将其改为

static readonly SERVER: string = 'http://localhost:9588';

或者改为本机的局域网Ip地址。上述写服务端的漏掉了在log中打印出本地的IP地址,我们可以打个log看看。在服务端项目中,打开根目录下app.js文件。将

app.listen(9588, () => {
  const os = require('os');
  const networkInterfaces = os.networkInterfaces();
  let localIpAddress = '';

  Object.keys(networkInterfaces).forEach((interfaceName) => {
    const interfaces = networkInterfaces[interfaceName];
    for (let i = 0; i < interfaces.length; i++) {
      const iface = interfaces[i];
      if (iface.family === 'IPv4' && !iface.internal) {
        localIpAddress = iface.address;
        break;
      }
    }
    if (localIpAddress) return;
  });

  console.log(`本机局域网访问地址: http://${localIpAddress}:9588`);
  console.log('服务器启动成功!');
});

保存后重新启动。

image.png

得到这个地址,可以替换上述的localhost地址。

然后打开entry/src/main/ets/pages/index.ets,点击右侧边栏Preview就能看到数据请求的结果啦。

image.png

如果想要在服务端更新数据,然后想使用自动服务重启服务器,就要先安装nodemon。

cnpm install nodemon -g

然后运行

npm run dev

2. 如何使用HTTP访问网络

2.1 在moudle.json5文件中配置网络访问权限

// module.json5
"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET",
    "reason": "$string:dependency_reason",
    "usedScene": {
      "abilities": [
        "EntryAbility"
      ],
      "when": "inuse"
    }
  }
]

2.2 封装http请求方法

封装下网络请求方法对前端开发人员来说是非常熟悉的,封装http请求对统一的接口逻辑进行管理,设置统一的错误处理机制,有利于维护和修改,而且简化了调用的方式。所以这个步骤是必不可少的。

  1. 引入http模块,使用creteHttp返回一个HttpRequest对象,里面包括request、requestInStream、destroy、on和off方法。
import { http } from '@kit.NetworkKit';
let httpRequest = http.createHttp();
  1. 请求格式
httpRequest.request('EXAMPLE_URL',
    {//可选参数},
    (err: BusinessError, data: http.HttpResponse)=>{

    }
)

填写HTTP请求的URL地址,可以带参数也可以不带参数。URL地址需要开发者自定义。请求的参数可以在可选参数中extraData中指定。

  1. 可选参数
  • method: http.RequestMethod.POST, // 可选,默认为http.RequestMethod.GET;当使用POST请求时此字段用于传递请求体内容,具体格式与服务端协商确定

  • extraData: 'data to send',//接口参数放置其中

  • expectDataType: http.HttpDataType.STRING, // 可选,指定返回数据的类型

  • usingCache: true, // 可选,默认为true

  • priority: 1, // 可选,默认为1

  • header: { 'Accept' : 'application/json' },// 开发者根据自身业务需要添加header字段

  • readTimeout: 60000, // 可选,默认为60000ms

  • connectTimeout: 60000, // 可选,默认为60000ms

  • usingProtocol: http.HttpProtocol.HTTP1_1, // 可选,协议类型默认值由系统自动指定

  • usingProxy: false, //可选,默认不使用网络代理,自API 10开始支持该属性

  • caPath: '/path/to/cacert.pem', // 可选,默认使用系统预设CA证书,自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开始支持该属性
  • certificatePinning:[ ]//可选,支持证书锁定配置信息的动态设置,自API 12开始支持该属性

  • multiFormDataList:[ ] //可选,仅当Header中,'content-Type'为'multipart/form-data'时生效,自API 11开始支持该属性(常见表单文件上传、或者复杂嵌套数据场景等)

2.4 错误处理

做错误处理的时候,需要先引入对应的模块

import { BusinessError } from '@kit.BasicServicesKit'

httpRequest.request('EXAMPLE_URL',
    {//可选参数},
    (err: BusinessError, data: http.HttpResponse)=>{
    if (!err) {
        // data.result为HTTP响应内容,可根据业务需要进行解析
        console.info('Result:' + JSON.stringify(data.result));
        console.info('code:' + JSON.stringify(data.responseCode));
        console.info('type:' + JSON.stringify(data.resultType));
        // data.header为HTTP响应头,可根据业务需要进行解析
        console.info('header:' + JSON.stringify(data.header));
        console.info('cookies:' + JSON.stringify(data.cookies)); // 自API version 8开始支持cookie
        // 取消订阅HTTP响应头事件
        httpRequest.off('headersReceive');
        // 当该请求使用完毕时,开发者务必调用destroy方法主动销毁该JavaScript Object。
        httpRequest.destroy();
      } else {
        console.info('error:' + JSON.stringify(err));
        // 取消订阅HTTP响应头事件
        httpRequest.off('headersReceive');
        // 当该请求使用完毕时,开发者务必调用destroy方法主动销毁该JavaScript Object。
        httpRequest.destroy();
      }
 }
)

2.5 案例中的封装

// HttpUtil.ets
import { http } from '@kit.NetworkKit';
...
export function httpRequestGet(url: string): Promise<ResponseResult> {
  let httpRequest = http.createHttp();
  let responseResult = httpRequest.request(url, {
    method: http.RequestMethod.GET,
    readTimeout: 10000,
    header: {
      'Content-Type': ContentType.JSON
    },
    connectTimeout: 10000,
    extraData: {}
  });
  let serverData: ResponseResult = new ResponseResult();
  // Processes the data and returns.
  return responseResult.then((value: http.HttpResponse) => {
    if (value.responseCode === 200) {
      // 获取返回数据
      let result = `${value.result}`;
      let resultJson: ResponseResult = JSON.parse(result);
      if (resultJson.code === 'success') {
        serverData.data = resultJson.data;
      }
      serverData.code = resultJson.code;
      serverData.msg = resultJson.msg;
    } else {
      serverData.msg = `'网络请求失败,请稍后尝试!'&${value.responseCode}`;
    }
    return serverData;
  }).catch(() => {
    serverData.msg = '网络请求失败,请稍后尝试!';
    return serverData;
  })
}

2.6 使用封装好的请求

封装了http的请求后,就是在model里面使用,下面按照get请求得到返回数据的方法得到新闻类型列表的结果。

import { httpRequestGet } from '../common/utils/HttpUtil';
class NewsViewModel {
    getNewsTypeList(): Promise<NewsTypeModel[]> {
      return new Promise((resolve: Function, reject: Function) => {
        let url = `${Const.SERVER}/${Const.GET_NEWS_TYPE}`;
        httpRequestGet(url).then((data: ResponseResult) => {
          if (data.code === Const.SERVER_CODE_SUCCESS) {
            resolve(data.data);
          } else {
            reject(Const.TabBars_DEFAULT_NEWS_TYPES);
          }
        }).catch(() => {
          reject(Const.TabBars_DEFAULT_NEWS_TYPES);
        });
      });
    }
}

3. 总结

通过查阅官方API文档,在@ohos.net.http (数据请求)的开头就写了说明如下

本模块首批接口从API version 6开始支持。后续版本的新增接口,采用上角标单独标记接口的起始版本。 建议使用Remote Communication Kit进行HTTP请求,Remote Communication Kit将持续演进。

emmm……官网课程当中提供的案例,不一定是最新的,更不一定的目前推荐的,牢记这条,毕竟鸿蒙还在不断发展的阶段。跟上鸿蒙的变化,以万变迎接万变,自是最稳定的招数。

接下来看看Remote Communication Kit进行HTTP请求是什么样子的。

  1. 先导入模块
import { rcp } from '@kit.RemoteCommunicationKit';
import { BusinessError } from '@kit.BasicServicesKit';
  1. 创建会话 区分不同方式的请求
const session = rcp.createSession();
// 1. get请求
session.get("EXAMPLE_URL1").then((response) => {
  console.info(`Response succeeded: ${response}`);
}).catch((err: BusinessError) => {
  console.error(`Response err: Code is ${err.code}, message is ${JSON.stringify(err)}`);
});

//2. post请求
session.post("EXAMPLE_URL2", "data to send").then((response) => {
  console.info(`Response succeeded: ${response}`);
}).catch((err: BusinessError) => {
  console.error(`Response err: Code is ${err.code}, message is ${JSON.stringify(err)}`);
});

//3. put请求
session.put("EXAMPLE_URL3", "data to send").then((response) => {
  console.info(`Response succeeded: ${response}`);
}).catch((err: BusinessError) => {
  console.error(`Response err: Code is ${err.code}, message is ${JSON.stringify(err)}`);
});

//4. delete请求
session.delete("EXAMPLE_URL4").then((response) => {
  console.info(`Response succeeded: ${response}`);
}).catch((err: BusinessError) => {
  console.error(`Response err: Code is ${err.code}, message is ${JSON.stringify(err)}`);
});

无论是选择哪种方案,目前都是支持的。封装好http请求,更改的时候也只需要更改这一块的文件就可以了,估摸这个后续变更也不会太大吧。