Usar WinInet em MQL5. Parte 2: Solicitações POST e Arquivos
Introdução
Na lição anterior "Usar WinInet.dll para trocar dados entre terminais via internet", nós aprendemos a trabalhar com a biblioteca, abrir páginas web, enviar e receber informações utilizando solicitações GET.
Nesta lição, nós iremos aprender a:
- criar e enviar solicitações POST simples a um servidor;
- enviar arquivos a um servidor usando o método de representação multipart/form-data;
- trabalhar com Cookies e ler informações a partir de websites utilizando o seu login.
Conforme anteriormente, recomendamos configurar um servidor proxy local Charles; isto será necessário durante o seu estudo e testes posteriores.
Solicitações POST
Para enviar informações, nós precisaremos das funções wininet.dll e da classe CMqlNet criada que foram descritas no artigo anterior.
Devido ao grande número de campos dos métodos CMqlNet::Request, nós tivemos que criar uma estrutura tagRequest separada que contém todos os campos requeridos para uma solicitação.
//------------------------------------------------------------------ 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 }
Além disso, nós precisamos substituir o cabeçalho do método CMqlNet::Request por um mais curto:
//+------------------------------------------------------------------+ 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); }
Agora vamos começar a trabalhar.
Enviar dados a um website do tipo "application/x-www-form-urlencoded"
Na lição anterior, nós analisamos o exemplo MetaArbitrage (monitoramento de cotações).
Vamos recordar que o EA envia preços de lances do seu símbolo utilizando uma solicitação GET; e, como resposta, ele recebe preços de outros corretores que são enviadas da mesma forma ao servidor a partir de outros terminais.
Para trocar uma solicitação GET por uma POST, é suficiente "esconder" a linha da solicitação em si no corpo da solicitação que segue após o seu cabeçalho.
BOOL HttpSendRequest(
__in HINTERNET hRequest,
__in LPCTSTR lpszHeaders,
__in DWORD dwHeadersLength,
__in LPVOID lpOptional,
__in DWORD dwOptionalLength
);
- hRequest [in]
Handle retornado por HttpOpenRequest. - lpszHeaders [in]
Ponteiro para uma linha que contém cabeçalhos a serem adicionados à solicitação. Este parâmetro pode ser nulo. - dwHeadersLength [in]
Tamanho do cabeçalho em bytes. - lpOptional [in]
Ponteiro para um array com dados uchar que são enviados imediatamente após o cabeçalho. Geralmente, este parâmetro é utilizado para operações POST e PUT. - dwOptionalLength [in]
Tamanho dos dados em bytes. O parâmetro pode ser =0; isso significa que nenhuma informação adicional foi enviada.
A partir da descrição da função, nós podemos entender que os dados são enviados como byte uchar-array (o quarto parâmetro da função). Isto é tudo o que precisamos saber nesta etapa.
No exemplo MetaArbitrage, a solicitação GET tem o seguinte aspecto:
www.fxmaster.de/metaarbitr.php?server=Metaquotes&pair=EURUSD&bid=1.4512&time=13286794
A solicitação em si é destacada com a cor vermelha. Assim, se nós precisarmos fazer uma solicitação POST, teremos que mover o seu texto para o array de dados lpOptional.
Vamos criar um script chamado MetaSwap, o qual irá enviar e receber informações sobre trocas de um símbolo.
#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(); }
A operação do script é muito simples.
Primeiramente, abre-se a sessão da internet INet.Open. Em seguida, a função SendData envia informações sobre as trocas do símbolo atual. Após, caso sejam enviadas com sucesso, as trocas recebidas são lidas com a utilização do ReadSwap e são exibidas no gráfico com UpdateInfo.
Neste momento, apenas a função SendData nos interessa.
//------------------------------------------------------------------ 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 }
Neste script, são demonstrados dois métodos de envio de informações - utilizando GET e POST, para que você perceba a diferença entre eles.
Descreveremos as variáveis da função uma a uma:
- Head - cabeçalho da solicitação descrevendo o tipo de seu conteúdo. Na realidade, este não é o cabeçalho completo da solicitação. Os outros campos do cabeçalho são criados pela biblioteca wininet.dll. Entretanto, eles podem ser modificados através da função HttpAddRequestHeaders.
- Path - este é o caminho para a instância de solicitação relativa ao domínio www.fxmaster.de inicial. Em outras palavras, é o caminho para um script php que processará a solicitação. A propósito, não é necessário solicitar apenas um script php, poderá ser uma página html comum (inclusive, nós tentamos solicitar um arquivo mq5 durante a nossa primeira lição).
- Data - estas são as informações que serão enviadas ao servidor. Os dados são escritos de acordo com as regras de passagem de parameter name=value. E o símbolo "&" é usado como separador de dados.
O principal detalhe é prestar atenção à diferença entre fazer solicitações GET e POST em tagRequest::Init.
No método GET, o caminho é enviado junto com o corpo da solicitação (unidos com o símbolo "?"), e o campo de dados lpOptional (denominado stData na estrutura) é deixado vazio.
No método POST, o caminho permanece independente e o corpo da solicitação é movido para lpOptional.
Como você pode perceber, a diferença não é significativa. O script metaswap.php do servidor, o qual recebe a solicitação, está anexo ao artigo.
Enviar dados "multipart/form-data"
Na realidade, solicitações POST não são análogas às solicitações GET (caso contrário, elas não seriam necessárias). As solicitações POST apresentam uma vantagem significativa - ao usá-las, você pode enviar arquivos de conteúdo binário.
A questão é que uma solicitação do tipo URL codificado pode enviar somente um conjunto limitado de símbolos. Caso contrário, os símbolos "não autorizado" serão substituídos por códigos. Dessa forma, durante o envio de dados binários, eles serão distorcidos. Assim, você não poderá enviar até mesmo um pequeno arquivo gif utilizando uma solicitação GET.
Para solucionar este problema, regras especiais de descrição de solicitação são desenvolvidas; elas permitem a troca de arquivos binários além de arquivos de texto.
Para alcançar este objetivo, o corpo da solicitação é dividido em seções. O principal detalhe é que cada seção pode ter o seu próprio tipo de dados. Por exemplo, o primeiro é um arquivo de texto, o próximo é uma imagem/jpeg, etc. Em outras palavras, uma solicitação enviada ao servidor pode conter diferentes tipos de dados de uma só vez.
Vamos observar a estrutura desta descrição através do exemplo de dados passados através do script MetaSwap.
Cabeçalho da solicitação - Head terá o seguinte formato:
Content-Type: multipart/form-data; boundary=SEPARATOR\r\n
A palavra-chave SEPARATOR – é um conjunto de símbolos aleatórios. Entretanto, você deve se certificar de que isto seja diferente dos dados de solicitação. Em outras palavras, esta deve ser uma linha única - algo aleatório como hdsJK263shxaDFHLsdhsDdjf9 ou qualquer outra sequência que vier a sua mente :). Em PHP, uma linha como esta é formada com o uso do código MD5 de um horário atual.
A solicitação POST em si tem o seguinte aspecto (para melhor compreensão, os campos estão destacados de acordo com o significado geral):
\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
Nós especificamos os locais para novas linhas "\r\n" explicitamente, porque eles são símbolos obrigatórios em uma solicitação. Como você pode perceber, os mesmos quatro campos são passados na solicitação e isso é feito da forma textual comum.
Os detalhes importantes ao posicionar separadores:
- Dois símbolos "--" são posicionados antes do separador.
- Para fechar o separador, dois símbolos "--" adicionais são colocados após ele.
No próximo exemplo, você pode observar um método correto para enviar arquivos em uma solicitação.
Imagine que um Expert Advisor capture a imagem de um gráfico e faça um relatório detalhado sobre conta em um arquivo de texto ao fechar uma posição.
\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
......conteúdo do arquivo gif.....
\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
......conteúdo do arquivo csv.....
\r\n
--SEPARATOR--\r\n
Dois novos cabeçalhos aparecem na solicitação:
Content-Type - descreve o tipo de conteúdo. Todos os tipos possíveis estão descritos com precisão no padrão RFC[2046]. Nós usamos dois tipos - image/gif e application/octet-stream.
Duas variantes ao escrever Content-Disposition, file e form-data, são equivalentes e serão corretamente processadas por PHP nos dois casos. Assim, você tem a opção de usar file ou form-data. Você pode observar melhor a diferença entre as representações das variantes no Charles.
Content-Transfer-Encoding - descreve a codificação do conteúdo. Poderá ser inexistente em dados de texto.
Para consolidar o material, escreveremos o script ScreenPost, o qual envia capturas de imagem ao servidor:
#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"); }
O script do servidor que recebe a informação:
<?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 ?>
é altamente recomendo que você se familiarize com as regras de recebimento de arquivos do servidor para evitar problemas de segurança!
Trabalhar com Cookies
Este assunto será descrito brevemente em adição à lição anterior para que você pense mais a respeito de suas funcionalidades.
Como você sabe, Cookies têm a função de evitar que os servidores façam solicitações contínuas de detalhes pessoais. Uma vez que um servidor recebe do usuário os detalhes pessoais requeridos para a sessão de trabalho atual, ele armazena um arquivo de texto com estas informações no computador do usuário. Mais tarde, quando o usuário acessa outras páginas, o servidor não solicita aquelas mesmas informações do usuário; ele recebe as informações do cache do navegador.
Por exemplo, quando você habilita a opção "Continuar conectado" ao fazer a autorização no servidor www.mql5.com, você salva um Cookie com os seus detalhes em seu computador. Na sua próxima visita ao website, o navegador irá transmitir o Cookie ao servidor sem lhe perguntar.
Caso tenha interesse, você pode abrir a pasta (WinXP) C:\Documents and Settings\\Cookies e visualizar o conteúdo dos diferentes websites que você visitou.
Em relação as nossa necessidades, os Cookies podem ser usados para ler as suas páginas do fórum do MQL5. Em outras palavras, você lerá as informações como se você estivesse autorizado no website com o seu login e então você irá analisar as páginas obtidas. A melhor alternativa é analisar Cookies utilizando um servidor proxy local Charles. Ele mostra informações detalhadas sobre todas as solicitações recebidas/enviadas, incluindo Cookies.
Por exemplo:
- Um Expert Advisor (ou um aplicativo externo) que solicita a página https://www.mql5.com/pt/job uma vez por hora e recebe a lista de novas ofertas de emprego.
- Além disso, ele também solicita uma ramificação, a página https://www.mql5.com/en/forum/53, por exemplo, e verifica se há novas mensagens.
- Ademais, ele poderá verificar se há ou não novas "mensagens privadas" nos fóruns.
A função InternetSetCookie é utilizada para estabelecer um Cookie em uma solicitação.
BOOL InternetSetCookie(
__in LPCTSTR lpszUrl,
__in LPCTSTR lpszCookieName,
__in LPCTSTR lpszCookieData
);
- lpszUrl [in] - Nome de um servidor, por exemplo, www.mql5.com
- lpszCookieName [in]- Nome de um Cookie
- lpszCookieData [in] - Dados para o Cookie
Para estabelecer diversos Cookies, utilize esta função para cada um deles.
Uma característica interessante: você pode utilizar InternetSetCookie a qualquer tempo, mesmo quando você não estiver conectado ao servidor.
Conclusão
Nós nos familiarizamos com outros tipos de solicitações HTTP, aprendemos a possibilidade de enviar arquivos binários, o que permite aumentar a facilidade de trabalhar com os seus servidores; e aprendemos os métodos para trabalhar com Cookies.
é possível determinar a seguinte lista de direções de desenvolvimentos futuros:
- Organização de armazenamento remoto de relatórios;
- Troca de arquivos entre usuários, atualização de versões de Expert Advisors/Indicadores.
- Criação de rastreadores personalizados que trabalham na sua conta e monitoram a atividade em um website.
Links úteis
- Um servidor proxy para visualizar os cabeçalhos enviados - http://www.charlesproxy.com/
- Descrição do WinHTTP - http://msdn.microsoft.com/en-us/library/aa385331%28VS.85%29.aspx
- Descrição da Sessão HTTP - http://msdn.microsoft.com/en-us/library/aa384322%28VS.85%29.aspx
- O kit de ferramentas Denwer para uma instalação local de Apache+PHP - http://www.denwer.ru/
- Tipos de cabeçalhos de solicitação - http://www.codenet.ru/webmast/php/HTTP-POST.php#part_3_2
- Tipos de solicitações - http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
- Tipos de solicitações - ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types.
- Estrutura do uso de HINTERNET - http://msdn.microsoft.com/en-us/library/aa383766%28VS.85%29.aspx
- Trabalhando com arquivos - http://msdn.microsoft.com/en-us/library/aa364232%28VS.85%29.aspx
- Tipos de dados para passar a MQL - http://msdn.microsoft.com/en-us/library/aa383751%28VS.85%29.aspx
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/276
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso