
Der Client im Connexus (Teil 7): Hinzufügen der Client-Schicht
Einführung
Dieser Artikel ist die Fortsetzung einer Reihe von Artikeln, in denen wir eine Bibliothek namens Connexus aufbauen werden. Im ersten Artikel haben wir die grundlegende Funktionsweise der Funktion WebRequest verstanden, jeden ihrer Parameter kennengelernt und auch einen Beispielcode erstellt, der die Verwendung dieser Funktion und ihre Schwierigkeiten demonstriert. Im letzten Artikel haben wir verstanden, was http-Methoden sind und auch, was die Statuscodes sind, die vom Server zurückgegeben werden und die Auskunft darüber geben, ob eine Anfrage erfolgreich bearbeitet wurde oder ob ein Fehler vom Client oder Server erzeugt wurde.
In diesem siebten Artikel der Serie werden wir den am meisten erwarteten Teil der gesamten Bibliothek hinzufügen, wir werden die Anfrage mit der WebRequest-Funktion machen, wir werden nicht direkt den Zugriff darauf erstellen, es werden einige Klassen und Schnittstellen im Prozess sein. Los geht's!
Um Sie an den aktuellen Stand der Bibliothek zu erinnern, sehen Sie hier das aktuelle Diagramm:
Ziel ist es, ein CHttpRequest-Objekt zu erhalten, d. h. eine fertige HTTP-Anforderung, die bereits mit Header, Body, URL, Methode und Timeout konfiguriert ist, und eine HTTP-Anforderung mit der Funktion WebRequest zu senden. Außerdem muss er die Anfrage verarbeiten und ein CHttpResponse-Objekt mit den Antwortdaten wie Header, Body, Statuscode und Gesamtdauer der Anfrage zurückgeben.
Erstellen der Klasse CHttpClient
Erstellen wir die am meisten erwartete Klasse von allen, die letzte Klasse der Bibliothek, die der Nutzer der Bibliothek instanziieren und verwenden wird. Diese Klasse heißt CHttpClient.mqh und befindet sich im folgenden Pfad: Include/Connexus/Core/CHttpClient.mqh. Ursprünglich wird die Datei ähnlich wie diese aussehen, ich habe bereits die entsprechenden Importe hinzugefügt://+------------------------------------------------------------------+ //| HttpClient.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "HttpRequest.mqh" #include "HttpResponse.mqh" #include "../Constants/HttpMethod.mqh" //+------------------------------------------------------------------+ //| class : CHttpClient | //| | //| [PROPERTY] | //| Name : CHttpClient | //| Heritage : No heritage | //| Description : Class responsible for linking the request and | //| response object with the transport layer. | //| | //+------------------------------------------------------------------+ class CHttpClient { public: CHttpClient(void); ~CHttpClient(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CHttpClient::CHttpClient(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CHttpClient::~CHttpClient(void) { } //+------------------------------------------------------------------+Diese Klasse hat die Aufgabe, ein Request-Objekt, d. h. CHttpRequest, mit der Funktion WebRequest in eine HTTP-Anfrage umzuwandeln und ein CHttpResponse-Objekt mit den Daten zurückzugeben. Lassen Sie uns diese Klasse erstellen. Dazu erstellen wir eine Methode Send(), die zwei Objekte empfangen muss, CHttpRequest und CHttpResponse, und die ein booleschen Wert zurückgeben muss.
class CHttpClient { public: CHttpClient(void); ~CHttpClient(void); //--- Basis function bool Send(CHttpRequest &request, CHttpResponse &response); };
Lassen Sie uns an der Implementierung dieser Funktion arbeiten. Schauen wir uns zunächst kurz die Parameter der Funktion WebRequest an. Es gibt 2 Varianten, von denen wir zunächst diejenige mit weniger Parametern verwenden werden:
int WebRequest( const string method, // HTTP method const string url, // URL const string headers, // headers int timeout, // timeout const char &data[], // the array of the HTTP message body char &result[], // an array containing server response data string &result_headers // headers of server response );Alle diese Parameter sind bereits in den empfangenen Objekten vorhanden und konfiguriert, sodass sie nur noch hinzugefügt werden müssen. Die Umsetzung würde folgendermaßen aussehen:
//+------------------------------------------------------------------+ //| Basis function | //+------------------------------------------------------------------+ bool CHttpClient::Send(CHttpRequest &request, CHttpResponse &response) { //--- 1. Request uchar body_request[]; request.Body().GetAsBinary(body_request); //--- 2. Response uchar body_response[]; string headers_response; //--- 3. Send ulong start = GetMicrosecondCount(); int status_code = WebRequest( request.Method().GetMethodDescription(), // HTTP method request.Url().FullUrl(), // URL request.Header().Serialize(), // Headers request.Timeout(), // Timeout body_request, // The array of the HTTP message body body_response, // An array containing server response data headers_response // Headers of server response ); ulong end = GetMicrosecondCount(); //--- 4. Add data in Response response.Clear(); response.Duration((end-start)/1000); response.StatusCode() = status_code; response.Body().AddBinary(body_response); response.Header().Parse(headers_response); //--- 5. Return is success return(response.StatusCode().IsSuccess()); } //+------------------------------------------------------------------+
Ich habe in den Kommentaren einige Zahlen hinzugefügt, um die einzelnen Schritte zu erläutern, und es ist sehr wichtig, dass Sie verstehen, was hier geschieht, da dies die zentrale Funktion der Bibliothek ist:
- REQUEST: Hier erstellen wir ein Array vom Typ uchar mit dem Namen body_request. Wir greifen auf den Hauptteil, dem Body, der Anfrage zu und erhalten den Hauptteil der Anfrage in binärer Form, indem wir das Array body_request übergeben, sodass dieses Array durch Einfügen der Body-Daten verändert wird.
- RESPONSE: Wir erstellen zwei Variablen, die von der Funktion WebRequest verwendet werden, um den Hauptteil und die Kopfzeile (header) der Antwort vom Server zurückzugeben.
- SENDEN: Dies ist das Herzstück des Ganzen. Wir rufen die Funktion WebRequest auf und übergeben alle Parameter, einschließlich URL und Timeout, sowie die in Schritt 2 erstellten Variablen, um den Hauptteil und die Kopfzeile der Antwort zu erhalten. Hier erstellen wir auch zwei weitere Hilfsvariablen, die die Funktion GetMicrosecondCount() verwenden, um die Zeit vor und nach der Anfrage zu speichern. Auf diese Weise erhalten wir die Zeit vor und nach der Anfrage, um die Dauer der Anfrage in Millisekunden zu berechnen.
- DATEN ALS ANTWORT HINZUFÜGEN: In diesem Fall verwenden wir nach der Beantwortung der Anfrage, unabhängig davon, ob sie erfolgreich war oder nicht, die Funktion Clear(), um alle Werte des Antwortobjekts zurückzusetzen, und wir ergänzen die Dauer anhand der folgenden Formel: (Ende - Anfang)/1000. Wir definieren auch den von der Funktion zurückgegebenen Statuscode und fügen den Hauptteil und die Kopfzeile zum Antwortobjekt hinzu.
- RÜCKGABE: Im letzten Schritt wird geprüft, ob die Anfrage einen Erfolgscode (zwischen 200 und 299) zurückgegeben hat. Auf diese Weise ist es möglich, festzustellen, ob die Anfrage abgeschlossen wurde, und für weitere Details einfach den Inhalt des Antwortobjekts, d. h. CHttpResponse, zu überprüfen.
Mit der Erstellung dieser Klasse würde das aktualisierte Diagramm etwa so aussehen:
Am Ende befinden sich alle Klassen in HttpClient. Lassen Sie uns einen einfachen Test durchführen.
Erster Test
Führen wir den ersten und einfachsten Test mit allem durch, was wir bisher gebaut haben. Lassen Sie uns eine GET-Anfrage und eine POST-Anfrage an httpbin.org stellen, einen kostenlosen Online-Dienst, mit dem Sie HTTP-Anfragen testen können. Es wurde von kennethreitz erstellt und ist ein OpenSource-Projekt (link). Wir haben diese öffentliche API bereits in früheren Artikeln verwendet, aber um es kurz zu erklären: httpbin funktioniert wie ein Spiegel, d. h. alles, was wir an den Server senden, wird an den Client zurückgesendet. Dies ist sehr nützlich für das Testen von Anwendungen, bei denen wir einfach wissen wollen, ob die Anfrage abgeschlossen wurde und welche Daten der Server erhalten hat.
Im Code erstellen wir eine neue Datei im Ordner Experts mit dem Namen TestRequest.mq5. Der endgültige Pfad lautet Experts/Connexus/Test/TestRequest.mq5. Wir werden nur die OnInit-Funktion aus der Datei verwenden, also verwerfen wir den Rest. Der Code wird wie folgt aussehen:
//+------------------------------------------------------------------+ //| TestRequest.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Importieren wir die Bibliothek, indem wir den Import mit #include
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Connexus/Core/HttpClient.mqh>
Lassen Sie uns nun drei Objekte erstellen:
- CHttpRequest : Hier werden die URL und die HTTP-Methode definiert. Timeout, Hauptteil und Kopfzeile sind bei GET-Anfragen optional, der Timeout hat den Standardwert von 5000 Millisekunden (5 Sekunden).
- CHttpResponse : Speichert das Ergebnis der Anfrage mit dem Statuscode und der Beschreibung sowie die Dauer der Anfrage in Millisekunden.
- CHttpClient : Die Klasse, die die Anfrage tatsächlich ausführt.
//--- Request object CHttpRequest request; request.Method() = HTTP_METHOD_GET; request.Url().Parse("https://httpbin.org/get"); //--- Response object CHttpResponse response; //--- Client http object CHttpClient client;
Und schließlich rufen wir die Funktion Send() auf, um die Anfrage zu stellen und die Daten auf dem Terminal auszudrucken. Der vollständige Code sieht wie folgt aus:
//+------------------------------------------------------------------+ //| TestRequest.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Connexus/Core/HttpClient.mqh> //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Request object CHttpRequest request; request.Method() = HTTP_METHOD_GET; request.Url().Parse("https://httpbin.org/get"); //--- Response object CHttpResponse response; //--- Client http object CHttpClient client; client.Send(request,response); Print(response.FormatString()); //--- Initialize return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+Bei der Ausführung des Codes erhalten wir dieses Ergebnis im Terminal:
HTTP Response: --------------- Status Code: HTTP_STATUS_OK [200] - OK Duration: 845 ms --------------- Headers: Date: Fri, 18 Oct 2024 17:52:35 GMT Content-Type: application/json Content-Length: 380 Connection: keep-alive Server: gunicorn/19.9.0 Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true --------------- Body: { "args": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Accept-Language": "pt,en;q=0.5", "Host": "httpbin.org", "User-Agent": "MetaTrader 5 Terminal/5.4620 (Windows NT 11.0.22631; x64)", "X-Amzn-Trace-Id": "Root=1-6712a063-5c19d0e03b85df9903cb0e91" }, "origin": "XXX.XX.XX.XX", "url": "https://httpbin.org/get" } ---------------
Dies sind die Daten des Antwortobjekts, d. h. der Statuscode mit Beschreibung, die Dauer der Anfrage in Millisekunden, die Kopfzeile und der Antwortkörper. Das macht das Leben der Entwickler viel einfacher, wenn sie ihre MQL5-Programme mit externen APIs verbinden. Wir haben es geschafft, eine HTTP-Anfrage sehr einfach zu gestalten. Um einen Vergleich der Entwicklung zu haben, werde ich Sie daran erinnern, wie eine Anfrage ohne die Bibliothek und eine andere mit ihr funktioniert:
- Ohne die Connexus-Bibliothek:
int OnInit() { //--- Defining variables string method = "GET"; // HTTP verb in string (GET, POST, etc...) string url = "https://httpbin.org/get"; // Destination URL string headers = ""; // Request header int timeout = 5000; // Maximum waiting time 5 seconds char data[]; // Data we will send (body) array of type char char result[]; // Data received as an array of type char string result_headers; // String with response headers string body = "{\"key\":\"value\"}"; StringToCharArray(body,data,0,WHOLE_ARRAY,CP_UTF8); ArrayRemove(data,ArraySize(data)-1); //--- Calling the function and getting the status_code int status_code = WebRequest(method,url,headers,timeout,data,result,result_headers); //--- Print the data Print("Status code: ",status_code); Print("Response: ",CharArrayToString(result)); // We use CharArrayToString to display the response in string form. //--- return(INIT_SUCCEEDED); }
- Mit der Connexus-Bibliothek:
int OnInit() { //--- Request object CHttpRequest request; request.Method() = HTTP_METHOD_GET; request.Url().Parse("https://httpbin.org/get"); request.Body().AddString("{\"key\":\"value\"}"); //--- Response object CHttpResponse response; //--- Client http object CHttpClient client; client.Send(request,response); Print(response.FormatString()); //--- Initialize return(INIT_SUCCEEDED); }
Es ist viel einfacher, mit der Bibliothek zu arbeiten, auf den Hauptteil in verschiedenen Formaten zuzugreifen, wie z.B. string, json oder binär und alle anderen verfügbaren Ressourcen. Es ist auch möglich, Teile der Anfrage wie URL, Hauptteil und Kopf zu ändern und das gleiche Objekt für eine andere Anfrage zu verwenden. Auf diese Weise können Sie auf vereinfachte Weise mehrere Anfragen hintereinander stellen.
Wir haben ein kleines Problem: COUPLING!
Es scheint so, als ob alles richtig funktioniert, und das tut es auch, aber alles kann verbessert werden, und hier in der Connexus-Bibliothek ist es nicht anders. Aber was ist denn nun das Problem? Wenn wir die Funktion WebRequest direkt in der CHttpClient-Klasse verwenden, haben wir einen stark gekoppelten Code, aber was ist ein stark gekoppelter Code?
Stellen Sie sich Folgendes vor: Sie bauen ein Kartenhaus. Jede Karte muss perfekt ausbalanciert sein, damit alles aufrecht bleibt. Stellen Sie sich vor, dass Sie statt einer leichten und flexiblen Burg, in der Sie eine Karte verschieben können, ohne alles umzustoßen, etwas haben, das wie ein Betonklotz aussieht. Wenn Sie eine Karte nehmen oder bewegen - passiert was? Das ganze Burg stürzt ein. Genau das passiert bei gekoppeltem Code.
Wenn wir in der Programmierung von Kopplung sprechen, ist das so, als ob die Teile Ihres Codes so miteinander verbunden wären, sodass, wenn ein Teil bewegt wird, auch mehrere andere bewegt werden müssen. Es ist wie mit dem festen Knoten, den man aus Versehen in den Schnürsenkel gemacht hat und der sich nur schwer wieder lösen lässt, ohne alles zu vermasseln. Das Ändern einer Funktion, das Modifizieren einer Klasse oder das Hinzufügen von etwas Neuem bereitet Kopfzerbrechen, weil alles miteinander verklebt ist.
Wenn zum Beispiel eine Funktion stark von einer anderen Funktion abhängt und diese Funktionen nicht getrennt voneinander „leben“ können, dann ist der Code stark gekoppelt. Und das ist schlecht, denn mit der Zeit wird es schwierig, sie zu pflegen, neue Funktionen hinzuzufügen oder sogar Fehler zu beheben, ohne Gefahr zu laufen, etwas kaputt zu machen. Dies ist genau das, was innerhalb der CHttpClient-Klasse passiert, wo Send() direkt von WebRequest abhängt; es ist derzeit nicht möglich, das eine vom anderen zu trennen. In einer idealen Welt wollen wir „entkoppelten“ Code, bei dem jedes Teil für sich arbeiten kann, wie Legosteine: Man kann sie ohne größere Probleme ein- und ausbauen.
Um weniger gekoppelten Code zu erstellen, verwenden wir Schnittstellen und Dependency Injection (Abhängigkeitsinjektion). Die Idee ist, eine Schnittstelle zu schaffen, die die notwendigen Operationen definiert, sodass die Klasse von dieser Abstraktion anstelle einer spezifischen Implementierung abhängt. So kann CHttpClient mit jedem Objekt kommunizieren, das die Schnittstelle implementiert, sei es WebRequest oder eine Mock-Funktion (FakeWebRequest). Schauen wir uns das Konzept der Schnittstellen und der Dependency Injection an:
- Die Rolle der Schnittstellen: Schnittstellen ermöglichen es uns, einen Vertrag zu definieren, den andere Klassen implementieren können. In unserem Fall können wir eine Schnittstelle namens IHttpTransport erstellen, die die Methoden definiert, die CHttpClient benötigt, um eine HTTP-Anfrage durchzuführen. Auf diese Weise hängt CHttpClient von der IHttpTransport-Schnittstelle und nicht von der WebRequest-Funktion ab, wodurch die Kopplung verringert wird.
- Dependency Injection: Um CHttpClient mit der konkreten Implementierung von IHttpTransport zu verbinden, verwenden wir Dependency Injection. Diese Technik besteht darin, die Abhängigkeit (WebRequest oder FakeWebRequest) als Parameter an CHttpClient zu übergeben, anstatt sie direkt innerhalb der Klasse zu instanziieren.
Was sind die Vorteile, wenn man den Code nicht gekoppelt lässt?
- Wartbarkeit: Wenn man mit entkoppeltem Code etwas ändern muss, ist das so, als würde man ein Teil eines Autos austauschen, ohne den gesamten Motor zu zerlegen. Die Teile Ihres Codes sind unabhängig voneinander, sodass die Änderung eines Teils keine Auswirkungen auf die anderen hat. Möchten Sie einen Fehler beheben oder eine Funktion verbessern? Nehmen Sie die Änderung vor, ohne Angst haben zu müssen, dass der Rest des Systems zusammenbricht.
- Wiederverwendbarkeit: Stellen Sie sich vor, Sie hätten Legosteine. Mit entkoppeltem Code können Sie Funktionsblöcke nehmen und sie in anderen Projekten oder Teilen des Systems verwenden, ohne an komplizierte Abhängigkeiten gebunden zu sein.
- Prüfbarkeit: Durch die Verwendung einer Schnittstelle wird es möglich, WebRequest durch eine Mock-Funktion zu ersetzen, die das erwartete Verhalten imitiert, sodass Sie Tests durchführen können, ohne von echten HTTP-Anfragen abhängig zu sein. Dieses Konzept der Simulation bringt uns zum nächsten Thema: Mocks.
Was sind Mocks?
Mocks sind simulierte Versionen von realen Objekten oder Funktionen, die dazu dienen, das Verhalten einer Klasse zu testen, ohne auf externe Implementierungen angewiesen zu sein. Wenn wir CHttpClient von WebRequest mithilfe einer Schnittstelle trennen, haben wir die Möglichkeit, eine simulierte Version zu übergeben, die denselben Vertrag erfüllt, ein so genanntes „Mock“. Auf diese Weise können wir CHttpClient in einer kontrollierten Umgebung testen und vorhersagen, wie es in verschiedenen Antwortszenarien reagieren würde, ohne tatsächlich HTTP-Aufrufe zu tätigen.
In einem System, in dem CHttpClient direkte Aufrufe an WebRequest macht, sind die Tests begrenzt und hängen von der Konnektivität und dem Verhalten des Servers ab. Durch Einfügen einer Attrappe der WebRequest-Funktion, die simulierte Ergebnisse zurückgibt, können wir jedoch verschiedene Szenarien testen und die Antwort des CHttpClient isoliert validieren. Diese Mocks sind sehr nützlich, um sicherzustellen, dass sich die Bibliothek wie erwartet verhält, auch in Fehlersituationen oder bei unerwarteten Antworten.
Als Nächstes werden wir sehen, wie man Schnittstellen und Mocks implementiert, um die Flexibilität und Testbarkeit unserer CHttpRequest-Klasse zu verbessern.
Hands on Code
Wie sollen wir das in der Praxis im Code umsetzen? Wir werden einige fortgeschrittenere Sprachfunktionen wie Klassen und Schnittstellen verwenden. Wenn Sie bis hierher gekommen sind und die vorherigen Artikel gelesen haben, sind Sie bereits einigermaßen mit Klassen vertraut, aber lassen Sie uns das Konzept noch einmal durchgehen.
- Klasse: Als „Vorlage“ für die Erstellung eines Objekts können Sie innerhalb einer Klasse Methoden und Attribute definieren. Methoden sind die Aktionen oder Verhaltensweisen, während Attribute die Merkmale oder Daten des Objekts sind. Nehmen wir ein Beispiel aus dem Kontext von MQL5. Sie haben eine Klasse namens CPosition. In dieser Klasse können Sie Attribute wie Preis, Typ (Kauf oder Verkauf), Take-Profit, Stop-Loss, Volumen usw. definieren. Die Klasse wird zur Erstellung von Objekten (Instanzen) verwendet, und dieses Objekt hat seine eigenen einzigartigen Eigenschaften.
- Schnittstelle: Eine Schnittstelle ist wie ein „Vertrag“, der nur die Methoden definiert, die eine Klasse implementieren muss, aber nicht sagt, wie diese Methoden implementiert werden sollen. Eine Schnittstelle enthält nicht die Implementierung der Methoden, sondern nur deren Signaturen (Namen, Parameter und Rückgabetypen).
Schauen wir uns nun ein praktisches Beispiel im Zusammenhang mit der Bibliothek an. Lassen Sie uns Schritt für Schritt vorgehen. Wir wollen auf die Funktion WebRequest zugreifen, aber ich möchte nicht direkt auf sie zugreifen, weil wir gesehen haben, dass dies den Code zu sehr koppelt. Ich möchte es Ihnen ermöglichen, Ihre eigene WebRequest-Funktion zu haben und die Bibliothek kann sie ohne Komplikationen verwenden. Schaffen wir Schichten zwischen der Funktion und denjenigen, die auf diese Funktion zugreifen. Diese Ebene wird die Schnittstelle sein.
Ich werde das anhand einiger Diagramme erklären, bevor ich direkt zum Code übergehe.
Wie im Diagramm dargestellt, greift die Klasse HttpClient direkt auf die Funktion WebRequest zu. Fügen wir eine Schicht zwischen der Klasse und der Funktion hinzu, so wird diese Schicht die Schnittstelle IHttpTransport sein.
Mit dieser Schnittstelle können wir die Klasse CHttpTransport erstellen, um diese Schnittstelle mit der Funktion WebRequest zu implementieren. Wie in der nachstehenden Abbildung dargestellt:
Wir können mehrere verschiedene Implementierungen für die Schnittstelle erstellen und sie an die CHttpClient-Klasse übergeben. Die Bibliothek verwendet CHttpTransport, dessen Implementierung WebRequest ist, aber wir können so viele andere hinzufügen, wie wir wollen, wie im Diagramm gezeigt:
|--- Connexus |--- |--- Core |--- |--- |--- HttpTransport.mqh |--- |--- Interface |--- |--- |--- IHttpTransport.mqh
Zunächst erstellen wir den Schnittstellencode, der die Eingabe und Ausgabe der Funktion definiert:
//+------------------------------------------------------------------+ //| transport interface | //+------------------------------------------------------------------+ interface IHttpTransport { int Request(const string method,const string url,const string cookie,const string referer,int timeout,const char &data[],int data_size,char &result[],string &result_headers); int Request(const string method,const string url,const string headers,int timeout,const char &data[],char &result[],string &result_headers); }; //+------------------------------------------------------------------+Beachten Sie, dass ich nur die Funktion deklariere, aber nicht den Körper der Funktion definiere. Ich teile dem Compiler mit, dass die Funktion eine ganze Zahl zurückgeben soll, der Name der Methode wird Request sein, und ich sage ihm, welche Parameter erwartet werden. Um den Hauptteil zu definieren, muss die Klasse verwendet werden, und wir teilen dem Compiler mit, dass die Klasse die Schnittstelle implementieren muss, in der Praxis sieht das so aus:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "../Interface/IHttpTransport.mqh" //+------------------------------------------------------------------+ //| class : CHttpTransport | //| | //| [PROPERTY] | //| Name : CHttpTransport | //| Heritage : IHttpTransport | //| Description : class that implements the transport interface, | //| works as an extra layer between the request and | //| the final function and WebRequest. | //| | //+------------------------------------------------------------------+ class CHttpTransport : public IHttpTransport { public: CHttpTransport(void); ~CHttpTransport(void); int Request(const string method,const string url,const string cookie,const string referer,int timeout,const char &data[],int data_size,char &result[],string &result_headers); int Request(const string method,const string url,const string headers,int timeout,const char &data[],char &result[],string &result_headers); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CHttpTransport::CHttpTransport(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CHttpTransport::~CHttpTransport(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CHttpTransport::Request(const string method,const string url,const string cookie,const string referer,int timeout,const char &data[],int data_size,char &result[],string &result_headers) { return(WebRequest(method,url,cookie,referer,timeout,data,data_size,result,result_headers)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CHttpTransport::Request(const string method,const string url,const string headers,int timeout,const char &data[],char &result[],string &result_headers) { return(WebRequest(method,url,headers,timeout,data,result,result_headers)); } //+------------------------------------------------------------------+
Durch die Verwendung von Schnittstellen und Mocks haben wir die Klasse CHttpRequest von der Funktion WebRequest entkoppelt und damit eine flexiblere und besser wartbare Bibliothek geschaffen. Dieser Ansatz gibt Entwicklern, die Connexus verwenden, die Flexibilität, das Verhalten der Bibliothek in verschiedenen Szenarien zu testen, ohne echte Anfragen zu stellen, was eine reibungslosere Integration und eine schnelle Anpassung an neue Anforderungen und Funktionen ermöglicht.
Jetzt müssen wir dies nur noch in der Klasse CHttpClient verwenden, also importieren wir die Schnittstelle IHttpTransport und erstellen eine Instanz:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "../Interface/IHttpTransport.mqh" //+------------------------------------------------------------------+ //| class : CHttpClient | //| | //| [PROPERTY] | //| Name : CHttpClient | //| Heritage : No heritage | //| Description : Class responsible for linking the request and | //| response object with the transport layer. | //| | //+------------------------------------------------------------------+ class CHttpClient { private: IHttpTransport *m_transport; // Instance to store the transport implementation public: CHttpClient(void); ~CHttpClient(void); //--- Basis function bool Send(CHttpRequest &request, CHttpResponse &response); }; //+------------------------------------------------------------------+
Um die Injektion von Abhängigkeiten durchzuführen, fügen wir der Klasse einen neuen Konstruktor hinzu, der die zu verwendende Transportschicht empfängt und in m_transport speichert. Wir werden auch den Standardkonstruktor so ändern, dass er, wenn keine Transportschicht definiert ist, diese automatisch mit CHttpTransport implementiert:
//+------------------------------------------------------------------+ //| class : CHttpClient | //| | //| [PROPERTY] | //| Name : CHttpClient | //| Heritage : No heritage | //| Description : Class responsible for linking the request and | //| response object with the transport layer. | //| | //+------------------------------------------------------------------+ class CHttpClient { private: IHttpTransport *m_transport; // Instance to store the transport implementation public: CHttpClient(IHttpTransport *transport); CHttpClient(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CHttpClient::CHttpClient(IHttpTransport *transport) { m_transport = transport; } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CHttpClient::CHttpClient(void) { m_transport = new CHttpTransport(); } //+------------------------------------------------------------------+
Da wir nun die Transportschicht haben, können wir sie in der Funktion „Send“ verwenden
//+------------------------------------------------------------------+ //| Basis function | //+------------------------------------------------------------------+ bool CHttpClient::Send(CHttpRequest &request, CHttpResponse &response) { //--- Request uchar body_request[]; request.Body().GetAsBinary(body_request); //--- Response uchar body_response[]; string headers_response; //--- Send ulong start = GetMicrosecondCount(); int status_code = m_transport.Request(request.Method().GetMethodDescription(),request.Url().FullUrl(),request.Header().Serialize(),request.Timeout(),body_request,body_response,headers_response); ulong end = GetMicrosecondCount(); //--- Add data in Response response.Clear(); response.Duration((end-start)/1000); response.StatusCode() = status_code; response.Body().AddBinary(body_response); response.Header().Parse(headers_response); //--- Return is success return(response.StatusCode().IsSuccess()); } //+------------------------------------------------------------------+
Es sei daran erinnert, dass sich die Art und Weise, wie die Klasse verwendet wird, nicht ändert, d. h. die gleichen Codes, die wir in diesem Artikel im Abschnitt „Tests“ verwendet haben, sind weiterhin gültig. Der Unterschied ist, dass wir jetzt die endgültige Funktion der Bibliothek ändern können.
Schlussfolgerung
Damit ist eine weitere Phase der Bibliothek abgeschlossen, nämlich die Erstellung der Client-Schicht. Durch die Verwendung von Schnittstellen und Mocks haben wir die CHttpClient-Klasse von der WebRequest-Funktion entkoppelt und damit eine flexiblere und einfach zu wartende Bibliothek geschaffen. Dieser Ansatz gibt Entwicklern, die Connexus verwenden, die Flexibilität, das Verhalten der Bibliothek in verschiedenen Szenarien zu testen, ohne echte Anfragen zu stellen, was eine reibungslosere Integration und eine schnelle Anpassung an neue Anforderungen und Funktionen ermöglicht.
Die Praxis der Entkopplung mit Hilfe von Schnittstellen und Mocks verbessert die Codequalität erheblich und trägt zur Erweiterbarkeit, Testbarkeit und langfristigen Wartbarkeit bei. Dieser Ansatz ist besonders vorteilhaft für Bibliotheken, bei denen die Entwickler oft flexible Tests und modulare Ersetzungen benötigen, was einen Mehrwert für alle Nutzer der Connexus-Bibliothek darstellt.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/16324





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.