鴻蒙實(shí)戰(zhàn)開發(fā):網(wǎng)絡(luò)層的藝術(shù)——優(yōu)雅封裝與搭建指南(中)

前言

在鴻蒙開發(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ò)請求錯誤,定義了BaseErrorNetworkError等類菠劝,使得錯誤類型一目了然赊舶,便于開發(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)用戈次。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市筒扒,隨后出現(xiàn)的幾起案子怯邪,更是在濱河造成了極大的恐慌,老刑警劉巖花墩,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悬秉,死亡現(xiàn)場離奇詭異,居然都是意外死亡冰蘑,警方通過查閱死者的電腦和手機(jī)和泌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祠肥,“玉大人武氓,你說我怎么就攤上這事〕鹣洌” “怎么了县恕?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剂桥。 經(jīng)常有香客問我忠烛,道長,這世上最難降的妖魔是什么权逗? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任美尸,我火速辦了婚禮,結(jié)果婚禮上斟薇,老公的妹妹穿的比我還像新娘师坎。我一直安慰自己,他們只是感情好堪滨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布胯陋。 她就那樣靜靜地躺著,像睡著了一般椿猎。 火紅的嫁衣襯著肌膚如雪惶岭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天犯眠,我揣著相機(jī)與錄音按灶,去河邊找鬼。 笑死筐咧,一個胖子當(dāng)著我的面吹牛鸯旁,可吹牛的內(nèi)容都是我干的噪矛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼铺罢,長吁一口氣:“原來是場噩夢啊……” “哼艇挨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起韭赘,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缩滨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后泉瞻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脉漏,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年袖牙,在試婚紗的時候發(fā)現(xiàn)自己被綠了侧巨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡鞭达,死狀恐怖司忱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情畴蹭,我是刑警寧澤坦仍,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站撮胧,受9級特大地震影響桨踪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芹啥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铺峭。 院中可真熱鬧墓怀,春花似錦、人聲如沸卫键。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莉炉。三九已至钓账,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間絮宁,已是汗流浹背梆暮。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绍昂,地道東北人啦粹。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓偿荷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親唠椭。 傳聞我的和親對象是個殘疾皇子跳纳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容