【3通り】MQL4/5でWebRequest()を使わずHTTPリクエスト

MQL

こんにちは。裁量の専業トレーダー兼、MQL開発者のmetroです。

この記事では、2017年からMQLを書き続けている僕が、
標準のWebRequest()関数を使わずに
HTTPリクエストを送信する方法をご紹介します。

3種類の方法でGET/POSTリクエスト両方を試します。
掲載しているコードはMQL4/5どちらでも使えます。

なお、動作環境はWindowsのみです。
(Windowsの標準.dllである「wininet.dll」を利用するため)

まず、HTTPリクエストについてのおさらいと、
標準のWebRequest()関数を避けるメリットとデメリットを説明して、
具体的な方法論に移ります。

リクエスト先はhttpbinです。
httpbinはリクエストした内容をjson形式で返してくれます。

Httpリクエストとは?

簡単に言うと、特定のURL(エンドポイント)に情報を要求したり
情報を送信したりすることです。

MetaTraderでHTTPリクエストを使うことで
実現できるのは例えばこんなこと。

  • MetaTraderで扱っていない商品の価格を参考にトレードを行う
  • Googleスプレッドシートに取引の履歴を自動で出力
  • トレードシグナルを配信する
  • LINEにトレードの執行を通知する
  • 作成したEAやインジのWeb認証を行う
  • その他APIを叩いてもろもろの情報を利用する

ようするに、何でもできます

標準の関数を使わないメリットとデメリット

メリット

  • 標準のWebRequest()で必要だった、ターミナル上でのURLの入力を省ける
    →ユーザーの作業を1つ減らせる
  • リクエスト先のホスト名をユーザーから隠せる
    →ユーザーにロジック配信元などの情報源などを知られずに処理できる

デメリット

  • 標準の方法ではないのでまともな日本語マニュアルがない
  • MQLマーケットでは出品できない
    →MQLの制限を回避するための非公式なやり方なので、セキュリティ的に問題ありと判断されるらしい
  • Mac上のMetaTraderでは使えない
    →前述の通り、Windows依存の.dllを使うため

上記を呑めるのであれば、やってみる価値はあるはずです。

オマタセシマシタ。それではLet’s リクエスト。

WinINet.mqh

ソースコードは以下のGitHubからDLできます。

このライブラリはMQL5では動作しますが
MQL4ではコンパイル時にエラーが出ます。

もちろん.mqhファイルを修正すれば使えますが、
C++で書かれたwininet.dllを理解して修正する必要があります。

したがってMQL4を書く場合には次に紹介するmql-httpをおすすめします。

早速、GETリクエストをするコードを見てみましょう。

GETメソッド

#include <WinINet.mqh>
string url = "https://httpbin.org/get?status=happy&user=metro";

void OnStart()
{
    // リクエストヘッダー設定
    string headers = "Authorization: Bearer gaghroihwpogrheqgor\r\n";
    headers += "User-Agent: It's metro!!\r\n";
    headers += "America: Japan\r\n";

    // リクエスト準備
    WininetRequest request;
    request.method = "GET";
    request.host = "httpbin.org";
    request.path = "/get?status=happy&user=metro";
    request.headers = headers;

    // レスポンス準備とリクエスト実行
    WininetResponse response;
    if (!WebReq(request, response))
        return;

    // 結果を表示
    Print("response data\r\n", response.GetDataStr());
}

レスポンス(GETメソッド)

{
  "args": {
    "status": "happy", 
    "user": "metro"
  }, 
  "headers": {
    "Accept-Encoding": "gzip, deflate", 
    "America": "Japan", 
    "Authorization": "Bearer gaghroihwpogrheqgor", 
    "Cache-Control": "no-cache", 
    "Content-Length": "0", 
    "Host": "httpbin.org", 
    "User-Agent": "It's metro!!", 
    "X-Amzn-Trace-Id": "Root=xxxxxxxxxxxxxxxxxxxxxxxx"
  }, 
  "origin": "xxx.xxx.xxx.xxx", 
  "url": "https://httpbin.org/get?status=happy&user=metro"
}

続いて、POSTリクエストを見てみましょう。

POSTメソッド

#include <WinINet.mqh>

void OnStart()
{
    // リクエストヘッダー設定
    string headers = "Authorization: Bearer gaghroihwpogrheqgor\r\n";
    headers += "User-Agent: It's metro!!\r\n";
    headers += "America: Japan\r\n";
    headers += "Content-Type: text/plain\r\n";
    headers += "accept: application/json\r\n";

    // リクエストボディ
    string postData = "{ \"Name\": \"metro\",  \"Age\": \"100\",  \"url\": \"https://twitter.com/metroporeturn\"}";

    // リクエスト準備
    WininetRequest request;
    request.method = "POST";
    request.host = "httpbin.org";
    request.path = "/post";
    request.headers = headers;
    request.data_str = postData;

    // レスポンス準備とリクエスト実行
    WininetResponse response;
    if (!WebReq(request, response))
        return;

    // 結果を表示
    Print("response data\r\n", response.GetDataStr());
}

レスポンス(POST)

{
  "args": {}, 
  "data": "{ \"Name\": \"metro\",  \"Age\": \"100\",  \"url\": \"https://twitter.com/metroporeturn\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "application/json", 
    "Accept-Encoding": "gzip, deflate", 
    "America": "Japan", 
    "Authorization": "Bearer gaghroihwpogrheqgor", 
    "Cache-Control": "no-cache", 
    "Content-Length": "78", 
    "Content-Type": "text/plain", 
    "Host": "httpbin.org", 
    "User-Agent": "It's metro!!", 
    "X-Amzn-Trace-Id": "Root=xxxxxxxxxxxxxxxxxxxxxxxx"
  }, 
  "json": {
    "Age": "100", 
    "Name": "metro", 
    "url": "https://twitter.com/metroporeturn"
  }, 
  "origin": "xxx.xxx.xxx.xxx", 
  "url": "https://httpbin.org/post"
}

最後に、リクエストとレスポンスの定義を記載します。

リクエストに関わる構造体と関数

// リクエスト構造体
struct WininetRequest {
    string           method;
    string           host;
    int              port;
    string           path;
    string           headers;
    uchar            data[];
    string           data_str;
    void             WininetRequest() {
        method = "GET";
        port = 443;
        path = "/";
        headers = "";
    }
};

// レスポンス構造体
struct WininetResponse {
    int              status;
    string           headers;
    string           location;
    uchar            data[];
    string           GetDataStr() {
        return UnicodeUnescape(data);
    }
};

// リクエスト実行関数
int WebReq(
    const string  method,           // HTTP method
    const string  host,             // host name
    const string  path,             // URL path
    int           port,             // port number
    bool          secure,           // use HTTPS
    const string  headers,          // HTTP request headers
    const uchar   &data[],          // HTTP request body
    uchar         &result[],        // server response data
    string        &result_headers,   // headers of server response
    string        &result_location        // redirect location
)

リクエストヘッダの編集もできますし、レスポンスもステータスコードからボディまで特に問題なさそうですね!

ただし、MQL4では使えない(自分で修正する必要がある)のが
う~んって感じ。

mql-http

ソースコードは以下のGitHubからDLできます。

このライブラリはMQL4/5どちらでも利用できますが
とくにMQL4でHTTPリクエストをする場合は
こちらがメインの選択肢になるかと思います。

GETメソッド

#include <Request.mqh>

void OnStart()
{
    // リクエスト先URL
    string url = "https://httpbin.org/get?status=happy&user=metro";

    // メソッドとリクエスト先を指定
    HttpRequest request("GET", url);

    // リクエストヘッダー設定
    request.AddHeader("Authorization", "Bearer gaghroihwpogrheqgor");
    request.AddHeader("User-Agent", "It's metro!");
    request.AddHeader("America", "Japan");

    // レスポンス準備とリクエスト実行
    HttpResponse response;
    request.Send(response);

    // 結果を表示
    printf("response data :\r\n%s", response.Body);
}

レスポンス(GET)

{
  "args": {
    "status": "happy", 
    "user": "metro"
  }, 
  "headers": {
    "Accept-Encoding": "gzip, deflate", 
    "America": "Japan", 
    "Authorization": "Bearer gaghroihwpogrheqgor", 
    "Cache-Control": "no-cache", 
    "Content-Length": "0", 
    "Host": "httpbin.org", 
    "User-Agent": "It's metro!!", 
    "X-Amzn-Trace-Id": "Root=xxxxxxxxxxxxxxxxxxxxxxxx"
  }, 
  "origin": "xxx.xxx.xxx.xxx", 
  "url": "https://httpbin.org/get?status=happy&user=metro"
}

それでは次、POSTメソッドでのリクエストです。

POSTメソッド

#include <Request.mqh>

void OnStart()
{
   // リクエスト先URL
    string url = "https://httpbin.org/post";

    // リクエストボディ
    string postData = "{ \"Name\": \"metro\",  \"Age\": \"100\",  \"url\": \"https://twitter.com/metroporeturn\"}";

    // メソッド、リクエスト先、リクエストボディを指定
    HttpRequest request("POST", url, postData);

    // リクエストヘッダー設定
    request.AddHeader("Authorization", "Basic gaghroihwpogrheqgor");
    request.AddHeader("User-Agent", "metro");
    request.AddHeader("America", "Japan");
    request.AddHeader("Content-Type", "text/plain");
    request.AddHeader("accept", "application/json");

    // レスポンス準備とリクエスト実行
    HttpResponse response;
    request.Send(response);

    // 結果を表示
    printf("response data :\r\n%s", response.Body);
}

レスポンス(POST)

{
  "args": {}, 
  "data": "{ \"Name\": \"metro\",  \"Age\": \"100\",  \"url\": \"https://twitter.com/metroporeturn\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "application/json", 
    "America": "Japan", 
    "Authorization": "Basic gaghroihwpogrheqgor", 
    "Cache-Control": "no-cache", 
    "Content-Length": "79", 
    "Content-Type": "text/plain", 
    "Host": "httpbin.org", 
    "User-Agent": "metro", 
    "X-Amzn-Trace-Id": "Root=xxxxxxxxxxxxxxxxxxxxxxxx"
  }, 
  "json": null, 
  "origin": "xxx.xxx.xxx.xxx", 
  "url": "https://httpbin.org/post"
}

最後に、クラスの定義を掲載。

リクエストに関わるクラスと構造体

// リクエストクラス
class HttpRequest {
private:
   string   m_verb;              
   string   m_url;               
   string   m_body;              
   string   m_referrer;          
   int      m_num_headers;       
   string   m_header_names[];    
   string   m_header_values[];   
   bool     m_header_coalesce[]; 
   
public:
   HttpRequest(const string verb, const string url, const string body = NULL, const string referrer = NULL);
   void AddHeader(const string name, const string value, const bool coalesce = false);   
   int Send(HttpResponse &response) const;
};

// レスポンス構造体
struct HttpResponse {
   int      StatusCode;
   string   Body;
};

先述したWinInet.mqhとの大きな違いは以下の2つ。

  • リクエストヘッダーの編集が直感的
  • レスポンスが必要最低限

前者は魅力的ですが、後者はちょっと微妙。
使い分けは好みって感じがします。

とはいえ、どちらを選んでもある程度、
満足の行くHTTPリクエストはできると思います。

次は3つ目ですが正直なところ、
多くの方が1,2つ目を使うと思うのでサッと紹介します。

mql_requests

ソースコードは以下のGitHubからDLできます。

スミマセン。
こちらのライブラリはMQL5でしか動作確認していません。

しかし、MQL5でしか見ていないのには理由があります。

それはレスポンスコードの取得ができないことです。
(レスポンスのボディは取得できます)

GitHubのissueにはissueには解決方法も記載されていますが
僕はステータスコードが取れないと知った時点で
使うのをやめてしまいました。

自分でmqhをいじるより、制作者自身が修正したものを使いたいですし
もっとよいライブラリ(先述の2つ)を見つけたので。

一応、以下にクラス定義だけ載せておきます。

リクエストに関わるクラスと構造体

// リクエストクラス
class Requests
{
public:
    int port;
    string host;
    string path;
    string parameters;
    string user;
    string password;

    int h_session;
    int h_connect;

    Response response;

    Requests()
    {
        response = Response();
        h_session = -1;
        h_connect = -1;
        user = "";
        password = "";
    };

    ~Requests() { close(); };

    bool check_dll(string &error);
    bool is_same_host(string url);
    bool open(string url);
    void close();
    void update_url_parts(string url);
    void read(int h_request, string &out);
    // --- GET ---
    Response get(string url, string &arr_data[][]);
    Response get(string url, RequestData &_request_data);
    Response get(string url, string _str_data = "");
    // --- POST ---
    Response post(string url, string &arr_data[][]);
    Response post(string url, RequestData &_request_data);
    Response post(string url, string _str_data);
    // --- SEND (GET or POST) ---
    Response send(string method, string url, string &arr_data[][]);
    Response send(string method, string url, RequestData &_request_data);
    Response send(string method, string url, string _str_data);
};

// レスポンス構造体
class Response {
public:
    string text;
    string status_code;
    string error;
    string url;
    string parameters;

    Response() {};
    ~Response() {};

    Response::Response(const Response &r) {
        text = r.text;
        status_code = r.status_code;
        error = r.error;
        url = r.url;
        parameters = r.parameters;
    }
};

こう見るとレスポンスの構造体には
情報がぎっしり詰まっていそうなもんですが

例えばresponse.status_codeでアクセスしても
空の文字列が返ってきます。

したがって1,2つ目を使うのがよいのかなと思います。

まとめ

いかがでしたか?
MQLでのHTTPリクエストはひとまずこれで十分ですね。

あなたの開発に役立てていただければとっても嬉しいです。

Xのフォローもお願いします。MQL開発のTipsを投稿しています。

タイトルとURLをコピーしました