まえがき
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;
}
}
三、例外の定義:明確なエラー処理ポリシー
ネットワークリクエストエラーを再カプセル化し、BaseError
やNetworkError
などのクラスを定義して、エラーの種類が一目でわかるようにし、開発者が問題を迅速に特定できるようにしました。
// カスタムエラータイプ
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なアプリケーションを構築することを願っています。