在鸿蒙的广袤开发世界中,网络层作为信息交换的桥梁,其重要性不言而喻。今天,我将带领大家一同探索如何以艺术般的手法,优雅地封装鸿蒙官方的网络库,为我们的应用搭建一个高效、灵活的网络层。我们在下一篇章中,将深入阐述如何利用这一封装完善的网络库,轻松驾驭网络层的开发与使用。
一、封装目的:可拓展与可拦截
在鸿蒙应用开发中,网络请求的封装不仅是为了简化开发流程,更是为了提高代码的复用性和可维护性。我们的封装目标主要围绕以下两点:
- 可拓展性:允许开发者根据业务需求,轻松扩展网络请求的功能,如添加自定义请求头、设置请求超时时间等。
- 可拦截性:提供网络请求的拦截机制,使得我们可以在请求发送前或响应返回后进行一系列操作,如添加日志记录、错误处理等。
二、定义基础元素:错误常量与字符串
1. 错误常量定义
为了统一管理网络请求中的错误码,我们定义了一个NetworkServiceErrorConst
类,用于存储各种网络请求可能遇到的错误码:
export class NetworkServiceErrorConst {
// 网络不可用
static readonly UN_AVAILABLE: number = 100000;
// URL错误
static readonly URL_ERROR: number = 100001;
// URL不存在错误
static readonly URL_NOT_EXIST_ERROR: number = 100002;
// 网络错误
static readonly NET_ERROR: number = 100003;
// ...其他可能的错误码
}
2. 错误字符串定义
同时,我们还需要定义与错误码对应的错误字符串,以便在应用中展示给用户:
{
"name": "network_unavailable",
"value": "网络不可用"
},
{
"name": "invalid_url_format",
"value": "URL格式不合法"
},
{
"name": "invalid_url_not_exist",
"value": "URL不存在"
}
三、实用工具集
URL 校验
为了确保网络请求中的URL格式正确,我们提供了一个isValidUrl
函数,它使用正则表达式来验证URL的有效性。
private isValidUrl(url: string): boolean {
// 正则表达式匹配各种可能的URL格式
const urlPattern = new RegExp(
'^(https?:\\/\\/)?' + // 协议
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // 域名
'((\\d{1,3}\\.){3}\\d{1,3}))' + // 或IPv4地址
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // 端口和路径
'(\\?[;&a-z\\d%_.~+=-]*)?' + // 查询字符串
'(\\#[-a-z\\d_]*)?$', // 片段定位符
'i' // 忽略大小写
);
return urlPattern.test(url); // 返回验证结果
}
// 使用示例
if (isValidUrl("http://example.com")) {
console.log("URL is valid.");
} else {
console.log("URL is invalid.");
}
参数拼接
当需要在URL中附加查询参数时,appendQueryParams
函数可以帮助我们轻松实现。它支持处理单个值或数组值的参数,并自动处理编码。
private appendQueryParams(url: string, queryParams: Map<string, any> | undefined): string {
if (!queryParams || queryParams.size === 0) {
return url;
}
const paramsArray: string[] = [];
queryParams.forEach((value, key) => {
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
paramsArray.push(`${encodeURIComponent(`${key}[${i}]`)}=${encodeURIComponent(value[i].toString())}`);
}
} else {
paramsArray.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.toString())}`);
}
});
// 检查URL是否已包含查询参数,并据此决定使用'?'或'&'作为分隔符
const separator = url.includes('?') ? '&' : '?';
return url + separator + paramsArray.join('&');
}
// 使用示例
const baseUrl = "http://example.com/search";
const params = new Map<string, any>();
params.set("q", "test");
params.set("page", 2);
const urlWithParams = appendQueryParams(baseUrl, params);
console.log(urlWithParams); // 输出: http://example.com/search?q=test&page=2
通过上述两个工具函数,我们可以确保网络请求的URL既正确又包含了所需的查询参数,从而提高了网络请求的准确性和可靠性。
四、编写网络拦截器
在网络请求和响应的过程中,网络拦截器(Interceptor)是一个非常重要的概念。它们允许我们在请求发送前、响应接收后或发生错误时执行特定的逻辑,比如添加网络参数、记录日志、处理错误等。
首先,我们定义一个NetworkInterceptor
接口,它规定了拦截器必须实现的方法:
import { http } from '@kit.NetworkKit';
import { RequestOptions } from '../NetworkService';
export interface NetworkInterceptor {
beforeRequest(request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void;
afterResponse(response: http.HttpResponse | object, request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void;
onError(error: Error, request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void;
}
接下来,我们实现一个默认的拦截器DefaultInterceptor
,它实现了NetworkInterceptor
接口:
import { http } from '@kit.NetworkKit';
import { RequestOptions } from '../NetworkService';
import { LibLogManager, TAG } from '../LogService';
export class DefaultInterceptor implements NetworkInterceptor {
beforeRequest(request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void {
// 可以在这里添加网络参数,或者对请求进行其他处理
httprequest.on('headersReceive', (header) => {
LibLogManager.getLogger().info(TAG, 'Received headers: ' + JSON.stringify(header));
});
// 如果有异步操作,需要返回Promise
// 这里没有异步操作,所以直接返回
}
afterResponse(response: http.HttpResponse | object, request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void {
// 响应接收后,可以处理响应数据,或者记录日志
httprequest.off('headersReceive'); // 移除事件监听器
LibLogManager.getLogger().info(TAG, 'Response received: ' + JSON.stringify(response));
// 如果有异步操作,需要返回Promise
// 这里没有异步操作,所以直接返回
}
onError(error: Error, request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void {
// 发生错误时,可以记录错误日志,或者进行错误处理
httprequest.off('headersReceive'); // 移除事件监听器
LibLogManager.getLogger().error(TAG, 'Network error occurred: ' + JSON.stringify(error));
// 如果有异步操作,需要返回Promise
// 这里没有异步操作,所以直接返回
}
}
注意:
- 在上面的
beforeRequest
方法中,我添加了一个对headersReceive
事件的监听。 - 在
afterResponse
和onError
方法中,我调用了httprequest.off('headersReceive')
来移除之前添加的事件监听器。这是为了避免内存泄漏,因为如果你不断发送新的请求而不移除旧的监听器,那么这些监听器将一直存在于内存中。 - 在实际项目中,你可能需要根据你的网络库和项目的需求来调整这些拦截器的实现。例如,你可能需要在
beforeRequest
方法中添加请求头、身份验证令牌等。在afterResponse
方法中,你可能需要处理JSON响应数据,或者将其转换为其他格式。在onError
方法中,你可能需要执行更复杂的错误处理逻辑,比如重试机制、错误上报等。
五、网络请求封装核心类
在网络编程中,发起HTTP请求是一项常见的任务。为了简化这一过程并使其更加标准化和可维护,我们创建了一个网络请求封装的核心类。这个类提供了一套灵活的API,允许用户通过配置化的方式发起各种HTTP请求。
1. 请求配置类:RequestOptions
首先,我们定义了一个RequestOptions
接口,它包含了发起HTTP请求所需的所有配置参数。这个接口的设计非常灵活,可以适应各种复杂的HTTP请求场景。
export interface RequestOptions {
baseUrl?: string; // 基础URL
act?: string; // 请求的动作或路径
method?: RequestMethod; // 请求方法,默认为GET
queryParams?: Map<string, any>; // 查询参数,支持多种数据类型
header?: Object; // 请求头信息
extraData?: string | Object | ArrayBuffer; // 额外的请求数据
expectDataType?: http.HttpDataType; // 预期的响应数据类型
usingCache?: boolean; // 是否使用缓存
priority?: number; // 请求的优先级
connectTimeout?: number; // 连接超时时间
readTimeout?: number; // 读取超时时间
multiFormDataList?: Array<http.MultiFormData>; // 用于POST表单请求的表单数据列表
}
2. 请求方法枚举:RequestMethod
为了支持各种HTTP请求方法,我们定义了一个RequestMethod
枚举。这个枚举包含了所有标准的HTTP请求方法,如GET、POST、PUT等。
export enum RequestMethod {
OPTIONS = "OPTIONS",
GET = "GET",
HEAD = "HEAD",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
TRACE = "TRACE",
CONNECT = "CONNECT"
}
3. 网络请求封装核心类:NetworkService
基于RequestOptions
和RequestMethod
,我们创建了一个名为NetworkService
的网络请求封装核心类。这个类提供了request
方法,用于发起HTTP请求。request
方法接受一个RequestOptions
对象作为参数,并根据该对象的配置发起相应的HTTP请求。
除了request
方法外,NetworkService
类还支持注册拦截器(Interceptor)。拦截器可以在请求发送前和响应返回后进行额外的处理,如添加请求头、处理响应数据等。这使得用户可以灵活地定制网络请求的行为。
export class NetworkService {
baseUrl:string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
private interceptors: NetworkInterceptor[] = [];
addInterceptor(interceptor: NetworkInterceptor): void {
this.interceptors.push(interceptor);
}
async request(requestOption: RequestOptions): Promise<http.HttpResponse | null> {
let response: http.HttpResponse | null = null;
let error: Error | null = null;
// 每一个httpRequest对应一个HTTP请求任务,不可复用
let httpRequest = http.createHttp();
//开始发请求
try {
//如果url是传入的,则用传入的url
requestOption.baseUrl = requestOption.baseUrl?requestOption.baseUrl:this.baseUrl;
// 调用拦截器的beforeRequest方法
for (const interceptor of this.interceptors) {
await interceptor.beforeRequest(requestOption, httpRequest);
}
if(requestOption.baseUrl === null || requestOption.baseUrl.trim().length === 0){
throw new NetworkError(NetworkServiceErrorConst.URL_NOT_EXIST_ERROR, Application.getInstance().resourceManager.getStringSync($r("app.string.invalid_url_not_exist")))
}
if (!LibNetworkStatus.getInstance().isNetworkAvailable()) {
LibLogManager.getLogger().error("HttpCore","网络不可用")
throw new NetworkError(NetworkServiceErrorConst.UN_AVILABLE, Application.getInstance().resourceManager.getStringSync($r("app.string.network_unavailable")))
}
if (!this.isValidUrl(requestOption.baseUrl)) {
LibLogManager.getLogger().error("HttpCore","url格式不合法")
throw new NetworkError(NetworkServiceErrorConst.URL_ERROR, Application.getInstance().resourceManager.getStringSync($r("app.string.invalid_url_format")))
}
let defalutHeader :Record<string,string> = {
'Content-Type': 'application/json'
}
let response = await httpRequest.request(this.appendQueryParams(requestOption.baseUrl, requestOption.queryParams), {
method: requestOption.method,
header: requestOption.header || defalutHeader,
extraData: requestOption.extraData, // 当使用POST请求时此字段用于传递内容
expectDataType: requestOption.expectDataType||http.HttpDataType.STRING, // 可选,指定返回数据的类型
usingCache: requestOption.usingCache, // 可选,默认为true
priority: requestOption.priority, // 可选,默认为1
connectTimeout: requestOption.connectTimeout, // 可选,默认为60000ms
readTimeout: requestOption.readTimeout, // 可选,默认为60000ms
multiFormDataList: requestOption.multiFormDataList,
})
if (http.ResponseCode.OK !== response.responseCode) {
response = response;
} else{
throw new NetworkResponseError(response.responseCode,Application.getInstance().resourceManager.getStringSync($r("app.string.network_unavailable")))
}
// 调用拦截器的afterResponse方法
for (const interceptor of this.interceptors) {
await interceptor.afterResponse(response, requestOption, httpRequest );
}
} catch (e) {
error = e;
}
// 根据是否有错误来调用拦截器的afterResponse或onError方法
if (error) {
for (const interceptor of this.interceptors) {
await interceptor.onError(error, requestOption, httpRequest);
}
httpRequest.destroy();
throw error; // 重新抛出错误以便调用者可以处理
} else{
httpRequest.destroy();
return response;
}
}
private isValidUrl(url: string): boolean {
}
private appendQueryParams(url: string, queryParams: Map<string, number|string|boolean|Array<number> | Array<string> | Array<boolean> >|undefined): string {
}
}
通过使用NetworkService
类,用户可以以更加专业和简洁的方式发起HTTP请求,并享受到配置化、拦截器等高级功能带来的便利。这不仅可以提高开发效率,还可以使代码更加清晰、易于维护。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。