
MQL5でのWinInet利用パート2:POSTリクエストとファイル
はじめに
前回のレッスン『WinInet.dllを使用しインターネットを介した端末間データ交換』ではライブラリ、オープンウェブページとの連携、GET リクエストを利用した情報送受信について学習しました。
今回は以下の方法を学習していきます。
- サーバーへのシンプルな POST リクエストを作成する
- 代表メソッド multipart/form-data を用いてサーバーにファイルを送信する
- Cookies と連携し、自身のログインを用いてウェブサイトから情報を読み出す
前回同様、ローカルプロキシサーバー Charles を設定することを強くお勧めします。これは調査とのちの実験に必要となるものです。
POST リクエスト
情報送信のためには wininet.dll 関数、前稿で述べている作成したクラス CMqlNet が必要となります。
CMqlNet::Request、メソッドのフィールドが多数であるため、リクエストに対して必要な全フィールドを持つ個別のストラクチャ tagRequest を作成する必要がありました。
//------------------------------------------------------------------ struct tagRequest struct tagRequest { string stVerb; // method of the request GET/POST/… string stObject; // path to an instance of request, for example "/index.htm" или "/get.php?a=1" string stHead; // request header string stData; // addition string of data bool fromFile; // if =true, then stData designates the name of a data file string stOut; // string for receiving an answer bool toFile; // if =true, then stOut designates the name of a file for receiving an answer void Init(string aVerb, string aObject, string aHead, string aData, bool from, string aOut, bool to); // function of initialization of all fields }; //------------------------------------------------------------------ Init void tagRequest::Init(string aVerb, string aObject, string aHead, string aData, bool from, string aOut, bool to) { stVerb=aVerb; // method of the request GET/POST/… stObject=aObject; // path to the page "/get.php?a=1" or "/index.htm" stHead=aHead; // request header, for example "Content-Type: application/x-www-form-urlencoded" stData=aData; // addition string of data fromFile=from; // if =true, the stData designates the name of a data file stOut=aOut; // field for receiving an answer toFile=to; // if =true, then stOut designates the name of a file for receiving an answer }
また、CMqlNet::Request メソッドのヘッダを短いものと置き換える必要があります。
//+------------------------------------------------------------------+ bool MqlNet::Request(tagRequest &req) { if(!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) { Print("-DLL not allowed"); return(false); } //--- checking whether DLLs are allowed in the terminal if(!MQL5InfoInteger(MQL5_DLLS_ALLOWED)) { Print("-DLL not allowed"); return(false); } //--- checking whether DLLs are allowed in the terminal if(req.toFile && req.stOut=="") { Print("-File not specified "); return(false); } uchar data[]; int hRequest,hSend; string Vers="HTTP/1.1"; string nill=""; //--- read file to array if(req.fromFile) { if(FileToArray(req.stData,data)<0) { Print("-Err reading file "+req.stData); return(false); } } else StringToCharArray(req.stData,data); if(hSession<=0 || hConnect<=0) { Close(); if(!Open(Host,Port,User,Pass,Service)) { Print("-Err Connect"); Close(); return(false); } } //--- creating descriptor of the request hRequest=HttpOpenRequestW(hConnect,req.stVerb,req.stObject,Vers,nill,0, INTERNET_FLAG_KEEP_CONNECTION|INTERNET_FLAG_RELOAD|INTERNET_FLAG_PRAGMA_NOCACHE,0); if(hRequest<=0) { Print("-Err OpenRequest"); InternetCloseHandle(hConnect); return(false); } //--- sending the request hSend=HttpSendRequestW(hRequest,req.stHead,StringLen(req.stHead),data,ArraySize(data)); //--- sending the file if(hSend<=0) { int err=0; err=GetLastError(err); Print("-Err SendRequest= ",err); } //--- reading the page if(hSend>0) ReadPage(hRequest,req.stOut,req.toFile); //--- closing all handles InternetCloseHandle(hRequest); InternetCloseHandle(hSend); if(hSend<=0) { Close(); return(false); } return(true); }
それでは作業を始めます。
"application/x-www-form-urlencoded" タイプのウェブサイトへのデータ送信
前回のレッスンですでに MetaArbitrage 例(クオート監視)の分析は済んでいます。
EA は GET リクエストを用いてシンボルの ビッド価格を送信することを思い出します。すると回答として他の端末からサーバーに同じ方法で送信された他の仲介会社の価格を受け取ります。
GET リクエストを POST リクエストに変えるには、ヘッダの次にくるリクエスト本文でリクエスト行そのものを『非表示にする』だけです。
BOOL HttpSendRequest(
__in HINTERNET hRequest,
__in LPCTSTR lpszHeaders,
__in DWORD dwHeadersLength,
__in LPVOID lpOptional,
__in DWORD dwOptionalLength
);
- hRequest [in]
HttpOpenRequest から返されるハンドル - lpszHeaders [in]
リクエストに追加されるヘッダを持つ行のポインタ。このパラメータは空です。 - dwHeadersLength [in]
ヘッダのバイトサイズ - lpOptional [in]
ヘッダのすぐ後に送信される uchar データを持つ配列のポインタ。通常このパラメータは POST および PUT 処理に使用されます。 - dwOptionalLength [in]
データのバイトサイズ。このパラメータは =0 となりえます。それは追加情報が何も送信されないことを意味します。
関数記述から、データはバイトの uchar 配列(関数の4番目のパラメータ)として送信されることが解ります。この段階で知っておくことは以上です。
MetaArbitrage 例では GET リクエストは以下のような記述となります。
www.fxmaster.de/metaarbitr.php?server=Metaquotes&pair=EURUSD&bid=1.4512&time=13286794
リクエストそのものは赤で強調されています。POST リクエスト作成が必要な場合は、そのテキストをデータの lpOptional 配列に移動します。
MetaSwap と呼ばれるスクリプトを作成します。これはシンボルのスワップに関する情報を送受信するものです。
#include <InternetLib.mqh> string Server[]; // array of server names double Long[], Short[]; // array for swap information MqlNet INet; // class instance for working //------------------------------------------------------------------ OnStart void OnStart() { //--- opening a session if (!INet.Open("www.fxmaster.de", 80, "", "", INTERNET_SERVICE_HTTP)) return; //--- zeroizing arrays ArrayResize(Server, 0); ArrayResize(Long, 0); ArrayResize(Short, 0); //--- the file for writing an example of swap information string file=Symbol()+"_swap.csv"; //--- sending swaps if (!SendData(file, "GET")) { Print("-err RecieveSwap"); return; } //--- read data from the received file if (!ReadSwap(file)) return; //--- refresh information about swaps on the chart UpdateInfo(); }
スクリプト処理はひじょうにシンプルです。
まずインターネットセッションINet.Openを開きます。それからSendData 関数が現在シンボルのスワップに関する情報を送信します。情報送信がうまくいったら、受け取られたスワップはReadSwap を使用して読み出され UpdateInfo を使用してチャートに表示されます。
この時点では SendData にのみ着目します。
//------------------------------------------------------------------ SendData bool SendData(string file, string mode) { string smb=Symbol(); string Head="Content-Type: application/x-www-form-urlencoded"; // header string Path="/mt5swap/metaswap.php"; // path to the page string Data="server="+AccountInfoString(ACCOUNT_SERVER)+ "&pair="+smb+ "&long="+DTS(SymbolInfoDouble(smb, SYMBOL_SWAP_LONG))+ "&short="+DTS(SymbolInfoDouble(smb, SYMBOL_SWAP_SHORT)); tagRequest req; // initialization of parameters if (mode=="GET") req.Init(mode, Path+"?"+Data, Head, "", false, file, true); if (mode=="POST") req.Init(mode, Path, Head, Data, false, file, true); return(INet.Request(req)); // sending request to the server }
本スクリプトでは情報送信のメソッドが2とおり示されます。GET および POSTの使用で、両者の違いを感じていただきます。
関数の変数を一つずつ記述します。
- Head - コンテンツタイプを記述するリクエストのヘッダです。実際、これはリクエストのヘッダ全体ではありません。ヘッダのその他フィールドは wininet.dll ライブラリによって作成されます。ただし、それらは HttpAddRequestHeaders 関数を使用して変更することができます。.
- Path - これは最初のドメイン www.fxmaster.de に関連したリクエストのインスタンスへのパスです。言い方を変えると、それはリクエストを処理する php スクリプトへのパスです。ところで、php スクリプトだけをリクエストする必要はありません。それは通常の html ページ(最初のレッスンで mq5 ファイルにリクエストしようというのも試みました)で行うことができます。
- Data - これはサーバーに送信される情報です。Data は parameter name=value を渡すルールに従って書かれます。記号 "&" はデータの分離子として使用されます。
そして主要な事柄です。tagRequest::Init 内で GET リクエストと POST リクエストを作成する際の違いに注意を払うことが必要です。
GET メソッドではパスはリクエスト本文(記号 "?" と一緒になっている)が一緒に送信され、データフィールド lpOptional (ストラクチャではstData と呼ばれます)は空のままにします。
POST メソッドではパスは独立しており、リクエスト本体は lpOptional に移動します。
ご覧のように違いは大きくはありません。リクエストを受け取るサーバースクリプト metaswap.php は本稿に添付があります。
"multipart/form-data" データ送信
実際POST リクエストは GET リクエストに対する類似体ではありません(そうでなければそれらは必要とされないはずです)。POST リクエストには大きなメリットがあります。それらを使用することでバイナリコンテントのファイルを送信することができるのです。
問題は urlencoded タイプが無限数のシンボルセットを送信できることです。そうでなければ『許可されていない』シンボルはコードと置き換わります。よってバイナリデータを送信する際、それらを変形します。よって GET を用いては小さな gif ファイルさえ送信することはできません。
この問題を解決するにはリクエストを記述する特別ルールが役立ちます。そのルールによりテキストファイルに加えバイナリファイルも交換できるようになるのです。
それにはリクエスト本体がセクションに分割されます。重点は各セクションが独自のデータタイプを持ちうることです。たとえば最初のセクションはテキストタイプ、次が画像/jpeg タイプ、などです。別の言い方をすれば、サーバーに送信されるあるリクエストには同時に複数のデータタイプが含まれるうる、ということです。
MetaSwap スクリプトによって渡されるデータ例でそのような記述のストラクチャを見ていきます。
リクエストのヘッダ Head は以下のように記述されます。
Content-Type: multipart/form-data; boundary=SEPARATOR\r\n
キーワードSEPARATOR は任意のシンボルセットです。ただし、これはリクエストデータの外部にくるように注意します。すなわち、この行はユニークである必要があり、わけのわからないhdsJK263shxaDFHLsdhsDdjf9 や、心に浮かぶなにかといったのようなものなのです :)。PHP ではそのような行は現在時刻の MD5 コードを用いて作成されます。
POST リクエストそのものは以下のように記述されます(理解しやすいようにフィールドは一般的意味に応じて強調表示しています)。
\r\n
--SEPARATOR\r\n
Content-Disposition: form-data; name="Server"\r\n
\r\n
MetaQuotes-Demo
\r\n
--SEPARATOR\r\n
Content-Disposition: form-data; name="Pair"\r\n
\r\n
EURUSD
\r\n
--SEPARATOR\r\n
Content-Disposition: form-data; name="Long"\r\n
\r\n
1.02
\r\n
--SEPARATOR\r\n
Content-Disposition: form-data; name="Short"\r\n
\r\n
-0.05
\r\n
--SEPARATOR--\r\n
ラインフィード箇所 "\r\n" を明確に指定します。なぜならそれらはリクエストに必須のシンボルだからです。ご覧のように同じ4つのフィールドがリクエスト内で渡されています。そこでは通常のテキスト方法で行われるのです。
分離子をつける際の重要点
- 記号 "--" は分離子の前に入れます。
- 終了の分離子にはその後に2つ記号 "--" を追加します。
次の例ではリクエスト内でファイルを渡す正しいメソッドを確認することができます。
ポジション終了時に Expert Advisor がチャートのスナップショットを撮り、テキストファイルでアカウントの詳細レポートを作成するとイメージしてください。
\r\n
--SEPARATOR\r\n
Content-Disposition: form-data; name="ExpertName"\r\n
\r\n
MACD_Sample
\r\n
--SEPARATOR\r\n
Content-Disposition: file; name="screen"; filename="screen.gif"\r\n
Content-Type: image/gif\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
......content of the gif file.....
\r\n
--SEPARATOR\r\n
Content-Disposition: form-data; name="statement"; filename="statement.csv"\r\n
Content-Type: application/octet-stream\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
......content of the csv file.....
\r\n
--SEPARATOR--\r\n
リクエスト内に新しいヘッダが2つあります。
Content-Type - コンテンツタイプを記述します。RFC[2046] 標準ですべての可能なタイプが正確に記述されます。われわれは2タイプを使用しました。image/gif および application/octet-streamです。
Content-Disposition ファイルとフォームデータのバリアント2つは等しく、どちらの場合にも PHP で正しく処理されます。よってご自身でファイルかフォームデータか選択して使用することができます。Charles でそれらの表現の違いを確認するとよいでしょう。
Content-Transfer-Encoding - コンテンツのエンコードを記述します。テキストデータとしては抽象的かもしれません。
資料を連続するためにスクリプト ScreenPost を書きます。それはサーバーにスクリーンショットを送信します。
#include <InternetLib.mqh> MqlNet INet; // class instance for working //------------------------------------------------------------------ OnStart void OnStart() { // opening session if (!INet.Open("www.fxmaster.de", 80, "", "", INTERNET_SERVICE_HTTP)) return; string giffile=Symbol()+"_"+TimeToString(TimeCurrent(), TIME_DATE)+".gif"; // name of file to be sent // creating screenshot 800х600px if (!ChartScreenShot(0, giffile, 800, 600)) { Print("-err ScreenShot "); return; } // reading gif file to the array int h=FileOpen(giffile, FILE_ANSI|FILE_BIN|FILE_READ); if (h<0) { Print("-err Open gif-file "+giffile); return; } FileSeek(h, 0, SEEK_SET); ulong n=FileSize(h); // determining the size of file uchar gif[]; ArrayResize(gif, (int)n); // creating uichar array according to the size of data FileReadArray(h, gif); // reading file to the array FileClose(h); // closing the file // creating file to be sent string sendfile="sendfile.txt"; h=FileOpen(sendfile, FILE_ANSI|FILE_BIN|FILE_WRITE); if (h<0) { Print("-err Open send-file "+sendfile); return; } FileSeek(h, 0, SEEK_SET); // forming a request string bound="++1BEF0A57BE110FD467A++"; // separator of data in the request string Head="Content-Type: multipart/form-data; boundary="+bound+"\r\n"; // header string Path="/mt5screen/screen.php"; // path to the page // writing data FileWriteString(h, "\r\n--"+bound+"\r\n"); FileWriteString(h, "Content-Disposition: form-data; name=\"EA\"\r\n"); // the "name of EA" field FileWriteString(h, "\r\n"); FileWriteString(h, "NAME_EA"); FileWriteString(h, "\r\n--"+bound+"\r\n"); FileWriteString(h, "Content-Disposition: file; name=\"data\"; filename=\""+giffile+"\"\r\n"); // field of the gif file FileWriteString(h, "Content-Type: image/gif\r\n"); FileWriteString(h, "Content-Transfer-Encoding: binary\r\n"); FileWriteString(h, "\r\n"); FileWriteArray(h, gif); // writing gif data FileWriteString(h, "\r\n--"+bound+"--\r\n"); FileClose(h); // closing the file tagRequest req; // initialization of parameters req.Init("POST", Path, Head, sendfile, true, "answer.htm", true); if (INet.Request(req)) Print("-err Request"); // sending the request to the server else Print("+ok Request"); }
情報を受け取るサーバースクリプトです。
$ea=$_POST['EA'];
$data=file_get_contents($_FILES['data']['tmp_name']); // information in the file
$file=$_FILES['data']['name'];
$h=fopen(dirname(__FILE__)."/$ea/$file", 'wb'); // creating a file in the EA folder
fwrite($h, $data); fclose($h); // saving data
?>
安全性を損なう問題を避けるため、ファイルをサーバーで受け取ることに関するルールを理解することは大いに推奨されます。
Cookies との連携
本件は前回レッスンで追記として、また今後考えるべきこととして簡単に述べました。
ご存じのとおり、Cookies はサーバーからの連続した個人詳細情報リクエストを回避します。ユーザーからの現在の作業セッションに対して要求される個人詳細情報が受け取られると、それはユーザーコンピュータ上のその情報を伴うテキストファイルを離れます。のち、ユーザーがページを移動すると、サーバーは再びユーザーに対してその情報をリクエストしません。自動的にブラウザキャッシュからその情報を入手します。
たとえば、www.mql5.com サーバーで承認するとき『私を記憶する』オプションを有効にしたら、ご自身のコンピュータで詳細情報を伴う Cookie を保存することになるのです。次にこのウェブサイトを訪れる際、ブラウザは問い合わせることなく Cookie をサーバーに渡します。
ご興味がおありなら、フォルダ (WinXP) C:\Documents と Settings\<User>\Cookies を開き、これまで訪れたことがある異なるウェブサイトのコンテンツを閲覧することができます。
われわれのニーズにに関しては Cookies を MQL5 フォーラムのご自身のページを読むために使用することができます。すなわち、ご自身のログインしたウェブサイトで承認されているかのように情報を読み、取得したページを分析するのです。最適なバリアントはローカルプロキシサーバ Charles を利用してCookies を分析することです。それはCookies を含むすべてのリクエスト送受信に関する詳細情報を表示します。
例
- https://www.mql5.com/en/job ページをリクエストする Expert Advisor (または外部アプリケーション)が1時間ごとに新規ジョブ提案リストを受け取ります。
- また、それはたとえば https://www.mql5.com/en/forum/53 というブランチをリクエストし新規メッセージがあるかチェックします。
- その上、フォーラムに『新規メッセージ』があるかチェックします。
Cookie をリクエストに設定するには InternetSetCookie 関数を使用します。
BOOL InternetSetCookie(
__in LPCTSTR lpszUrl,
__in LPCTSTR lpszCookieName,
__in LPCTSTR lpszCookieData
);
- lpszUrl [in] - Name of a server, for example, www.mql5.com
- lpszCookieName [in]- Name of a Cookie
- lpszCookieData [in] - Data for the Cookie
複数の Cookies を設定するには毎回設定時にこの関数を呼びます。
興味深い機能:InternetSetCookie の呼び出しはいつでも行うことができます。サーバーに接続していないときでさえ可能です。
おわりに
別の HTTP リクエストタイプを理解し、バイナリファイルを送信する可能性を得ました。そのことによりサーバーと連携する利便性を拡大できます。また Cookies と連携する方法も学習しました。
今後の開発への方向性として以下をリストアップすることができます。
- レポートのリモートストレージを整理する
- ユーザー間でのファイル交換、Expert Advisors およびインディケータのバージョンアップ
- アカウント上で動作するカスタムスキャナーの作成とウェブサイトにおける監視活動
便利なリンク
- 送信済みヘッダを閲覧するためのプロキシサーバー- http://www.charlesproxy.com/
- WinHTTP の記述- http://msdn.microsoft.com/en-us/library/aa385331%28VS.85%29.aspx
- HTTP セッションの記述 - http://msdn.microsoft.com/en-us/library/aa384322%28VS.85%29.aspx
- Apache+PHP のローカルインストールのための Denwer ツールキット- http://www.denwer.ru/
- リクエストヘッダタイプ - http://www.codenet.ru/webmast/php/HTTP-POST.php#part_3_2
- リクエストタイプ - http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
- リクエストタイプ - ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types
- HINTERNET 利用のストラクチャ - http://msdn.microsoft.com/en-us/library/aa383766%28VS.85%29.aspx
- ファイルとの連携 - http://msdn.microsoft.com/en-us/library/aa364232%28VS.85%29.aspx
- MQL に渡すデータタイプ- http://msdn.microsoft.com/en-us/library/aa383751%28VS.85%29.aspx
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/276





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