赞
踩
在HarmonyOS应用开发中,通过HTTP访问网络,可以使用官方提供的@ohos.net.http模块。但是官方提供的直接使用不太好使用,需要封装下才好。推荐使用前端开发中流行的axios网络客户端库,如果是前端开发者,用 axios也会更加顺手。
Axios 是一个著名的基于 JavaScript 的开源库,用于浏览器和 Node.js 等环境中发送 HTTP 请求。它支持 Promise API,并且可以处理 XMLHttpRequests 和 Fetch API 背后的复杂性,为开发者提供了一种简洁易用的方式来实现 AJAX(Asynchronous JavaScript and XML)请求。
最早浏览器页面在向服务器请求数据,返回的是整个页面的数据,页面会强制刷新一下,这对于用户来讲并不是很友好。我们只想修改页面的部分数据,但是从服务器端返回的却是整个页面。于是出现一种新的技术,异步网络请求Ajax(Asynchronous JavaScript and XML),它能与后台服务器进行少量数据交换,使网页实现异步局部更新。
由于浏览器中原生的XMLHttpRequest API较难使用,于是又有了更多用于实现ajax的javascript框架出现,比如我们熟悉的jQuery、Dojo、YUI等等。而如今一个叫axios的轻量框架逐步脱颖而出,它本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范。
主要特性:
在HarmonyOS中,官方提供了@ohos/net.http 模块进行网络访问。它是官方提供的基础HTTP数据请求能力库,直接提供了对HTTP协议的底层支持,开发者可以通过这个模块发送GET、POST等HTTP请求,并处理响应结果。由于它是系统级别的API,其优点在于性能和兼容性得到保证,适用于基本的HTTP通信需求。
虽然官方提供了@ohos/net.http 模块进行网络访问,但是Axios库可以看作是一种功能更强大和易用的封装,且接口使用上更符合前端开发者的惯用习惯。Axios库 以其强大的功能性和易用性成为现代JavaScript应用中非常流行的HTTP客户端库。
直接使用原始的axios库肯定是不行,在HarmonyOS中的Axios库,模块名字是@ohos/axios。
@ohos/axios第三方库是基于axios库进行适配,使其可以运行在OpenHarmony中的一个发送网络请求库,并且本库沿用axios库现有用法和特性,使其更加适合于鸿蒙项目的开发。
@ohos/axios 模块可以理解为是对官方HTTP API的一个封装或者扩展,它提供了一种更高级别的抽象和便利性,可能包含了更多的功能特性,比如自动转换数据格式、错误处理、拦截器机制以及对于Promise的良好支持等,这些都是为了简化开发流程,提高开发效率。在实际开发应用时,如果需要更丰富和灵活的网络请求管理功能,通常推荐使用 @ohos/axios 这样的封装库。
通过对@ohos/axis源码的查看,发现也确实是使用ohos.net.http模块,对原库v1.3.4版本进行适配,使其可以应用在HarmonyOS上,并沿用其现有用法和特性。
ohos/axios 模块源码:
接口列表
接口 | 参数 | 功能 |
---|---|---|
axios(config) | config:请求配置 | 发送请求 |
axios.create(config) | config:请求配置 | 创建实例 |
axios.request(config) | config:请求配置 | 发送请求 |
axios.get(url[, config]) | url:请求地址 config:请求配置 | 发送get请求 |
axios.delete(url[, config]) | url:请求地址 config:请求配置 | 发送delete请求 |
axios.post(url[, data[, config]]) | url:请求地址 data:发送请求体数据 config:请求配置 | 发送post请求 |
axios.put(url[, data[, config]]) | url:请求地址 data:发送请求体数据 config:请求配置 | 发送put请求 |
属性列表
属性 | 描述 |
---|---|
axios.defaults['xxx'] | 默认设置 。值为请求配置 config 中的配置项 例如 axios.defaults.headers 获取头部信息 |
axios.interceptors | 拦截器。参考 拦截器 的使用 |
ohpm install @ohos/axis
如果提示无ohpm命令,则是环境变量没有配置。 点击配置ohpm命令查看详细配置。
"dependencies": { "@ohos/axis": "^2.1.0"}
需要配置 ohos.permission.INTERNET权限。在工程目录entry\src\main中找到module.json5文件,配置网络请求权限。
- {
- "module": {
- "name": "entry",
- "type": "entry",
- "description": "$string:module_desc",
- "mainElement": "EntryAbility",
- "deviceTypes": [
- "phone"
- ],
- "requestPermissions": [
- {
- "name": "ohos.permission.INTERNET"
- }
- ]
- }
- }
- import axios from '@ohos/axios'
- //创建axios的实例
- const instance = axios.create({
- baseURL: "http://xx.xx.xx.xx", //基路径,要看API帮助文档的特征来确定基路径
- timeout: 5000, //请求超时的时间
- headers: {
- "Content-Type": "application/json"
- }
- })
-
- //响应拦截器,通过响应拦截器进一步对返回的数据做处理
- instance.interceptors.response.use((response) => {
- //只返回接口有数据的结果
- if (200 === response.status) {
- return response.data; //接口返回的数据
- }
- return Promise.reject(response); //表示请求有错,交给catch来处理结构
- }, err => {
- return Promise.reject(err)
- })
-
- /**
- * get请求
- * @param params = {} 查询参数
- * @returns
- */
- export function httpGet(url:string, params = {}) {
- return instance.get<any>(url, {
- params
- })
- }
-
- /**
- * post请求
- * @param data = {} 请求体数据
- * @returns
- */
- export function httpPost(url:string, data = {}) {
- return instance.post<any>(url, {
- data
- })
- }
axios模块封装:
- //AxiosHttp.ets
-
- import axios, {
- AxiosInstance,
- AxiosRequestConfig,
- AxiosRequestHeaders,
- AxiosResponse,
- InternalAxiosRequestConfig
- } from "@ohos/axios";
- import { LogUtils } from '../utils/LogUtils';
-
-
- /**
- * 定义接口响应包装类
- */
- export interface BaseResponse {
- //wanAndroid-API响应体
- errorCode: number
- errorMsg: string
- //拓展xxx-API响应体
- }
-
- /**
- * 接口实现类包装,例如有其他业务可以再次继承实现xxxResponse
- */
- export interface ApiResponse<T = any> extends BaseResponse {
- //wanAndroid-API响应体
- data: T | any;
- //拓展xxx-API响应体
- }
-
- /**
- * 封装后,不支持传入拦截器
- * 需要自己定义接口继承 AxiosRequestConfig类型
- * 从而支持传入拦截器,但拦截器选项应为可选属性
- * 之后请求实例传入的options为继承了AxiosRequestConfig的自定义类型
- */
- interface InterceptorHooks {
- requestInterceptor?: (config: HttpRequestConfig) => Promise<HttpRequestConfig>;
- requestInterceptorCatch?: (error: any) => any;
- responseInterceptor?: (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
- responseInterceptorCatch?: (error: any) => any;
- }
-
- // @ts-ignore
- interface HttpRequestConfig extends InternalAxiosRequestConfig {
- showLoading?: boolean; //是否展示请求loading
- checkResultCode?: boolean; //是否检验响应结果码
- checkLoginState?: boolean //校验用户登陆状态
- needJumpToLogin?: boolean //是否需要跳转到登陆页面
- interceptorHooks?: InterceptorHooks;
- headers?: AxiosRequestHeaders
- }
-
-
- /**
- * 网络请求构造
- * 基于axios框架实现
- */
- class AxiosHttpRequest {
- config: HttpRequestConfig;
- interceptorHooks?: InterceptorHooks;
- instance: AxiosInstance;
-
- constructor(options: HttpRequestConfig) {
- this.config = options;
- this.interceptorHooks = options.interceptorHooks;
- this.instance = axios.create(options);
- this.setupInterceptor()
- }
-
- setupInterceptor(): void {
- this.instance.interceptors.request.use(
- //这里主要是高版本的axios中设置拦截器的时候里面的Config属性必须是InternalAxiosRequestConfig,但是InternalAxiosRequestConfig里面的headers是必传,所以在实现的子类我设置成非必传会报错,加了个忽略注解
- // @ts-ignore
- this.interceptorHooks?.requestInterceptor,
- this.interceptorHooks?.requestInterceptorCatch,
- );
- this.instance.interceptors.response.use(
- this.interceptorHooks?.responseInterceptor,
- this.interceptorHooks?.responseInterceptorCatch,
- );
- }
-
- // 类型参数的作用,T决定AxiosResponse实例中data的类型
- request<T = any>(config: HttpRequestConfig): Promise<T> {
- return new Promise<T>((resolve, reject) => {
- this.instance
- .request<any, T>(config)
- .then(res => {
- resolve(res);
- })
- .catch((err) => {
- LogUtils.error("网络请求Request异常:", err.message)
- errorHandler(err)
- if (err) {
- reject(err);
- }
- });
- });
- }
-
- get<T = any>(config: HttpRequestConfig): Promise<T> {
- return this.request({ ...config, method: 'GET' });
- }
-
- post<T = any>(config: HttpRequestConfig): Promise<T> {
- return this.request({ ...config, method: 'POST' });
- }
-
- delete<T = any>(config: HttpRequestConfig): Promise<T> {
- return this.request({ ...config, method: 'DELETE' });
- }
-
- patch<T = any>(config: HttpRequestConfig): Promise<T> {
- return this.request({ ...config, method: 'PATCH' });
- }
- }
-
- export function errorHandler(error: any) {
- if (error instanceof AxiosError) {
- showToast(error.message)
- } else if (error != undefined && error.response != undefined && error.response.status) {
- switch (error.response.status) {
- // 401: 未登录
- // 未登录则跳转登录页面,并携带当前页面的路径
- // 在登录成功后返回当前页面,这一步需要在登录页操作。
- case 401:
-
- break;
- // 403 token过期
- // 登录过期对用户进行提示
- // 清除本地token和清空vuex中token对象
- // 跳转登录页面
- case 403:
- showToast("登录过期,请重新登录")
- // 清除token
- // localStorage.removeItem('token');
- break;
- // 404请求不存在
- case 404:
- showToast("网络请求不存在")
- break;
-
- // 其他错误,直接抛出错误提示
- default:
- showToast(error.response.data.message)
- }
-
- }
- }
-
- export default AxiosHttpRequest
- //AxiosRequest.ets
- import {AxiosHttpRequest,errorHandler} from './AxiosHttp'
- import { AxiosError, AxiosRequestHeaders } from '@ohos/axios';
- import { LogUtils } from '../utils/LogUtils';
- import showToast from '../utils/ToastUtils';
- import { hideLoadingDialog, showLoadingDialog } from '../utils/DialogUtils';
- import { StorageUtils } from '../utils/StorageUtils';
- import { StorageKeys } from '../constants/StorageKeys';
- import { JsonUtils } from '../utils/JsonUtils';
- import { Router } from '../route/Router';
- import { RoutePath } from '../route/RoutePath';
-
- /**
- * axios请求客户端创建
- */
- const axiosClient = new AxiosHttpRequest({
- baseURL: "/api",
- timeout: 10 * 1000,
- checkResultCode: false,
- headers: {
- 'Content-Type': 'application/json'
- } as AxiosRequestHeaders,
- interceptorHooks: {
- requestInterceptor: async (config) => {
- // 在发送请求之前做一些处理,例如打印请求信息
- LogUtils.debug('网络请求Request 请求方法:', `${config.method}`);
- LogUtils.debug('网络请求Request 请求链接:', `${config.url}`);
- LogUtils.debug('网络请求Request Params:', `\n${JsonUtils.stringify(config.params)}`);
- LogUtils.debug('网络请求Request Data:', `${JsonUtils.stringify(config.data)}`);
- axiosClient.config.showLoading = config.showLoading
- if (config.showLoading) {
- showLoadingDialog("加载中...")
- }
- if (config.checkLoginState) {
- let hasLogin = await StorageUtils.get(StorageKeys.USER_LOGIN, false)
- LogUtils.debug('网络请求Request 登录状态校验>>>', `${hasLogin.toString()}`);
- if (hasLogin) {
- return config
- } else {
- if (config.needJumpToLogin) {
- Router.push(RoutePath.TestPage)
- }
- throw new AxiosError("请登录")
- }
- }
- return config;
- },
- requestInterceptorCatch: (err) => {
- LogUtils.error("网络请求RequestError", err.toString())
- if (axiosClient.config.showLoading) {
- hideLoadingDialog()
- }
- return err;
- },
- responseInterceptor: (response) => {
- //优先执行自己的请求响应拦截器,在执行通用请求request的
- if (axiosClient.config.showLoading) {
- hideLoadingDialog()
- }
- LogUtils.debug('网络请求响应Response:', `\n${JsonUtils.stringify(response.data)}`);
- if (response.status === 200) {
- // @ts-ignore
- const checkResultCode = response.config.checkResultCode
- if (checkResultCode && response.data.errorCode != 0) {
- showToast(response.data.errorMsg)
- return Promise.reject(response)
- }
- return Promise.resolve(response.data);
- } else {
- return Promise.reject(response);
- }
- },
- responseInterceptorCatch: (error) => {
- if (axiosClient.config.showLoading) {
- hideLoadingDialog()
- }
- LogUtils.error("网络请求响应异常", error.toString())
- errorHandler(error);
- return Promise.reject(error);
- },
- }
- });
-
- export default axiosClient;
经过封装后,使用变得很简单了。示例如下:
-
- import axiosClient from './AxiosRequest'
-
- let baseUrl = "https://www.wanandroid.com/"
-
- //返回数据结构定义
- interface HomeModelIssueList {
- releaseTime: number;
- type: string;
- date: number;
- publishTime: number;
- count: number;
- }
-
- interface HomeModel {
- issueList: HomeModelIssueList[];
- itemList: HomeModelIssueList[];
- nextPageUrl: string;
- nextPublishTime: number;
- newestIssueType: string;
- }
-
- interface BannerDataModelData {
- desc: string;
- id: number;
- imagePath: string;
- isVisible: number;
- order: number;
- title: string;
- type: number;
- url: string;
- }
-
- /**
- * 请求首页数据-axios客户端请求
- * @param date
- * @returns
- */
- export function getHomeListAxios(date: string = "") {
- return axiosClient.get<HomeModel>({
- url: baseUrl + "api/v2/feed",
- params: { "date": date },
- showLoading: true
- // headers: { "Accept": "application/json" } as AxiosRequestHeaders
- })
- }
-
- /**
- * 获取分类详情接口
- * @param id
- * @param start
- */
- export function getCategoryDetailList(id: number, start: number) {
- return axiosClient.get<HomeModel>(
- {
- url: baseUrl + "api/v4/categories/videoList",
- params: {
- "id": id,
- "start": start,
- "udid": CommonConstants.UUID,
- deviceModel: CommonConstants.DEVICE_NUM
- }
- }
- )
- }
-
- /**
- * 获取wanAndroid首页Banner数据,测试校验API data:T泛型数据
- * @returns
- */
- export function getWanAndroidBanner() {
- return axiosClient.get<ApiResponse<BannerDataModelData[]>>(
- {
- url: wanAndroidUrl + "/banner/json",
- checkResultCode: true,
- showLoading: true
- }
- )
- }
在HarmonyOS(OpenHarmony)中,@ohos/net.http 是官方提供的一个用于进行HTTP通信的基础模块。开发者可以利用这个模块发送和接收HTTP请求与响应,实现应用程序与服务器之间的数据交互。
@ohos/net.http模块直接使用起来不太友好,简易封装如下:
- //http.ets
- /**
- * 定义接口响应包装类
- */
- import http from '@ohos.net.http';
-
- export interface BaseResponse {
- //wanAndroid-API响应体
- errorCode: number
- errorMsg: string
- //拓展xxx-API响应体
- }
-
- /**
- * 接口实现类包装,例如有其他业务可以再次继承实现xxxResponse
- */
- export interface ApiResponse<T = any> extends BaseResponse {
- //wanAndroid-API响应体
- data: T | any;
- //拓展xxx-API响应体
- }
-
- interface HttpRequestConfig extends http.HttpRequestOptions {
- showLoading?: boolean; //是否展示请求loading
- checkResultCode?: boolean; //是否检验响应结果码
- checkLoginState?: boolean //校验用户登陆状态
- needJumpToLogin?: boolean //是否需要跳转到登陆页面
- url?: string, //请求网络链接
- }
-
-
- /**
- * 网络请求构造器
- * 基于鸿蒙默认的http框架实现
- */
- class HttpBuilder {
- httpClient: http.HttpRequest
- config: HttpRequestConfig
-
- constructor(options: HttpRequestConfig) {
- this.httpClient = http.createHttp()
- this.config = options
- this.setupInterceptor()
- }
- /**
- * 配置属性拦截器
- */
- setupInterceptor() {
-
- }
-
- request<T = any>(config: HttpRequestConfig): Promise<T> {
- return new Promise<T>((resolve, reject) => {
- this.httpClient.request(
- config.url,
- config,
- (error, data) => {
- if (!error) {
- resolve(data.result as T);
- } else {
- reject(error)
- }
- // 当该请求使用完毕时,调用destroy方法主动销毁x
- this.httpClient.destroy()
- }
- )
- })
- }
-
- get<T = any>(config: HttpRequestConfig): Promise<T> {
- return this.request({ ...config, method: http.RequestMethod.GET })
- }
-
- post<T = any>(config: HttpRequestConfig): Promise<T> {
- return this.request({ ...config, method: http.RequestMethod.POST })
- }
- }
-
- export default HttpBuilder
- import http from '@ohos.net.http';
- import showToast from '../utils/ToastUtils';
- import HttpBuilder from './http';
-
- //接口发送超时
- const READ_TIMEOUT = 100000
- //接口读取超时
- const CONNECT_TIMEOUT = 100000
-
- let baseUrl = "https://www.wanandroid.com/"
-
- const httpClient = new HttpBuilder({
- readTimeout: READ_TIMEOUT,
- connectTimeout: CONNECT_TIMEOUT
- })
-
- //返回数据结构定义
- interface HomeModelIssueList {
- releaseTime: number;
- type: string;
- date: number;
- publishTime: number;
- count: number;
- }
-
- interface HomeModel {
- issueList: HomeModelIssueList[];
- itemList: HomeModelIssueList[];
- nextPageUrl: string;
- nextPublishTime: number;
- newestIssueType: string;
- }
-
- /**
- * 请求数据--系统http请求
- * @param date
- * @returns
- */
- export function getHomeList(date: string = "") {
- return httpClient.get<HomeModel>({
- url: baseUrl + "api/v2/feed",
- extraData: { "date": date }
- })
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。