こんにちは。裁量の専業トレーダー兼、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
このライブラリは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を投稿しています。
Tweets by metroporeturn