
HTTPとConnexus(第2回):HTTPアーキテクチャとライブラリ設計の理解
はじめに
この記事は、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
各部には特定の役割があります。
- プロトコル:httpやhttpsなど、通信に使用するプロトコルを示します(例:https://)。
- ドメイン:リソースがホストされているサーバーの名前。ドメイン名(example.comなど)でもIPアドレス(192.168.1.1など)でもかまいません。
- ポート:通信に使用するサーバーポートを指定するオプションの番号。省略された場合、ブラウザはhttpには80、httpsには443のようなデフォルトのポートを使用します(例:8080)。
- パス:サーバー上のリソースまたはルートを指定します。ページ、APIエンドポイント、またはファイルを表すことができます(例:/api/v1/users)。
- クエリパラメータ:サーバーに追加情報を渡すために使用します。疑問符の後に続き、キーと値のペアで形成されます。複数のパラメータは「&」で区切られます(例:?name=John&age=30)。
- 断片: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 ¶ms[]); // 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 ¶ms[]):複数のパラメータを一度に追加します。「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 ¶ms[]); // 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 ¶ms[]) { //--- 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





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索