
Utilizzo di WinInet in MQL5. Parte 2: Richieste e file POST
Introduzione
Nella lezione precedente, "Using WinInet.dll for Data Exchange between Terminals via the Internet", abbiamo imparato a lavorare con la libreria, aprire pagine web, inviare e ricevere informazioni tramite richieste GET.
In questa lezione impareremo a:
- creare e inviare semplici richieste POST a un server;
- inviare file a un server utilizzando il metodo di rappresentazione multipart/form-data;
- lavorare con i cookie e leggere le informazioni dai siti web utilizzando il tuo nome utente.
Come in precedenza, consiglio vivamente di configurare un server proxy locale Charles; sarà necessario per il tuo studio e ulteriori esperimenti.
Richieste POST
Per inviare informazioni, avremo bisogno di quelle funzioni wininet.dll e della classe creata CMqlNet che sono state descritte in dettaglio nell'articolo precedente.
A causa dell'elevato numero di campi nei metodi CMqlNet::Request, abbiamo dovuto creare una struttura separata tagRequest che contiene tutti i campi richiesti per una richiesta.
//------------------------------------------------------------------ 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 }
Inoltre, dobbiamo sostituire l'intestazione del metodo CMqlNet::Request con una più corta:
//+------------------------------------------------------------------+ 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); }
Ora iniziamo a lavorare.
Invio di dati a un sito web del tipo "application/x-www-form-urlencoded"
Nella lezione precedente abbiamo analizzato l'esempio del MetaArbitrage (monitoraggio delle quotazioni).
Ricordiamo che l'EA invia i prezzi Bid del suo simbolo utilizzando una richiesta GET; come risposta riceve i prezzi di altri broker che vengono inviati allo stesso modo al server da altri terminali.
Per modificare una richiesta GET in una richiesta POST è sufficiente "nascondere" la riga di richiesta stessa nel corpo della richiesta che segue la sua intestazione.
BOOL HttpSendRequest(
__in HINTERNET hRequest,
__in LPCTSTR lpszHeaders,
__in DWORD dwHeadersLength,
__in LPVOID lpOptional,
__in DWORD dwOptionalLength
);
- hRequest [in]
Handle restituito da HttpOpenRequest. - lpszHeaders [in]
Puntatore a una riga contenente le intestazioni da aggiungere alla richiesta. Questo parametro potrebbe essere vuoto. - dwHeadersLength [in]
Dimensione dell'intestazione in byte. - lpOptional [in]
Puntatore a un array con dati uchar che viene inviato subito dopo l'intestazione. In genere, questo parametro viene utilizzato per le operazioni POST e PUT. - dwOptionalLength [in]
Dimensione dei dati in byte. Il parametro può essere = 0; significa che non viene inviata alcuna informazione aggiuntiva.
Dalla descrizione della funzione, possiamo capire che i dati vengono inviati come un byte uchar-array (il quarto parametro della funzione). Questo è tutto ciò che dobbiamo sapere in questa fase.
Nell'esempio MetaArbitrage, la richiesta GET ha il seguente aspetto:
www.fxmaster.de/metaarbitr.php?server=Metaquotes&pair=EURUSD&bid=1.4512&time=13286794
La richiesta stessa è evidenziata con il colore rosso. Quindi, se abbiamo bisogno di fare una richiesta POST, dovremmo spostare il suo testo nell'array di dati lpOptional.
Creiamo uno script chiamato MetaSwap, il quale invierà e riceverà informazioni sugli scambi di un simbolo.
#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(); }
Il funzionamento dello script è molto semplice.
Innanzitutto viene aperta la sessione Internet INet.Open. Quindi la funzione SendData invia informazioni sugli scambi del simbolo corrente. Quindi, se viene inviato con successo, gli swap ricevuti vengono letti utilizzando ReadSwap e visualizzati sul grafico utilizzando UpdateInfo.
In questo momento ci interessa solo la funzione 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 }
In questo script vengono dimostrati due metodi di invio delle informazioni: utilizzando GET e POS per farti notare la differenza tra loro.
Descriviamo una per una le variabili della funzione:
- Head - intestazione della richiesta che descrive il tipo dei suoi contenuti. In realtà, questa non è l'intera intestazione della richiesta. Gli altri campi dell'intestazione sono creati dalla libreria wininet.dll. Tuttavia, possono essere modificati utilizzando la funzione HttpAddRequestHeaders.
- Percorso - questo è il percorso dell'istanza della richiesta relativamente al dominio iniziale www.fxmaster.de. In altre parole, è il percorso di uno script php che elaborerà la richiesta. A proposito, non è necessario richiedere solo uno script php. Può essere una normale pagina html (abbiamo anche provato a richiedere un file mq5 durante la nostra prima lezione).
- Dati - queste sono le informazioni che vengono inviate al server. I dati vengono scritti secondo le regole di passaggio del parameter name=value. Il segno "&" viene utilizzato come separatore di dati.
E la cosa principale - prestare attenzione alla differenza tra fare richieste GET e POST in tagRequest::Init.
Nel metodo GET, il percorso viene inviato insieme al corpo della richiesta (unito dal segno "?") e il campo dati lpOptional (denominato stData nella struttura) viene lasciato vuoto.
Nel metodo POST, il percorso esiste da solo e il corpo della richiesta viene spostato in lpOptional.
Come puoi vedere, la differenza non è significativa. Lo script del server metaswap.php che riceve la richiesta è allegato all'articolo.
Invio di dati "multipart/form-data"
In realtà, le richieste POST non sono analoghe alle richieste GET (altrimenti non sarebbero necessarie). Le richieste POST hanno un vantaggio significativo: utilizzandole, puoi inviare file con contenuto binario.
Si tratta di una richiesta di tipo urlencoded che è autorizzata a inviare un set limitato di simboli. In caso contrario, i simboli "non consentiti" verranno sostituiti con codici. Pertanto, quando si inviano dati binari, saranno distorti. Quindi non sei in grado di inviare nemmeno un piccolo file gif utilizzando una richiesta GET.
Per risolvere questo problema vengono elaborate regole speciali per descrivere una richiesta; consentono lo scambio con file binari oltre a quelli di testo.
Per raggiungere questo obiettivo, il corpo della richiesta è suddiviso in sezioni. La cosa principale è che ogni sezione può avere il proprio tipo di dati. Ad esempio, il primo è un testo, il successivo è un'immagine/jpeg, ecc. In altre parole, una richiesta inviata al server può contenere più tipi di dati contemporaneamente.
Diamo un'occhiata alla struttura di tale descrizione con l'esempio dei dati passati dallo script MetaSwap.
L'intestazione della richiesta Head avrà il seguente aspetto:
Tipo di contenuto: multipart/form-data; boundary=SEPARATOR\r\n
La parola chiave SEPARATOR – è un insieme casuale di simboli. Tuttavia, questo è ciò che devi osservare per essere al di fuori dei dati della richiesta. In altre parole, questa linea deve essere unica - alcuni abracadabra come hdsJK263shxaDFHLsdhsDdjf9 o qualsiasi altra cosa ti venga in mente :). In PHP, tale linea viene formata utilizzando il codice MD5 di un tempo corrente.
La richiesta POST stessa si presenta come segue (per una più facile comprensione, i campi sono evidenziati secondo il significato generale):
\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
Specifichiamo esplicitamente gli spazi per i feed di riga "\r\n", perché sono simboli obbligatori in una richiesta. Come puoi vedere, nella richiesta vengono passati gli stessi quattro campi, quindi viene eseguita nel solito modo testuale.
Le importanti peculiarità del posizionamento dei separatori:
- Due simboli "--" sono posti prima del separatore.
- Per il separatore di chiusura vengono aggiunti due simboli aggiuntivi "--" dopo di esso.
Nel prossimo esempio, puoi vedere un metodo corretto per passare i file in una richiesta.
Immagina che un Expert Advisor crei un'istantanea del grafico e un rapporto dettagliato sul conto in un file di testo quando chiude una posizione.
\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
Tipo di contenuto: image/gif\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
......contenuto del file gif.....
\r\n
--SEPARATOR\r\n
Content-Disposition: form-data; name="statement"; filename="statement.csv"\r\n
Tipo di contenuto: application/octet-stream\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
......contenuto del file csv.....
\r\n
--SEPARATOR--\r\n
Nella richiesta vengono visualizzate due nuove intestazioni:
Content-Type - descrive il tipo di contenuto. Tutti i possibili tipi sono accuratamente descritti nello standard RFC[2046]. Abbiamo usato due tipi: image/gif e application/octet-stream.
Due varianti di scrittura Content-Disposition: file e form-data sono equivalenti e vengono elaborati correttamente da PHP in entrambi i casi. Quindi puoi usare file o form-data a tua scelta. Puoi vedere meglio la differenza tra le loro rappresentazioni in Charles.
Content-Transfer-Encoding - descrive la codifica del contenuto. Può essere assente per i dati di testo.
Per consolidare il materiale, scriviamo lo script ScreenPost, il quale invia gli screenshot al server:
#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"); }
Lo script del server che riceve le informazioni:
<?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 ?>
Si consiglia vivamente di familiarizzare con le regole di ricezione dei file dal server per evitare problemi di sicurezza!
Lavorare con i cookie
Questo argomento verrà brevemente descritto come un'aggiunta alla lezione precedente e uno spunto di riflessione sulle sue caratteristiche.
Come sapete, i cookie hanno lo scopo di evitare la richiesta continua di dati personali da parte dei server. Una volta che un server riceve i dettagli personali richiesti per la sessione di lavoro corrente da un utente, lascia un file di testo con tali informazioni sul computer dell'utente. Inoltre, quando l'utente si sposta tra le pagine, il server non richiede più tali informazioni all'utente; prende automaticamente le informazioni dalla cache del browser.
Ad esempio, quando abiliti l'opzione "Ricordami" mentre dai l’autorizzazione sul server www.mql5.com, salvi un Cookie con i tuoi dettagli sul tuo computer. Alla successiva visita del sito, il browser passerà il Cookie al server senza che te lo chieda.
Se sei interessato, puoi aprire la cartella (WinXP) C:\Documents and Settings\<Utente>\Cookies e visualizzare i contenuti dei diversi siti web che hai visitato.
Per quanto riguarda le nostre esigenze, i cookie possono essere utilizzati per leggere le tue pagine del forum MQL5. In altre parole, leggerai le informazioni come se avessi l’autorizzazione nel sito con il tuo nome utente e poi analizzerai le pagine ottenute. La variante ottimale è analizzare i cookie utilizzando un server proxy locale Charles. Mostra informazioni dettagliate su tutte le richieste ricevute/inviate, inclusi i Cookie.
Per esempio:
- Un Expert Advisor (o un'applicazione esterna) che richiede una volta all'ora la pagina https://www.mql5.com/en/job e riceve l'elenco delle nuove offerte di lavoro.
- Inoltre richiede un ramo, ad esempio https://www.mql5.com/en/forum/53, e controlla se ci sono nuovi messaggi.
- Inoltre, può controllare se ci sono nuovi "messaggi privati" nei forum.
Per impostare un cookie in una richiesta, viene utilizzata la funzione InternetSetCookie.
BOOL InternetSetCookie(
__in LPCTSTR lpszUrl,
__in LPCTSTR lpszCookieName,
__in LPCTSTR lpszCookieData
);
- lpszUrl [in] - Nome di un server, ad esempio www.mql5.com
- lpszCookieName [in]- Nome di un Cookie
- lpszCookieData [in] - Dati per il cookie
Per impostare più Cookie, richiama questa funzione per ciascuno di essi.
Una caratteristica interessante: si può effettuare una chiamata a InternetSetCookie in qualsiasi momento, anche quando non si è connessi al server.
Conclusione
Abbiamo conosciuto un altro tipo di richieste HTTP, ottenuto la possibilità di inviare file binari, cosa che permette di estendere le possibilità di lavorare con i vostri server, e abbiamo appreso i metodi per lavorare con i cookie.
Possiamo creare il seguente elenco di direzioni di ulteriori sviluppi:
- Organizzazione dell'archiviazione remota dei report;
- Scambio di file tra utenti, aggiornamento delle versioni di Expert Advisor/indicatori;
- Creazione di scanner personalizzati che funzionano con il tuo account e monitorano l'attività in un sito web.
Link utili:
- Un server proxy per la visualizzazione delle intestazioni inviate - http://www.charlesproxy.com/
- Descrizione di WinHTTP - http://msdn.microsoft.com/en-us/library/aa385331%28VS.85%29.aspx
- Descrizione della sessione HTTP - http://msdn.microsoft.com/en-us/library/aa384322%28VS.85% 29.aspx
- Il toolkit Denwer per un'installazione locale di Apache+PHP - http://www.denwer.ru/
- Tipi di intestazioni di richiesta - http://www.codenet.ru/webmast/php/HTTP-POST.php#part_3_2
- Tipi di richieste - http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
- Tipi di richieste - ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types.
- Struttura dell'utilizzo di HINTERNET - http://msdn.microsoft.com/en-us/library/aa383766%28VS.85% 29.aspx
- Utilizzo dei file: http://msdn.microsoft.com/en-us/library/aa364232%28VS.85%29.aspx
- Tipi di dati da passare a MQL - http://msdn.microsoft.com/en-us/library/aa383751%28VS .85%29.aspx
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/276





- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso