
HTTP und Connexus (Teil 2): Verstehen der HTTP-Architektur und des Bibliotheksdesigns
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. In diesem Artikel werden wir etwas mehr über das HTTP-Protokoll erfahren, wie eine URL funktioniert und welche Elemente verwendet werden, um eine URL zu erstellen, und wir werden zwei erste Klassen erstellen, nämlich
- CQueryParam: Klasse zur Verwaltung von Abfrageparametern in der URL
- CURL: Klasse, die alle Elemente einer URL enthält, einschließlich einer Instanz von CQueryParam
Was ist HTTP?
HTTP ist ein Kommunikationsprotokoll, das für die Datenübertragung im Internet verwendet wird. Es basiert auf dem TCP/IP-Protokoll, das die Verbindung zwischen dem Client und dem Server herstellt. HTTP ist ein zustandsloses Protokoll, was bedeutet, dass jede Anfrage unabhängig ist, ohne Kenntnis der vorherigen Anfragen. Eine HTTP-Anfrage und -Antwort besteht aus drei Hauptbestandteilen:
1. Request Zeile
Die Anforderungszeile (request) enthält drei Elemente:
- HTTP-Methode: Legt die auszuführende Aktion fest (GET, POST, PUT, DELETE, usw.).
- URL: Gibt die angeforderte Ressource an.
- HTTP-Version: Gibt die verwendete Protokollversion an (HTTP/1.1, HTTP/2 usw.).
Beispiel für Anfrage (REQUEST) und Antwort (RESPONSE):
REQUEST
GET /index.html HTTP/1.1
RESPONSE
HTTP/1.1 200 OK
2. Request Headers
Kopfzeilen (headers) liefern zusätzliche Informationen über die Anfrage, wie z. B. den Inhaltstyp, den Nutzeragenten (Browser) und die Authentifizierung. Beispiel:
Host: www.exemplo.com User-Agent: Mozilla/5.0 Accept-Language: en-US,en;q=0.5
3. Request Body
Nicht alle Anfragen haben einen Hauptteil, den „body“, aber bei Methoden wie POST und PUT, bei denen Daten (z. B. Formulare oder Dateien) an den Server gesendet werden, ist er üblich.
Allgemeine HTTP-Methoden
HTTP-Methoden sind wichtig, um die Art der Aktion zu bestimmen, die vom Client beim Server angefordert wird. Jede Methode hat einen bestimmten Zweck, z. B. das Abrufen von Daten, das Senden von Informationen oder das Ändern einer Ressource. Lassen Sie uns einen tieferen Einblick in die gängigsten HTTP-Methoden, ihre Verwendungszwecke und bewährte Verfahren nehmen.
- GET: Die GET-Methode ist die am weitesten verbreitete Methode des HTTP-Protokolls. Sie wird verwendet, um Daten von einem Server abzurufen oder zu suchen, ohne dessen Zustand zu verändern. Das Hauptmerkmal von GET ist, dass es idempotent ist, d. h. mehrere GET-Anfragen für dieselbe Ressource ändern den Zustand der Anwendung nicht.
- Eigenschaften
- Keine Seiteneffekte: Nur Daten lesen. Auf dem Server sollte sich nichts ändern.
- Leerer Hauptteil: Im Allgemeinen enthält eine GET-Anfrage keinen Hauptteil (body).
- Cachefähig: GET-Antworten können vom Browser zwischengespeichert werden, um die Leistung zu verbessern.
- Wann ist sie zu verwenden?
- Zum Holen statischer Daten wie HTML-Seiten, Bilder, Dateien oder Informationen in einem Format wie JSON.
- Zum Abrufen von Informationen aus einer Datenbank, ohne die Daten zu verändern.
- POST: Die POST-Methode wird verwendet, um Daten an den Server zu senden, normalerweise um neue Ressourcen zu erstellen. Im Gegensatz zu GET wird hier der Zustand des Servers geändert. POST ist nicht idempotent, d. h., wenn Sie dieselbe Anfrage mehrmals senden, können Sie mehrere Ressourcen erstellen.
- Eigenschaften
- Ändert den Zustand des Servers: Wird in der Regel zur Erstellung neuer Ressourcen verwendet.
- Antrag auf Einrichtung vorhanden: Enthält die Daten, die an den Server gesendet werden sollen.
- Nicht cachefähig: Im Allgemeinen sollten POST-Anfragen nicht zwischengespeichert werden.
- Wann ist sie zu verwenden?
- Zum Übersenden von Formularen mit Daten.
- Zum Erstellen neuer Ressourcen, z. B. indem ein neues Element zu einer Datenbank hinzugefügt wird.
- PUT: Die PUT-Methode wird verwendet, um eine Ressource auf dem Server zu aktualisieren. Wenn die Ressource noch nicht vorhanden ist, kann sie mit PUT erstellt werden. Das Hauptmerkmal von PUT ist, dass es idempotent ist: mehrere PUT-Anfragen mit demselben Anfragerumpf führen immer zum selben Ergebnis.
- Eigenschaften
- Idempotent: Wiederholte Anfragen mit demselben Text haben die gleiche Wirkung.
- Vollständige Ressource gesendet: Der Hauptteil der Anfrage (body) enthält in der Regel eine vollständige Darstellung der zu aktualisierenden Ressource.
- Kann erstellen oder aktualisieren: Wenn die Ressource nicht existiert, kann PUT sie erstellen.
- Wann ist sie zu verwenden?
- Um eine bestehende Ressource vollständig zu aktualisieren oder zu ersetzen.
- Zum Erstellen einer Ressource, wenn sie nicht existiert, über eine bestimmte URL.
- DELETE: Die DELETE-Methode wird verwendet, um eine bestimmte Ressource vom Server zu entfernen. Wie PUT ist auch DELETE idempotent, d. h., wenn Sie mehrere DELETE-Anfragen für dieselbe Ressource stellen, ist das Ergebnis dasselbe: Die Ressource wird entfernt (oder bleibt verschwunden, wenn sie bereits gelöscht wurde).
- Eigenschaften
- Idempotent: Wenn eine Ressource gelöscht werden soll, die bereits gelöscht wurde, gibt der Server einen Erfolg zurück.
- Keinen Hauptteil (body): Normalerweise hat die Anfrage DELETE keinen Body.
- Wann ist sie zu verwenden?
- Um bestimmte Server-Ressourcen zu entfernen, z. B. das Löschen von Daten aus einer Datenbank.
- PATCH: Die PATCH-Methode wird für partielle Aktualisierungen einer Ressource verwendet. Im Gegensatz zu PUT, wo Sie die vollständige Darstellung der Ressource senden müssen, können Sie mit PATCH nur die Felder ändern, die aktualisiert werden müssen.
- HEAD: Ähnlich wie GET, jedoch ohne den Hauptteil (body) der Antwort. Dient zur Überprüfung von Informationen über eine Ressource.
- OPTIONS: Dient zur Beschreibung der Kommunikationsoptionen mit dem Server, einschließlich der unterstützten Methoden.
- TRACE: Wird für Diagnosen verwendet und gibt zurück, was vom Client an den Server gesendet wurde.
HTTP Statuscodes
HTTP-Statuscodes sind Antworten, die der Server an den Client sendet, um ihn über das Ergebnis einer Anfrage zu informieren. Diese Codes sind numerisch und zeigen an, ob die Anfrage erfolgreich war oder fehlgeschlagen ist, sowie Fehler oder Weiterleitungen. Sie sind in fünf Hauptkategorien unterteilt, die jeweils einen bestimmten Zahlenbereich aufweisen und eine klare und detaillierte Rückmeldung über den Verlauf der Bearbeitung der Anfrage geben.
Im Folgenden werden die einzelnen Kategorien und einige der am häufigsten verwendeten Codes näher erläutert.
1xx: Informative Antworten: Statuscodes der Reihe 1xx zeigen an, dass der Server die Anfrage erhalten hat und dass der Client auf weitere Informationen warten sollte. Diese Antworten werden in der täglichen Praxis selten verwendet, können aber in bestimmten Szenarien wichtig sein.
2xx: Erfolg: Statuscodes der Reihe **2xx** zeigen an, dass die Anfrage **erfolgreich** war. Dies sind die Codes, die wir in den meisten Fällen sehen wollen, da sie anzeigen, dass die Interaktion zwischen Client und Server wie erwartet verlaufen ist.
3xx: Umgeleitet: Codes der Reihe 3xx zeigen an, dass der Client eine zusätzliche Aktion durchführen muss, um die Anfrage abzuschließen, in der Regel eine Umleitung zu einer anderen URL.
4xx: Client-Fehler: Codes der Reihe 4xx zeigen an, dass bei der Anfrage des Clients ein Fehler aufgetreten ist. Diese Fehler können durch ein falsches Datenformat, fehlende Authentifizierung oder Zugriffsversuche auf nicht existierende Ressourcen verursacht werden.
5xx: Server-Fehler: Codes der Reihe 5xx zeigen an, dass bei der Bearbeitung der Anfrage ein interner Fehler auf dem Server aufgetreten ist. Bei diesen Fehlern handelt es sich in der Regel um Backend-Probleme, wie z. B. interne Dienstausfälle, Konfigurationsfehler oder Systemüberlastung.
Erstellen einer URL
Eine URL (Uniform Resource Locator) ist die Art und Weise, wie wir Ressourcen im Web identifizieren und darauf zugreifen. Sie besteht aus mehreren Elementen, die wichtige Informationen liefern, wie z. B. den Serverstandort, die angeforderte Ressource und optional zusätzliche Parameter, die zum Filtern oder Anpassen von Antworten verwendet werden können.
Im Folgenden werden die einzelnen Komponenten einer URL und die Verwendung von Abfrageparametern zur Übermittlung zusätzlicher Informationen in einer HTTP-Anfrage näher erläutert.
URL-Struktur
Eine typische URL hat dieses Format:
protocol://domain:port/path?query=params#fragment
Jedes Teil hat eine bestimmte Aufgabe:
- Protocol: Gibt an, welches Protokoll für die Kommunikation verwendet wird, z. B. http oder https . Beispiel: https://.
- Domain: Name des Servers, auf dem die Ressource gehostet wird. Dies kann ein Domänenname (z. B. example.com ) oder eine IP-Adresse (z. B. 192.168.1.1 ) sein.
- Port: Optionale Zahl, die den Server-Port angibt, der für die Kommunikation verwendet werden soll. Wird diese Option nicht angegeben, verwendet der Browser die Standardports, z. B. 80 für http und 443 für https. Beispiel: :8080 .
- Path: Gibt die Ressource oder Route auf dem Server an. Dies können Seiten, API-Endpunkte oder Dateien sein. Beispiel: /api/v1/users .
- Query Params: Wird verwendet, um zusätzliche Informationen an den Server zu übermitteln. Sie folgen auf das Fragezeichen ( ? ) und werden aus Schlüssel-Wert-Paaren gebildet. Mehrere Parameter werden durch & getrennt. Beispiel: ?name=John&age=30 .
- Fragment: Bezeichnet einen bestimmten Teil der Ressource, z. B. einen Ankerpunkt innerhalb einer HTML-Seite. Folgt auf das Rautenzeichen ( # ). Beispiel: #section2 . Normalerweise sind Fragmente weder nützlich noch werden sie verwendet, um Daten von APIs zu konsumieren. Dies liegt daran, dass Fragmente ausschließlich auf der Client-Seite verarbeitet werden, d. h. vom Browser oder von der Schnittstelle der Anwendung, die die Webseite konsumiert. Der Server empfängt das URL-Fragment nicht und kann es daher nicht in HTTP-Anforderungen verwenden, die an den Server gesendet werden, z. B. wenn eine API genutzt wird. Aus diesem Grund wird unsere Bibliothek keine Fragmente unterstützen.
Vollständiges Beispiel
Analysieren wir die folgende URL:
https://www.exemplo.com:8080/market/symbols?active=EURUSD&timeframe=h1
Hier haben wir:
- Protocol: https
- Domain: www.example.com
- Port: 8080
- Path: /market/symbols
- Query Params: active=EURUSD&timeframe=h1
Praktische Anwendung: Beginnen wir die Connexus-Bibliothek zu erstellen
Um mit dem Aufbau der Connexus-Bibliothek zu beginnen, werden wir uns auf die Klassen konzentrieren, die für die Erstellung und Bearbeitung von URLs und Abfrageparametern verantwortlich sind. Wir werden ein Modul erstellen, das bei der Erstellung von URLs und dem Hinzufügen von Abfrageparametern dynamisch und programmatisch hilft.
Struktur der Klasse
Wir beginnen mit der Erstellung einer CURL-Klasse, die für die Erstellung und Bearbeitung von URLs zuständig ist. Es ermöglicht dem Nutzer, auf einfache Weise Abfrageparameter hinzuzufügen, die Basis-URL zu erstellen und verschiedene Elemente der URL effizient zu handhaben. Um die Abfrageparameter einer URL organisiert und effizient zu verwalten, verwenden wir eine Klasse namens CJson. Das Ziel dieser Klasse ist es, die Abfrageparameter (die normalerweise als String in der URL übergeben werden) in ein strukturiertes und einfach zu verwaltendes Format zu konvertieren: JSON.
Was ist JSON?
Bevor wir uns mit den Funktionen der Klasse CJson befassen, ist es wichtig, das JSON-Format (JavaScript Object Notation) zu verstehen, falls Sie damit noch nicht vertraut sind. JSON ist ein sehr verbreitetes Datenformat, das im Web zur Darstellung strukturierter Daten verwendet wird. Sie besteht aus Schlüsselpaaren, wobei jedem Schlüssel ein Wert zugeordnet ist. Diese Paare werden durch Kommata getrennt und zwischen geschweiften Klammern „{}“ gruppiert
Beispiel für ein JSON-Objekt:
{ "name": "John", "age": 30, "city": "New York" }
Hier haben wir ein JSON-Objekt, das drei Schlüsselpaare „name“, „age“ und „city“ mit ihren jeweiligen Werten enthält. Bei Abfrageparametern einer URL funktioniert jeder Parameter auf ähnliche Weise: Es gibt einen Schlüssel (Parametername) und einen Wert (der mit diesem Schlüssel verbundene Wert).
Zweck der Klasse CJson
Die Klasse CJson wird verwendet, um die URL-Abfrageparameter in einem JSON-Format zu organisieren. Dadurch wird es einfacher, diese Parameter zu manipulieren, zu lesen und sogar zu validieren, bevor sie in die endgültige URL aufgenommen werden. Anstatt mit einer Reihe von Parametern wie ?name=John&age=30 zu arbeiten, können Sie mit einem strukturierten Objekt arbeiten, was den Code sauberer und verständlicher macht. Die Klasse CJson wird auch für das Senden und Empfangen von Daten nützlich sein, was wir in den nächsten Artikeln sehen werden.
Erstellen der ersten Klassen
Wir beginnen mit der Erstellung eines Ordners namens Connexus im Verzeichnis „Include“ im Metaeditor. Wir erstellen innerhalb des Connexus-Ordners einen weiteren Ordner mit dem Namen URL und einen weiteren mit dem Namen Data. Innerhalb des URL-Ordners erstellen wir eine Datei mit dem Namen URL und QueryParam, und ich werde dem Artikel auch die CJson-Klasse beifügen, die dem Data-Ordner hinzugefügt werden sollte. Ich werde nicht näher auf die Implementierung dieser Klasse eingehen, aber sie ist einfach zu nutzen, glauben Sie mir. Die Struktur sollte in etwa wie folgt aussehen
MQL5 |--- include |--- |--- Connexus |--- |--- |--- Data |--- |--- |--- |--- Json.mqh |--- |--- |--- URL |--- |--- |--- |--- QueryParam.mqh |--- |--- |--- |--- URL.mqh
QueryParam
Beginnen wir mit der Klasse CQueryParam. Diese Klasse ist für das Hinzufügen, Entfernen, Suchen und Serialisieren von Abfrageparametern zuständig und bietet außerdem Hilfsmethoden wie das Bereinigen von Daten und das Parsen von Abfragezeichenfolgen. Wir beginnen mit der Erstellung der Klasse mit einem privaten Objekt vom Typ CJson, um die Abfrageparameter als Schlüssel-Wert-Paare zu speichern.
//+------------------------------------------------------------------+ //| class : CQueryParam | //| | //| [PROPERTY] | //| Name : CQueryParam | //| Heritage : No heritage | //| Description : Manages query parameters for HTTP requests | //| | //+------------------------------------------------------------------+ class CQueryParam { private: CJson m_query_param; // Storage for query parameters public: CQueryParam(void); ~CQueryParam(void); //--- Functions to manage query parameters void AddParam(string key, string value); // Add a key-value pair void AddParam(string param); // Add a single key-value parameter void AddParams(const string ¶ms[]); // Add multiple parameters void RemoveParam(const string key); // Remove a parameter by key string GetParam(const string key) const; // Retrieve a parameter value by key bool HasParam(const string key); // Check if parameter exists int ParamSize(void); // Get the number of parameters //--- Auxiliary methods bool Parse(const string query_param); // Parse a query string string Serialize(void); // Serialize parameters into a query string void Clear(void); // Clear all parameters };
Lassen Sie uns nun die wichtigsten Methoden untersuchen und verstehen, wie jede von ihnen zum Funktionieren der Klasse beiträgt.
- AddParam(string key, string value): Diese Methode ist für das Hinzufügen eines neuen Parameters zur Liste der Abfrageparameter zuständig. Sie erhält den Schlüssel und den Wert als Parameter und speichert sie im Objekt m_query_param.
- AddParam(string param): Diese Methode fügt einen Parameter hinzu, der bereits als Schlüssel=Wert formatiert ist. Sie prüft, ob die Zeichenkette das Zeichen = enthält, und wenn ja, teilt sie die Zeichenkette in zwei Werte auf, einen für den Schlüssel und einen für den Wert, und speichert sie.
- AddParams(const string ¶ms[]): Diese Methode fügt mehrere Parameter auf einmal hinzu. Sie übernimmt ein Array von Strings im Format Schlüssel=Wert und ruft die Methode AddParam für jedes Element im Array auf.
- RemoveParam(const string key): Mit dieser Methode wird ein Parameter aus der Liste der Abfrageparameter entfernt. Sie findet den Schlüssel und entfernt ihn aus dem Objekt m_query_param. - GetParam(const string key): Diese Methode gibt den Wert eines bestimmten Parameters zurück, wobei der Schlüssel als Eingabe verwendet wird.
- HasParam(const string key): Diese Methode prüft, ob ein bestimmter Parameter bereits hinzugefügt wurde.
- ParamSize(void): Diese Methode gibt die Anzahl der gespeicherten Abfrageparameter zurück.
- Parse(const string query_param): Die Methode Parse() empfängt eine Zeichenkette mit Abfrageparametern, wandelt sie in Schlüssel-Wert-Paare um und speichert sie im Objekt m_query_param. Es teilt die Zeichenfolge durch die Zeichen & (die die Parameter trennen) und = (das Schlüssel und Wert trennt).
- Serialize(void): Die Methode Serialize() erzeugt eine formatierte Zeichenkette mit allen gespeicherten Abfrageparametern. Es verkettet die Parameter im Format Schlüssel=Wert und trennt jedes Paar mit &.
- Clear(void): Die Methode Clear() löscht alle gespeicherten Parameter und setzt das Objekt zurück.
Nachfolgend finden Sie den Code mit den implementierten Funktionen. Denken Sie daran, den CJSON-Import hinzuzufügen:
//+------------------------------------------------------------------+ //| Include the file CJson class | //+------------------------------------------------------------------+ #include "../Data/Json.mqh" //+------------------------------------------------------------------+ //| class : CQueryParam | //| | //| [PROPERTY] | //| Name : CQueryParam | //| Heritage : No heritage | //| Description : Manages query parameters for HTTP requests | //| | //+------------------------------------------------------------------+ class CQueryParam { private: CJson m_query_param; // Storage for query parameters public: CQueryParam(void); ~CQueryParam(void); //--- Functions to manage query parameters void AddParam(string key, string value); // Add a key-value pair void AddParam(string param); // Add a single key-value parameter void AddParams(const string ¶ms[]); // Add multiple parameters void RemoveParam(const string key); // Remove a parameter by key string GetParam(const string key) const; // Retrieve a parameter value by key bool HasParam(const string key); // Check if parameter exists int ParamSize(void); // Get the number of parameters //--- Auxiliary methods bool Parse(const string query_param); // Parse a query string string Serialize(void); // Serialize parameters into a query string void Clear(void); // Clear all parameters }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CQueryParam::CQueryParam(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CQueryParam::~CQueryParam(void) { } //+------------------------------------------------------------------+ //| Adds a key-value pair to the query parameters | //+------------------------------------------------------------------+ void CQueryParam::AddParam(string key, string value) { m_query_param[key] = value; } //+------------------------------------------------------------------+ //| Adds a single parameter from a formatted string | //+------------------------------------------------------------------+ void CQueryParam::AddParam(string param) { //--- Check if the input string contains an "=" symbol, which indicates a key-value pair if(StringFind(param,"=") >= 0) { //--- Declare an array to hold the key and value after splitting the string string key_value[]; //--- Split the input string using "=" as the delimiter and store the result in the key_value array int size = StringSplit(param,StringGetCharacter("=",0),key_value); //--- If the size of the split result is exactly 2 (meaning a valid key-value pair was found) if(size == 2) { // Add the key-value pair to the m_query_param map // key_value[0] is the key, key_value[1] is the value m_query_param[key_value[0]] = key_value[1]; } } } //+------------------------------------------------------------------+ //| Adds multiple parameters from an array of formatted strings | //+------------------------------------------------------------------+ void CQueryParam::AddParams(const string ¶ms[]) { //--- Get the size of the input array 'params' int size = ArraySize(params); //--- Loop through each element in the 'params' array. for(int i=0;i<size;i++) { //--- Call the AddParam function to add each parameter to the m_query_param map. this.AddParam(params[i]); } } //+------------------------------------------------------------------+ //| Removes a parameter by key | //+------------------------------------------------------------------+ void CQueryParam::RemoveParam(const string key) { m_query_param.Remove(key); } //+------------------------------------------------------------------+ //| Retrieves a parameter value by key | //+------------------------------------------------------------------+ string CQueryParam::GetParam(const string key) const { return(m_query_param[key].ToString()); } //+------------------------------------------------------------------+ //| Checks if a parameter exists by key | //+------------------------------------------------------------------+ bool CQueryParam::HasParam(const string key) { return(m_query_param.FindKey(key) != NULL); } //+------------------------------------------------------------------+ //| Returns the number of parameters stored | //+------------------------------------------------------------------+ int CQueryParam::ParamSize(void) { return(m_query_param.Size()); } //+------------------------------------------------------------------+ //| Parses a query string into parameters | //| Input: query_param - A string formatted as a query parameter | //| Output: bool - Always returns true, indicating successful parsing| //+------------------------------------------------------------------+ bool CQueryParam::Parse(const string query_param) { //--- Split the input string by '&', separating the individual parameters string params[]; int size = StringSplit(query_param, StringGetCharacter("&",0), params); //--- Iterate through each parameter string for(int i=0; i<size; i++) { //--- Split each parameter string by '=', separating the key and value string key_value[]; StringSplit(params[i], StringGetCharacter("=",0), key_value); //--- Check if the split resulted in exactly two parts: key and value if (ArraySize(key_value) == 2) { //--- Assign the value to the corresponding key in the map m_query_param[key_value[0]] = key_value[1]; } } //--- Return true indicating that parsing was successful return(true); } //+------------------------------------------------------------------+ //| Serializes the stored parameters into a query string | //| Output: string - A string representing the serialized parameters | //+------------------------------------------------------------------+ string CQueryParam::Serialize(void) { //--- Initialize an empty string to build the query parameter string string query_param = ""; //--- Iterate over each key-value pair in the parameter map for(int i=0; i<m_query_param.Size(); i++) { //--- Append a '?' at the beginning to indicate the start of parameters if(i == 0) { query_param = "?"; } //--- Construct each key-value pair as 'key=value' if(i == m_query_param.Size()-1) { //--- If it's the last pair, don't append '&' query_param += m_query_param[i].m_key + "=" + m_query_param[i].ToString(); } else { //--- Otherwise, append '&' after each pair query_param += m_query_param[i].m_key + "=" + m_query_param[i].ToString() + "&"; } } //--- Return the constructed query parameter string return(query_param); } //+------------------------------------------------------------------+ //| Clears all stored parameters | //+------------------------------------------------------------------+ void CQueryParam::Clear(void) { m_query_param.Clear(); } //+------------------------------------------------------------------+
URL
Nun, da wir eine Klasse haben, die für die Arbeit mit Abfrageparametern verantwortlich ist, arbeiten wir an der Klasse CURL, die den Rest erledigen wird, indem sie Protokoll, Host, Port usw. verwendet. Hier ist eine erste Implementierung der CURL-Klasse in MQL5, die eingebunden werden muss:
//+------------------------------------------------------------------+ //| URL.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #include "QueryParam.mqh" class CURL { public: CURL(void); ~CURL(void); }; CURL::CURL(void) { } CURL::~CURL(void) { } //+------------------------------------------------------------------+
Erstellen wir eine ENUM erstellen, die die gängigsten Protokolle enthält:
//+------------------------------------------------------------------+ //| Enum to represent different URL protocol | //+------------------------------------------------------------------+ enum ENUM_URL_PROTOCOL { URL_PROTOCOL_NULL = 0, // No protocol defined URL_PROTOCOL_HTTP, // HTTP protocol URL_PROTOCOL_HTTPS, // HTTPS protocol URL_PROTOCOL_WS, // WebSocket (WS) protocol URL_PROTOCOL_WSS, // Secure WebSocket (WSS) protocol URL_PROTOCOL_FTP // FTP protocol };
In das private Feld der Klasse fügen wir eine Struktur ein, die die grundlegenden Elemente einer URL bildet, und eine Instanz dieser Struktur namens m_url
private: //--- Structure to hold components of a URL struct MqlURL { ENUM_URL_PROTOCOL protocol; // URL protocol string host; // Host name or IP uint port; // Port number string path; // Path after the host CQueryParam query_param; // Query parameters as key-value pairs }; MqlURL m_url; // Instance of MqlURL to store the URL details
Wir erstellen die „Setter“ und „Getter“ und ihre Implementierungen
//+------------------------------------------------------------------+ //| class : CURL | //| | //| [PROPERTY] | //| Name : CURL | //| Heritage : No heritage | //| Description : Define a class CURL to manage and manipulate URLs | //| | //+------------------------------------------------------------------+ class CURL { public: CURL(void); ~CURL(void); //--- Methods to access and modify URL components ENUM_URL_PROTOCOL Protocol(void) const; // Get the protocol void Protocol(ENUM_URL_PROTOCOL protocol); // Set the protocol string Host(void) const; // Get the host void Host(const string host); // Set the host uint Port(void) const; // Get the port void Port(const uint port); // Set the port string Path(void) const; // Get the path void Path(const string path); // Set the path CQueryParam *QueryParam(void); // Access query parameters }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CURL::CURL(void) { this.Clear(); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CURL::~CURL(void) { } //+------------------------------------------------------------------+ //| Getter for protocol | //+------------------------------------------------------------------+ ENUM_URL_PROTOCOL CURL::Protocol(void) const { return(m_url.protocol); } //+------------------------------------------------------------------+ //| Setter for protocol | //+------------------------------------------------------------------+ void CURL::Protocol(ENUM_URL_PROTOCOL protocol) { m_url.protocol = protocol; } //+------------------------------------------------------------------+ //| Getter for host | //+------------------------------------------------------------------+ string CURL::Host(void) const { return(m_url.host); } //+------------------------------------------------------------------+ //| Setter for host | //+------------------------------------------------------------------+ void CURL::Host(const string host) { m_url.host = host; } //+------------------------------------------------------------------+ //| Getter for port | //+------------------------------------------------------------------+ uint CURL::Port(void) const { return(m_url.port); } //+------------------------------------------------------------------+ //| Setter for port | //+------------------------------------------------------------------+ void CURL::Port(const uint port) { m_url.port = port; } //+------------------------------------------------------------------+ //| Getter for path | //+------------------------------------------------------------------+ string CURL::Path(void) const { return(m_url.path); } //+------------------------------------------------------------------+ //| Setter for path | //+------------------------------------------------------------------+ void CURL::Path(const string path) { m_url.path = path; } //+------------------------------------------------------------------+ //| Accessor for query parameters (returns a pointer) | //+------------------------------------------------------------------+ CQueryParam *CURL::QueryParam(void) { return(GetPointer(m_url.query_param)); } //+------------------------------------------------------------------+
Jetzt werden wir an der Engine unserer Klasse arbeiten, indem wir neue Funktionen hinzufügen, um mit diesen Daten zu arbeiten, sie sind:
-
Clear(void) : Die Methode Clear() ist dafür verantwortlich, alle in der Klasse gespeicherten Daten zu löschen und ihre Attribute auf leere oder Standardwerte zurückzusetzen. Diese Methode ist nützlich, wenn Sie die Klasseninstanz wiederverwenden möchten, um eine neue URL zu erstellen, oder wenn Sie sicherstellen müssen, dass keine alten Daten versehentlich in eine neue Operation aufgenommen werden. Mit anderen Worten, die Klasse wird „zurückgesetzt“ und alle zuvor gespeicherten Informationen werden entfernt.
So funktioniert es:
- Setzt die Klassenattribute je nach Datentyp auf leer oder null (leerer String für Protokoll, Domäne usw.).
- Entfernt alle Abfrageparameter und setzt den Pfad auf den Standardwert zurück.
- Nach dem Aufruf von Clear() befindet sich die Klasseninstanz in einem Anfangszustand, als ob sie gerade erst erstellt worden wäre.
Beispiel:
Wenn die Klasse zuvor gespeichert wurde:
- Protocol: https
- Domain: api.example.com
- Path: /v1/users
- Query Params: id=123&active=true
Nach dem Aufruf von Clear() werden alle diese Werte auf zurückgesetzt:
- Protocol: ""
- Domain: ""
- Path: ""
- Query Params: ""
Damit ist die Klasse in der Lage, eine neue URL von Grund auf zu erstellen.
-
BaseUrl(void) : Diese Methode ist für die Erzeugung und Rückgabe des Basisteils der URL zuständig, der sich aus dem Protokoll (z. B. http , https ), der Domäne (z. B. www.example.com ) und optional dem Port (z. B. :8080 ) zusammensetzt. Die Methode gewährleistet, dass die wesentlichen Elemente für die Kommunikation mit dem Server korrekt sind. Mit dieser Methode haben Sie die Möglichkeit, dynamische URLs zusammenzustellen, wobei Sie immer vom Basisteil ausgehen. Dies kann nützlich sein, wenn Sie die Basis der URL für den Zugriff auf verschiedene Ressourcen auf demselben Server wiederverwenden möchten.
-
PathAndQuery(void) : Diese Methode ist für die Generierung des Pfadteils der Ressource und die Verkettung der Abfrageparameter zuständig, die Sie zuvor hinzugefügt haben. Der Pfad gibt in der Regel die Ressource an, auf die Sie auf dem Server zugreifen möchten, während Sie mit den Abfrageparametern zusätzliche Details wie Filter oder Paginierung angeben können. Indem Sie den Pfad und die Abfrageparameter von der Basis-URL trennen, können Sie die verschiedenen Teile der URL übersichtlicher zusammenstellen. Diese Methode gibt eine Zeichenkette zurück, die direkt in einer HTTP-Anfrage oder in anderen Methoden verwendet werden kann, die diese Struktur benötigen.
-
FullUrl(void) : Dies ist die Methode, die alle Teile der URL „kompiliert“ und die vollständige, gebrauchsfertige URL zurückgibt. Sie kombiniert BaseURL() und PathAndQuery(), um die endgültige URL zu bilden, die Sie direkt in einer HTTP-Anfrage verwenden können. Wenn Sie die vollständige URL benötigen, um eine HTTP-Anfrage zu senden, ist diese Methode der einfachste Weg, um sicherzustellen, dass die URL richtig formatiert ist. Dadurch werden Fehler vermieden, wie z. B. das Vergessen, die Basis- und Abfrageparameter zu verketten.
Beispiel: Wenn die Klasse die folgenden Werte gespeichert hat:
- Protocol: https
- Domain: api.example.com
- Path: /v1/users
- Query Params: id=123&active=true
Wenn Sie Serialize() aufrufen, gibt die Funktion Folgendes zurück:
https://api.exemplo.com/v1/users?id=123&active=true
-
Parse(const string url) : Macht das Gegenteil von FullUrl(void) . Es nimmt eine vollständige URL als Argument und trennt ihre Komponenten in einer organisierten Weise. Ziel ist es, eine URL in kleinere Teile zu zerlegen (Protokoll, Domäne, Port, Pfad, Abfrageparameter usw.), sodass der Programmierer mit diesen Elementen einzeln arbeiten kann. Dies ist besonders nützlich, wenn Sie eine URL erhalten und deren Details verstehen oder programmatisch ändern müssen.
So funktioniert es:
- Empfängt eine Zeichenkette mit einer vollständigen URL.
- Analysiert (oder „parst“) die Zeichenfolge und identifiziert jeden Teil der URL: das Protokoll ( http , https ), die Domäne, den Port (falls vorhanden), den Pfad und alle Abfrageparameter.
- Weist diese Werte den internen Attributen der Klasse zu, wie z. B. protocol, host, path, queryParams und behandelt die Trennzeichen wie :// , / , ? und & korrekt, um die URL in ihre Teile zu zerlegen.
Beispiel: eine gegebene URL:
https://api.example.com:8080/v1/users?id=123&active=true
Beim Aufruf von Parse() weist die Funktion die folgenden Werte zu:
- Protocol: https
- Domain: api.example.com
- Port: 8080
- Path: /v1/users
- Query Params: id=123 , active=true
So können Sie auf jeden Teil der URL programmtechnisch zugreifen, was die Manipulation oder das Parsen der URL erleichtert.
-
UrlProtocolToStr(ENUM_URL_PROTOCOL protocol) : Gibt das Protokoll in einer Zeichenkette zurück, nützlich z.B. für die Umwandlung von ENUM_URL_PROTOCOL in eine einfache Zeichenkette:
- URL_PROTOCOL_HTTP → „http“
- URL_PROTOCOL_HTTPS → „httpS“
- URL_PROTOCOL_WSS → „wss“
- usw...
Jede dieser Methoden spielt eine wesentliche Rolle bei der Konstruktion und Manipulation von URLs. Mit diesen Funktionen wird die Connexus-Bibliothek äußerst flexibel, um den dynamischen Anforderungen von APIs gerecht zu werden, unabhängig davon, ob URLs von Grund auf neu erstellt oder bestehende URLs geparst werden. Durch die Implementierung dieser Methoden können Entwickler URLs programmatisch zusammenstellen, wodurch Fehler vermieden und die Kommunikation mit Servern optimiert werden. Nachstehend finden Sie den Code mit den implementierten Funktionen:
//+------------------------------------------------------------------+ //| Define constants for different URL protocols | //+------------------------------------------------------------------+ #define HTTP "http" #define HTTPS "https" #define WS "ws" #define WSS "wss" #define FTP "ftp" //+------------------------------------------------------------------+ //| class : CURL | //| | //| [PROPERTY] | //| Name : CURL | //| Heritage : No heritage | //| Description : Define a class CURL to manage and manipulate URLs | //| | //+------------------------------------------------------------------+ class CURL { private: string UrlProtocolToStr(ENUM_URL_PROTOCOL protocol); // Helper method to convert protocol enum to string public: //--- Methods to parse and serialize the URL void Clear(void); // Clear/reset the URL string BaseUrl(void); // Return the base URL (protocol, host, port) string PathAndQuery(void); // Return the path and query part of the URL string FullUrl(void); // Return the complete URL bool Parse(const string url); // Parse a URL string into components }; //+------------------------------------------------------------------+ //| Convert URL protocol enum to string | //+------------------------------------------------------------------+ string CURL::UrlProtocolToStr(ENUM_URL_PROTOCOL protocol) { if(protocol == URL_PROTOCOL_HTTP) { return(HTTP); } if(protocol == URL_PROTOCOL_HTTPS) { return(HTTPS); } if(protocol == URL_PROTOCOL_WS) { return(WS); } if(protocol == URL_PROTOCOL_WSS) { return(WSS); } if(protocol == URL_PROTOCOL_FTP) { return(FTP); } return(NULL); } //+------------------------------------------------------------------+ //| Clear or reset the URL structure | //+------------------------------------------------------------------+ void CURL::Clear(void) { m_url.protocol = URL_PROTOCOL_NULL; m_url.host = ""; m_url.port = 0; m_url.path = ""; m_url.query_param.Clear(); } //+------------------------------------------------------------------+ //| Construct the base URL from protocol, host, and port | //+------------------------------------------------------------------+ string CURL::BaseUrl(void) { //--- Checks if host is not null or empty if(m_url.host != "" && m_url.host != NULL) { MqlURL url = m_url; //--- Set default protocol if not defined if(url.protocol == URL_PROTOCOL_NULL) { url.protocol = URL_PROTOCOL_HTTPS; } //--- Set default port based on the protocol if(url.port == 0) { url.port = (url.protocol == URL_PROTOCOL_HTTPS) ? 443 : 80; } //--- Construct base URL (protocol + host) string serialized_url = this.UrlProtocolToStr(url.protocol) + "://" + url.host; //--- Include port in URL only if it's not the default port for the protocol if(!(url.protocol == URL_PROTOCOL_HTTP && url.port == 80) && !(url.protocol == URL_PROTOCOL_HTTPS && url.port == 443)) { serialized_url += ":" + IntegerToString(m_url.port); } return(serialized_url); } else { return("Error: Invalid host"); } } //+------------------------------------------------------------------+ //| Construct path and query string from URL components | //+------------------------------------------------------------------+ string CURL::PathAndQuery(void) { MqlURL url = m_url; //--- Ensure path starts with a "/" if(url.path == "") { url.path = "/"; } else if(StringGetCharacter(url.path,0) != '/') { url.path = "/" + url.path; } //--- Remove any double slashes from the path StringReplace(url.path,"//","/"); //--- Check for invalid spaces in the path if(StringFind(url.path," ") >= 0) { return("Error: Invalid characters in path"); } //--- Return the full path and query string return(url.path + url.query_param.Serialize()); } //+------------------------------------------------------------------+ //| Return the complete URL (base URL + path + query) | //+------------------------------------------------------------------+ string CURL::FullUrl(void) { return(this.BaseUrl() + this.PathAndQuery()); } //+------------------------------------------------------------------+ //| Parse a URL string and extract its components | //+------------------------------------------------------------------+ bool CURL::Parse(const string url) { //--- Create an instance of MqlURL to hold the parsed data MqlURL urlObj; //--- Parse protocol from the URL int index_end_protocol = 0; //--- Check if the URL starts with "http://" if(StringFind(url,"http://") >= 0) { urlObj.protocol = URL_PROTOCOL_HTTP; index_end_protocol = 7; } else if(StringFind(url,"https://") >= 0) { urlObj.protocol = URL_PROTOCOL_HTTPS; index_end_protocol = 8; } else if(StringFind(url,"ws://") >= 0) { urlObj.protocol = URL_PROTOCOL_WS; index_end_protocol = 5; } else if(StringFind(url,"wss://") >= 0) { urlObj.protocol = URL_PROTOCOL_WSS; index_end_protocol = 6; } else if(StringFind(url,"ftp://") >= 0) { urlObj.protocol = URL_PROTOCOL_FTP; index_end_protocol = 6; } else { return(false); // Unsupported protocol } //--- Separate the endpoint part after the protocol string endpoint = StringSubstr(url,index_end_protocol); // Get the URL part after the protocol string parts[]; // Array to hold the split components of the URL //--- Split the endpoint by the "/" character to separate path and query components int size = StringSplit(endpoint,StringGetCharacter("/",0),parts); //--- Handle the host and port part of the URL string host_port[]; //--- If the first part (host) contains a colon (":"), split it into host and port if(StringSplit(parts[0],StringGetCharacter(":",0),host_port) > 1) { urlObj.host = host_port[0]; // Set the host urlObj.port = (uint)StringToInteger(host_port[1]); // Convert and set the port } else { urlObj.host = parts[0]; //--- Set default port based on the protocol if(urlObj.protocol == URL_PROTOCOL_HTTP) { urlObj.port = 80; } if(urlObj.protocol == URL_PROTOCOL_HTTPS) { urlObj.port = 443; } } //--- If there's no path, default to "/" if(size == 1) { urlObj.path += "/"; // Add a default root path "/" } //--- Loop through the remaining parts of the URL (after the host) for(int i=1;i<size;i++) { //--- If the path contains an empty part, return false (invalid URL) if(parts[i] == "") { return(false); } //--- If the part contains a "?" (indicating query parameters) else if(StringFind(parts[i],"?") >= 0) { string resource_query[]; //--- Split the part by "?" to separate the resource and query if(StringSplit(parts[i],StringGetCharacter("?",0),resource_query) > 0) { urlObj.path += "/"+resource_query[0]; urlObj.query_param.Parse(resource_query[1]); } } else { //--- Otherwise, add to the path as part of the URL urlObj.path += "/"+parts[i]; } } //--- Assign the parsed URL object to the member variable m_url = urlObj; return(true); } //+------------------------------------------------------------------+
Schließlich fügen wir zwei weitere neue Funktionen hinzu, die den Entwicklern helfen sollen, und zwar:
- ShowData(void): Druckt die URL-Elemente separat aus, um die Fehlersuche zu erleichtern und zu verstehen, welche Daten in der Klasse gespeichert sind. Zum Beispiel:
https://api.exemplo.com/v1/users?id=123&active=true
Die Funktion sollte dies zurückgeben:
Protocol: https Host: api.exemplo.com Port: 443 Path: /v1/users Query Param: { "id":123, "active":true }
- Compare(CURL &url): Diese Funktion empfängt eine weitere Instanz der CURL-Klasse. Sie sollte true zurückgeben, wenn die in beiden Instanzen gespeicherten URLs gleich sind, andernfalls sollte sie false zurückgeben. Es kann nützlich sein, den Vergleich von serialisierten URLs zu vermeiden, um Zeit zu sparen. Beispiel ohne die Funktion Compare()
// Example without using the Compare() method CURl url1; CURl url2; if(url1.FullUrl() == url2.FullUrl()) { Print("Equals URL"); } // Example with method Compare() CURl url1; CURl url2; if(url1.Compare(url2)) { Print("Equals URL"); }
Nachfolgend finden Sie den Code für die Implementierung jeder dieser Funktionen:
//+------------------------------------------------------------------+ //| class : CURL | //| | //| [PROPERTY] | //| Name : CURL | //| Heritage : No heritage | //| Description : Define a class CURL to manage and manipulate URLs | //| | //+------------------------------------------------------------------+ class CURL { public: //--- Auxiliary methods bool Compare(CURL &url); // Compare two URLs string ShowData(); // Show URL details as a string }; //+------------------------------------------------------------------+ //| Compare the current URL with another URL | //+------------------------------------------------------------------+ bool CURL::Compare(CURL &url) { return (m_url.protocol == url.Protocol() && m_url.host == url.Host() && m_url.port == url.Port() && m_url.path == url.Path() && m_url.query_param.Serialize() == url.QueryParam().Serialize()); } //+------------------------------------------------------------------+ //| Display the components of the URL as a formatted string | //+------------------------------------------------------------------+ string CURL::ShowData(void) { return( "Protocol: "+EnumToString(m_url.protocol)+"\n"+ "Host: "+m_url.host+"\n"+ "Port: "+IntegerToString(m_url.port)+"\n"+ "Path: "+m_url.path+"\n"+ "Query Param: "+m_url.query_param.Serialize()+"\n" ); } //+------------------------------------------------------------------+
Wir haben beide Klassen für die Arbeit mit URLs fertiggestellt, gehen wir nun zum Testen über.
Tests
Nun, da wir unsere ersten Klassen fertig haben, lassen Sie uns URLs durch die Klassen erstellen und auch den umgekehrten Prozess machen, von einer URL werden wir das Element mit der Klasse trennen. Um die Tests durchzuführen, werde ich eine Datei mit dem Namen TestUrl.mq5 im folgendem Pfad erstellen: Experts/Connexus/TestUrl.mq5.
int OnInit() { //--- Creating URL CURL url; url.Host("example.com"); url.Path("/api/v1/data"); Print("Test1 | # ",url.FullUrl() == "https://example.com/api/v1/data"); //--- Changing parts of the URL url.Host("api.example.com"); Print("Test2 | # ",url.FullUrl() == "https://api.example.com/api/v1/data"); //--- Parse URL url.Clear(); string url_str = "https://api.example.com/api/v1/data"; Print("Test3 | # ",url.Parse(url_str)); Print("Test3 | - Protocol # ",url.Protocol() == URL_PROTOCOL_HTTPS); Print("Test3 | - Host # ",url.Host() == "api.example.com"); Print("Test3 | - Port # ",url.Port() == 443); Print("Test3 | - Path # ",url.Path() == "/api/v1/data"); //--- return(INIT_SUCCEEDED); }
Wenn wir den EA ausführen, haben wir die folgenden Daten im Terminal:
Test1 | # true Test2 | # true Test3 | # true Test3 | - Protocol # true Test3 | - Host # true Test3 | - Port # true Test3 | - Path # true
Schlussfolgerung
In diesem Artikel haben wir uns eingehend mit der Funktionsweise des HTTP-Protokolls befasst, angefangen bei grundlegenden Konzepten wie den HTTP-Verben (GET, POST, PUT, DELETE) bis hin zu den Antwortstatuscodes, die uns helfen, die Antwort auf eine Anfrage zu interpretieren. Um die Verwaltung von URLs in Ihren MQL5-Anwendungen zu vereinfachen, haben wir die Klasse „CQueryParam“ entwickelt, die einen einfachen und effizienten Weg zur Manipulation von Abfrageparametern bietet. Darüber hinaus haben wir die Klasse „CURL“ implementiert, die eine dynamische Änderung von Teilen der URL ermöglicht, wodurch der Prozess der Erstellung und Bearbeitung von HTTP-Anfragen flexibler und robuster wird.
Mit diesen Ressourcen verfügen Sie bereits über eine gute Grundlage für die Integration Ihrer Anwendungen mit externen APIs, die die Kommunikation zwischen Ihrem Code und Webservern erleichtern. Wir stehen jedoch erst am Anfang. Im nächsten Artikel werden wir unsere Reise in die HTTP-Welt fortsetzen, wo wir dedizierte Klassen erstellen werden, um mit den **Headern** und **Body** von Anfragen zu arbeiten, was noch mehr Kontrolle über HTTP-Interaktionen ermöglicht.
Bleiben Sie auf dem Laufenden, denn wir bauen eine wichtige Bibliothek auf, die Ihre Fähigkeiten zur API-Integration auf die nächste Stufe heben wird!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/15897





- 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.