MQL5'te WinInet'i Kullanma. Bölüm 2: POST İstekleri ve Dosyalar
Giriş
Bir önceki "İnternet üzerinden Terminaller Arası Veri Alışverişi için WinInet.dll Kullanımı" adlı makalede, kitaplık ile çalışmayı, web sayfalarını açmayı, GET isteklerini kullanarak bilgi göndermeyi ve almayı öğrenmiştik.
Bu makalede, şunları yapmayı öğreneceğiz:
- Bir sunucuya basit POST istekleri oluşturma ve gönderme;
- multipart/form-data temsil yöntemini kullanarak dosyaları bir sunucuya gönderme;
- Çerezlerle çalışma ve oturum açma bilgilerinizi kullanarak web sitelerindeki bilgileri okuma.
Daha önce olduğu gibi, yerel bir proxy sunucusu kurmanızı şiddetle tavsiye ediyorum Charles; bu, çalışmanız ve sonraki denemeleriniz için gerekli olacaktır.
POST İstekleri
Bilgi göndermek için, önceki makalede ayrıntılı olarak açıklanan wininet.dll işlevlerine ve oluşturulan CMqlNet sınıfına ihtiyacımız olacak.
CMqlNet::Request yöntemlerindeki çok sayıda alan nedeniyle, bir istek için gerekli tüm alanları içeren ayrı bir tagRequest yapısı oluşturmamız gerekiyordu.
//------------------------------------------------------------------ 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 }
Ayrıca, CMqlNet::Request yönteminin başlığını daha kısa bir başlıkla değiştirmemiz gerekiyor:
//+------------------------------------------------------------------+ 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); }
O halde çalışmaya başlayalım...
"application/x-www-form-urlencoded" Türündeki Bir Web Sitesine Veri Gönderme
Önceki derste, MetaArbitrage örneğini (fiyat tekliflerinin izlenmesi) analiz etmiştik.
EA'nın bir GET isteği kullanarak sembolünün Alış fiyatlarını gönderdiğini ve cevap olarak diğer terminallerden sunucuya aynı şekilde gönderilen diğer aracıların fiyatlarını aldığını hatırlayalım.
Bir GET isteğini bir POST isteğine dönüştürmek için, başlığından sonra gelen isteğin gövdesinde istek satırının kendisini "gizlemek" yeterlidir.
BOOL HttpSendRequest(
__in HINTERNET hRequest,
__in LPCTSTR lpszHeaders,
__in DWORD dwHeadersLength,
__in LPVOID lpOptional,
__in DWORD dwOptionalLength
);
- hRequest [in]
HttpOpenRequest tarafından döndürülen tanıtıcı. - lpszHeaders [in]
İsteğe eklenecek başlıkları içeren bir satırın işaretçisi. Bu parametre boş olabilir. - dwHeadersLength [in]
Başlığın bayt cinsinden boyutu. - lpOptional [in]
Başlıktan hemen sonra gönderilen uchar verilerini içeren bir diziye işaretçi. Genellikle bu parametre POST ve PUT işlemleri için kullanılır. - dwOptionalLength [in]
Bayt cinsinden veri boyutu. Parametre =0 olabilir; bu, ek bilgi gönderilmediği anlamına gelir.
İşlevin açıklamasından, verilerin bir baytlık uchar-dizisi (işlevin dördüncü parametresi) olarak gönderildiğini anlayabiliriz. Bu aşamada bilmemiz gereken tek şey bu.
MetaArbitrage örneğinde, GET isteği aşağıdaki gibi görünür:
www.fxmaster.de/metaarbitr.php?server=Metaquotes&pair=EURUSD&bid=1.4512&time=13286794
İsteğin kendisi kırmızı renkle vurgulanır. Bu nedenle, bir POST isteği yapmamız gerekirse, metnini lpOptional veri dizisine taşımalıyız.
Bir sembolün swap'ları hakkında bilgi gönderecek ve alacak olan MetaSwap adlı bir script dosyası oluşturalım.
#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(); }
Script dosyasının çalışması çok basittir.
Öncelikle INet.Open İnternet oturumu açılır. Ardından SendData işlevi, mevcut sembolün swap'ları hakkında bilgi gönderir. Bundan sonra, başarılı bir şekilde gönderilirse, alınan swap'lar ReadSwap kullanılarak okunur ve UpdateInfo kullanılarak grafikte görüntülenir.
Şu anda, yalnızca SendData işleviyle ilgileniyoruz.
//------------------------------------------------------------------ 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 }
Bu script dosyasında, bilgi göndermenin iki yöntemi gösterilmektedir - Bu, aralarındaki farkı anlamanız için GET ve POST kullanılarak yapılmıştır.
İşlevin değişkenlerini tek tek tanımlayalım:
- Head - İçeriğinin türünü açıklayan isteğin başlığı. Aslında bu, isteğin tüm başlığı değildir. Başlığın diğer alanları wininet.dll kitaplığı tarafından oluşturulur. Ancak, HttpAddRequestHeaders işlevi kullanılarak değiştirilebilir.
- Path - Bu, ilk etki alanına (www.fxmaster.de) göre istek örneğine giden yoldur. Başka bir deyişle, isteği işleyecek olan bir php script dosyası'na giden yoldur. Bu arada yalnızca php script dosyası istemek zorunda değilsiniz; bu, sıradan bir html sayfası da olabilir (ilk dersimizde mq5 dosyası istemeyi dahi denedik).
- Data - Sunucuya gönderilen bilgidir. Veriler parametre adı=değer geçiş kurallarına göre yazılır. "&" işareti veri ayırıcı olarak kullanılır.
Ve asıl mesele - tagRequest::Init'de GET ve POST istekleri yapmak arasındaki farka dikkat edin.
GET yönteminde, yol istek gövdesiyle birlikte ("?" işaretiyle birleştirilmiş) gönderilir ve veri alanı lpOptional (yapıda stData olarak adlandırılmış) boş bırakılır.
POST yönteminde, yol kendi başına bulunur ve istek gövdesi lpOptional öğesine taşınır.
Gördüğünüz gibi, fark önemli değildir. İsteği alan metaswap.php sunucu script dosyası makaleye eklenmiştir.
"Multipart/form-data" Verilerini Gönderme
Aslında, POST istekleri GET isteklerine benzemez (aksi takdirde bunlara ihtiyaç duyulmazdı). POST isteklerinin önemli bir avantajı vardır; bunları kullanarak ikili içerikli dosyalar gönderebilirsiniz.
Mesele, urlencoded türünün bir isteğinin sınırlı sayıda sembol göndermesine izin verilmesidir. Aksi takdirde, "izin verilmeyen" semboller kodlarla değiştirilecektir. Dolayısıyla, ikili veri gönderirken bozulacaklardır. Yani bir GET isteği kullanarak küçük bir gif dosyası dahi gönderemezsiniz.
Bu sorunu çözmek için, bir isteği tanımlamanın özel kuralları belirlenir; metin dosyalarına ek olarak ikili dosyalarla değiş tokuşa izin verirler.
Bu hedefe ulaşmak için, isteğin gövdesi bölümlere ayrılmıştır. Asıl mesele, her bölümün kendi veri türüne sahip olabilmesidir. Örneğin, ilki metin, sonraki resim/jpeg vb. Başka bir deyişle, sunucuya gönderilen bir istek, aynı anda birkaç tür veri içerebilir.
MetaSwap script dosyası tarafından iletilen veri örneğiyle bu tür bir açıklamanın yapısına bir göz atalım.
Head isteğinin başlığı aşağıdaki gibi görünecektir:
İçerik Türü: multipart/form-data; boundary=SEPARATOR\r\n
SEPARATOR – anahtar sözcüğü rastgele bir semboller kümesidir. Ancak, bunun istek verilerinin dışında kalmasına dikkat etmelisiniz. Diğer bir deyişle, bu satır benzersiz olmalıdır - hdsJK263shxaDFHLsdhsDdjf9 gibi bazı anlamsız sözler veya aklınıza gelen başka bir şey :). PHP'de böyle bir satır, geçerli bir zamanın MD5 kodu kullanılarak oluşturulur.
POST isteğinin kendisi aşağıdaki gibidir (daha kolay anlaşılması için alanlar genel anlama göre vurgulanmıştır):
\r\n
--SEPARATOR\r\n
İçerik-Düzeni: form-veri; name="Server"\r\n
\r\n
MetaQuotes-Demo
\r\n
--SEPARATOR\r\n
İçerik-Düzeni: form-veri; name="Pair"\r\n
\r\n
EURUSD
\r\n
--SEPARATOR\r\n
İçerik-Düzeni: form-veri; name="Long"\r\n
\r\n
1.02
\r\n
--SEPARATOR\r\n
İçerik-Düzeni: form-veri; name="Short"\r\n
\r\n
-0.05
\r\n
--SEPARATOR--\r\n
"\r\n" satır beslemeleri için yerleri açıkça belirtiriz; zira bunlar bir istekte zorunlu sembollerdir. Gördüğünüz gibi, istekte aynı dört alan iletilir; bu, durumda, bu normal metin yolunda yapılır.
Ayırıcıları yerleştirmenin önemli özellikleri:
- Ayırıcıdan önce iki sembol "--" yerleştirilir.
- Kapanış ayırıcısı için, ondan sonra iki ek sembol "--" eklenir.
Sonraki örnekte, bir istekte dosyaları iletmek için doğru bir yöntem görebilirsiniz.
Bir Expert Advisor'ın, bir pozisyonu kapatırken bir metin dosyasında hesapla ilgili bir grafik anlık görüntüsü ve ayrıntılı bir rapor oluşturduğunu düşünün.
\r\n
--SEPARATOR\r\n
İçerik-Düzeni: form-veri; name="ExpertName"\r\n
\r\n
MACD_Sample
\r\n
--SEPARATOR\r\n
İçerik-Düzeni: dosya; name="screen"; filename="screen.gif"\r\n
İçerik-Türü: image/gif\r\n
İçerik-Aktarım-Kodlaması: binary\r\n
\r\n
......gif dosyasının içeriği.....
\r\n
--SEPARATOR\r\n
İçerik-Yerleştirme: form-data; name="statement"; filename="statement.csv"\r\n
İçerik-Türü: application/octet-stream\r\n
İçerik-Aktarım-Kodlaması: binary\r\n
\r\n
......csv dosyasının içeriği......
\r\n
--SEPARATOR--\r\n
İstekte iki yeni başlık görünür:
İçerik-Türü - İçeriğin türünü tanımlar. Tüm olası türler, RFC[2046] standardında doğru bir şekilde açıklanmıştır. İki tür kullandık - image/gif ve application/octet-stream.
İçerik-Düzeni - dosya ve form-data yazmanın iki çeşidi eşdeğerdir ve her iki durumda da PHP tarafından doğru şekilde işlenir. Böylece dosya veya form-verilerini tercihinize göre kullanabilirsiniz. Charles'daki temsilleri arasındaki farkı daha iyi görebilirsiniz.
İçerik-Aktarım-Kodlaması - İçeriğin kodlamasını açıklar. Metin verileri için mevcut olmayabilir.
Materyali birleştirmek için, sunucuya ekran görüntüleri gönderen ScreenPost script dosyasını yazalım:
#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"); }
Bilgileri alan sunucu script dosyası:
<?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 ?>
Güvenlik sorunlarından kaçınmak için sunucu tarafından dosya alma kurallarını bilmeniz şiddetle tavsiye edilir!
Çerezlerle Çalışma
Bu konu bir önceki derse ek olarak kısaca anlatılacak ve özellikleri hakkında fikir edinilmesini sağlayacaktır.
Bildiğiniz gibi, Çerezler, sunucular tarafından kişisel bilgilerin sürekli olarak talep edilmesini önlemeye yöneliktir. Bir sunucu, bir kullanıcıdan mevcut çalışma oturumu için gerekli olan kişisel bilgileri aldığında, kullanıcı bilgisayarında bu bilgileri içeren bir metin dosyası bırakır. Ayrıca, kullanıcı sayfalar arasında hareket ettiğinde, sunucu bu bilgiyi kullanıcıdan tekrar istemez; bilgileri otomatik olarak tarayıcı önbelleğinden alır.
Örneğin, www.mql5.com sunucusunda yetkilendirme yaparken "Beni hatırla" seçeneğini etkinleştirdiğinizde, bilgisayarınıza ayrıntılarınızla birlikte bir Çerez kaydedersiniz. Web sitesini bir sonraki ziyaretinizde, tarayıcı size sormadan Çerezi sunucuya iletecektir.
İlgileniyorsanız, (WinXP) C:\Documents and Settings\<User>\Cookies klasörünü açabilir ve ziyaret ettiğiniz farklı web sitelerinin içeriklerini görüntüleyebilirsiniz.
İhtiyaçlarımızla ilgili olarak, MQL5 forum sayfalarınızı okumak için Çerezler kullanılabilir. Diğer bir deyişle, girdiğiniz web sitesinde yetkiniz varmış gibi bilgileri okuyacak ve ardından elde edilen sayfaları analiz edeceksiniz. En optimum değişken, bir yerel proxy sunucusu olan Charles'ı kullanarak Çerezleri analiz etmektir. Bu, çerezler dahil tüm alınan/gönderilen istekler hakkında ayrıntılı bilgi gösterir.
Örneğin:
- https://www.mql5.com/en/job sayfasını saatte bir talep eden ve yeni iş tekliflerinin listesini alan bir Expert Advisor (veya harici bir uygulama).
- Ayrıca bir dal talep eder; örneğin https://www.mql5.com/en/forum/53 ve yeni mesaj olup olmadığını kontrol eder.
- Buna ek olarak, forumlarda yeni "özel mesajlar" olup olmadığını kontrol edebilir.
Bir istekte Çerez ayarlamak için, InternetSetCookie işlevi kullanılır.
BOOL InternetSetCookie(
__in LPCTSTR lpszUrl,
__in LPCTSTR lpszCookieName,
__in LPCTSTR lpszCookieData
);
- lpszUrl [in] - Sunucu adı; örneğin, www.mql5.com
- lpszCookieName [in]- Çerez adı
- lpszCookieData [in] - Çerez için Veriler
Birkaç Çerez ayarlamak için, her biri için bu işlevi çağırın.
İlginç bir özellik: InternetSetCookie çağrısı, sunucuya bağlı olmasanız dahi herhangi bir zamanda yapılabilir.
Sonuç
Başka bir HTTP isteği türüyle tanıştık, sunucularınızla çalışma olanaklarını genişletmeye izin veren ikili dosyalar gönderme olanağını elde ettik ve Çerezlerle çalışma yöntemlerini öğrendik.
Daha fazla geliştirme için aşağıdaki yönerge listesini uygulamaya koyabiliriz:
- Raporların uzak depolanmasının düzenlenmesi;
- Kullanıcılar arasında dosya alışverişi, Expert Advisor'ların/göstergelerin sürümlerinin güncellenmesi;
- Hesabınız altında çalışan ve bir web sitesindeki etkinliği izleyen özel tarayıcıların oluşturulması.
Faydalı Bağlantılar:
- Gönderilen üst bilgileri görüntülemek için proxy sunucusu - http://www.charlesproxy.com/
- WinHTTP açıklaması - http://msdn.microsoft.com/en-us/library/aa385331%28VS.85%29.aspx
- HTTP Oturumunun Açıklaması - http://msdn.microsoft.com/en-us/library/aa384322%28VS.85%29.aspx
- Apache+PHP'nin yerel kurulumu için Denwer araç kiti - http://www.denwer.ru/
- İstek üstbilgisi türleri - http://www.codenet.ru/webmast/php/HTTP-POST.php#part_3_2
- İstek türü - http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
- İstek türü - ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/media-types.
- HINTERNET kullanmanın yapısı - http://msdn.microsoft.com/en-us/library/aa383766%28VS.85%29.aspx
- Dosyalarla çalışma - http://msdn.microsoft.com/en-us/library/aa364232%28VS.85%29.aspx
- MQL'ye iletilecek veri türleri - http://msdn.microsoft.com/en-us/library/aa383751%28VS.85%29.aspx
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/276
- Ücretsiz ticaret uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Gizlilik ve Veri Koruma Politikasını ve MQL5.com Kullanım Şartlarını kabul edersiniz