English Русский Deutsch
preview
HTTPとConnexus(第2回):HTTPアーキテクチャとライブラリ設計の理解

HTTPとConnexus(第2回):HTTPアーキテクチャとライブラリ設計の理解

MetaTrader 5 | 26 11月 2024, 10:07
96 0
joaopedrodev
joaopedrodev

はじめに

この記事は、Connexusと呼ばれるライブラリを構築する連載の続きです。最初の記事では、WebRequest関数の基本機能を理解し、その各パラメータを学び、関数の使用例や注意点を示すサンプルコードを作成しました。この記事では、HTTPプロトコルやURLの仕組みや要素をさらに掘り下げて理解し、次の2つの基本クラスを作成します。

  • CQueryParam:URLのクエリパラメータを管理するクラス
  • CURL:CQueryParamのインスタンスを内包し、URLのすべての要素を管理するクラス


HTTPについて

HTTPは、Web上のデータ転送に使用される通信プロトコルです。HTTPは、クライアントとサーバー間の接続を確立するTCP/IPプロトコルの上で動作します。HTTPはステートレスプロトコルであり、各リクエストは独立しており、以前のリクエスト情報を持ちません。 HTTPリクエストと応答は次の3つの主要な部分で構成されます。

1. リクエストライン

リクエストラインには以下の3つの要素が含まれています。

  • HTTPメソッド:実行するアクションを定義する(GET、POST、PUT、DELETEなど)
  • URL:リクエストされたリソースを指定する
  • HTTPバージョン:使用されているプロトコルのバージョンを示す(HTTP/1.1、HTTP/2など)

以下は、リクエストと応答の例です。

REQUEST
GET /index.html HTTP/1.1

RESPONSE
HTTP/1.1 200 OK

2. リクエストヘッダー

ヘッダーは、コンテンツタイプ、ユーザーエージェント(ブラウザ)、認証のような、リクエストに関する追加情報を提供します。以下は例です。

Host: www.exemplo.com
User-Agent: Mozilla/5.0
Accept-Language: en-US,en;q=0.5

3. リクエストの本体

すべてのリクエストが本体を持つわけではありませんが、POSTやPUTのような、データ(フォームやファイルなど)をサーバーに送るメソッドでは一般的です。


一般的なHTTPメソッド

HTTPメソッドは、クライアントがサーバーにリクエストするアクションの種類を決定する重要な部分です。各メソッドは、データの取得、情報の送信、リソースの変更など、特定の目的を定義します。最も一般的なHTTPメソッドとその使い方、ベストプラクティスについて深く掘り下げてみましょう。

  • GET: HTTPプロトコルで最も広く使用され、サーバーの状態を変更せずに、サーバーからデータを取得したり検索します。つまり、同じリソースに対して複数のGETリクエストをおこなっても、アプリケーションの状態は変化しません。
    • 特徴
      • 副作用なし:読み取りデータのみ。サーバー上では何も変わらない。
      • 空のリクエスト本体:通常、GETリクエストには本体はない。
      • キャッシュ可能:GET応答はブラウザによってキャッシュされ、パフォーマンスを向上させることができる。
    • 用途
      • HTMLページ、画像、ファイル、JSONのような形式の情報などの静的データを取得する。
      • データを変更せずにデータベースから情報を取得する。
  • POST: サーバーにデータを送信するために使用され、通常は新しいリソースを作成します。GETとは異なり、サーバーの状態が変更されます。POSTは冪等ではないので、同じリクエストを複数回送ると、複数のリソースを作成することができます。
    • 特徴
      • サーバーの状態を変更する:通常、新しいリソースを作成するために使用される。
      • リクエスト本体の存在:サーバーに送信されるデータを含む。
      • キャッシュ不可能:一般的に、POSTリクエストはキャッシュされるべきではない。
    • 用途
      • データ入りのフォームを送信する。
      • データベースに新しい項目を追加するなど、新しいリソースを作成する。
  • PUT: サーバー上のリソースを更新するために使用されます。リソースがまだ存在しない場合は、PUTを使用して作成することができます。PUTの主な特徴は、それが冪等であるということで、同じリクエスト本体で複数のPUTリクエストをおこなうと、常に同じ結果が得られます。
    • 特徴
      • 冪等:同じ本体で繰り返しリクエストしても同じ効果がある。
      • 全リソースを送信:リクエスト本体は通常、更新されるリソースの完全な表現を含む。
      • 作成または更新が可能:リソースが存在しない場合、作成することができる。
    • 用途
      • 既存のリソースを完全に更新または置き換える。
      • リソースが存在しない場合、特定のURLを通してリソースを作成する。
  • DELETE: サーバーから特定のリソースを削除するために使われます。PUTと同様、これは冪等です。つまり、同じリソースに対して複数のDELETEリクエストを実行した場合、結果は同じになります。リソースは削除されます(または、すでに削除されている場合は削除されたままになります)。
    • 特徴
      • 冪等 :すでに削除されているリソースを「削除」すると、サーバーは成功を返す。
      • 本体なし:通常、DELETEリクエストは本体を持たない。
    • 用途
      • データベースからデータを削除するなど、特定のサーバーリソースを削除する。
  • PATCH: リソースの部分更新に使用されます。リソースの完全な表現を送信する必要があるPUTとは異なり、PATCHでは更新が必要なフィールドのみを変更することができます。
  • HEAD:GETに似ているが、応答本体はありません。リソースに関する情報を確認するために使用されます。
  • OPTIONS:サーバーとの通信オプション(サポートされている方法を含む)を記述するために使用されます。
  • TRACE:診断に使用され、クライアントからサーバーに送信された内容を返します。


HTTPステータスコード

HTTPステータスコードとは、サーバーがクライアントにリクエストの結果を知らせるために送る応答のことです。これらのコードは数値で、リクエストが成功したか失敗したか、またエラーやリダイレクトを示します。これらは5つの主要なカテゴリーに分けられ、それぞれ特定の数値範囲を持ち、リクエストの処理中に何が起こったかについて明確で詳細なフィードバックを提供します。

ここでは、各カテゴリの詳細と、最も一般的に使用されるコードを紹介します。

1xx: 有益な回答: 1xxシリーズのステータスコードは、サーバーがリクエストを受信し、クライアントが詳細情報を待つ必要があることを示します。これらの応答は日常的に使用されることはほとんどありませんが、特定のシナリオでは重要になることがあります。

2xx:成功:2xxシリーズのステータスコードは、リクエストが**成功**したことを示します。これらのコードは、クライアントとサーバー間のやりとりが期待通りにおこなわれたことを示すもので、最も望ましいコードです。

3xx: リダイレクト: 3xxシリーズのコードは、クライアントがリクエストを完了するために追加のアクション(通常は別のURLへのリダイレクト)を実行する必要があることを示します。

4xx: クライアントエラー: 4xxシリーズのコードは、クライアントのリクエストにエラーがあったことを示すします。これらのエラーは、データ形式が正しくない、認証がない、存在しないリソースにアクセスしようとした、などの原因で発生することがあります。

5xx:サーバーエラー: 5xxシリーズのコードは、リクエストを処理しようとしている間にサーバーで内部エラーが発生したことを示します。これらのエラーは通常、内部サービスの障害、設定エラー、システムの過負荷などのバックエンドの問題です。


URLの構築

URL (Uniform Resource Locator)とは、Web上のリソースを特定し、アクセスする方法です。これは、サーバーの場所、リクエストされたリソース、そしてオプションとして、応答をフィルタリングしたりカスタマイズしたりするために使用できる追加パラメータなどの重要な情報を提供するいくつかの要素で構成されています。

以下では、URLの各コンポーネントと、HTTPリクエストで追加情報を渡すためにクエリパラメータがどのように使用されるかを詳しく説明します。

URL構造

典型的なURLは次のような形式です。

protocol://domain:port/path?query=params#fragment

各部には特定の役割があります。

  1. プロトコル:httpやhttpsなど、通信に使用するプロトコルを示します(例:https://)。
  2. ドメイン:リソースがホストされているサーバーの名前。ドメイン名(example.comなど)でもIPアドレス(192.168.1.1など)でもかまいません。
  3. ポート:通信に使用するサーバーポートを指定するオプションの番号。省略された場合、ブラウザはhttpには80、httpsには443のようなデフォルトのポートを使用します(例:8080)。
  4. パス:サーバー上のリソースまたはルートを指定します。ページ、APIエンドポイント、またはファイルを表すことができます(例:/api/v1/users)。
  5. クエリパラメータ:サーバーに追加情報を渡すために使用します。疑問符の後に続き、キーと値のペアで形成されます。複数のパラメータは「&」で区切られます(例:?name=John&age=30)。
  6. 断片:HTMLページ内のアンカーポイントなど、リソースの特定の部分を示します。ハッシュ文字(#)に続きます(例:#section2)。通常、フラグメントは有用ではなく、APIからデータを消費するためにも使用されません。これは、フラグメントがもっぱらクライアントサイド、つまりブラウザか、Webページを消費するアプリケーションのインターフェイスで処理されるためです。サーバーはURLのフラグメントを受け取らないので、APIを消費するときなど、サーバーに送信されるHTTPリクエストでは使用できません。このため、私たちのライブラリではフラグメントをサポートしません。


完全な例

下のURLを分析してみましょう。

https://www.exemplo.com:8080/market/symbols?active=EURUSD&timeframe=h1

具体的には、次のようなパラメータが含まれます。

  • プロトコル:https
  • ドメイン:www.example.com
  • ポート:8080
  • パス:/market/symbols
  • クエリーパラム:active=EURUSD&timeframe=h1


ハンズオン:Connexusライブラリの作成

Connexusライブラリの作成を開始するために、URLとクエリパラメータの構築と操作を担当するクラスに焦点を当てます。URLの作成とクエリパラメータの追加を動的にプログラムでおこなうためのモジュールを作成します。


クラス構成

まず、URLの構築と操作を担当するCURLクラスを作成します。これにより、ユーザーはクエリパラメーターを簡単に追加し、ベースとなるURLを構築し、URLのさまざまな要素を効率的に処理できるようになります。URLのクエリパラメータを整理された効率的な方法で管理するために、CJsonと呼ばれるクラスを使用します。このクラスの目的は、クエリパラメータ(通常はURLの文字列として渡される)を、構造化された管理しやすい形式(JSON)に変換することです。


JSONについて

CJsonクラスの機能に入る前に、JSON (JavaScript Object Notation)形式を理解しておくことが重要です。JSONは、構造化データを表現するためにWeb上で使用される非常に一般的なデータ形式です。キーペアで構成され、各キーは関連する値を持ちます。これらのペアはカンマで区切られ、中括弧({})でグループ化されます。

以下は、JSONオブジェクトの例です。

{
  "name": "John",
  "age": 30,
  "city": "New York"
}

ここでは、"name"、"age"、"city"の3つのキーペアとそれぞれの値を含むJSONオブジェクトがあります。URLクエリパラメータの場合、各パラメータは同じように動作します。つまり、キー(パラメータ名)と値(そのキーに関連付けられた値)があります。


CJsonクラスの目的

CJsonクラスは、URLクエリパラメータをJSON形式で整理するために使用されます。これにより、最終的なURLにパラメータを含める前に、これらのパラメータを操作したり、読み取ったり、さらには検証したりすることが容易になります。「?name=John&age=30」のようなパラメータ文字列を扱う代わりに、構造化されたオブジェクトを扱うことができるので、コードがすっきりし、理解しやすくなります。CJsonクラスはデータの送受信にも役立ちます。


最初のクラスを作成する

まず、MetaeditorのincludesにConnexusというフォルダを作成します。Connexusフォルダの中にURLというフォルダとDataというフォルダを作成し、URLフォルダの中にURLとQueryParamというファイルを作成します。このクラスの実装についてはあまり詳しく説明しませんが、使い方は簡単です。構造は次のようになるはずです。

MQL5
 |--- include
 |--- |--- Connexus
 |--- |--- |--- Data
 |--- |--- |--- |--- Json.mqh
 |--- |--- |--- URL
 |--- |--- |--- |--- QueryParam.mqh
 |--- |--- |--- |--- URL.mqh


クエリパラメータ

まず、CQueryParamクラスを使ってみましょう。このクラスは、クエリパラメータの追加、削除、検索、シリアライズをおこない、データのクリーニングやクエリ文字列のパースなどの補助メソッドも提供します。まず、クエリパラメータをキーと値のペアとして格納するために、CJson型のプライベートオブジェクトを持つクラスを作成します。

//+------------------------------------------------------------------+
//| class : CQueryParam                                              |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CQueryParam                                        |
//| Heritage    : No heritage                                        |
//| Description : Manages query parameters for HTTP requests         |
//|                                                                  |
//+------------------------------------------------------------------+
class CQueryParam
  {
private:

   CJson             m_query_param;                      // Storage for query parameters

public:
                     CQueryParam(void);
                    ~CQueryParam(void);

   //--- Functions to manage query parameters
   void              AddParam(string key, string value); // Add a key-value pair
   void              AddParam(string param);             // Add a single key-value parameter
   void              AddParams(const string &params[]);  // Add multiple parameters
   void              RemoveParam(const string key);      // Remove a parameter by key
   string            GetParam(const string key) const;   // Retrieve a parameter value by key
   bool              HasParam(const string key);         // Check if parameter exists
   int               ParamSize(void);                    // Get the number of parameters

   //--- Auxiliary methods
   bool              Parse(const string query_param);    // Parse a query string
   string            Serialize(void);                    // Serialize parameters into a query string
   void              Clear(void);                        // Clear all parameters
  };

では、主なメソッドを調べ、それぞれがクラスの機能にどのように貢献しているかを理解しましょう。

  • AddParam(string key, string value):クエリパラメータのリストに新しいパラメータを追加します。キーと値をパラメータとして受け取り、m_query_paramオブジェクトに格納します。
  • AddParam(string param):key=valueとしてフォーマットされたパラメータを追加します。文字列に「=」があるかどうかをチェックし、「=」があれば、文字列をキーと値の2つの値に分割して格納します。
  • AddParams(const string &params[]):複数のパラメータを一度に追加します。「key=value」形式の文字列の配列を受け取り、配列の各項目に対してAddParamメソッドを呼び出します。
  • RemoveParam(const string key):クエリパラメータのリストからパラメータを削除します。キーの位置を特定し、m_query_paramオブジェクトから削除します。- GetParam(const string key):キーを入力として、特定のパラメータの値を返します。
  • HasParam(const string key):指定されたパラメータが既に追加されているかどうかをチェックします。
  • ParamSize(void):格納されているクエリパラメータの数を返します。
  • Parse(const string query_param):Parse()メソッドは、クエリパラメータの文字列を受け取り、それらをキーと値のペアに変換してm_query_paramオブジェクトに格納します。文字列を&(パラメータを区切る)と=(キーと値を区切る)で分割します。
  • Serialize(void):保存されているすべてのクエリパラメータを含むフォーマット文字列を生成します。パラメータをkey=value形式で連結し、各ペアを&で区切ります。
  • Clear(void):保存されているすべてのパラメータをクリアし、オブジェクトをリセットします。

CJSONインポートを追加することを忘れないでください。

//+------------------------------------------------------------------+
//| Include the file CJson class                                     |
//+------------------------------------------------------------------+
#include "../Data/Json.mqh"
//+------------------------------------------------------------------+
//| class : CQueryParam                                              |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CQueryParam                                        |
//| Heritage    : No heritage                                        |
//| Description : Manages query parameters for HTTP requests         |
//|                                                                  |
//+------------------------------------------------------------------+
class CQueryParam
  {
private:

   CJson             m_query_param;                      // Storage for query parameters

public:
                     CQueryParam(void);
                    ~CQueryParam(void);

   //--- Functions to manage query parameters
   void              AddParam(string key, string value); // Add a key-value pair
   void              AddParam(string param);             // Add a single key-value parameter
   void              AddParams(const string &params[]);  // Add multiple parameters
   void              RemoveParam(const string key);      // Remove a parameter by key
   string            GetParam(const string key) const;   // Retrieve a parameter value by key
   bool              HasParam(const string key);         // Check if parameter exists
   int               ParamSize(void);                    // Get the number of parameters

   //--- Auxiliary methods
   bool              Parse(const string query_param);    // Parse a query string
   string            Serialize(void);                    // Serialize parameters into a query string
   void              Clear(void);                        // Clear all parameters
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CQueryParam::CQueryParam(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CQueryParam::~CQueryParam(void)
  {
  }
//+------------------------------------------------------------------+
//| Adds a key-value pair to the query parameters                    |
//+------------------------------------------------------------------+
void CQueryParam::AddParam(string key, string value)
  {
   m_query_param[key] = value;
  }
//+------------------------------------------------------------------+
//| Adds a single parameter from a formatted string                  |
//+------------------------------------------------------------------+
void CQueryParam::AddParam(string param)
  {
   //--- Check if the input string contains an "=" symbol, which indicates a key-value pair
   if(StringFind(param,"=") >= 0)
     {
      //--- Declare an array to hold the key and value after splitting the string
      string key_value[];
      
      //--- Split the input string using "=" as the delimiter and store the result in the key_value array
      int size = StringSplit(param,StringGetCharacter("=",0),key_value);
      
      //--- If the size of the split result is exactly 2 (meaning a valid key-value pair was found)
      if(size == 2)
        {
         // Add the key-value pair to the m_query_param map
         // key_value[0] is the key, key_value[1] is the value
         m_query_param[key_value[0]] = key_value[1];
        }
     }
  }
//+------------------------------------------------------------------+
//| Adds multiple parameters from an array of formatted strings      |
//+------------------------------------------------------------------+
void CQueryParam::AddParams(const string &params[])
  {
   //--- Get the size of the input array 'params'
   int size = ArraySize(params);
   
   //--- Loop through each element in the 'params' array.
   for(int i=0;i<size;i++)
     {
      //--- Call the AddParam function to add each parameter to the m_query_param map.
      this.AddParam(params[i]);
     }
  }
//+------------------------------------------------------------------+
//| Removes a parameter by key                                       |
//+------------------------------------------------------------------+
void CQueryParam::RemoveParam(const string key)
  {
   m_query_param.Remove(key);
  }
//+------------------------------------------------------------------+
//| Retrieves a parameter value by key                               |
//+------------------------------------------------------------------+
string CQueryParam::GetParam(const string key) const
  {
   return(m_query_param[key].ToString());
  }
//+------------------------------------------------------------------+
//| Checks if a parameter exists by key                              |
//+------------------------------------------------------------------+
bool CQueryParam::HasParam(const string key)
  {
   return(m_query_param.FindKey(key) != NULL);
  }
//+------------------------------------------------------------------+
//| Returns the number of parameters stored                          |
//+------------------------------------------------------------------+
int CQueryParam::ParamSize(void)
  {
   return(m_query_param.Size());
  }
//+------------------------------------------------------------------+
//| Parses a query string into parameters                            |
//| Input: query_param - A string formatted as a query parameter     |
//| Output: bool - Always returns true, indicating successful parsing|
//+------------------------------------------------------------------+
bool CQueryParam::Parse(const string query_param)
  {
   //--- Split the input string by '&', separating the individual parameters
   string params[];
   int size = StringSplit(query_param, StringGetCharacter("&",0), params);

   //--- Iterate through each parameter string
   for(int i=0; i<size; i++)
     {
      //--- Split each parameter string by '=', separating the key and value
      string key_value[];
      StringSplit(params[i], StringGetCharacter("=",0), key_value);

      //--- Check if the split resulted in exactly two parts: key and value
      if (ArraySize(key_value) == 2)
        {
         //--- Assign the value to the corresponding key in the map
         m_query_param[key_value[0]] = key_value[1];
        }
     }
   //--- Return true indicating that parsing was successful
   return(true);
  }
//+------------------------------------------------------------------+
//| Serializes the stored parameters into a query string             |
//| Output: string - A string representing the serialized parameters |
//+------------------------------------------------------------------+
string CQueryParam::Serialize(void)
  {
   //--- Initialize an empty string to build the query parameter string
   string query_param = "";

   //--- Iterate over each key-value pair in the parameter map
   for(int i=0; i<m_query_param.Size(); i++)
     {
      //--- Append a '?' at the beginning to indicate the start of parameters
      if(i == 0)
        {
         query_param = "?";
        }

      //--- Construct each key-value pair as 'key=value'
      if(i == m_query_param.Size()-1)
        {
         //--- If it's the last pair, don't append '&'
         query_param += m_query_param[i].m_key + "=" + m_query_param[i].ToString();
        }
      else
        {
         //--- Otherwise, append '&' after each pair
         query_param += m_query_param[i].m_key + "=" + m_query_param[i].ToString() + "&";
        }
     }

   //--- Return the constructed query parameter string
   return(query_param);
  }
//+------------------------------------------------------------------+
//| Clears all stored parameters                                     |
//+------------------------------------------------------------------+
void CQueryParam::Clear(void)
  {
   m_query_param.Clear();
  }
//+------------------------------------------------------------------+


URL

クエリパラメータを扱うクラスができたので、あとはプロトコル、ホスト、ポートなどを使ってCURLクラスを作りましょう。以下はMQL5でのCURLクラスの初期実装です。CQueryParamクラスをインポートすることを忘れないでください。

//+------------------------------------------------------------------+
//|                                                          URL.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "QueryParam.mqh"
class CURL
  {
public:
                     CURL(void);
                    ~CURL(void);
  };
CURL::CURL(void)
  {
  }
CURL::~CURL(void)
  {
  }
//+------------------------------------------------------------------+

最もよく使われるプロトコルを含むENUMを作ってみましょう。

//+------------------------------------------------------------------+
//| Enum to represent different URL protocol                         |
//+------------------------------------------------------------------+
enum ENUM_URL_PROTOCOL
  {
   URL_PROTOCOL_NULL = 0,  // No protocol defined
   URL_PROTOCOL_HTTP,      // HTTP protocol
   URL_PROTOCOL_HTTPS,     // HTTPS protocol
   URL_PROTOCOL_WS,        // WebSocket (WS) protocol
   URL_PROTOCOL_WSS,       // Secure WebSocket (WSS) protocol
   URL_PROTOCOL_FTP        // FTP protocol
  };

クラスのプライベートフィールドに、URLの基本要素を形成する構造体と、この構造体のインスタンスであるm_urlを追加します。

private:
   
   //--- Structure to hold components of a URL
   struct MqlURL
     {
      ENUM_URL_PROTOCOL protocol;      // URL protocol
      string            host;          // Host name or IP
      uint              port;          // Port number
      string            path;          // Path after the host
      CQueryParam       query_param;   // Query parameters as key-value pairs
     };
   MqlURL            m_url;            // Instance of MqlURL to store the URL details

セッターとゲッター、そしてそれらの実装を作成します。

//+------------------------------------------------------------------+
//| class : CURL                                                     |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CURL                                               |
//| Heritage    : No heritage                                        |
//| Description : Define a class CURL to manage and manipulate URLs  |
//|                                                                  |
//+------------------------------------------------------------------+
class CURL
  {
public:
                     CURL(void);
                    ~CURL(void);
   
   
   //--- Methods to access and modify URL components
   ENUM_URL_PROTOCOL Protocol(void) const;                  // Get the protocol
   void              Protocol(ENUM_URL_PROTOCOL protocol);  // Set the protocol
   string            Host(void) const;                      // Get the host
   void              Host(const string host);               // Set the host
   uint              Port(void) const;                      // Get the port
   void              Port(const uint port);                 // Set the port
   string            Path(void) const;                      // Get the path
   void              Path(const string path);               // Set the path
   CQueryParam       *QueryParam(void);                     // Access query parameters
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CURL::CURL(void)
  {
   this.Clear();
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CURL::~CURL(void)
  {
  }
//+------------------------------------------------------------------+
//| Getter for protocol                                              |
//+------------------------------------------------------------------+
ENUM_URL_PROTOCOL CURL::Protocol(void) const
  {
   return(m_url.protocol);
  }
//+------------------------------------------------------------------+
//| Setter for protocol                                              |
//+------------------------------------------------------------------+
void CURL::Protocol(ENUM_URL_PROTOCOL protocol)
  {
   m_url.protocol = protocol;
  }
//+------------------------------------------------------------------+
//| Getter for host                                                  |
//+------------------------------------------------------------------+
string CURL::Host(void) const
  {
   return(m_url.host);
  }
//+------------------------------------------------------------------+
//| Setter for host                                                  |
//+------------------------------------------------------------------+
void CURL::Host(const string host)
  {
   m_url.host = host;
  }
//+------------------------------------------------------------------+
//| Getter for port                                                  |
//+------------------------------------------------------------------+
uint CURL::Port(void) const
  {
   return(m_url.port);
  }
//+------------------------------------------------------------------+
//| Setter for port                                                  |
//+------------------------------------------------------------------+
void CURL::Port(const uint port)
  {
   m_url.port = port;
  }
//+------------------------------------------------------------------+
//| Getter for path                                                  |
//+------------------------------------------------------------------+
string CURL::Path(void) const
  {
   return(m_url.path);
  }
//+------------------------------------------------------------------+
//| Setter for path                                                  |
//+------------------------------------------------------------------+
void CURL::Path(const string path)
  {
   m_url.path = path;
  }
//+------------------------------------------------------------------+
//| Accessor for query parameters (returns a pointer)                |
//+------------------------------------------------------------------+
CQueryParam *CURL::QueryParam(void)
  {
   return(GetPointer(m_url.query_param));
  }
//+------------------------------------------------------------------+

このデータを扱うための新しい関数を追加します。

  • Clear(void):クラスに格納されているすべてのデータをクリアし、その属性を空またはデフォルト値にリセットします。クラスのインスタンスを再利用して新しいURLを構築したい場合や、古いデータが誤って新しい操作に含まれないようにする必要がある場合に便利です。言い換えれば、クラスを「リセット」し、それまで保存されていた情報をすべて削除します。

    仕組み

    • データ型に応じて、クラス属性を空またはヌルに設定します(プロトコル、ドメインなどの場合は空文字列)。
    • すべてのクエリパラメータを削除し、パスをデフォルト値にリセットします。
    • Clear()を呼び出すと、クラスインスタンスは作成されたばかりの初期状態になります。


    以下は例です。

    以前に格納されていたクラスの場合

    • プロトコル:https
    • ドメイン:api.example.com
    • パス:/v1/ユーザー
    • クエリパラメータ:id=123&active=true


    Clear()を呼び出すと、これらの値はすべてリセットされます。

    • プロトコル:""
    • ドメイン:""
    • パス:""
    • クエリパラメータ:""


    これで、クラスはゼロから新しいURLを構築する準備が整ったことになります。

  • BaseUrl(void):プロトコル(例:http 、 https)、ドメイン(例:www.example.com)、およびオプションでポート(例:8080)で構成されるURLのベース部分を生成して返します。このメソッドは、サーバーとの通信に不可欠な要素が正しいことを確認します。このメソッドによって、常にベース部分から始まる動的なURLを柔軟に構成することができます。URLのベースを再利用して、同じサーバー上の異なるリソースにアクセスしたい場合に便利です。

  • PathAndQuery(void):リソースのパス部分を生成し、以前に追加したクエリパラメータを連結する役割を担います。パスは通常、サーバー上でアクセスするリソースを指定しますが、クエリパラメータを使用すると、フィルターやページ区切りなどの追加の詳細を提供できます。パスパラメータとクエリパラメータを基本URLから分離することで、URLのさまざまな部分をより整理された方法で構成できます。このメソッドは、HTTPリクエストまたはこの構造を必要とする他のメソッドで直接使用できる文字列を返します。

  • FullUrl(void):URLのすべての部分を「コンパイル」し、完全なすぐに使用できるURLを返すメソッドです。BaseURL()PathAndQuery()を組み合わせて、HTTPリクエストで直接使用できる最終的なURLを形成します。HTTPリクエストを送信するために完全なURLが必要な場合、このメソッドはURLが適切にフォーマットされていることを保証する最も簡単な方法で、ベースパラメータとクエリパラメータを連結し忘れるなどのエラーを防止できます。

    以下は例です。クラスが以下の値を保存している場合:

    • プロトコル:https
    • ドメイン:api.example.com
    • パス:/v1/ユーザー
    • クエリパラメータ:id=123&active=true


    Serialize()を呼び出すと、関数は以下を戻します。

    https://api.exemplo.com/v1/users?id=123&active=true

  • Parse(const string url):FullUrl(void)の逆の処理をおこないます。完全なURLを引数として受け取り、その構成要素を整理された方法で分離します。目標は、URLを小さな部分(プロトコル、ドメイン、ポート、パス、クエリパラメータなど)に分解して、プログラマーがこれらの要素を個別に操作できるようにすることです。これは、URLを受け取ってその詳細を理解したり、プログラムで変更したりする必要がある場合に特に便利です。

    仕組み

    • 完全なURLを含む文字列を受け取る。
    • 文字列を解析(または「解析」)し、URLの各パーツ(プロトコル(http、https)、ドメイン、ポート(ある場合)、パス、クエリパラメータ)を特定する。
    • これらの値を、protocol、host、path、queryParamsなどのクラスの内部属性に割り当てる。- URLを分割するために、://、/、?、&のような区切り文字を正しく処理する。


    以下は例です。以下のURLについて、

    https://api.example.com:8080/v1/users?id=123&active=true

    Parse()を呼び出すと、この関数は以下の値を代入します。

    • プロトコル:https
    • ドメイン:api.example.com
    • ポート:8080
    • パス:/v1/ユーザー
    • クエリパラメータ:id=123,active=true


    これにより、URLの各部分にプログラムでアクセスできるようになり、URLの操作や解析が容易になります。

  • UrlProtocolToStr(ENUM_URL_PROTOCOL protocol):プロトコルを文字列で返します。ENUM_URL_PROTOCOLを単純な文字列に変換する場合などに便利です。

    • URL_PROTOCOL_HTTP → "http"
    • URL_PROTOCOL_HTTPS → "httpS"
    • URL_PROTOCOL_WSS → "wss"
    • その他...

これらのメソッドはそれぞれ、URLの構築や操作において重要な役割を果たしています。これらの機能により、Connexusライブラリは、ゼロからURLを作成する場合でも、既存のURLを解析する場合でも、APIの動的なニーズに対応できる高い柔軟性を備えています。これらのメソッドを実装することで、開発者はプログラムでURLを構成することができ、エラーを回避し、サーバーとの通信を最適化することができます。以下は、実装された関数のコードです。

//+------------------------------------------------------------------+
//| Define constants for different URL protocols                     |
//+------------------------------------------------------------------+
#define HTTP "http"
#define HTTPS "https"
#define WS "ws"
#define WSS "wss"
#define FTP "ftp"
//+------------------------------------------------------------------+
//| class : CURL                                                     |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CURL                                               |
//| Heritage    : No heritage                                        |
//| Description : Define a class CURL to manage and manipulate URLs  |
//|                                                                  |
//+------------------------------------------------------------------+
class CURL
  {
private:
   string            UrlProtocolToStr(ENUM_URL_PROTOCOL protocol); // Helper method to convert protocol enum to string
   
public:
   //--- Methods to parse and serialize the URL
   void              Clear(void);                           // Clear/reset the URL
   string            BaseUrl(void);                         // Return the base URL (protocol, host, port)
   string            PathAndQuery(void);                    // Return the path and query part of the URL
   string            FullUrl(void);                         // Return the complete URL
   bool              Parse(const string url);               // Parse a URL string into components
  };
//+------------------------------------------------------------------+
//| Convert URL protocol enum to string                              |
//+------------------------------------------------------------------+
string CURL::UrlProtocolToStr(ENUM_URL_PROTOCOL protocol)
  {
   if(protocol == URL_PROTOCOL_HTTP)   { return(HTTP);   }
   if(protocol == URL_PROTOCOL_HTTPS)  { return(HTTPS);  }
   if(protocol == URL_PROTOCOL_WS)     { return(WS);     }
   if(protocol == URL_PROTOCOL_WSS)    { return(WSS);    }
   if(protocol == URL_PROTOCOL_FTP)    { return(FTP);    }
   return(NULL);
  }
//+------------------------------------------------------------------+
//| Clear or reset the URL structure                                 |
//+------------------------------------------------------------------+
void CURL::Clear(void)
  {
   m_url.protocol = URL_PROTOCOL_NULL;
   m_url.host = "";
   m_url.port = 0;
   m_url.path = "";
   m_url.query_param.Clear();
  }
//+------------------------------------------------------------------+
//| Construct the base URL from protocol, host, and port             |
//+------------------------------------------------------------------+
string CURL::BaseUrl(void)
  {
   //--- Checks if host is not null or empty
   if(m_url.host != "" && m_url.host != NULL)
     {
      MqlURL url = m_url;
      
      //--- Set default protocol if not defined
      if(url.protocol == URL_PROTOCOL_NULL)
        {
         url.protocol = URL_PROTOCOL_HTTPS;
        }
      
      //--- Set default port based on the protocol
      if(url.port == 0)
        {
         url.port = (url.protocol == URL_PROTOCOL_HTTPS) ? 443 : 80;
        }
      
      //--- Construct base URL (protocol + host)
      string serialized_url = this.UrlProtocolToStr(url.protocol) + "://" + url.host;
      
      //--- Include port in URL only if it's not the default port for the protocol
      if(!(url.protocol == URL_PROTOCOL_HTTP && url.port == 80) &&
         !(url.protocol == URL_PROTOCOL_HTTPS && url.port == 443))
        {
         serialized_url += ":" + IntegerToString(m_url.port);
        }
      
      return(serialized_url);
     }
   else
     {
      return("Error: Invalid host");
     }
  }
//+------------------------------------------------------------------+
//| Construct path and query string from URL components              |
//+------------------------------------------------------------------+
string CURL::PathAndQuery(void)
  {
   MqlURL url = m_url;
   
   //--- Ensure path starts with a "/"
   if(url.path == "")
     {
      url.path = "/";
     }
   else if(StringGetCharacter(url.path,0) != '/')
     {
      url.path = "/" + url.path;
     }
   
   //--- Remove any double slashes from the path
   StringReplace(url.path,"//","/");
   
   //--- Check for invalid spaces in the path
   if(StringFind(url.path," ") >= 0)
     {
      return("Error: Invalid characters in path");
     }
   
   //--- Return the full path and query string
   return(url.path + url.query_param.Serialize());
  }
//+------------------------------------------------------------------+
//| Return the complete URL (base URL + path + query)                |
//+------------------------------------------------------------------+
string CURL::FullUrl(void)
  {
   return(this.BaseUrl() + this.PathAndQuery());
  }
//+------------------------------------------------------------------+
//| Parse a URL string and extract its components                    |
//+------------------------------------------------------------------+
bool CURL::Parse(const string url)
  {
   //--- Create an instance of MqlURL to hold the parsed data
   MqlURL urlObj;
   
   //--- Parse protocol from the URL
   int index_end_protocol = 0;
   
   //--- Check if the URL starts with "http://"
   if(StringFind(url,"http://") >= 0)
     {
      urlObj.protocol = URL_PROTOCOL_HTTP;
      index_end_protocol = 7;
     }
   else if(StringFind(url,"https://") >= 0)
     {
      urlObj.protocol = URL_PROTOCOL_HTTPS;
      index_end_protocol = 8;
     }
   else if(StringFind(url,"ws://") >= 0)
     {
      urlObj.protocol = URL_PROTOCOL_WS;
      index_end_protocol = 5;
     }
   else if(StringFind(url,"wss://") >= 0)
     {
      urlObj.protocol = URL_PROTOCOL_WSS;
      index_end_protocol = 6;
     }
   else if(StringFind(url,"ftp://") >= 0)
     {
      urlObj.protocol = URL_PROTOCOL_FTP;
      index_end_protocol = 6;
     }
   else
     {
      return(false); // Unsupported protocol
     }
   
   //--- Separate the endpoint part after the protocol
   string endpoint = StringSubstr(url,index_end_protocol);  // Get the URL part after the protocol
   string parts[];                                          // Array to hold the split components of the URL
   
   //--- Split the endpoint by the "/" character to separate path and query components
   int size = StringSplit(endpoint,StringGetCharacter("/",0),parts);
   
   //--- Handle the host and port part of the URL
   string host_port[];
   
   //--- If the first part (host) contains a colon (":"), split it into host and port
   if(StringSplit(parts[0],StringGetCharacter(":",0),host_port) > 1)
     {
      urlObj.host = host_port[0];                        // Set the host
      urlObj.port = (uint)StringToInteger(host_port[1]); // Convert and set the port
     }
   else
     {
      urlObj.host = parts[0];
      
      //--- Set default port based on the protocol
      if(urlObj.protocol == URL_PROTOCOL_HTTP)
        {
         urlObj.port = 80;
        }
      if(urlObj.protocol == URL_PROTOCOL_HTTPS)
        {
         urlObj.port = 443;
        }
     }
   
   //--- If there's no path, default to "/"
   if(size == 1)
     {
      urlObj.path += "/"; // Add a default root path "/"
     }
   
   //--- Loop through the remaining parts of the URL (after the host)
   for(int i=1;i<size;i++)
     {
      //--- If the path contains an empty part, return false (invalid URL)
      if(parts[i] == "")
        {
         return(false);
        }
      //--- If the part contains a "?" (indicating query parameters)
      else if(StringFind(parts[i],"?") >= 0)
        {
         string resource_query[];
         
         //--- Split the part by "?" to separate the resource and query
         if(StringSplit(parts[i],StringGetCharacter("?",0),resource_query) > 0)
           {
            urlObj.path += "/"+resource_query[0];
            urlObj.query_param.Parse(resource_query[1]);
           }
        }
      else
        {
         //--- Otherwise, add to the path as part of the URL
         urlObj.path += "/"+parts[i];
        }
     }
   
   //--- Assign the parsed URL object to the member variable
   m_url = urlObj;
   return(true);
  }
//+------------------------------------------------------------------+

最後に、開発者を支援するために2つの新強い関数を追加します。

  • ShowData(void): URL要素を個別に表示し、デバッグやクラスにどのようなデータが格納されているかを理解するのに役立ちます。次は例です。

https://api.exemplo.com/v1/users?id=123&active=true

この関数は以下を返すべきです。

Protocol: https
Host: api.exemplo.com
Port: 443
Path: /v1/users
Query Param: {
   "id":123,
   "active":true
}

  • Compare(CURL &url):CURLクラスの別のインスタンスを受け取ります。両方のインスタンスに格納されているURLが同じであればtrueを返し、そうでなければfalseを返す。シリアライズされたURLの比較を避けることができ、時間の節約になります。以下は、Compare()関数を使用しない例です。
    // Example without using the Compare() method
    CURl url1;
    CURl url2;
    
    if(url1.FullUrl() == url2.FullUrl())
      {
       Print("Equals URL");
      }
    
    
    // Example with method Compare()
    CURl url1;
    CURl url2;
    
    if(url1.Compare(url2))
      {
       Print("Equals URL");
      }
        
    
        
    

以下は、それぞれの関数を実装するためのコードです。

//+------------------------------------------------------------------+
//| class : CURL                                                     |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CURL                                               |
//| Heritage    : No heritage                                        |
//| Description : Define a class CURL to manage and manipulate URLs  |
//|                                                                  |
//+------------------------------------------------------------------+
class CURL
  {
public:
   //--- Auxiliary methods
   bool              Compare(CURL &url);                    // Compare two URLs
   string            ShowData();                            // Show URL details as a string
  };
//+------------------------------------------------------------------+
//| Compare the current URL with another URL                         |
//+------------------------------------------------------------------+
bool CURL::Compare(CURL &url)
  {
   return (m_url.protocol == url.Protocol() &&
           m_url.host == url.Host() &&
           m_url.port == url.Port() &&
           m_url.path == url.Path() &&
           m_url.query_param.Serialize() == url.QueryParam().Serialize());
  }
//+------------------------------------------------------------------+
//| Display the components of the URL as a formatted string          |
//+------------------------------------------------------------------+
string CURL::ShowData(void)
  {
   return(
      "Protocol: "+EnumToString(m_url.protocol)+"\n"+
      "Host: "+m_url.host+"\n"+
      "Port: "+IntegerToString(m_url.port)+"\n"+
      "Path: "+m_url.path+"\n"+
      "Query Param: "+m_url.query_param.Serialize()+"\n"
   );
  }
//+------------------------------------------------------------------+

URLを扱うための2つのクラスが完成したので、テストに移ります。


テスト

さて、初期クラスの準備ができたので、クラスを通してURLを作成し、逆のプロセスもおこなってみましょう。テストを実行するために、Experts/Connexus/TestUrl.mq5というパスに従ってTestUrl.mq5というファイルを作成します。

int OnInit()
  {
   //--- Creating URL
   CURL url;
   url.Host("example.com");
   url.Path("/api/v1/data");
   Print("Test1 | # ",url.FullUrl() == "https://example.com/api/v1/data");
   
   //--- Changing parts of the URL
   url.Host("api.example.com");
   Print("Test2 | # ",url.FullUrl() == "https://api.example.com/api/v1/data");
   
   //--- Parse URL
   url.Clear();
   string url_str = "https://api.example.com/api/v1/data";
   Print("Test3 | # ",url.Parse(url_str));
   Print("Test3 | - Protocol # ",url.Protocol() == URL_PROTOCOL_HTTPS);
   Print("Test3 | - Host # ",url.Host() == "api.example.com");
   Print("Test3 | - Port # ",url.Port() == 443);
   Print("Test3 | - Path # ",url.Path() == "/api/v1/data");
   
//---
   return(INIT_SUCCEEDED);
  }

EAを実行すると、ターミナルに以下のデータが表示されます。

Test1 | # true
Test2 | # true
Test3 | # true
Test3 | - Protocol # true
Test3 | - Host # true
Test3 | - Port # true
Test3 | - Path # true


結論

この記事では、HTTP動詞(GET、POST、PUT、DELETE)といった基本概念から、応答ステータスコードを用いたリクエスト結果の解釈まで、HTTPプロトコルの仕組みを詳しく解説しました。また、MQL5アプリケーションでのURL管理を効率化するため、クエリパラメータを簡単かつ効果的に操作できるCQueryParamクラスを構築しました。さらに、URLの特定部分を動的に変更可能にするCURLクラスを実装し、HTTPリクエストの作成および処理プロセスをより柔軟かつ堅牢なものにしました。

これらのツールを活用すれば、アプリケーションを外部APIと統合し、コードとWebサーバー間の通信を円滑におこなうための強力な基盤を構築できます。しかし、これはまだ始まりに過ぎません。次回の記事では、HTTPの世界をさらに深掘りし、リクエストのヘッダーと本体を操作するための専用クラスを構築し、HTTP通信の制御をさらに強化します。

API統合のスキルを次の段階へ引き上げるための重要なライブラリを開発していくので、今後の投稿にもぜひご期待ください。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/15897

MQL5用スキャルピングオーダーフロー MQL5用スキャルピングオーダーフロー
このMetaTrader 5エキスパートアドバイザー(EA)は、高度なリスク管理を備えたスキャルピングオーダーフロー戦略を実装しています。複数のテクニカル指標を使用し、オーダーフローの不均衡に基づいて取引機会を特定します。バックテストは潜在的な収益性を示しているが、特にリスク管理と取引結果の比率において、さらなる最適化の必要性を強調しています。経験豊富なトレーダーに適していますが、本番運用の前に十分なテストと理解が必要です。
MQL5とPythonで自己最適化エキスパートアドバイザーを構築する(第4回):スタッキングモデル MQL5とPythonで自己最適化エキスパートアドバイザーを構築する(第4回):スタッキングモデル
本日は、自らの失敗から学習するAI搭載の取引アプリケーションの構築方法について解説します。特に、「スタッキング」と呼ばれる手法を紹介します。この手法では、2つのモデルを組み合わせて1つの予測をおこないます。1つ目のモデルは通常、性能が比較的低い学習者であり、2つ目のモデルはその学習者の残差を学習する、より高性能なモデルです。目標は、これらのモデルをアンサンブルとして統合することで、より高精度な予測を実現することです。
PythonとMQL5による多銘柄分析(前編):NASDAQ集積回路メーカー PythonとMQL5による多銘柄分析(前編):NASDAQ集積回路メーカー
ポートフォリオのリターンを最大化するために、AIを活用してポジションサイジングと注文数量を最適化する方法について解説します。本稿では、アルゴリズムを用いて最適なポートフォリオを特定し、期待リターンやリスク許容度に応じてポートフォリオを調整する手法を紹介します。このプロセスでは、SciPyライブラリやMQL5言語を活用し、保有中のすべてのデータを基に、最適かつ分散化されたポートフォリオを構築します。
知っておくべきMQL5ウィザードのテクニック(第40回):Parabolic SAR(パラボリックSAR) 知っておくべきMQL5ウィザードのテクニック(第40回):Parabolic SAR(パラボリックSAR)
パラボリックSAR (Stop-and-Reversal)は、トレンドの確認と終了点を示す指標です。トレンドの見極めが遅れるため、その主な目的は、ポジションのトレーリングストップロスを位置づけることです。ウィザードで組み立てられるエキスパートアドバイザー(EA)のカスタムシグナルクラスを活用して、本当にEAのシグナルとして使えるかどうか調べてみました。