English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
MQL5でのWinInet利用パート2:POSTリクエストとファイル

MQL5でのWinInet利用パート2:POSTリクエストとファイル

MetaTrader 5 | 28 10月 2015, 10:55
1 665 0
---
---


はじめに

前回のレッスン『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 およびインディケータのバージョンアップ
  • アカウント上で動作するカスタムスキャナーの作成とウェブサイトにおける監視活動


便利なリンク

  1. 送信済みヘッダを閲覧するためのプロキシサーバー- http://www.charlesproxy.com/
  2. WinHTTP の記述- http://msdn.microsoft.com/en-us/library/aa385331%28VS.85%29.aspx
  3. HTTP セッションの記述 - http://msdn.microsoft.com/en-us/library/aa384322%28VS.85%29.aspx
  4. Apache+PHP のローカルインストールのための Denwer ツールキット- http://www.denwer.ru/
  5. リクエストヘッダタイプ - http://www.codenet.ru/webmast/php/HTTP-POST.php#part_3_2
  6. リクエストタイプ - http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
  7. リクエストタイプ - ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types
  8. HINTERNET 利用のストラクチャ - http://msdn.microsoft.com/en-us/library/aa383766%28VS.85%29.aspx
  9. ファイルとの連携 - http://msdn.microsoft.com/en-us/library/aa364232%28VS.85%29.aspx
  10. MQL に渡すデータタイプ- http://msdn.microsoft.com/en-us/library/aa383751%28VS.85%29.aspx


MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/276

添付されたファイル |
metaswap.zip (0.66 KB)
screenpost.zip (0.33 KB)
internetlib.mqh (12.82 KB)
screenpost.mq5 (2.63 KB)
metaswap.mq5 (4.84 KB)
MQL5のエリオット波動の自動分析の実装 MQL5のエリオット波動の自動分析の実装
市場分析の最も人気なメソッドの一つとして、エリオット波動法則があります。しかし、このプロセスは、かなり複雑であり、追加ツールを使用せざるをえません。その一つとして、自動マーカーがあります。この記事は、MQL5言語でのエリオット波動の自動分析ツールの作成を紹介します。
支払いと支払い方法 支払いと支払い方法
MQL5.communityサービスは、トレーダーだけでなく、MetaTraderターミナル用アプリケーションの開発者にも素晴らしい機会を提供します。この記事では、MQL5サービスの支払いが実行される方法、収益を引き出す方法、そして、操作のセキュリティを確保する方法について説明します。
予備インディケータによるメモリ消費削減 予備インディケータによるメモリ消費削減
インディケータが計算のために他の多くのインディケータ値を使用すれば、それは多くのメモリを費やすこととなります。本稿では予備インディケータを使用する際のメモリ消費を減らす方法についていくつか述べていきます。保存されたメモリにより、クライアント端末において同時に使用する通貨ペア数、インディケータ、戦略を増やすことができるようになります。それはトレード ポートフォリオの信頼性も向上します。単純にご自身のコンピュータの技術的リソースを気に掛けることで、それはデポジットの資金リソースに変わる可能性があるのです。
トレードラブ博士または いかに心配することを止め、自習 Expert Advisorを作成したか トレードラブ博士または いかに心配することを止め、自習 Expert Advisorを作成したか
ちょうど1年前 jooは彼の記事 "Genetic Algorithms - It's Easy!"の中で MQL5で遺伝的アルゴリズムの実装用ツールを提供してくれました。今われわれはそのツールを使用して特定の境界条件において自身のパラメータを遺伝的に最適化する Expert Advisor を作成しようとしています。