English Русский Deutsch 日本語
preview
HTTP y Connexus (Parte 2): Comprensión de la arquitectura HTTP y el diseño de bibliotecas

HTTP y Connexus (Parte 2): Comprensión de la arquitectura HTTP y el diseño de bibliotecas

MetaTrader 5Ejemplos | 13 mayo 2025, 07:40
78 0
joaopedrodev
joaopedrodev

Introducción

Este artículo es la continuación de una serie de artículos donde construiremos una biblioteca llamada Connexus. En el primer artículo, comprendimos el funcionamiento básico de la función WebRequest, entendiendo cada uno de sus parámetros y también creamos un código de ejemplo que demuestra el uso de esta función y sus dificultades. En este artículo continuaremos entendiendo un poco más sobre el protocolo HTTP, cómo funciona una URL y qué elementos se utilizan para construir una, y crearemos dos clases iniciales, que son:

  • CQueryParam: Clase para gestionar parámetros de consulta en URL.
  • CURL: Clase que contiene todos los elementos de una URL, incluida una instancia de CQueryParam.


¿Qué es HTTP?

HTTP es un protocolo de comunicación utilizado para transferir datos en la web. Funciona sobre el protocolo TCP/IP, que establece la conexión entre el cliente y el servidor. HTTP es un protocolo sin estado, lo que significa que cada solicitud es independiente y no conoce las solicitudes anteriores. Una solicitud y una respuesta HTTP se componen de tres partes principales:

1. Línea de solicitud

La línea de solicitud contiene tres elementos:

  • Método HTTP: Define la acción a realizar (GET, POST, PUT, DELETE, etc.).
  • URL: Especifica el recurso solicitado.
  • Versión HTTP: Indica la versión de protocolo utilizada (HTTP/1.1, HTTP/2, etc.).

Ejemplo de solicitud y respuesta:

REQUEST
GET /index.html HTTP/1.1

RESPONSE
HTTP/1.1 200 OK

2. Cabeceras de solicitud

Las cabeceras proporcionan información adicional sobre la solicitud, como el tipo de contenido, el agente de usuario (navegador) y la autenticación. Ejemplo:

Host: www.exemplo.com
User-Agent: Mozilla/5.0
Accept-Language: en-US,en;q=0.5

3. Cuerpo de la solicitud

No todas las solicitudes tienen un cuerpo, pero es común en métodos como POST y PUT, donde los datos (como formularios o archivos) se envían al servidor.


Métodos HTTP comunes

Los métodos HTTP son esenciales para determinar el tipo de acción que el cliente solicita al servidor. Cada método define un propósito específico, como obtener datos, enviar información o modificar un recurso. Analicemos con más profundidad los métodos HTTP más comunes, sus usos y mejores prácticas.

  • GET: El método GET es el más utilizado en el protocolo HTTP. Se utiliza para recuperar o buscar datos de un servidor sin cambiar su estado. La característica principal de GET es que es idempotente, es decir, realizar múltiples solicitudes GET para el mismo recurso no cambia el estado de la aplicación.
    • Características
      • Sin efectos secundarios: Sólo datos de lectura. No debería cambiar nada en el servidor.
      • Cuerpo de solicitud vacío: Generalmente, no hay cuerpo en una petición GET.
      • Almacenable en caché: Las respuestas a solicitudes GET pueden ser almacenadas en caché por el navegador para mejorar el rendimiento.
    • ¿Cuándo utilizarlo?
      • Obtenga datos estáticos como páginas HTML, imágenes, archivos o información en un formato como JSON.
      • Recuperar información de una base de datos sin alterar los datos.
  • POST: El método POST se utiliza para enviar datos al servidor, normalmente para crear nuevos recursos. A diferencia de GET, cambia el estado del servidor. POST no es idempotente, lo que significa que si envía la misma solicitud varias veces, puede crear varios recursos.
    • Características
      • Cambia el estado del servidor: Típicamente se utiliza para crear nuevos recursos.
      • Cuerpo de la solicitud presente: Contiene los datos que se enviarán al servidor.
      • No almacenable en caché: En general, las solicitudes POST no deben almacenarse en caché.
    • ¿Cuándo utilizarlo?
      • Enviar formularios con datos.
      • Crear nuevos recursos, como agregar un nuevo elemento a una base de datos.
  • PUT: El método PUT se utiliza para actualizar un recurso en el servidor. Si el recurso aún no existe, se puede utilizar PUT para crearlo. La característica principal de PUT es que es idempotente: realizar múltiples solicitudes PUT con el mismo cuerpo de solicitud siempre producirá el mismo resultado.
    • Características
      • Idempotente: Las solicitudes repetidas con el mismo cuerpo tendrán el mismo efecto.
      • Recurso completo enviado: El cuerpo de la solicitud normalmente contiene una representación completa del recurso que se está actualizando.
      • Puede crear o actualizar: Si el recurso no existe, PUT puede crearlo.
    • ¿Cuándo utilizarlo?
      • Actualizar o reemplazar completamente un recurso existente.
      • Crea un recurso, si no existe, a través de una URL específica.
  • DELETE: El método DELETE se utiliza para eliminar un recurso específico del servidor. Al igual que PUT, es idempotente, lo que significa que si se realizan varias peticiones DELETE para el mismo recurso, el resultado será el mismo: el recurso se eliminará (o seguirá desaparecido si ya se ha eliminado).
    • Características
      • Idempotente: Si eliminas un recurso que ya ha sido eliminado, el servidor devolverá éxito.
      • Sin cuerpo: Normalmente, la solicitud DELETE no tiene cuerpo.
    • ¿Cuándo utilizarlo?
      • Eliminar recursos específicos del servidor, como borrar datos de una base de datos.
  • PATCH: El método PATCH se utiliza para actualizaciones parciales de un recurso. A diferencia de PUT, en el que es necesario enviar la representación completa del recurso, PATCH permite modificar sólo los campos que necesitan actualización.
  • HEAD: Similar a GET, pero sin el cuerpo de respuesta. Se utiliza para comprobar información sobre un recurso.
  • OPTIONS: Permite describir las opciones de comunicación con el servidor, incluidos los métodos admitidos.
  • TRACE: Utilizado para diagnósticos, devuelve lo enviado por el cliente al servidor.


Códigos de estado HTTP

Los códigos de estado HTTP son respuestas que el servidor envía al cliente para informarle el resultado de una solicitud. Estos códigos son numéricos e indican si la solicitud fue exitosa o fallida, así como errores o redirecciones. Se dividen en cinco categorías principales, cada una con un rango específico de números, proporcionando retroalimentación clara y detallada sobre lo que sucedió durante el procesamiento de la solicitud.

A continuación se ofrece una visión más detallada de cada categoría y algunos de los códigos más utilizados.

1xx: Respuestas informativas: Los códigos de estado de la serie 1xx indican que el servidor ha recibido la solicitud y que el cliente debe esperar a recibir más información. Estas respuestas rara vez se utilizan en la práctica diaria, pero pueden ser importantes en determinados escenarios.

2xx: Éxito: Los códigos de estado de la serie 2xx indican que la solicitud se ha realizado con éxito. Estos son los códigos que queremos ver la mayor parte del tiempo, ya que indican que la interacción entre el cliente y el servidor fue como se esperaba.

3xx: Redirecciones: Los códigos de la serie 3xx indican que el cliente necesita realizar alguna acción adicional para completar la solicitud, normalmente una redirección a otra URL.

4xx: Errores del cliente: Los códigos de la serie 4xx indican que se ha producido un error en la petición realizada por el cliente. Estos errores pueden deberse a un formato de datos incorrecto, falta de autenticación o intentos de acceder a recursos inexistentes.

5xx: Errores del servidor: Los códigos de la serie 5xx indican que se ha producido un error interno en el servidor al intentar procesar la solicitud. Estos errores suelen ser problemas de backend, como fallos de servicios internos, errores de configuración o sobrecarga del sistema.


Construyendo una URL

Una URL (Uniform Resource Locator) es la forma en que identificamos y accedemos a los recursos en la web. Se compone de varios elementos que proporcionan información esencial, como la ubicación del servidor, el recurso solicitado y, opcionalmente, parámetros adicionales que se pueden utilizar para filtrar o personalizar las respuestas.

A continuación, detallaremos cada componente de una URL y cómo se utilizan los parámetros de consulta para pasar información adicional en una solicitud HTTP.

Estructura de URL

Una URL típica sigue este formato:

protocolo://dominio:puerto/ruta?consulta=parámetros#fragmento

Cada parte tiene un papel específico:

  1. Protocolo: Indica qué protocolo se utilizará para la comunicación, como http o https. Ejemplo: https://.
  2. Dominio: Nombre del servidor donde está alojado el recurso. Puede ser un nombre de dominio (por ejemplo, ejemplo.com ) o una dirección IP (por ejemplo, 192.168.1.1 ).
  3. Puerto: Número opcional que especifica el puerto del servidor que debe utilizarse para la comunicación. Si se omite, el navegador utiliza los puertos predeterminados, como 80 para http y 443 para https. Ejemplo: :8080.
  4. Ruta: Especifica el recurso o ruta en el servidor. Puede representar páginas, puntos finales de API o archivos. Ejemplo: /api/v1/usuarios.
  5. Parámetros de consulta: Se utiliza para pasar información adicional al servidor. Siguen al signo de interrogación ( ? ) y están formados por pares clave-valor. Los parámetros múltiples se separan con &. Ejemplo: ?nombre=John&edad=30 .
  6. Fragmento: Indica una parte específica del recurso, como un punto de anclaje dentro de una página HTML. Sigue al carácter almohadilla ( # ). Ejemplo: #sección2. Normalmente, los fragmentos no son útiles ni se utilizan para consumir datos de las API. Esto se debe a que los fragmentos se procesan exclusivamente en el lado del cliente, es decir, por el navegador o por la interfaz de la aplicación que está consumiendo la página web. El servidor no recibe el fragmento de la URL, por lo que no se puede utilizar en peticiones HTTP enviadas al servidor, como cuando se consume una API. Por este motivo, nuestra biblioteca no admitirá fragmentos.


Ejemplo completo

Analicemos la URL a continuación:

https://www.exemplo.com:8080/market/symbols?active=EURUSD&timeframe=h1

Aquí tenemos:

  • Protocolo: https
  • Dominio: www.example.com
  • Puerto: 8080
  • Ruta: /market/symbols
  • Parámetros de consulta: active=EURUSD&timeframe=h1


Manos a la obra: Inicio de la construcción de la biblioteca Connexus

Para comenzar a construir la biblioteca Connexus, nos centraremos en las clases responsables de construir y manipular URL y parámetros de consulta. Construiremos un módulo que ayude a crear URL y agregar parámetros de consulta de forma dinámica y programática.


Estructura de clases

Comenzaremos creando una clase CURL, que será responsable de construir y manipular las URL. Permitirá al usuario agregar fácilmente parámetros de consulta, construir la URL base y manejar diferentes elementos de la URL de manera eficiente. Para gestionar los parámetros de consulta de una URL de forma organizada y eficiente, utilizaremos una clase llamada CJson. El objetivo de esta clase es convertir los parámetros de consulta (que normalmente se pasan como una cadena en la URL) en un formato estructurado y fácil de gestionar: JSON.


¿Qué es JSON?

Antes de sumergirnos en la funcionalidad de la clase CJson, es importante entender el formato JSON (JavaScript Object Notation), si aún no estás familiarizado con él. JSON es un formato de datos muy común utilizado en la web para representar datos estructurados. Se compone de pares clave, donde cada clave tiene un valor asociado. Estos pares están separados por comas y agrupados entre llaves «{}».

Ejemplo de un objeto JSON:

{
  "name": "John",
  "age": 30,
  "city": "New York"
}

Aquí tenemos un objeto JSON que contiene tres pares de claves «nombre», «edad» y «ciudad», con sus respectivos valores. En el caso de los parámetros de consulta de una URL, cada parámetro funciona de forma similar: hay una clave (nombre del parámetro) y un valor (el valor asociado a esa clave).


Finalidad de la clase CJson

La clase CJson se utilizará para organizar los parámetros de consulta de la URL en un formato JSON. Esto hace que sea más fácil manipular, leer e incluso validar estos parámetros antes de incluirlos en la URL final. En lugar de trabajar con una cadena de parámetros como ?nombre=John&edad=30 , puedes trabajar con un objeto estructurado, haciendo que el código sea más limpio y más comprensible. La clase CJson también será útil para enviar y recibir datos, lo que veremos en los próximos artículos.


Creación de las primeras clases

Comenzamos creando una carpeta llamada Connexus en includes en el MetaEditor. Dentro de la carpeta Connexus crear otra carpeta llamada URL y otra llamada Data , crear un archivo llamado URL y QueryParam dentro de la carpeta URL y tambien dejare adjunto al articulo la clase CJson, la cual debe ser agregada a la carpeta Data. No entraré en muchos detalles sobre la implementación de esta clase, pero es fácil de usar, créeme. La estructura debería verse así:

MQL5
 |--- include
 |--- |--- Connexus
 |--- |--- |--- Data
 |--- |--- |--- |--- Json.mqh
 |--- |--- |--- URL
 |--- |--- |--- |--- QueryParam.mqh
 |--- |--- |--- |--- URL.mqh


Parámetro de consulta

Comencemos trabajando con la clase CQueryParam. Esta clase será responsable de agregar, eliminar, buscar y serializar parámetros de consulta, además de ofrecer métodos auxiliares como limpieza de datos y análisis de cadenas de consulta. Comenzamos creando la clase con un objeto privado de tipo CJson, para almacenar los parámetros de consulta como pares clave-valor.

//+------------------------------------------------------------------+
//| 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 &params[]);  // 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
  };

Ahora, exploremos los métodos principales y entendamos cómo cada uno de ellos contribuye al funcionamiento de la clase.

  • AddParam(string key, string value): Este método se encarga de añadir un nuevo parámetro a la lista de parámetros de consulta. Recibe la clave y el valor como parámetros y los almacena en el objeto m_query_param.
  • AddParam(string param): Este método añade un parámetro ya formateado como clave=valor . Comprueba si la cadena tiene el carácter = y, en caso afirmativo, divide la cadena en dos valores, uno para la clave y otro para el valor, y los almacena.
  • AddParams(const string &params[]): Este método añade varios parámetros a la vez. Recibe un array de cadenas en formato clave=valor y llama al método AddParam para cada elemento del array.
  • RemoveParam(const string key): Este método elimina un parámetro de la lista de parámetros de consulta. Localiza la clave y la elimina del objeto m_query_param. - GetParam(const string key): Este método devuelve el valor de un parámetro específico, utilizando la clave como entrada.
  • HasParam(const string key): Este método comprueba si ya se ha añadido un parámetro determinado.
  • ParamSize(void): Este método devuelve el número de parámetros de consulta almacenados.
  • Parse(const string query_param): El método Parse() recibe una cadena de parámetros de consulta y los convierte en pares clave-valor, almacenándolos en el objeto m_query_param. Divide la cadena string por los caracteres & (que separan los parámetros) y = (que separa clave y valor).
  • Serialize(void): El método Serialize() genera una cadena formateada que contiene todos los parámetros de consulta almacenados. Concatena los parámetros en el formato clave=valor y separa cada par con &.
  • Clear(void): El método Clear() borra todos los parámetros almacenados, reseteando el objeto.

A continuación se muestra el código con las funciones implementadas, recuerde añadir la importación CJson:

//+------------------------------------------------------------------+
//| 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 &params[]);  // 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 &params[])
  {
   //--- 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

Ahora que tenemos una clase responsable de trabajar con parámetros de consulta, trabajemos en la clase CURL que hará el resto, utilizando protocolo, host, puerto, etc. Aquí hay una implementación inicial de la clase CURL en MQL5, recuerde importar la clase CQueryParam:

//+------------------------------------------------------------------+
//|                                                          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)
  {
  }
//+------------------------------------------------------------------+

Creemos un ENUM que contenga los protocolos más populares

//+------------------------------------------------------------------+
//| 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
  };

En el campo privado de la clase añadiremos una estructura que forme los elementos básicos de una URL, y una instancia de esta estructura llamada 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

Creamos los setters y getters, y sus implementaciones.

//+------------------------------------------------------------------+
//| 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));
  }
//+------------------------------------------------------------------+

Ahora trabajaremos en el motor de nuestra clase, añadiendo nuevas funciones para trabajar con estos datos, ellas son:

  • Clear(void) : El método Clear() se encarga de borrar todos los datos almacenados en la clase, restableciendo sus atributos a valores vacíos o por defecto. Este método es útil cuando desea reutilizar la instancia de clase para crear una nueva URL o cuando necesita asegurarse de que no se incluyan accidentalmente datos antiguos en una nueva operación. En otras palabras, “reinicia” la clase, eliminando toda la información almacenada previamente.

    Cómo funciona:

    • Establece los atributos de la clase como vacíos o nulos, dependiendo del tipo de datos (cadena vacía para protocolo, dominio, etc.).
    • Elimina todos los parámetros de consulta y restablece la ruta al valor predeterminado.
    • Después de llamar a Clear(), la instancia de clase estará en un estado inicial, como si acabara de ser creada.


    Ejemplo:

    Si la clase previamente almacenada:

    • Protocolo: https
    • Dominio: api.example.com
    • Ruta: /v1/users
    • Parámetros de consulta: id=123&active=true


    Después de llamar a Clear(), todos estos valores se restablecerán a:

    • Protocolo: ""
    • Dominio: ""
    • Ruta: ""
    • Parámetros de consulta: ""


    Esto deja a la clase lista para construir una nueva URL desde cero.

  • BaseUrl(void) : Este método se encarga de generar y devolver la parte base de la URL, compuesta por el protocolo (por ejemplo: http, https), el dominio (como "www.example.com") y, opcionalmente, el puerto (por ejemplo: 8080). El método garantiza que los elementos esenciales para la comunicación con el servidor sean correctos. Este método le permite tener la flexibilidad de componer URL dinámicas, comenzando siempre desde la parte base. Puede ser útil cuando desea reutilizar la base de la URL para acceder a diferentes recursos en el mismo servidor.

  • PathAndQuery(void) : Este método se encarga de generar la parte de la ruta del recurso y de concatenar los parámetros de consulta añadidos anteriormente. La ruta generalmente especifica el recurso al que desea acceder en el servidor, mientras que los parámetros de consulta le permiten proporcionar detalles adicionales, como filtros o paginación. Al separar la ruta y los parámetros de consulta de la URL base, puede componer diferentes partes de la URL de una manera más organizada. Este método devuelve una cadena que se puede utilizar directamente en una solicitud HTTP o en otros métodos que necesitan esta estructura.

  • FullUrl(void) : Este es el método que «compila» todas las partes de la URL y devuelve la URL completa, lista para usar. Combina BaseURL() y PathAndQuery() para formar la URL final que puedes utilizar directamente en una petición HTTP. Si necesita la URL completa para enviar una solicitud HTTP, este método es la forma más sencilla de garantizar que la URL tenga el formato correcto. Evita errores como olvidar concatenar los parámetros base y de consulta.

    Example: Si la clase ha almacenado los siguientes valores:

    • Protocolo: https
    • Dominio: api.example.com
    • Ruta: /v1/users
    • Parámetros de consulta: id=123&active=true


    Al llamar a Serialize() , la función retornará:

    https://api.exemplo.com/v1/users?id=123&active=true

  • Parse(const string url) : Hace lo contrario que FullUrl(void). Toma una URL completa como argumento y separa sus componentes de forma organizada. El objetivo es descomponer una URL en partes más pequeñas (protocolo, dominio, puerto, ruta, parámetros de consulta, etc.), de modo que el programador pueda trabajar con estos elementos individualmente. Esto es especialmente útil si recibe una URL y necesita comprender sus detalles o modificarlos mediante programación.

    Cómo funciona:

    • Recibe una cadena que contiene una URL completa.
    • Analiza (o «parsea») la cadena, identificando cada parte de la URL: el protocolo (http, https), el dominio, el puerto (si existe), la ruta y cualquier parámetro de consulta.
    • Asigna estos valores a los atributos internos de la clase, como protocol, host, path, queryParams. - Maneja correctamente separadores como :// , / , ? y & para dividir la URL en sus partes.


    Ejemplo: Dada la URL:

    https://api.example.com:8080/v1/users?id=123&active=true

    Al llamar a Parse() , la función asignará los siguientes valores:

    • Protocolo: https
    • Dominio: api.example.com
    • Puerto: 8080
    • Ruta: /v1/users
    • Parámetros de consulta: id=123 , active=true


    Esto le permite acceder a cada parte de la URL mediante programación, lo que facilita su manipulación o análisis.

  • UrlProtocolToStr(ENUM_URL_PROTOCOL protocol) : Devuelve el protocolo en una cadena, útil para convertir ENUM_URL_PROTOCOL a una simple cadena, por ejemplo:

    • URL_PROTOCOL_HTTP → “http”
    • URL_PROTOCOL_HTTPS → “httpS”
    • URL_PROTOCOL_WSS → “wss”
    • etc…

Cada uno de estos métodos juega un papel esencial en la construcción y manipulación de URL. Con estas características, la biblioteca Connexus se vuelve altamente flexible para satisfacer las necesidades dinámicas de las API, ya sea creando URL desde cero o analizando URL existentes. Al implementar estos métodos, los desarrolladores pueden componer URL de forma programada, evitando errores y optimizando la comunicación con los servidores. A continuación se muestra el código con las funciones implementadas:

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Por último, agregaremos dos nuevas funciones más para ayudar a los desarrolladores, son:

  • ShowData(void): Imprime los elementos de la URL por separado, lo que nos ayuda a depurar y comprender qué datos se almacenan en la clase. Por ejemplo:

https://api.exemplo.com/v1/users?id=123&active=true

La función debe devolver esto:

Protocol: https
Host: api.exemplo.com
Port: 443
Path: /v1/users
Query Param: {
   "id":123,
   "active":true
}

  • Compare(CURL &url): Esta función recibe otra instancia de la clase CURL. Debe devolver true si las URL almacenadas en ambas instancias son las mismas, en caso contrario debe devolver false. Puede ser útil evitar comparar URL serializadas, ahorrando tiempo. Ejemplo sin la función 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");
      }
        
    
        
    

A continuación se muestra el código para implementar cada una de estas funciones:

//+------------------------------------------------------------------+
//| 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"
   );
  }
//+------------------------------------------------------------------+

Hemos terminado las dos clases para trabajar con URLs, pasemos a las pruebas.


Pruebas

Ahora que tenemos nuestras clases iniciales listas, vamos a crear URLs a través de las clases y también hagamos el proceso inverso, de una URL separaremos el elemento usando la clase. Para realizar las pruebas crearé un archivo llamado TestUrl.mq5 siguiendo esta ruta 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);
  }

Al ejecutar el EA, tenemos los siguientes datos en el terminal:

Test1 | # true
Test2 | # true
Test3 | # true
Test3 | - Protocol # true
Test3 | - Host # true
Test3 | - Port # true
Test3 | - Path # true


Conclusión

En este artículo hemos explorado en profundidad el funcionamiento del protocolo HTTP, desde conceptos básicos como los verbos HTTP (GET, POST, PUT, DELETE) hasta los códigos de estado de respuesta que nos ayudan a interpretar el retorno de las peticiones. Para facilitar la gestión de URLs en tus aplicaciones MQL5, hemos creado la clase `CQueryParam`, que ofrece una forma simple y eficiente de manipular los parámetros de consulta. Además, hemos implementado la clase `CURL`, que permite la modificación dinámica de partes de la URL, haciendo que el proceso de creación y gestión de peticiones HTTP sea más flexible y robusto.

Con estos recursos en la mano, ya tienes una buena base para integrar tus aplicaciones con APIs externas, facilitando la comunicación entre tu código y los servidores web. Sin embargo, no hemos hecho más que empezar. En el próximo artículo, continuaremos nuestro viaje en el mundo HTTP, donde construiremos clases dedicadas para trabajar con las "cabeceras" y el "cuerpo" de las peticiones, permitiendo aún más control sobre las interacciones HTTP.

¡Esté atento a las próximas publicaciones mientras creamos una biblioteca esencial que llevará sus habilidades de integración de API al siguiente nivel!

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/15897

Optimización del modelo de nubes atmosféricas — Atmosphere Clouds Model Optimization (ACMO): Práctica Optimización del modelo de nubes atmosféricas — Atmosphere Clouds Model Optimization (ACMO): Práctica
En este artículo, seguiremos profundizando en la aplicación del algoritmo ACMO (Atmospheric Cloud Model Optimisation). En particular, discutiremos dos aspectos clave: el movimiento de las nubes hacia regiones de bajas presiones y la modelización del proceso de lluvia, incluida la inicialización de las gotas y su distribución entre las nubes. También analizaremos otras técnicas que desempeñan un papel importante a la hora de gestionar el estado de las nubes y garantizar su interacción con el entorno.
Redes neuronales en el trading: Superpoint Transformer (SPFormer) Redes neuronales en el trading: Superpoint Transformer (SPFormer)
En este artículo, nos familiarizaremos con un método de segmentación de objetos 3D basado en el Superpoint Transformer (SPFormer), que elimina la necesidad de agregar datos intermedios, lo cual acelera el proceso de segmentación y mejora el rendimiento del modelo.
Redes neuronales en el trading: Enfoque sin máscara para la predicción del movimiento de precios Redes neuronales en el trading: Enfoque sin máscara para la predicción del movimiento de precios
En este artículo nos familiarizaremos con el método Mask-Attention-Free Transformer (MAFT) y su aplicación en el ámbito del trading. A diferencia de los Transformers tradicionales, que requieren el enmascaramiento de los datos durante el procesamiento de la secuencia, el MAFT optimiza el proceso de atención eliminando la necesidad de enmascaramiento, lo que mejora significativamente la eficiencia computacional.
Reimaginando las estrategias clásicas en MQL5 (Parte III): Previsión del FTSE 100 Reimaginando las estrategias clásicas en MQL5 (Parte III): Previsión del FTSE 100
En esta serie de artículos, revisaremos estrategias de negociación muy conocidas para averiguar si podemos mejorarlas utilizando la IA. En el artículo de hoy, exploraremos el FTSE 100 e intentaremos predecir el índice utilizando una parte de los valores individuales que lo componen.