鸿蒙实战开发:网络层的艺术——优雅封装与搭建指南(中)

前言

在鸿蒙开发的广袤天地中,网络层的搭建与封装无疑是构建高效、稳定应用的基石。继上篇的探索之后,本文将继续深入网络层的优化之旅,揭秘如何通过类型转换器、请求查询附加器以及丰富的常量参数,将网络层的构建艺术推向一个新的高度。

一、网络请求的深度优化

数据类型转换器:定义与实践

在网络请求的世界里,数据格式的转换至关重要。我们通过定义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;
  }

}

三、异常定义:清晰的错误处理策略

我们重新封装了网络请求错误,定义了BaseErrorNetworkError等类,使得错误类型一目了然,便于开发者快速定位问题。

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);
  }

}

结语

本文深入探讨了网络层的封装与优化,从数据转换到错误处理,每一步都体现了构建高效网络服务的艺术。希望这些实践能够帮助开发者在鸿蒙开发中游刃有余,构建出更加健壮和用户友好的应用。

相关推荐
爱桥代码的程序媛7 小时前
鸿蒙开发设备管理:【@ohos.multimodalInput.touchEvent (触摸输入事件)】
程序员·harmonyos·鸿蒙·openharmony·设备管理·鸿蒙开发·输入事件
m0_6442226110 小时前
HarmonyOS开发实战:UDP通讯示例规范
网络·驱动开发·嵌入式硬件·udp·harmonyos·鸿蒙·harmonyos next
张紫娃10 小时前
【鸿蒙学习笔记】数据类型
笔记·学习·harmonyos
MobTech袤博科技14 小时前
ShareSDK HarmonyOS NEXT集成指南
java·服务器·华为·harmonyos
心为你而跳15 小时前
华为---配置基本的访问控制列表(ACL)
网络·华为·acl·访问控制列表
bright78915 小时前
【HarmonyOS】模拟器一直停留在开机页面,无法进入手机桌面
智能手机·鸿蒙
Android技术栈19 小时前
鸿蒙开发Ability Kit(程序框架服务):【向用户申请单次授权】
程序员·移动开发·harmonyos·鸿蒙系统·openharmony·鸿蒙开发·程序框架
m0_6442226120 小时前
HarmonyOS开发探索:自定义键盘-webview
前端·华为·计算机外设·移动开发·harmonyos·鸿蒙开发
MobTech袤博科技20 小时前
MobPush HarmonyOS NEXT 版本集成指南
华为·harmonyos
软通动力1 天前
SwanLinkOS首批实现与HarmonyOS NEXT互联互通,软通动力子公司鸿湖万联助力鸿蒙生态统一互联
harmonyos·openharmony