前言
在鸿蒙开发的广袤天地中,网络层的搭建与封装无疑是构建高效、稳定应用的基石。继上篇的探索之后,本文将继续深入网络层的优化之旅,揭秘如何通过类型转换器、请求查询附加器以及丰富的常量参数,将网络层的构建艺术推向一个新的高度。
一、网络请求的深度优化
数据类型转换器:定义与实践
在网络请求的世界里,数据格式的转换至关重要。我们通过定义DataConverter
接口,实现了对请求与响应数据类型的灵活转换。
export interface DataConverter {
requestConvert(extraData: string | Object | ArrayBuffer): string | Object | ArrayBuffer;
responseConvert(data: string | Object | ArrayBuffer, responseType: http.HttpDataType): string | Object | ArrayBuffer;
}
默认数据转换器:JSON转换器的实现
我们实现了一个默认的JsonDataConverter
,它将请求数据转换为JSON字符串,并根据响应类型将响应数据转换为适当的格式。
export class JsonDataConverter implements DataConverter {
requestConvert(extraData: string | Object | ArrayBuffer): string | Object | ArrayBuffer {
// 将请求数据转换为JSON字符串
return JSONUtil.beanToJsonStr(extraData);
}
responseConvert(data: string | Object | ArrayBuffer, responseType: http.HttpDataType): string | Object | ArrayBuffer {
// 根据responseType将响应数据转换为相应的格式
switch (responseType) {
case http.HttpDataType.STRING:
return JSON.parse(data as string);
case http.HttpDataType.OBJECT:
return data;
default:
return data;
}
}
}
参数附加器:灵活重组请求数据
参数附加器QueryParamAppender
接口允许我们对发送的请求数据进行重组,满足诸如参数签名等业务需求。
// 定义一个用于附加查询参数的接口
export interface QueryParamAppender {
append(queryParams?: Map<string, number|string|boolean|Array<number> | Array<string> | Array<boolean> >): string|undefined;
}
默认附加器:简化查询参数的处理
通过CustomQueryParamAppender
的实现,我们简化了查询参数的编码和附加过程。
export class CustomQueryParamAppender implements QueryParamAppender {
append(queryParams?: Map<string, string | number | boolean | number[] | string[] | boolean[]> | undefined): string|undefined {
if (queryParams===undefined || queryParams.size === 0) {
return;
}
const paramsArray: string[] = [];
for (const qp of queryParams) {
let key = qp[0]
let value = qp[1]
let encodedValue = '';
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
encodedValue += `${encodeURIComponent(`${key}[${i}]`)}=${encodeURIComponent(value[i].toString())}&`;
}
if (encodedValue.length > 0) {
encodedValue = encodedValue.slice(0, -1); // 移除最后一个 '&'
}
} else {
encodedValue = encodeURIComponent(key) + '=' + encodeURIComponent(value.toString());
}
paramsArray.push(encodedValue);
}
return paramsArray.join('&');
}
}
二、常量定义:构建网络层的坚实基础
通过定义一系列的常量,我们为网络请求的错误处理提供了统一的接口。这些常量不仅包括了各种网络错误的场景,还涵盖了HTTP状态码的含义,为开发者提供了清晰的指导。
{
"name": "network_unavailable",
"value": "网络不可用"
},
{
"name": "invalid_url_format",
"value": "URL格式不合法"
},
{
"name": "invalid_url_not_exist",
"value": "URL不存在"
},
{
"name": "parameter_error",
"value": "参数错误"
},
{
"name": "permission_denied",
"value": "权限被拒绝"
},
{
"name": "unsupported_protocol",
"value": "不支持的协议"
},
{
"name": "bad_url_format",
"value": "URL使用错误的/非法的格式或缺少URL"
},
{
"name": "could_not_resolve_proxy_name",
"value": "无法解析代理名称"
},
{
"name": "could_not_resolve_host_name",
"value": "无法解析主机名"
},
{
"name": "could_not_connect_to_server",
"value": "无法连接到服务器"
},
{
"name": "weird_server_reply",
"value": "服务器回复异常"
},
{
"name": "access_denied_to_remote_resource",
"value": "访问远程资源被拒绝"
},
{
"name": "http2_framing_layer_error",
"value": "HTTP2帧层错误"
},
{
"name": "transferred_partial_file",
"value": "传输了部分文件"
},
{
"name": "failed_writing_data_to_disk",
"value": "将数据写入磁盘/应用程序失败"
},
{
"name": "upload_failed",
"value": "上传失败"
},
{
"name": "failed_to_open_read_local_data",
"value": "无法打开/读取本地数据"
},
{
"name": "out_of_memory",
"value": "内存不足"
},
{
"name": "timeout_reached",
"value": "达到超时时间"
},
{
"name": "redirects_exceeded",
"value": "达到重定向的最大次数"
},
{
"name": "server_returned_nothing",
"value": "服务器未返回任何内容(无头信息,无数据)"
},
{
"name": "failed_sending_data_to_peer",
"value": "向对等端发送数据失败"
},
{
"name": "failure_receiving_data_from_peer",
"value": "从对等端接收数据失败"
},
{
"name": "ssl_certificate_problem",
"value": "本地SSL证书问题"
},
{
"name": "unsupported_ssl_cipher",
"value": "不支持指定的SSL加密算法"
},
{
"name": "ssl_peer_certificate_or_ssh_remote_key_not_ok",
"value": "SSL对等证书或SSH远程密钥不正确"
},
{
"name": "unrecognized_http_content_or_transfer_encoding",
"value": "无法识别的HTTP内容或传输编码"
},
{
"name": "maximum_file_size_exceeded",
"value": "超过最大文件大小"
},
{
"name": "disk_full_or_allocation_exceeded",
"value": "磁盘已满或分配超过限制"
},
{
"name": "remote_file_already_exists",
"value": "远程文件已存在"
},
{
"name": "ssl_ca_cert_problem",
"value": "SSL CA证书问题(路径?访问权限?)"
},
{
"name": "remote_file_not_found",
"value": "远程文件未找到"
},
{
"name": "authentication_function_error",
"value": "身份验证函数返回错误"
},
{
"name": "unknown_other_error",
"value": "未知的其他错误"
},
{
"name": "bad_request",
"value": "客户端请求的语法错误,服务器无法理解。"
},
{
"name": "unauthorized",
"value": "请求要求身份验证。"
},
{
"name": "forbidden",
"value": "服务器理解请求客户端的请求,但是拒绝执行此请求。"
},
{
"name": "not_found",
"value": "服务器无法根据客户端的请求找到资源(网页)。"
},
{
"name": "method_not_allowed",
"value": "客户端请求中的方法被禁止。"
},
{
"name": "request_timeout",
"value": "请求超时。"
},
{
"name": "unsupported_media_type",
"value": "服务器不支持请求的格式(如请求中包含了服务器不支持的MIME类型)。"
},
{
"name": "internal_server_error",
"value": "服务器内部错误,无法完成请求。"
},
{
"name": "bad_gateway",
"value": "作为网关或代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。"
},
{
"name": "service_unavailable",
"value": "由于超载或系统维护,服务器目前无法处理请求。"
},
{
"name": "gateway_timeout",
"value": "作为网关或代理工作的服务器尝试执行请求时,未能及时从上游服务器收到需要的响应。"
}
常量类代码使用
import { Application } from '../../app/Application'
import { NetworkError } from '../../exception/NetworkError'
export class NetworkServiceErrorConst {
// 网络不可用
static readonly UN_AVILABLE: number = 100000
// url错误
static readonly URL_ERROR: number = 100001
// url 不存在 错误
static readonly URL_NOT_EXIST_ERROR: number = 100002
static readonly PARAMETER_ERROR: number = 401;
static readonly PERMISSION_DENIED: number = 201;
static readonly UNSUPPORTED_PROTOCOL: number = 2300001;
static readonly BAD_URL_FORMAT: number = 2300003;
static readonly COULD_NOT_RESOLVE_PROXY_NAME: number = 2300005;
static readonly COULD_NOT_RESOLVE_HOST_NAME: number = 2300006;
static readonly COULD_NOT_CONNECT_TO_SERVER: number = 2300007;
static readonly WEIRD_SERVER_REPLY: number = 2300008;
static readonly ACCESS_DENIED_TO_REMOTE_RESOURCE: number = 2300009;
static readonly HTTP2_FRAMING_LAYER_ERROR: number = 2300016;
static readonly TRANSFERRED_PARTIAL_FILE: number = 2300018;
static readonly FAILED_WRITING_DATA_TO_DISK: number = 2300023;
static readonly UPLOAD_FAILED: number = 2300025;
static readonly FAILED_TO_OPEN_READ_LOCAL_DATA: number = 2300026;
static readonly OUT_OF_MEMORY: number = 2300027;
static readonly TIMEOUT_REACHED: number = 2300028;
static readonly REDIRECTS_EXCEEDED: number = 2300047;
static readonly SERVER_RETURNED_NOTHING: number = 2300052;
static readonly FAILED_SENDING_DATA_TO_PEER: number = 2300055;
static readonly FAILURE_RECEIVING_DATA_FROM_PEER: number = 2300056;
static readonly SSL_CERTIFICATE_PROBLEM: number = 2300058;
static readonly UNSUPPORTED_SSL_CIPHER: number = 2300059;
static readonly SSL_PEER_CERTIFICATE_OR_SSH_REMOTE_KEY_NOT_OK: number = 2300060;
static readonly UNRECOGNIZED_HTTP_CONTENT_OR_TRANSFER_ENCODING: number = 2300061;
static readonly MAXIMUM_FILE_SIZE_EXCEEDED: number = 2300063;
static readonly DISK_FULL_OR_ALLOCATION_EXCEEDED: number = 2300070;
static readonly REMOTE_FILE_ALREADY_EXISTS: number = 2300073;
static readonly SSL_CA_CERT_PROBLEM: number = 2300077;
static readonly REMOTE_FILE_NOT_FOUND: number = 2300078;
static readonly AUTHENTICATION_FUNCTION_ERROR: number = 2300094;
static readonly UNKNOWN_OTHER_ERROR: number = 2300999;
// 4xx Client Error
static readonly BAD_REQUEST: number = 400;
static readonly UNAUTHORIZED: number = 401;
static readonly FORBIDDEN: number = 403;
static readonly NOT_FOUND: number = 404;
static readonly METHOD_NOT_ALLOWED: number = 405;
static readonly REQUEST_TIMEOUT: number = 408;
static readonly UNSUPPORTED_MEDIA_TYPE: number = 415;
// 5xx Server Error
static readonly INTERNAL_SERVER_ERROR: number = 500;
static readonly BAD_GATEWAY: number = 502;
static readonly SERVICE_UNAVAILABLE: number = 503;
static readonly GATEWAY_TIMEOUT: number = 504;
public static getNetworkError(code: number): NetworkError{
return new NetworkError(code, NetworkServiceErrorConst.getErrorReason(code));
}
public static getErrorReason(errorCode: number): string {
let reason = "";
switch (errorCode) {
case NetworkServiceErrorConst.UN_AVILABLE:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.network_unavailable'));
break;
case NetworkServiceErrorConst.URL_ERROR:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.invalid_url_format'));
break;
case NetworkServiceErrorConst.URL_NOT_EXIST_ERROR:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.invalid_url_not_exist'));
break;
case NetworkServiceErrorConst.PARAMETER_ERROR:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.parameter_error'));
break;
case NetworkServiceErrorConst.PERMISSION_DENIED:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.permission_denied'));
break;
case NetworkServiceErrorConst.UNSUPPORTED_PROTOCOL:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.unsupported_protocol'));
break;
case NetworkServiceErrorConst.BAD_URL_FORMAT:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.bad_url_format'));
break;
case NetworkServiceErrorConst.COULD_NOT_RESOLVE_PROXY_NAME:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.could_not_resolve_proxy_name'));
break;
case NetworkServiceErrorConst.COULD_NOT_RESOLVE_HOST_NAME:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.could_not_resolve_host_name'));
break;
case NetworkServiceErrorConst.COULD_NOT_CONNECT_TO_SERVER:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.could_not_connect_to_server'));
break;
case NetworkServiceErrorConst.WEIRD_SERVER_REPLY:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.weird_server_reply'));
break;
case NetworkServiceErrorConst.ACCESS_DENIED_TO_REMOTE_RESOURCE:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.access_denied_to_remote_resource'));
break;
case NetworkServiceErrorConst.HTTP2_FRAMING_LAYER_ERROR:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.http2_framing_layer_error'));
break;
case NetworkServiceErrorConst.TRANSFERRED_PARTIAL_FILE:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.transferred_partial_file'));
break;
case NetworkServiceErrorConst.FAILED_WRITING_DATA_TO_DISK:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.failed_writing_data_to_disk'));
break;
case NetworkServiceErrorConst.UPLOAD_FAILED:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.upload_failed'));
break;
case NetworkServiceErrorConst.FAILED_TO_OPEN_READ_LOCAL_DATA:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.failed_to_open_read_local_data'));
break;
case NetworkServiceErrorConst.OUT_OF_MEMORY:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.out_of_memory'));
break;
case NetworkServiceErrorConst.TIMEOUT_REACHED:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.timeout_reached'));
break;
case NetworkServiceErrorConst.REDIRECTS_EXCEEDED:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.redirects_exceeded'));
break;
case NetworkServiceErrorConst.SERVER_RETURNED_NOTHING:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.server_returned_nothing'));
break;
case NetworkServiceErrorConst.FAILED_SENDING_DATA_TO_PEER:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.failed_sending_data_to_peer'));
break;
case NetworkServiceErrorConst.FAILURE_RECEIVING_DATA_FROM_PEER:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.failure_receiving_data_from_peer'));
break;
case NetworkServiceErrorConst.SSL_CERTIFICATE_PROBLEM:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.ssl_certificate_problem'));
break;
case NetworkServiceErrorConst.UNSUPPORTED_SSL_CIPHER:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.unsupported_ssl_cipher'));
break;
case NetworkServiceErrorConst.SSL_PEER_CERTIFICATE_OR_SSH_REMOTE_KEY_NOT_OK:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.ssl_peer_certificate_or_ssh_remote_key_not_ok'));
break;
case NetworkServiceErrorConst.UNRECOGNIZED_HTTP_CONTENT_OR_TRANSFER_ENCODING:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.unrecognized_http_content_or_transfer_encoding'));
break;
case NetworkServiceErrorConst.MAXIMUM_FILE_SIZE_EXCEEDED:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.maximum_file_size_exceeded'));
break;
case NetworkServiceErrorConst.DISK_FULL_OR_ALLOCATION_EXCEEDED:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.disk_full_or_allocation_exceeded'));
break;
case NetworkServiceErrorConst.REMOTE_FILE_ALREADY_EXISTS:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.remote_file_already_exists'));
break;
case NetworkServiceErrorConst.SSL_CA_CERT_PROBLEM:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.ssl_ca_cert_problem'));
break;
case NetworkServiceErrorConst.REMOTE_FILE_NOT_FOUND:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.remote_file_not_found'));
break;
case NetworkServiceErrorConst.AUTHENTICATION_FUNCTION_ERROR:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.authentication_function_error'));
break;
case NetworkServiceErrorConst.UNKNOWN_OTHER_ERROR:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.unknown_other_error'));
break;
case NetworkServiceErrorConst.BAD_REQUEST:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.bad_request'));
break;
case NetworkServiceErrorConst.UNAUTHORIZED:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.unauthorized'));
break;
case NetworkServiceErrorConst.FORBIDDEN:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.forbidden'));
break;
case NetworkServiceErrorConst.NOT_FOUND:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.not_found'));
break;
case NetworkServiceErrorConst.METHOD_NOT_ALLOWED:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.method_not_allowed'));
break;
case NetworkServiceErrorConst.REQUEST_TIMEOUT:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.request_timeout'));
break;
case NetworkServiceErrorConst.UNSUPPORTED_MEDIA_TYPE:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.unsupported_media_type'));
break;
case NetworkServiceErrorConst.INTERNAL_SERVER_ERROR:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.internal_server_error'));
break;
case NetworkServiceErrorConst.BAD_GATEWAY:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.bad_gateway'));
break;
case NetworkServiceErrorConst.SERVICE_UNAVAILABLE:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.service_unavailable'));
break;
case NetworkServiceErrorConst.GATEWAY_TIMEOUT:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.gateway_timeout'));
break;
default:
reason = Application.getInstance().resourceManager.getStringSync($r('app.string.unknown_other_error'));
break;
}
return reason;
}
}
三、异常定义:清晰的错误处理策略
我们重新封装了网络请求错误,定义了BaseError
和NetworkError
等类,使得错误类型一目了然,便于开发者快速定位问题。
typescript
// 自定义错误类型
import { http } from '@kit.NetworkKit';
export abstract class BaseError extends Error{
}
//基本网络错误
export class NetworkError extends BaseError {
code : number
constructor(code: number,message: string) {
super(message);
this.name = 'NetworkError'
this.code = code
}
}
//网络请求code错误
export class NetworkResponseError extends BaseError {
code : http.ResponseCode | number;
constructor(code: http.ResponseCode | number,message: string) {
super(message);
this.name = 'NetworkResponseError'
this.code = code
}
}
四、拦截器:网络请求的守卫
通过优化拦截器接口,我们能够在请求发送前后以及发生错误时,执行特定的逻辑,如日志记录、权限验证等。
export interface NetworkInterceptor {
beforeRequest(request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void;
afterResponse(response: http.HttpResponse , request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void;
onError(error: BaseError, request: RequestOptions, httprequest: http.HttpRequest): Promise<void> | void;
}
拦截器默认实现:
import { NetworkInterceptor } from './NetworkInterceptor';
import { NetworkServiceErrorConst } from '../NetworkServiceErrorConst';
import { RequestOptions } from '../NetworkService';
import http from '@ohos.net.http';
import { LibLogManager } from '../../LibLog';
import { BaseError } from '../../../exception/NetworkError';
import { JSONUtil } from '../../JSONUtil';
const TAG = "DefaultInterceptor"
// 创建一个符合RequestOptions接口的对象
const requestOptions: RequestOptions = {
baseUrl: 'https://api.example.com',
act: 'someAction'
};
export class DefaultInterceptor implements NetworkInterceptor {
beforeRequest(request: RequestOptions, httprequest: http.HttpRequest): void | Promise<void> {
LibLogManager.getLogger().info(TAG,'request: ' + JSONUtil.beanToJsonStr(request));
httprequest.on('headersReceive', (header) => {
LibLogManager.getLogger().info(TAG,'header: ' + JSONUtil.beanToJsonStr(header));
});
}
afterResponse(response: http.HttpResponse, request: RequestOptions, httprequest: http.HttpRequest): void | Promise<void> {
httprequest.off('headersReceive');
LibLogManager.getLogger().info(TAG,'response: ' + JSONUtil.beanToJsonStr(response));
}
onError(error: BaseError, request: RequestOptions, httprequest: http.HttpRequest): void | Promise<void> {
httprequest.off('headersReceive');
LibLogManager.getLogger().error(TAG,'error: ' + JSON.stringify(error));
}
}
五、核型网络层代码:网络服务的心脏
在本节中,我们将展示如何通过NetworkService
类,实现一个强大而灵活的网络请求处理机制。这个类集成了数据转换、参数附加、异常处理等所有核心功能。
typescript
import { NetworkInterceptor } from './interceptor/NetworkInterceptor';
import { http } from '@kit.NetworkKit';
import { LibNetworkStatus } from '../network/LibNetworkStatus';
import { LibLogManager } from '../LibLog';
import { BaseError, NetworkError, NetworkResponseError } from '../../exception/NetworkError';
import { NetworkServiceErrorConst } from './NetworkServiceErrorConst';
import { Application } from '../../app/Application'
import { HashMap } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { DataConverter } from './converter/DataConverter';
import { QueryParamAppender } from './appender/QueryParamAppender';
import { CustomQueryParamAppender } from './appender/CustomQueryParamAppender';
// 1、创建RequestOption.ets 配置类
export interface RequestOptions {
baseUrl?: string;
act?: string;
method?: RequestMethod; // default is GET
queryParams ?: Map<string, number|string|boolean|Array<number> | Array<string> | Array<boolean> >;
header?: Record<string,string> | Map<string,string> | HashMap<string,string>;
extraData?: string | Object | ArrayBuffer;
expectDataType?: http.HttpDataType;
usingCache?: boolean;
priority?: number;
connectTimeout?: number;
readTimeout?: number;
multiFormDataList?:Array<http.MultiFormData>;
}
export enum RequestMethod {
OPTIONS = "OPTIONS",
GET = "GET",
HEAD = "HEAD",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
TRACE = "TRACE",
CONNECT = "CONNECT"
}
export class NetworkService {
baseUrl:string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
private _dataConverter?: DataConverter | undefined; // 指定转换器
public set dataConverter(value: DataConverter | undefined) {
this._dataConverter = value;
}
private _queryParamAppender: QueryParamAppender = new CustomQueryParamAppender(); // 指定查询参数附加规则
public set queryParamAppender(value: QueryParamAppender) {
this._queryParamAppender = value;
}
private interceptors: NetworkInterceptor[] = [];
addInterceptor(interceptor: NetworkInterceptor): void {
this.interceptors.push(interceptor);
}
async request(requestOption: RequestOptions): Promise<http.HttpResponse> {
let response: http.HttpResponse | null = null;
let error: BaseError | 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);
}
let url = requestOption.baseUrl + requestOption.act;
if (this._queryParamAppender) {
let param = this._queryParamAppender.append(requestOption.queryParams);
if(param){
url = url + "?" + param
}
}
// 使用转换器转换请求数据
if (this._dataConverter && requestOption.extraData) {
requestOption.extraData = this._dataConverter.requestConvert(requestOption.extraData);
}
if(requestOption.baseUrl === null || requestOption.baseUrl.trim().length === 0){
throw NetworkServiceErrorConst.getNetworkError(NetworkServiceErrorConst.URL_NOT_EXIST_ERROR)
}
if (!LibNetworkStatus.getInstance().isNetworkAvailable()) {
LibLogManager.getLogger().error("HttpCore","网络不可用")
throw NetworkServiceErrorConst.getNetworkError(NetworkServiceErrorConst.UN_AVILABLE)
}
if (!this.isValidUrl(requestOption.baseUrl)) {
LibLogManager.getLogger().error("HttpCore","url格式不合法")
throw NetworkServiceErrorConst.getNetworkError(NetworkServiceErrorConst.URL_ERROR)
}
let defalutHeader :Record<string,string> = {
'Content-Type': 'application/json'
}
let expectDataType = requestOption.expectDataType||http.HttpDataType.STRING;
response = await httpRequest.request(url , {
method: requestOption.method,
header: requestOption.header || defalutHeader,
extraData: requestOption.extraData, // 当使用POST请求时此字段用于传递内容
expectDataType: expectDataType, // 可选,指定返回数据的类型
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) {
throw new NetworkResponseError(response.responseCode, NetworkServiceErrorConst.getErrorReason(response.responseCode))
}
// 使用转换器转换响应数据
if (response && this._dataConverter) {
response.result = this._dataConverter.responseConvert(response.result, expectDataType);
}
// 调用拦截器的afterResponse方法
for (const interceptor of this.interceptors) {
await interceptor.afterResponse(response, requestOption, httpRequest);
}
} catch (e) {
if(e instanceof NetworkResponseError || e instanceof NetworkError){
error = e;
} else {
let err = e as BusinessError;
error = NetworkServiceErrorConst.getNetworkError(err.code)
}
}
// 根据是否有错误来调用拦截器的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 {
// 正则表达式匹配 URL
const urlPattern = new RegExp(
'^(https?:\/\/)?' + // protocol
'((([a-z\d]([a-z\d-]*[a-z\d])*)\.)+[a-z]{2,}|' + // domain name
'((\d{1,3}\.){3}\d{1,3}))' + // OR ip (v4) address
'(\:\d+)?(\/[-a-z\d%_.~+]*)*' + // port and path
'(\?[;&a-z\d%_.~+=-]*)?' + // query string
'(\#[-a-z\d_]*)?$', // fragment locator
'i' // ignore case
);
return urlPattern.test(url);
}
}
结语
本文深入探讨了网络层的封装与优化,从数据转换到错误处理,每一步都体现了构建高效网络服务的艺术。希望这些实践能够帮助开发者在鸿蒙开发中游刃有余,构建出更加健壮和用户友好的应用。