MQL4やMQL5でのHTTP通信は、既存のWebRequest関数によって可能になっています。
しかし、この関数にはいくつかの制限があります。
これを解決するため、GETとPOSTリクエストに対応し、開発の利便性を大幅に向上させる新しい関数、WebRequest2を作成しました。この関数はWinAPIを活用し、従来の制限を打破します。
サンプルとして、画像付きLINE通知のソースコードを紹介します。
3行でわかる本記事の内容
- WebRequest関数のデメリットを解消
- オリジナル関数「WebRequest2」の紹介
- 画像付きLINE通知のソースコードをプレゼント
WebRequest関数のデメリットと対策
MQL言語は、ニッチなプログラミング言語です。そのため、ChatGPTのようなAIにおいても、MQL言語の正確なコードを提供するのは困難です。
この言語で、HTTPリクエストを実行しようとする際に多くの場合、標準的なWebRequest関数に基づいたコードの提案が行われます。
しかし、WebRequest関数は利用にあたっていくつかのデメリットがあり、多くの開発者はその使用を避けているのが現状です。
2段階のプロセスを踏む必要がある
これらの問題に直面し、WinAPIを利用してHTTPリクエストを行うコードを作成するものの、通常はWebRequest関数を用いた標準的なコードから始めます。
そこから、WinAPIを使ってHTTPリクエストを実行する、という2段階のプロセスを踏むことになります。
このプロセスは非効率的で時間を要するため、よりシンプルな方法の必要性が明らかでした。
そもそもHTTP通信(リクエスト)とは?
特定のWebサイト、Webアプリにアクセスして情報を送受信することです。
簡単に言うと受信がGET、送信がPOSTです。
GET(受信)の代表例としては経済指標の取得。
POST(送信)の代表例としてはLINE通知があります。
デメリットの対策
この問題に対処するため、下記の【オリジナル関数】WebRequest2 の開発に至りました。
【オリジナル関数】WebRequest2
- WebRequest関数と同じ引数を持つ
- 関数名の末尾に「2」を付けるだけで、WinAPIを使用したHTTPリクエストを実行できる
このアプローチにより、開発者はChatGPTなどのAIを利用してWebRequest関数に基づくコードを簡単に取得し、そのコードを微調整するだけでWinAPIを活用したHTTPリクエストに変換できるようになりました。
この革新的な解決策により、MQLプログラミングにおけるHTTP通信のプロセスが、大幅に簡略化されました。
従来のWebRequest関数とメリット・デメリット
従来のWebRequestのバージョンは以下の2パターンがあります。
パターン1
int WebRequest(
const string method, // HTTP メソッド
const string url, // URL
const string cookie, // クッキー
const string referer, // リファラ
int timeout, // 時間切れ
const char &data[], // HTTPメッセージ本文の配列
int data_size, // data[] 配列のバイトサイズ
char &result[], // サーバ応答データを含む配列
string &result_headers // サーバ応答ヘッダ
);
パターン2
int WebRequest(
const string method, // HTTP メソッド
const string url, // URL
const string headers, // ヘッダ
int timeout, // 時間切れ
const char &data[], // HTTPメッセージ本文の配列
char &result[], // サーバ応答データを含む配列
string &result_headers // サーバ応答ヘッダ
);
メリットとデメリット
- HTTP通信を行う、MQL唯一の関数
- 引数を指定するだけで、簡単にHTTP通信が行える
- セキュリティ面で安全※
※「WebRequestを許可するURLリスト」によって、許可したURLにのみ接続を行う。
- 事前に「WebRequestを許可するURLリスト」を指定する必要があり設定が面倒。またこれによりユーザー側に認証などで使用しているURLが知られてしまう。
- テスターで使用不可
- インジケーターから使用不可
これらの問題により、多くの開発者はWebRequestを使用しない手段を模索してきました。
オリジナル関数のWebRequest2を作成する
WebRequest2関数の使い方
WebRequest2関数は、以下の構造でHTTPリクエストを行います。
既存のWebRequest関数(前章の②)の引数をそのままに、従来のWebRequest関数を使用せずにWinAPIを利用してHTTPリクエストを行います。
int WebRequest2(
const string method, // HTTPメソッド
const string url, // URL
const string headers, // ヘッダ
int timeout, // タイムアウト
char &data[], // HTTPメッセージ本文の配列
char &result[], // サーバ応答データを含む配列
string &result_headers // サーバ応答ヘッダ
);
- 全体的なコードがやや長くなる。
- GETとPOSTメソッドにのみ対応しており、他のHTTPメソッドには対応していない。
オタクのみんなは長いコードにも慣れてると思うから平気だよねっ(‘Д’)
サクッとコピペしちゃおー♪
WebRequest2関数のソースコード
今回のコードは長くなっているので、下記のソースコードを開くからご覧ください。
//+------------------------------------------------------------------+
//| GETまたはPOSTリクエストを実行し、データを取得 ワンダフルFXライフ
//+------------------------------------------------------------------+
#define INTERNET_DEFAULT_HTTPS_PORT 443
#define INTERNET_DEFAULT_HTTP_PORT 80
#define INTERNET_SERVICE_HTTP 3
#define HTTP_QUERY_STATUS_CODE 19
#define HTTP_QUERY_RAW_HEADERS_CRLF 21
#define INTERNET_FLAG_SECURE 0x00800000
#define INTERNET_FLAG_PRAGMA_NOCACHE 0x00000100
#define INTERNET_FLAG_KEEP_CONNECTION 0x00400000
#define INTERNET_FLAG_RELOAD 0x80000000
#define INTERNET_OPTION_CONNECT_TIMEOUT 2
#define INTERNET_OPTION_SEND_TIMEOUT 5
#define INTERNET_OPTION_RECEIVE_TIMEOUT 6
#import "wininet.dll"
int InternetOpenW(string userAgent, int accessType, string proxyServer, string bypassProxyFor, int flags);
int InternetOpenUrlW(int internetSession, string url, string header, int headerLength, int flags, int context);
int InternetConnectW(int sessionHandle, string serverUrl, int serverPort, string userCredentials, string userPassword, int serviceType, int connectionFlags, int context);
int HttpOpenRequestW(int connectionHandle, string httpVerb, string objectUrl, string httpVersion, string referrer, string acceptTypes, uint requestFlags, int context);
bool HttpSendRequestW(int hRequest, string lpszHeaders, int dwHeadersLength, uchar &lpOptional[], int dwOptionalLength);
bool InternetReadFile(int hFile, uchar &lpBuffer[], int dwNumberOfBytesToRead, int &lpdwNumberOfBytesRead);
bool InternetCloseHandle(int hInternet);
bool HttpQueryInfoA(int hRequest, int dwInfoLevel, uchar &lpBuffer[], int &lpdwBufferLength, int &lpdwIndex);
bool InternetSetOptionW(int hInternet, int dwOption, string lpBuffer, int dwBufferLength);
#import
int WebRequest2(const string method, const string url,const string headers, int timeout, char & data[], char & result[], string & result_headers)
{
int secure = 0;
int port = INTERNET_DEFAULT_HTTP_PORT;
if(StringFind(url, "https") >= 0)
{
secure = INTERNET_FLAG_SECURE;
port = INTERNET_DEFAULT_HTTPS_PORT;
}
//URLからホストとエンドポイントの抽出
string host = "", endpoint = "";
int response = -1;
int http_pos = StringFind(url, "//");
if(http_pos >= 0)
{
int next_slash = StringFind(url, "/", http_pos + 2);
if(next_slash < 0)
next_slash = StringLen(url);
host = StringSubstr(url, http_pos + 2, next_slash - http_pos - 2);
endpoint = StringSubstr(url, next_slash);
}
// インターネットセッションを開始
int session = InternetOpenW("Mozilla/5.0", 0, "", "", 0);
if(!session)
{
string error = "エラー:インターネットセッションを開けません";
StringToCharArray(error, result);
return(response);
}
// タイムアウトの設定
string timeoutStr = IntegerToString(timeout);
bool timeout_res = InternetSetOptionW(session, INTERNET_OPTION_CONNECT_TIMEOUT, timeoutStr, sizeof(timeout));
timeout_res = InternetSetOptionW(session, INTERNET_OPTION_SEND_TIMEOUT, timeoutStr, sizeof(timeout));
timeout_res = InternetSetOptionW(session, INTERNET_OPTION_RECEIVE_TIMEOUT, timeoutStr, sizeof(timeout));
if(method == "GET")
{
// URLを開く
int connect = InternetOpenUrlW(session, url, headers, StringLen(headers), secure, 0);
if(!connect)
{
InternetCloseHandle(session);
string error = "エラー:URLを開けません";
StringToCharArray(error, result);
return(response);
}
//レスポンス確認
{
int bufferLength = 1024;
uchar result_array[];
string result_str = "";
ArrayResize(result_array, bufferLength);
int index = 0;
// HTTPステータスコードを取得
if(HttpQueryInfoA(connect, HTTP_QUERY_STATUS_CODE, result_array, bufferLength, index))
{
for(int i = 0; i < bufferLength; i++)
{
uchar ch[1];
ch[0] = result_array[i];
if(ch[0] != NULL)
result_str += CharArrayToString(ch);
else
result_str += "\n";
}
}
response = (int)result_str;
}
char receive[1024];
int byteSize = 0;
int totalBytesRead = 0;
// レスポンスデータを読み取る
while(InternetReadFile(connect, receive, 1024, byteSize))
{
if(byteSize <= 0)
break;
ArrayResize(result, totalBytesRead + byteSize);
for(int i = 0; i < byteSize; ++i)
{
result[totalBytesRead + i] = receive[i];
}
totalBytesRead += byteSize;
}
InternetCloseHandle(connect);
}
else
if(method == "POST")
{
// ホストに接続
int connect = InternetConnectW(session, host, port, "", "", INTERNET_SERVICE_HTTP, 0, 0);
if(!connect)
{
InternetCloseHandle(session);
string error = "エラー:接続できません";
StringToCharArray(error, result);
return(response);
}
// HTTPリクエストを開く
int hRequest = HttpOpenRequestW(connect, method, endpoint, "HTTP/1.1", "", NULL, secure | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_RELOAD | INTERNET_FLAG_PRAGMA_NOCACHE, 0);
if(!hRequest)
{
InternetCloseHandle(connect);
InternetCloseHandle(session);
string error = "エラー:HTTPリクエストを開けません";
StringToCharArray(error, result);
return(response);
}
if(!HttpSendRequestW(hRequest, headers, StringLen(headers), data, ArraySize(data)))
{
InternetCloseHandle(hRequest);
InternetCloseHandle(connect);
InternetCloseHandle(session);
string error = "エラー:HTTPリクエストの送信に失敗しました。";
StringToCharArray(error, result);
return(response);
}
//レスポンス確認
{
int bufferLength = 1024;
uchar result_array[];
string result_str = "";
int index = 0;
ArrayResize(result_array, bufferLength);
// HTTPステータスコードを取得
if(HttpQueryInfoA(hRequest, HTTP_QUERY_STATUS_CODE, result_array, bufferLength, index))
{
for(int i = 0; i < bufferLength; i++)
{
uchar ch[1];
ch[0] = result_array[i];
if(ch[0] != NULL)
result_str += CharArrayToString(ch);
else
result_str += "\n";
}
}
response = (int)result_str;
}
//リクエストヘッダー取得
{
int bufferLength = 1024;
uchar result_array[];
string result_str = "";
ArrayResize(result_array, bufferLength);
int index = 0;
// HTTPステータスコードを取得
if(HttpQueryInfoA(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, result_array, bufferLength, index))
{
for(int i = 0; i < bufferLength; i++)
{
uchar ch[1];
ch[0] = result_array[i];
if(ch[0] != NULL)
result_str += CharArrayToString(ch);
else
result_str += "\n";
}
}
result_headers = result_str;
}
char receive[1024];
int byteSize = 0;
int totalBytesRead = 0;
// レスポンスデータを読み取る
while(InternetReadFile(hRequest, receive, 1024, byteSize))
{
if(byteSize <= 0)
break;
ArrayResize(result, totalBytesRead + byteSize);
for(int i = 0; i < byteSize; ++i)
{
result[totalBytesRead + i] = receive[i];
}
totalBytesRead += byteSize;
}
InternetCloseHandle(hRequest);
InternetCloseHandle(connect);
}
// インターネットセッションを閉じる
InternetCloseHandle(session);
return(response);
}
えっwwwwwww
長っwwwwwww
さすがに長いよ(‘Д’)
やっぱりWebRequest使おう・・・
オイラの努力・・・(‘ω’)
サンプルコード【画像付きLINE通知】
以下のコードをスクリプトとして保存するだけで、画像付きLINE通知が可能です。LINEトークンの取得方法はBingで調べてください。
#property copyright "FX WAN."
#property link "https://twitter.com/wonderfulfxlife"
#property version "1.00"
#property script_show_inputs
#property strict
// ここでLINEの通知のためのトークンや、保存する画像の名前を設定しています。
input string LineToken = "";
input string FileName = "test.png";
//+------------------------------------------------------------------+
//| このスクリプトが開始すると、この関数が実行されます。 |
//+------------------------------------------------------------------+
void OnStart()
{
// DLLの使用許可
if((bool)MQLInfoInteger(MQL_DLLS_ALLOWED) == false)
{
Print("DLLの使用を許可してください");
return;
}
// 現在の画面のスクリーンショットを取って、FileNameで指定した名前で保存します。
ChartScreenShot(0, FileName, 800, 600);
// LINEにメッセージと画像を送るための関数を呼び出します。
LineNotify(LineToken, "テスト☺", FileName);
}
//+------------------------------------------------------------------+
//| LINEに通知を送るための関数 |
//+------------------------------------------------------------------+
void LineNotify(string token, string message, string fileName = "")
{
// LINEの通知を送るためのURL
string url = "https://notify-api.line.me/api/notify";
// データの区切りのための文字列
string boundary = "WanScriptsSample";
// 通知を送るための情報をまとめたもの
string headers = "Authorization: Bearer " + token + "\r\n" + "Content-Type: multipart/form-data; boundary=" + boundary + "\r\n";
// 送るメッセージを設定
string body = "--" + boundary + "\r\n";
body += "Content-Disposition: form-data; name=\"message\"\r\n\r\n";
body += message + "\r\n";
// ファイル名が指定されていたら画像付きLINE通知、指定されていなかったら文字のみの通知
int size = 0;
uchar post_data[];
if(fileName != "")
{
// 画像を送るための情報を追加
body += "--" + boundary + "\r\n";
body += "Content-Disposition: form-data; name=\"imageFile\"; filename=\"" + fileName + "\"\r\n";
body += "Content-Type: image/png\r\n\r\n";
size = StringToCharArray(body, post_data, 0, WHOLE_ARRAY, CP_UTF8);
// 画像ファイルを開く
uchar img[];
int handle = FileOpen(fileName, FILE_BIN | FILE_READ);
if(handle != INVALID_HANDLE)
{
// 画像のデータを読み込む
FileReadArray(handle, img);
FileClose(handle);
}
// 画像のデータを追加
size += ArrayCopy(post_data, img, size - 1);
// データの終わりを示す
StringToCharArray("\r\n" + "--" + boundary + "--\r\n", post_data, size - 1);
}
else
{
// 文字を送るための情報を追加
size = StringToCharArray(body, post_data, 0, WHOLE_ARRAY, CP_UTF8);
// データの終わりを示す
StringToCharArray("--" + boundary + "--\r\n", post_data, size - 1);
}
// LINEに通知を送る
int timeout = 5000;
char result[];
string headers_res;
int res = WebRequest2("POST", url, headers, timeout, post_data, result, headers_res);
if(res == -1)
Print("HTTPリクエストエラー");// エラーメッセージを表示
else
string res_str = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); // レスポンスを取得する
}
//+------------------------------------------------------------------+
//| GETまたはPOSTリクエストを実行し、データを取得 ワンダフルFXライフ
//+------------------------------------------------------------------+
#define INTERNET_DEFAULT_HTTPS_PORT 443
#define INTERNET_DEFAULT_HTTP_PORT 80
#define INTERNET_SERVICE_HTTP 3
#define HTTP_QUERY_STATUS_CODE 19
#define HTTP_QUERY_RAW_HEADERS_CRLF 21
#define INTERNET_FLAG_SECURE 0x00800000
#define INTERNET_FLAG_PRAGMA_NOCACHE 0x00000100
#define INTERNET_FLAG_KEEP_CONNECTION 0x00400000
#define INTERNET_FLAG_RELOAD 0x80000000
#define INTERNET_OPTION_CONNECT_TIMEOUT 2
#define INTERNET_OPTION_SEND_TIMEOUT 5
#define INTERNET_OPTION_RECEIVE_TIMEOUT 6
#import "wininet.dll"
int InternetOpenW(string userAgent, int accessType, string proxyServer, string bypassProxyFor, int flags);
int InternetOpenUrlW(int internetSession, string url, string header, int headerLength, int flags, int context);
int InternetConnectW(int sessionHandle, string serverUrl, int serverPort, string userCredentials, string userPassword, int serviceType, int connectionFlags, int context);
int HttpOpenRequestW(int connectionHandle, string httpVerb, string objectUrl, string httpVersion, string referrer, string acceptTypes, uint requestFlags, int context);
bool HttpSendRequestW(int hRequest, string lpszHeaders, int dwHeadersLength, uchar &lpOptional[], int dwOptionalLength);
bool InternetReadFile(int hFile, uchar &lpBuffer[], int dwNumberOfBytesToRead, int &lpdwNumberOfBytesRead);
bool InternetCloseHandle(int hInternet);
bool HttpQueryInfoA(int hRequest, int dwInfoLevel, uchar &lpBuffer[], int &lpdwBufferLength, int &lpdwIndex);
bool InternetSetOptionW(int hInternet, int dwOption, string lpBuffer, int dwBufferLength);
#import
int WebRequest2(const string method, const string url, const string headers, int timeout, char & data[], char & result[], string & result_headers)
{
int secure = 0;
int port = INTERNET_DEFAULT_HTTP_PORT;
if(StringFind(url, "https") >= 0)
{
secure = INTERNET_FLAG_SECURE;
port = INTERNET_DEFAULT_HTTPS_PORT;
}
//URLからホストとエンドポイントの抽出
string host = "", endpoint = "";
int response = -1;
int http_pos = StringFind(url, "//");
if(http_pos >= 0)
{
int next_slash = StringFind(url, "/", http_pos + 2);
if(next_slash < 0)
next_slash = StringLen(url);
host = StringSubstr(url, http_pos + 2, next_slash - http_pos - 2);
endpoint = StringSubstr(url, next_slash);
}
// インターネットセッションを開始
int session = InternetOpenW("Mozilla/5.0", 0, "", "", 0);
if(!session)
{
string error = "エラー:インターネットセッションを開けません";
StringToCharArray(error, result);
return(response);
}
// タイムアウトの設定
string timeoutStr = IntegerToString(timeout);
bool timeout_res = InternetSetOptionW(session, INTERNET_OPTION_CONNECT_TIMEOUT, timeoutStr, sizeof(timeout));
timeout_res = InternetSetOptionW(session, INTERNET_OPTION_SEND_TIMEOUT, timeoutStr, sizeof(timeout));
timeout_res = InternetSetOptionW(session, INTERNET_OPTION_RECEIVE_TIMEOUT, timeoutStr, sizeof(timeout));
if(method == "GET")
{
// URLを開く
int connect = InternetOpenUrlW(session, url, headers, StringLen(headers), secure, 0);
if(!connect)
{
InternetCloseHandle(session);
string error = "エラー:URLを開けません";
StringToCharArray(error, result);
return(response);
}
//レスポンス確認
{
int bufferLength = 1024;
uchar result_array[];
string result_str = "";
ArrayResize(result_array, bufferLength);
int index = 0;
// HTTPステータスコードを取得
if(HttpQueryInfoA(connect, HTTP_QUERY_STATUS_CODE, result_array, bufferLength, index))
{
for(int i = 0; i < bufferLength; i++)
{
uchar ch[1];
ch[0] = result_array[i];
if(ch[0] != NULL)
result_str += CharArrayToString(ch);
else
result_str += "\n";
}
}
response = (int)result_str;
}
char receive[1024];
int byteSize = 0;
int totalBytesRead = 0;
// レスポンスデータを読み取る
while(InternetReadFile(connect, receive, 1024, byteSize))
{
if(byteSize <= 0)
break;
ArrayResize(result, totalBytesRead + byteSize);
for(int i = 0; i < byteSize; ++i)
{
result[totalBytesRead + i] = receive[i];
}
totalBytesRead += byteSize;
}
InternetCloseHandle(connect);
}
else
if(method == "POST")
{
// ホストに接続
int connect = InternetConnectW(session, host, port, "", "", INTERNET_SERVICE_HTTP, 0, 0);
if(!connect)
{
InternetCloseHandle(session);
string error = "エラー:接続できません";
StringToCharArray(error, result);
return(response);
}
// HTTPリクエストを開く
int hRequest = HttpOpenRequestW(connect, method, endpoint, "HTTP/1.1", "", "", secure | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_RELOAD | INTERNET_FLAG_PRAGMA_NOCACHE, 0);
if(!hRequest)
{
InternetCloseHandle(connect);
InternetCloseHandle(session);
string error = "エラー:HTTPリクエストを開けません";
StringToCharArray(error, result);
return(response);
}
if(!HttpSendRequestW(hRequest, headers, StringLen(headers), data, ArraySize(data)))
{
InternetCloseHandle(hRequest);
InternetCloseHandle(connect);
InternetCloseHandle(session);
string error = "エラー:HTTPリクエストの送信に失敗しました。";
StringToCharArray(error, result);
return(response);
}
//レスポンス確認
{
int bufferLength = 1024;
uchar result_array[];
string result_str = "";
int index = 0;
ArrayResize(result_array, bufferLength);
// HTTPステータスコードを取得
if(HttpQueryInfoA(hRequest, HTTP_QUERY_STATUS_CODE, result_array, bufferLength, index))
{
for(int i = 0; i < bufferLength; i++)
{
uchar ch[1];
ch[0] = result_array[i];
if(ch[0] != NULL)
result_str += CharArrayToString(ch);
else
result_str += "\n";
}
}
response = (int)result_str;
}
//リクエストヘッダー取得
{
int bufferLength = 1024;
uchar result_array[];
string result_str = "";
ArrayResize(result_array, bufferLength);
int index = 0;
// HTTPステータスコードを取得
if(HttpQueryInfoA(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, result_array, bufferLength, index))
{
for(int i = 0; i < bufferLength; i++)
{
uchar ch[1];
ch[0] = result_array[i];
if(ch[0] != NULL)
result_str += CharArrayToString(ch);
else
result_str += "\n";
}
}
result_headers = result_str;
}
char receive[1024];
int byteSize = 0;
int totalBytesRead = 0;
// レスポンスデータを読み取る
while(InternetReadFile(hRequest, receive, 1024, byteSize))
{
if(byteSize <= 0)
break;
ArrayResize(result, totalBytesRead + byteSize);
for(int i = 0; i < byteSize; ++i)
{
result[totalBytesRead + i] = receive[i];
}
totalBytesRead += byteSize;
}
InternetCloseHandle(hRequest);
InternetCloseHandle(connect);
}
// インターネットセッションを閉じる
InternetCloseHandle(session);
return(response);
}
//+------------------------------------------------------------------+
まとめ
MQL4、およびMQL5でHTTP通信を行うために開発された、改良版のWebRequest2関数について紹介しました。
この関数は、従来のWebRequest関数のデメリットを解消し、GETとPOSTメソッドに対応することで、開発者にとってより簡単なツール作成を実現します。
WebRequest2関数の特徴
開発者はこの関数を利用して、より効率的、かつ効果的なトレーディングツールやインジケーターを作成することが可能になります。