前言
在鴻蒙開發(fā)的廣袤天地中阿蝶,網(wǎng)絡(luò)層的搭建與封裝無疑是構(gòu)建高效诀艰、穩(wěn)定應(yīng)用的基石柬甥。繼上篇的探索之后,本文將繼續(xù)深入網(wǎng)絡(luò)層的優(yōu)化之旅其垄,揭秘如何通過類型轉(zhuǎn)換器苛蒲、請求查詢附加器以及豐富的常量參數(shù),將網(wǎng)絡(luò)層的構(gòu)建藝術(shù)推向一個新的高度绿满。
一臂外、網(wǎng)絡(luò)請求的深度優(yōu)化
數(shù)據(jù)類型轉(zhuǎn)換器:定義與實(shí)踐
在網(wǎng)絡(luò)請求的世界里,數(shù)據(jù)格式的轉(zhuǎn)換至關(guān)重要喇颁。我們通過定義DataConverter
接口漏健,實(shí)現(xiàn)了對請求與響應(yīng)數(shù)據(jù)類型的靈活轉(zhuǎn)換。
export interface DataConverter {
requestConvert(extraData: string | Object | ArrayBuffer): string | Object | ArrayBuffer;
responseConvert(data: string | Object | ArrayBuffer, responseType: http.HttpDataType): string | Object | ArrayBuffer;
}
默認(rèn)數(shù)據(jù)轉(zhuǎn)換器:JSON轉(zhuǎn)換器的實(shí)現(xiàn)
我們實(shí)現(xiàn)了一個默認(rèn)的JsonDataConverter
橘霎,它將請求數(shù)據(jù)轉(zhuǎn)換為JSON字符串蔫浆,并根據(jù)響應(yīng)類型將響應(yīng)數(shù)據(jù)轉(zhuǎn)換為適當(dāng)?shù)母袷健?/p>
export class JsonDataConverter implements DataConverter {
requestConvert(extraData: string | Object | ArrayBuffer): string | Object | ArrayBuffer {
// 將請求數(shù)據(jù)轉(zhuǎn)換為JSON字符串
return JSONUtil.beanToJsonStr(extraData);
}
responseConvert(data: string | Object | ArrayBuffer, responseType: http.HttpDataType): string | Object | ArrayBuffer {
// 根據(jù)responseType將響應(yīng)數(shù)據(jù)轉(zhuǎn)換為相應(yīng)的格式
switch (responseType) {
case http.HttpDataType.STRING:
return JSON.parse(data as string);
case http.HttpDataType.OBJECT:
return data;
default:
return data;
}
}
}
參數(shù)附加器:靈活重組請求數(shù)據(jù)
參數(shù)附加器QueryParamAppender
接口允許我們對發(fā)送的請求數(shù)據(jù)進(jìn)行重組,滿足諸如參數(shù)簽名等業(yè)務(wù)需求姐叁。
// 定義一個用于附加查詢參數(shù)的接口
export interface QueryParamAppender {
append(queryParams?: Map<string, number|string|boolean|Array<number> | Array<string> | Array<boolean> >): string|undefined;
}
默認(rèn)附加器:簡化查詢參數(shù)的處理
通過CustomQueryParamAppender
的實(shí)現(xiàn)瓦盛,我們簡化了查詢參數(shù)的編碼和附加過程。
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('&');
}
}
二七蜘、常量定義:構(gòu)建網(wǎng)絡(luò)層的堅(jiān)實(shí)基礎(chǔ)
通過定義一系列的常量谭溉,我們?yōu)榫W(wǎng)絡(luò)請求的錯誤處理提供了統(tǒng)一的接口。這些常量不僅包括了各種網(wǎng)絡(luò)錯誤的場景橡卤,還涵蓋了HTTP狀態(tài)碼的含義扮念,為開發(fā)者提供了清晰的指導(dǎo)。
{
"name": "network_unavailable",
"value": "網(wǎng)絡(luò)不可用"
},
{
"name": "invalid_url_format",
"value": "URL格式不合法"
},
{
"name": "invalid_url_not_exist",
"value": "URL不存在"
},
{
"name": "parameter_error",
"value": "參數(shù)錯誤"
},
{
"name": "permission_denied",
"value": "權(quán)限被拒絕"
},
{
"name": "unsupported_protocol",
"value": "不支持的協(xié)議"
},
{
"name": "bad_url_format",
"value": "URL使用錯誤的/非法的格式或缺少URL"
},
{
"name": "could_not_resolve_proxy_name",
"value": "無法解析代理名稱"
},
{
"name": "could_not_resolve_host_name",
"value": "無法解析主機(jī)名"
},
{
"name": "could_not_connect_to_server",
"value": "無法連接到服務(wù)器"
},
{
"name": "weird_server_reply",
"value": "服務(wù)器回復(fù)異常"
},
{
"name": "access_denied_to_remote_resource",
"value": "訪問遠(yuǎn)程資源被拒絕"
},
{
"name": "http2_framing_layer_error",
"value": "HTTP2幀層錯誤"
},
{
"name": "transferred_partial_file",
"value": "傳輸了部分文件"
},
{
"name": "failed_writing_data_to_disk",
"value": "將數(shù)據(jù)寫入磁盤/應(yīng)用程序失敗"
},
{
"name": "upload_failed",
"value": "上傳失敗"
},
{
"name": "failed_to_open_read_local_data",
"value": "無法打開/讀取本地?cái)?shù)據(jù)"
},
{
"name": "out_of_memory",
"value": "內(nèi)存不足"
},
{
"name": "timeout_reached",
"value": "達(dá)到超時時間"
},
{
"name": "redirects_exceeded",
"value": "達(dá)到重定向的最大次數(shù)"
},
{
"name": "server_returned_nothing",
"value": "服務(wù)器未返回任何內(nèi)容(無頭信息碧库,無數(shù)據(jù))"
},
{
"name": "failed_sending_data_to_peer",
"value": "向?qū)Φ榷税l(fā)送數(shù)據(jù)失敗"
},
{
"name": "failure_receiving_data_from_peer",
"value": "從對等端接收數(shù)據(jù)失敗"
},
{
"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遠(yuǎn)程密鑰不正確"
},
{
"name": "unrecognized_http_content_or_transfer_encoding",
"value": "無法識別的HTTP內(nèi)容或傳輸編碼"
},
{
"name": "maximum_file_size_exceeded",
"value": "超過最大文件大小"
},
{
"name": "disk_full_or_allocation_exceeded",
"value": "磁盤已滿或分配超過限制"
},
{
"name": "remote_file_already_exists",
"value": "遠(yuǎn)程文件已存在"
},
{
"name": "ssl_ca_cert_problem",
"value": "SSL CA證書問題(路徑柜与?訪問權(quán)限?)"
},
{
"name": "remote_file_not_found",
"value": "遠(yuǎn)程文件未找到"
},
{
"name": "authentication_function_error",
"value": "身份驗(yàn)證函數(shù)返回錯誤"
},
{
"name": "unknown_other_error",
"value": "未知的其他錯誤"
},
{
"name": "bad_request",
"value": "客戶端請求的語法錯誤嵌灰,服務(wù)器無法理解弄匕。"
},
{
"name": "unauthorized",
"value": "請求要求身份驗(yàn)證。"
},
{
"name": "forbidden",
"value": "服務(wù)器理解請求客戶端的請求沽瞭,但是拒絕執(zhí)行此請求迁匠。"
},
{
"name": "not_found",
"value": "服務(wù)器無法根據(jù)客戶端的請求找到資源(網(wǎng)頁)。"
},
{
"name": "method_not_allowed",
"value": "客戶端請求中的方法被禁止。"
},
{
"name": "request_timeout",
"value": "請求超時城丧。"
},
{
"name": "unsupported_media_type",
"value": "服務(wù)器不支持請求的格式(如請求中包含了服務(wù)器不支持的MIME類型)延曙。"
},
{
"name": "internal_server_error",
"value": "服務(wù)器內(nèi)部錯誤,無法完成請求亡哄。"
},
{
"name": "bad_gateway",
"value": "作為網(wǎng)關(guān)或代理工作的服務(wù)器嘗試執(zhí)行請求時枝缔,從上游服務(wù)器接收到無效的響應(yīng)。"
},
{
"name": "service_unavailable",
"value": "由于超載或系統(tǒng)維護(hù)蚊惯,服務(wù)器目前無法處理請求愿卸。"
},
{
"name": "gateway_timeout",
"value": "作為網(wǎng)關(guān)或代理工作的服務(wù)器嘗試執(zhí)行請求時,未能及時從上游服務(wù)器收到需要的響應(yīng)截型。"
}
常量類代碼使用
import { Application } from '../../app/Application'
import { NetworkError } from '../../exception/NetworkError'
export class NetworkServiceErrorConst {
// 網(wǎng)絡(luò)不可用
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;
}
}
三趴荸、異常定義:清晰的錯誤處理策略
我們重新封裝了網(wǎng)絡(luò)請求錯誤,定義了BaseError
和NetworkError
等類菠劝,使得錯誤類型一目了然赊舶,便于開發(fā)者快速定位問題睁搭。
// 自定義錯誤類型
import { http } from '@kit.NetworkKit';
export abstract class BaseError extends Error{
}
//基本網(wǎng)絡(luò)錯誤
export class NetworkError extends BaseError {
code : number
constructor(code: number,message: string) {
super(message);
this.name = 'NetworkError'
this.code = code
}
}
//網(wǎng)絡(luò)請求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
}
}
四赶诊、攔截器:網(wǎng)絡(luò)請求的守衛(wèi)
通過優(yōu)化攔截器接口,我們能夠在請求發(fā)送前后以及發(fā)生錯誤時园骆,執(zhí)行特定的邏輯舔痪,如日志記錄、權(quán)限驗(yàn)證等锌唾。
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;
}
攔截器默認(rèn)實(shí)現(xiàn):
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"
// 創(chuàng)建一個符合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));
}
}
五锄码、核型網(wǎng)絡(luò)層代碼:網(wǎng)絡(luò)服務(wù)的心臟
在本節(jié)中,我們將展示如何通過NetworkService
類晌涕,實(shí)現(xiàn)一個強(qiáng)大而靈活的網(wǎng)絡(luò)請求處理機(jī)制滋捶。這個類集成了數(shù)據(jù)轉(zhuǎn)換、參數(shù)附加余黎、異常處理等所有核心功能重窟。
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、創(chuàng)建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; // 指定轉(zhuǎn)換器
public set dataConverter(value: DataConverter | undefined) {
this._dataConverter = value;
}
private _queryParamAppender: QueryParamAppender = new CustomQueryParamAppender(); // 指定查詢參數(shù)附加規(guī)則
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對應(yīng)一個HTTP請求任務(wù)惧财,不可復(fù)用
let httpRequest = http.createHttp();
//開始發(fā)請求
try {
//如果url是傳入的巡扇,則用傳入的url
requestOption.baseUrl = requestOption.baseUrl?requestOption.baseUrl:this.baseUrl;
// 調(diào)用攔截器的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
}
}
// 使用轉(zhuǎn)換器轉(zhuǎn)換請求數(shù)據(jù)
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","網(wǎng)絡(luò)不可用")
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, // 當(dāng)使用POST請求時此字段用于傳遞內(nèi)容
expectDataType: expectDataType, // 可選,指定返回?cái)?shù)據(jù)的類型
usingCache: requestOption.usingCache, // 可選垮衷,默認(rèn)為true
priority: requestOption.priority, // 可選厅翔,默認(rèn)為1
connectTimeout: requestOption.connectTimeout, // 可選,默認(rèn)為60000ms
readTimeout: requestOption.readTimeout, // 可選搀突,默認(rèn)為60000ms
multiFormDataList: requestOption.multiFormDataList,
})
if (http.ResponseCode.OK !== response.responseCode) {
throw new NetworkResponseError(response.responseCode, NetworkServiceErrorConst.getErrorReason(response.responseCode))
}
// 使用轉(zhuǎn)換器轉(zhuǎn)換響應(yīng)數(shù)據(jù)
if (response && this._dataConverter) {
response.result = this._dataConverter.responseConvert(response.result, expectDataType);
}
// 調(diào)用攔截器的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)
}
}
// 根據(jù)是否有錯誤來調(diào)用攔截器的afterResponse或onError方法
if (error) {
for (const interceptor of this.interceptors) {
await interceptor.onError(error, requestOption, httpRequest);
}
httpRequest.destroy();
throw error; // 重新拋出錯誤以便調(diào)用者可以處理
} else{
httpRequest.destroy();
return response!;
}
}
private isValidUrl(url: string): boolean {
// 正則表達(dá)式匹配 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);
}
}
結(jié)語
本文深入探討了網(wǎng)絡(luò)層的封裝與優(yōu)化刀闷,從數(shù)據(jù)轉(zhuǎn)換到錯誤處理,每一步都體現(xiàn)了構(gòu)建高效網(wǎng)絡(luò)服務(wù)的藝術(shù)。希望這些實(shí)踐能夠幫助開發(fā)者在鴻蒙開發(fā)中游刃有余甸昏,構(gòu)建出更加健壯和用戶友好的應(yīng)用戈次。