0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HarmonyOS実践開発:ネットワーク層のアート——エレガントなカプセル化と構築ガイド(中編)

Last updated at Posted at 2025-06-28

まえがき

HarmonyOSの広大な開発の世界で、ネットワーク層の構築とカプセル化は、効率的で安定したアプリケーションを構築するための礎石です。前回の探求に続き、本稿ではネットワーク層の最適化の旅を深く掘り下げ、タイプコンバーター、リクエストクエリアッパダー、豊富な定数パラメーターを通じて、ネットワーク層の構築の芸術を新たな高みへと引き上げる方法を解明します。

一、ネットワークリクエストの深度最適化

データ型コンバーター:定義と実践

ネットワークリクエストの世界では、データ形式の変換が極めて重要です。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 クライアントエラー
  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 サーバーエラー
  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などのクラスを定義して、エラーの種類が一目でわかるようにし、開発者が問題を迅速に特定できるようにしました。

// カスタムエラータイプ
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
  }
}

// ネットワーク要求コードエラー
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クラスを使用して、強力で柔軟なネットワーク要求処理メカニズムを実現する方法を示します。このクラスは、データ変換、パラメーター附加、例外処理などのすべてのコア機能を統合しています。

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. RequestOptions.ets 設定クラスの作成
export interface RequestOptions {
  baseUrl?: string;
  act?: string;
  method?: RequestMethod; // デフォルトは 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);
  }

}

結語

本稿では、データ変換からエラー処理まで、ネットワーク層のカプセル化と最適化を深く掘り下げ、効率的なネットワークサービスの構築のアートを体現しました。これらの実践が、開発者がHarmonyOSの開発で自在に動くのを助けて、より強健でユーザーフriendlyなアプリケーションを構築することを願っています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?