
Utilisation de WinInet dans MQL5. Partie 2 : Requêtes et fichiers POST
Introduction
Dans la leçon précédente « Utilisation de WinInet.dll pour l'échange de données entre terminaux via Internet », nous avons appris à travailler avec la bibliothèque, ouvrir des pages Web, envoyer et recevoir des informations à l'aide de requêtes GET.
Dans cette leçon, nous allons apprendre à :
- créer et envoyer des requêtes POST simples à un serveur ;
- envoyer des fichiers à un serveur en utilisant la méthode de représentation multipart/form-data ;
- travailler avec les cookies et lire les informations des sites Web à l'aide de votre identifiant.
Comme précédemment, je recommande fortement de mettre en place un serveur proxy local Charles ; il sera nécessaire pour vos études et vos expériences ultérieures.
Demandes POST
Pour envoyer des informations, nous aurons besoin de ces fonctions wininet.dll et de la classe créée CMqlNet qui ont été décrites en détail dans l'article précédent.
En raison du grand nombre de champs dans les méthodes CMqlNet::Request, nous avons dû créer une structure distincte tagRequest qui contient tous les champs requis pour une demande.
//------------------------------------------------------------------ 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 }
De plus, nous devons remplacer l'en-tête de la méthode CMqlNet::Request par un plus court :
//+------------------------------------------------------------------+ 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); }
Commençons à travailler.
Envoi de données vers un site Web de type « application/x-www-form-urlencoded »
Dans la leçon précédente, nous avons analysé l'exemple de MetaArbitrage (suivi des cotations).
Rappelons que l'EA envoie les prix de l'offre de son symbole à l'aide d'une requête GET ; et en réponse, il reçoit les prix d'autres courtiers qui sont envoyés de la même manière au serveur depuis d'autres terminaux.
Pour changer une requête GET en requête POST, il suffit de « cacher » la ligne de requête elle-même dans le corps de la requête qui vient après son en-tête.
BOOL HttpSendRequest(
__dans HINTERNET hRequest,
__dans LPCTSTR lpszHeaders,
__en DWORD dwHeadersLength,
__dans LPVOID lpOptional,
__in DWORD dwOptionalLength
);
- hRequest [in]
Handle renvoyé par HttpOpenRequest. - lpszHeaders [in]
Pointeur vers une ligne contenant des en-têtes à ajouter à la requête. Ce paramètre peut être vide. - dwHeadersLength [in]
Taille de l'en-tête en octets. - lpOptional [in]
Pointeur vers un tableau de données uchar qui est envoyé juste après l'en-tête. En général, ce paramètre est utilisé pour les opérations POST et PUT. - dwOptionalLength [in]
Taille des données en octets. Le paramètre peut être =0 ; cela signifie qu'aucune information supplémentaire n'est envoyée.
D'après la description de la fonction, nous pouvons comprendre que les données sont envoyées sous forme d'octet uchar-array (le quatrième paramètre de la fonction). C'est tout ce que nous devons savoir à ce stade.
Dans l'exemple de MetaArbitrage, la requête GET se présente comme suit :
www.fxmaster.de/metaarbitr.php?server=Metaquotes&pair=EURUSD&bid=1.4512&time=13286794
La demande elle-même est mise en évidence par la couleur rouge. Ainsi, si nous devons effectuer une requête POST, nous devons déplacer son texte dans le tableau de données lpOptional.
Créons un script appelé MetaSwap, qui enverra et recevra des informations sur les échanges d'un symbole.
#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(); }
Le fonctionnement du script est très simple.
Tout d'abord, la session Internet INet.Open est ouverte. Ensuite, la fonction SendData envoie des informations sur les échanges du symbole actuel. Ensuite, s'il est envoyé avec succès, les swaps reçus sont lus à l'aide de ReadSwap et affichés sur le graphique à l'aide de UpdateInfo.
Pour l'instant, nous nous intéressons uniquement à la fonction 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 }
Dans ce script, deux méthodes d'envoi d'informations sont démontrées - en utilisant GET et POST, pour que vous puissiez sentir la différence entre elles.
Décrivons les variables de la fonction une par une :
- Tête - en-tête de la demande décrivant le type de son contenu. En réalité, il ne s'agit pas de la totalité de l'en-tête de la demande. Les autres champs de l'en-tête sont créés par la bibliothèque wininet.dll. Cependant, ils peuvent être modifiés à l'aide de la fonction HttpAddRequestHeaders.
- Chemin - il s'agit du chemin d'accès à l'instance de demande par rapport au domaine initial www.fxmaster.de. En d'autres termes, il s'agit du chemin d'accès à un script php qui traitera la demande. A propos, il n'est pas nécessaire de demander uniquement un script php, il peut s'agir d'une page html ordinaire (nous avons même essayé de demander un fichier mq5 lors de notre première leçon).
- Données - ce sont les informations qui sont envoyées au serveur. Les données sont écrites selon les règles de passage du paramètre nom=valeur. Le signe « & » est utilisé comme séparateur de données.
Et surtout, faites attention à la différence entre les requêtes GET et POST dans tagRequest::Init.
Dans la méthode GET, le chemin d'accès est envoyé avec le corps de la demande (uni par le signe « ? »), et le champ de données lpOptional (appelé stData dans la structure) est laissé vide.
Dans la méthode POST, le chemin d'accès existe en tant que tel, et le corps de la demande est déplacé vers lpOptional .
Comme vous pouvez le constater, la différence n'est pas significative. Le script serveur metaswap.php qui reçoit la requête est joint à l'article.
Envoi de données « multipart/form-data »
En fait, les requêtes POST ne sont pas analogues aux requêtes GET (sinon elles ne seraient pas nécessaires). Les requêtes POST présentent un avantage considérable - elles permettent d'envoyer des fichiers au contenu binaire.
Il s'agit d'une requête du type urlencoded qui est autorisée à envoyer un ensemble limité de symboles. Sinon, les symboles « non autorisés » seront remplacés par des codes. Ainsi lors de l'envoi de données binaires, elles seront déformées. Vous n'êtes donc pas en mesure d'envoyer même un petit fichier gif à l'aide d'une requête GET.
Pour résoudre ce problème, des règles spéciales de description d'une requête sont élaborées ; elles permettent d'échanger des fichiers binaires en plus des fichiers texte.
Pour atteindre cet objectif, le corps de la demande est divisé en sections. L'essentiel est que chaque section puisse avoir son propre type de données. Par exemple, le premier est un texte, le suivant est une image/jpeg, etc. En d'autres termes, une requête envoyée au serveur peut contenir plusieurs types de données à la fois.
Voyons la structure d'une telle description à l'aide de l'exemple des données transmises par le script MetaSwap.
L'en-tête de la requête Tête sera le suivant :
Type de contenu : multipart/form-data ; limite=SEPARATOR\r\n
Le mot-clé SEPARATOR – est un ensemble aléatoire de symboles. Cependant, vous devez veiller à ce que cela soit en dehors des données de la demande. En d'autres termes, cette ligne doit être unique - un abracadabra comme hdsJK263shxaDFHLsdhsDdjf9 ou tout autre élément qui vous vient à l'esprit :). En PHP, une telle ligne est formée en utilisant le code MD5 d'une heure courante.
La requête POST elle-même se présente comme suit (pour faciliter la compréhension, les champs sont mis en évidence en fonction de leur signification générale) :
\r\n
--SEPARATOR\r\n
Disposition du contenu : données de formulaire ; nom="Serveur"\r\n
\r\n
MetaQuotes-Demo
\r\n
--SEPARATOR\r\n
Disposition du contenu : données de formulaire ; nom="Pair"\r\n
\r\n
EURUSD
\r\n
--SEPARATOR\r\n
Disposition du contenu : données de formulaire ; nom="Long"\r\n
\r\n
1.02
\r\n
--SEPARATOR\r\n
Disposition du contenu : données de formulaire ; nom="Short"\r\n
\r\n
-0,05
\r\n
--SEPARATOR--\r\n
Nous spécifions explicitement les emplacements des sauts de ligne « \r\n », car ce sont des symboles obligatoires dans une requête. Comme vous pouvez le constater, les quatre mêmes champs sont transmis dans la demande, mais cela se fait de la manière habituelle du texte.
Les particularités importantes de la mise en place des séparateurs :
- Deux symboles « -- » sont placés avant le séparateur.
- Pour le séparateur de fermeture, deux symboles supplémentaires « -- » sont ajoutés après celui-ci.
Dans l'exemple suivant, vous pouvez voir une méthode correcte de transmission de fichiers dans une requête.
Imaginez qu'un Expert Advisor réalise un instantané du graphique et un rapport détaillé sur le compte dans un fichier texte lors de la clôture d'une position.
\r\n
--SEPARATOR\r\n
Disposition du contenu : données de formulaire ; nom="ExpertName"\r\n
\r\n
MACD_Sample
\r\n
--SEPARATOR\r\n
Contenu-Disposition : fichier; nom="screen" ; nom de fichier="screen.gif"\r\n
Type de contenu : image/gif\r\n
Codage de transfert de contenu : binaire\r\n
\r\n
......contenu du fichier gif.....
\r\n
--SEPARATOR\r\n
Disposition du contenu : données de formulaire ; nom="statement" ; nom de fichier="statement.csv"\r\n
Type de contenu : application/octet-stream\r\n
Codage de transfert de contenu : binaire\r\n
\r\n
......contenu du fichier csv.....
\r\n
--SEPARATOR--\r\n
Deux nouveaux en-têtes apparaissent dans la requête :
Content-Type - décrit le type de contenu. Tous les types possibles sont décrits avec précision dans la norme RFC[2046]. Nous avons utilisé deux types - image/gif et application/octet-stream.
Deux variantes d'écriture de Content-Disposition - file et formulaire-donné sont équivalentes et sont correctement traitées par PHP dans les deux cas. Vous pouvez donc utiliser le fichier ou le formulaire de données au choix. Vous pouvez mieux voir la différence entre leurs représentations dans Charles.
Content-Transfer-Encoding - il décrit l'encodage du contenu. Il peut être absent pour les données textuelles.
Pour consolider le matériel, écrivons le script ScreenPost, qui envoie des captures d'écran au serveur :
#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"); }
Le script du serveur qui reçoit les informations :
<?php $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 ?>
Il est fortement recommandé de se familiariser avec les règles de réception des fichiers par le serveur pour éviter les problèmes de sécurité !
Travailler avec des cookies
Ce sujet sera brièvement décrit comme un complément à la leçon précédente et une piste de réflexion sur ses caractéristiques.
Comme vous le savez, les cookies sont destinés à éviter que les serveurs ne demandent continuellement des données personnelles. Lorsqu'un serveur reçoit d'un utilisateur les données personnelles requises pour la session de travail en cours, il laisse un fichier texte contenant ces informations sur l'ordinateur de l'utilisateur. De plus, lorsque l'utilisateur passe d'une page à l'autre, le serveur ne demande pas à nouveau ces informations à l'utilisateur ; il les extrait automatiquement du cache du navigateur.
Par exemple, lorsque vous activez l'option « Se souvenir de moi » lors de l'autorisation sur le serveur www.mql5.com, vous enregistrez un cookie avec vos coordonnées sur votre ordinateur. Lors de la prochaine visite du site Web, le navigateur transmettra le Cookie au serveur sans vous le demander.
Si cela vous intéresse, vous pouvez ouvrir le dossier (WinXP) C:\Documents and Settings\<User>\Cookies et afficher le contenu des différents sites Web que vous avez visités.
En ce qui concerne nos besoins, les cookies peuvent être utilisés pour la lecture de vos pages du forum MQL5. En d'autres termes, vous lirez les informations comme si vous étiez autorisé sur le site Web sous votre identifiant, puis vous analyserez les pages obtenues. La variante optimale consiste à analyser les cookies à l'aide d'un serveur proxy local Charles. Il montre des informations détaillées sur toutes les demandes reçues/envoyées, y compris les cookies.
Par exemple :
- Un Expert Advisor (ou une application externe) qui sollicite la page https://www.mql5.com/en/jobune fois par heure et reçoit la liste des nouvelles offres d'emploi.
- Il demande également une branche, par exemple https://www.mql5.com/en/forum/53, et vérifie s'il y a de nouveaux messages.
- De plus, il peut vérifier s'il y a de nouveaux « messages privés » sur les forums.
Pour définir un cookie dans une requête, la fonction InternetSetCookie est utilisée.
BOOL InternetSetCookie(
__dans LPCTSTR lpszUrl,
____dans LPCTSTR lpszCookieName,
____dans LPCTSTR lpszCookieData
);
- lpszUrl [in] - Nom d'un serveur, par exemple, www.mql5.com
- lpszCookieName [in] - Nom d'un cookie
- lpszCookieData [in] - Données pour le cookie
Pour définir plusieurs Cookies, appelez cette fonction pour chacun d'eux.
Une fonctionnalité intéressante : un appel d'InternetSetCookie peut être effectué à tout moment, même lorsque vous n'êtes pas connecté au serveur.
Conclusion
Nous nous sommes familiarisés avec un autre type de demandes HTTP, nous avons obtenu la possibilité d'envoyer des fichiers binaires, ce qui permet d'étendre les possibilités de travailler avec vos serveurs ; et nous avons appris les méthodes de travail avec les cookies.
Nous pouvons dresser la liste suivante des orientations des développements futurs :
- Organisation du stockage à distance des rapports ;
- Echange de fichiers entre utilisateurs, mise à jour des versions des Expert Advisors/indicateurs ;
- Création de scanners personnalisés qui fonctionnent sous votre compte et surveillent l'activité d'un site web.
Liens utiles
- Un serveur proxy pour visualiser les en-têtes envoyés - http://www.charlesproxy.com/
- Description de WinHTTP - http://msdn.microsoft.com/en-us/library/aa385331%28VS.85%29.aspx
- Description de la session HTTP - http://msdn.microsoft.com/en-us/library/aa384322%28VS.85%29.aspx
- La boîte à outils Denwer pour une installation locale d'Apache+PHP - http://www.denwer.ru/
- Types d'en-têtes de requête - http://www.codenet.ru/webmast/php/HTTP-POST.php#part_3_2
- Types de requêtes - http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
- Types de requêtes - ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types.
- Structure d'utilisation de http://msdn.microsoft.com/en-us/library/aa383766%28VS.85%29.aspx
- Utilisation de fichiers - http://msdn.microsoft.com/en-us/library/aa364232%28VS.85%29.aspx
- Types de données à transmettre à MQL - http://msdn.microsoft.com/en-us/library/aa383751%28VS.85%29.aspx
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/276





- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation