English Русский 中文 Español Deutsch 日本語
preview
HTTP e Connexus (Parte 2): Entendendo a Arquitetura HTTP e o Design de Bibliotecas

HTTP e Connexus (Parte 2): Entendendo a Arquitetura HTTP e o Design de Bibliotecas

MetaTrader 5Exemplos |
32 1
joaopedrodev
joaopedrodev

Introdução

Este artigo é a continuação de uma série de artigos onde iremos construir uma biblioteca chamada Connexus. No primeiro artigo, entendemos o funcionamento básico da função WebRequest, analisando cada um de seus parâmetros e também criamos um exemplo de código que demonstra o uso dessa função e suas dificuldades. Neste artigo, continuaremos entendendo um pouco mais sobre o protocolo HTTP, como funciona uma URL e quais elementos são usados para construir uma, e vamos criar duas classes iniciais, que são:

  • CQueryParam: Classe para gerenciar parâmetros de consulta em uma URL
  • CURL: Classe que contém todos os elementos de uma URL, incluindo uma instância de CQueryParam


O que é HTTP?

HTTP é um protocolo de comunicação utilizado para transferir dados na web. Ele funciona sobre o protocolo TCP/IP, que estabelece a conexão entre o cliente e o servidor. HTTP é um protocolo stateless (sem estado), o que significa que cada requisição é independente, sem conhecimento das requisições anteriores. Uma requisição e resposta HTTP é composta por três partes principais:

1. Linha de Requisição

A linha de requisição contém três elementos:

  • Método HTTP: Define a ação a ser realizada (GET, POST, PUT, DELETE, etc.).
  • URL: Especifica o recurso solicitado.
  • Versão do HTTP: Indica a versão do protocolo utilizada (HTTP/1.1, HTTP/2, etc.).

Exemplo de requisição e resposta:

REQUEST
GET /index.html HTTP/1.1

RESPONSE
HTTP/1.1 200 OK

2. Cabeçalhos da Requisição

Os cabeçalhos fornecem informações adicionais sobre a requisição, como tipo de conteúdo, agente do usuário (navegador) e autenticação. Exemplo:

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

3. Corpo da Requisição

Nem todas as requisições possuem corpo, mas é comum em métodos como POST e PUT, onde dados (como formulários ou arquivos) são enviados ao servidor.


Principais Métodos HTTP

Os métodos HTTP são essenciais para determinar o tipo de ação solicitada pelo cliente ao servidor. Cada método define um propósito específico, como buscar dados, enviar informações ou modificar um recurso. Vamos nos aprofundar nos métodos HTTP mais comuns, seus usos e melhores práticas.

  • GET: O método GET é o mais utilizado no protocolo HTTP. É usado para recuperar ou buscar dados de um servidor sem alterar seu estado. A principal característica do GET é ser idempotente, ou seja, fazer várias requisições GET para o mesmo recurso não altera o estado da aplicação.
    • Características
      • Sem efeitos colaterais: Apenas leitura de dados. Não deve alterar nada no servidor.
      • Corpo da requisição vazio: Geralmente, não há corpo em uma requisição GET.
      • Cacheável: As respostas GET podem ser armazenadas em cache pelo navegador para melhorar a performance.
    • Quando usar?
      • Buscar dados estáticos como páginas HTML, imagens, arquivos ou informações em formatos como JSON.
      • Obter informações de um banco de dados sem alterar dados.
  • POST: O método POST é usado para enviar dados ao servidor, normalmente para criar novos recursos. Diferente do GET, ele altera o estado do servidor. POST não é idempotente, o que significa que se você enviar a mesma requisição várias vezes, pode criar múltiplos recursos.
    • Características
      • Altera o estado do servidor: Geralmente usado para criar novos recursos.
      • Corpo da requisição presente: Contém os dados que serão enviados ao servidor.
      • Não cacheável: Geralmente, requisições POST não devem ser armazenadas em cache.
    • Quando usar?
      • Enviar formulários com dados.
      • Criar novos recursos, como adicionar um novo item em um banco de dados.
  • PUT: O método PUT é utilizado para atualizar um recurso no servidor. Se o recurso ainda não existir, o PUT pode ser usado para criá-lo. A principal característica do PUT é ser idempotente: fazer várias requisições PUT com o mesmo corpo sempre produz o mesmo resultado.
    • Características
      • Idempotente: Requisições repetidas com o mesmo corpo terão o mesmo efeito.
      • Recurso completo enviado: O corpo da requisição geralmente contém a representação completa do recurso sendo atualizado.
      • Pode criar ou atualizar: Se o recurso não existir, o PUT pode criá-lo.
    • Quando usar?
      • Atualizar ou substituir completamente um recurso existente.
      • Criar um recurso, se ele não existir, através de uma URL específica.
  • DELETE: O método DELETE é usado para remover um recurso específico do servidor. Assim como o PUT, é idempotente, ou seja, se você realizar várias requisições DELETE para o mesmo recurso, o resultado será o mesmo: o recurso será removido (ou permanecerá ausente se já tiver sido excluído).
    • Características
      • Idempotente: Se você deletar um recurso que já foi deletado, o servidor retornará sucesso.
      • Sem corpo: Normalmente, a requisição DELETE não possui corpo.
    • Quando usar?
      • Remover recursos específicos do servidor, como excluir dados de um banco de dados.
  • PATCH: O método PATCH é utilizado para atualizações parciais de um recurso. Diferente do PUT, onde você precisa enviar a representação completa do recurso, o PATCH permite modificar apenas os campos que precisam ser atualizados.
  • HEAD: Semelhante ao GET, mas sem o corpo da resposta. Usado para verificar informações sobre um recurso.
  • OPTIONS: Usado para descrever as opções de comunicação com o servidor, incluindo os métodos suportados.
  • TRACE: Usado para diagnóstico, retorna o que foi enviado pelo cliente ao servidor.


Códigos de Status HTTP

Os códigos de status HTTP são respostas que o servidor envia ao cliente para informar o resultado de uma requisição. Esses códigos são numéricos e indicam se a requisição foi bem-sucedida ou falhou, além de erros ou redirecionamentos. Eles são divididos em cinco categorias principais, cada uma com um intervalo de números específico, fornecendo um retorno claro e detalhado sobre o que aconteceu durante o processamento da requisição.

Veja abaixo uma visão mais detalhada de cada categoria e alguns dos códigos mais utilizados.

1xx: Respostas Informativas: Códigos de status da série 1xx indicam que o servidor recebeu a requisição e que o cliente deve aguardar por mais informações. Essas respostas são raramente usadas na prática diária, mas podem ser importantes em certos cenários.

2xx: Sucesso: Códigos de status da série 2xx indicam que a requisição foi bem-sucedida. São os códigos que queremos ver na maioria das vezes, pois indicam que a interação entre cliente e servidor ocorreu conforme o esperado.

3xx: Redirecionamentos: Códigos da série 3xx indicam que o cliente precisa realizar alguma ação adicional para completar a requisição, normalmente um redirecionamento para outra URL.

4xx: Erros do Cliente: Códigos da série 4xx indicam que houve um erro na requisição feita pelo cliente. Esses erros podem ser causados por formato de dados incorreto, falta de autenticação ou tentativas de acessar recursos inexistentes

5xx: Erros do Servidor: Códigos da série 5xx indicam que houve um erro interno no servidor ao tentar processar a requisição. Normalmente são falhas de backend, como falhas em serviços internos, erros de configuração ou sobrecarga do sistema.


Construindo uma URL

Uma URL (Uniform Resource Locator) é a forma como identificamos e acessamos recursos na web. Ela é composta por vários elementos que fornecem informações essenciais, como a localização do servidor, o recurso solicitado e, opcionalmente, parâmetros adicionais que podem ser usados para filtrar ou personalizar respostas.

Abaixo, detalhamos cada componente de uma URL e como os parâmetros de consulta são usados para passar informações adicionais em uma requisição HTTP.

Estrutura da URL

Uma URL típica segue este formato:

protocolo://domínio:porta/caminho?consulta=parametros#fragmento

Cada parte possui um papel específico:

  1. Protocolo: Indica qual protocolo será usado na comunicação, como http ou https. Exemplo: https:// .
  2. Domínio: Nome do servidor onde o recurso está hospedado. Pode ser um nome de domínio (ex: exemplo.com) ou um endereço IP (ex: 192.168.1.1).
  3. Porta: Número opcional que especifica a porta do servidor que deve ser utilizada para a comunicação. Se omitido, o navegador usa as portas padrão, como 80 para http e 443 para https. Exemplo: :8080 .
  4. Caminho: Especifica o recurso ou rota no servidor. Pode representar páginas, endpoints de API ou arquivos. Exemplo: /api/v1/users .
  5. Parâmetros de Consulta: Usados para passar informações adicionais ao servidor. Vêm após o ponto de interrogação (?) e são formados por pares chave-valor. Múltiplos parâmetros são separados por &. Exemplo: ?nome=João&idade=30 .
  6. Fragmento: Indica uma parte específica do recurso, como um ponto âncora dentro de uma página HTML. Vem após o caractere sustenido (#). Exemplo: #section2 . Normalmente, fragmentos não são úteis nem usados para consumir dados de APIs. Isso porque os fragmentos são processados exclusivamente no lado do cliente, ou seja, pelo navegador ou pela interface da aplicação que está consumindo a página web. O servidor não recebe o fragmento da URL, e por isso ele não pode ser usado em requisições HTTP enviadas ao servidor, como quando se consome uma API. Por esse motivo, nossa biblioteca não dará suporte a fragmentos.


Exemplo Completo

Vamos analisar a URL abaixo:

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

Aqui temos:

  • Protocolo: https
  • Domínio: www.example.com
  • Porta: 8080
  • Caminho: /market/symbols
  • Parâmetros de Consulta: active=EURUSD&timeframe=h1


Mão na Massa: Iniciando a Construção da Biblioteca Connexus

Para começar a construir a biblioteca Connexus, vamos focar nas classes responsáveis por construir e manipular URLs e parâmetros de consulta. Vamos construir um módulo que auxilia na criação de URLs e adição de parâmetros de consulta de forma dinâmica e programática.


Estrutura das Classes

Vamos começar criando uma classe CURL, que será responsável por construir e manipular URLs. Ela permitirá que o usuário adicione facilmente parâmetros de consulta, construa a URL base e manipule diferentes elementos da URL de forma eficiente. Para gerenciar os Parâmetros de Consulta de uma URL de maneira organizada e eficiente, vamos utilizar uma classe chamada CJson. O objetivo desta classe é converter os parâmetros de consulta (que normalmente são passados como uma string na URL) em um formato estruturado e fácil de gerenciar: JSON.


O que é JSON?

Antes de mergulharmos na funcionalidade da classe CJson, é importante entender o formato JSON (JavaScript Object Notation), caso você ainda não esteja familiarizado. JSON é um formato de dados muito comum na web para representar dados estruturados. Consiste em pares chave e valor, onde cada chave tem um valor associado. Esses pares são separados por vírgulas e agrupados entre chaves “{}”

Exemplo de objeto JSON:

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

Aqui temos um objeto JSON que contém três pares chave-valor: “name”, “age” e “city”, com seus respectivos valores. No caso dos parâmetros de consulta (query params) de uma URL, cada parâmetro funciona de maneira semelhante: existe uma chave (nome do parâmetro) e um valor (o valor associado a essa chave).


Propósito da Classe CJson

A classe CJson será usada para organizar os parâmetros de consulta da URL em um formato JSON. Isso facilita a manipulação, leitura e até a validação desses parâmetros antes de incluí-los na URL final. Em vez de lidar com uma string de parâmetros como ?name=John&age=30, você pode lidar com um objeto estruturado, tornando o código mais limpo e compreensível. A classe CJson também será útil para enviar e receber dados, como veremos nos próximos artigos.


Criando as primeiras classes

Começamos criando uma pasta chamada Connexus dentro de includes no Metaeditor. Dentro da pasta Connexus, crie outra pasta chamada URL e outra chamada Data. Crie um arquivo chamado URL e outro chamado QueryParam dentro da pasta URL e também deixarei anexada ao artigo a classe CJson, que deve ser adicionada à pasta Data. Não entrarei em muitos detalhes sobre a implementação dessa classe, mas ela é fácil de usar, pode confiar. A estrutura deve ficar mais ou menos assim:

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


QueryParam

Vamos começar trabalhando com a classe CQueryParam. Esta classe será responsável por adicionar, remover, buscar e serializar os parâmetros de consulta, além de oferecer métodos auxiliares como limpeza de dados e parsing de strings de consulta. Começamos criando a classe com um objeto privado do tipo CJson, para armazenar os parâmetros de consulta como pares chave-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
  };

Agora, vamos explorar os principais métodos e entender como cada um deles contribui para o funcionamento da classe.

  • AddParam(string key, string value): Este método é responsável por adicionar um novo parâmetro à lista de query params. Ele recebe a chave e o valor como parâmetros e os armazena no objeto m_query_param.
  • AddParam(string param): Este método adiciona um parâmetro já formatado como key=value. Ele verifica se a string possui o caractere = e, caso tenha, divide a string em dois valores, um para a chave e outro para o valor, e os armazena.
  • AddParams(const string &params[]): Este método adiciona múltiplos parâmetros de uma vez. Recebe um array de strings no formato key=value e chama o método AddParam para cada item do array.
  • RemoveParam(const string key): Este método remove um parâmetro da lista de query params. Localiza a chave e a remove do objeto m_query_param. - GetParam(const string key): Este método retorna o valor de um parâmetro específico, usando a chave como entrada.
  • HasParam(const string key): Este método verifica se determinado parâmetro já foi adicionado.
  • ParamSize(void): Este método retorna a quantidade de parâmetros de consulta armazenados
  • Parse(const string query_param): O método Parse() recebe uma string de query params e converte em pares chave-valor, armazenando no objeto m_query_param. Ele separa a string pelos caracteres & (que separam os parâmetros) e = (que separa chave e valor).
  • Serialize(void): O método Serialize() gera uma string formatada contendo todos os parâmetros de consulta armazenados. Ele concatena os parâmetros no formato key=value e separa cada par com &.
  • Clear(void): O método Clear() limpa todos os parâmetros armazenados, reiniciando o objeto.

Abaixo está o código com as funções implementadas. Lembre-se de adicionar o import do 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

Agora que temos uma classe responsável por trabalhar com query params, vamos trabalhar na classe CURL que fará o restante, usando protocolo, host, porta etc. Aqui está uma implementação inicial da classe CURL em MQL5, lembre-se de importar a classe 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)
  {
  }
//+------------------------------------------------------------------+

Vamos criar um ENUM que contenha os protocolos mais 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
  };

No campo privado da classe, adicionaremos uma estrutura que forma os elementos básicos de uma URL e uma instância dessa estrutura chamada 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

Criamos os setters e getters, e suas implementações

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

Agora vamos trabalhar na engine da nossa classe, adicionando novas funções para trabalhar com esses dados. São elas:

  • Clear(void): O método Clear() é responsável por limpar todos os dados armazenados na classe, resetando seus atributos para valores vazios ou padrões. Esse método é útil quando você quer reutilizar a instância da classe para montar uma nova URL ou quando precisa garantir que nenhum dado antigo seja incluído por engano em uma nova operação. Em outras palavras, ele “reseta” a classe, removendo todas as informações previamente armazenadas.

    Como funciona:

    • Define os atributos da classe como vazios ou nulos, dependendo do tipo de dado (string vazia para protocolo, domínio, etc.).
    • Remove todos os parâmetros de consulta e reseta o caminho (path) para o valor padrão.
    • Após chamar Clear(), a instância da classe estará em estado inicial, como se tivesse acabado de ser criada.


    Exemplo:

    Se anteriormente a classe armazenava:

    • Protocolo: https
    • Domínio: api.example.com
    • Caminho: /v1/users
    • Query Params: id=123&active=true


    Após chamar Clear(), todos esses valores serão redefinidos para:

    • Protocolo: ""
    • Domínio: ""
    • Caminho: ""
    • Query Params: ""


    Isso deixa a classe pronta para construir uma nova URL do zero.

  • BaseUrl(void): Este método é responsável por gerar e retornar a parte base da URL, que é composta pelo protocolo (ex: http, https), domínio (ex: www.example.com) e, opcionalmente, a porta (ex: :8080). O método garante que os elementos essenciais para comunicação com o servidor estejam corretos. Esse método permite flexibilidade para compor URLs dinâmicas, sempre partindo da parte base. Pode ser útil quando você deseja reutilizar a base da URL para acessar diferentes recursos no mesmo servidor.

  • PathAndQuery(void): Este método é responsável por gerar a parte do caminho do recurso (path) e concatenar os parâmetros de consulta que você adicionou anteriormente. O caminho geralmente especifica o recurso que deseja acessar no servidor, enquanto os parâmetros de consulta permitem fornecer detalhes adicionais, como filtros ou paginação. Ao separar caminho e parâmetros da base da URL, você pode compor diferentes partes da URL de forma mais organizada. Esse método retorna uma string que pode ser usada diretamente em uma requisição HTTP ou em outros métodos que precisem dessa estrutura.

  • FullUrl(void): Este é o método que “compila” todas as partes da URL e retorna a URL completa, pronta para uso. Ele combina BaseURL() e PathAndQuery() para formar a URL final que você pode usar diretamente em uma requisição HTTP. Se você precisar da URL completa para enviar uma requisição, esse método é a maneira mais fácil de garantir que a URL está devidamente formatada. Evita erros como esquecer de concatenar a base e os parâmetros de consulta.

    Exemplo: Se a classe armazenou os seguintes valores:

    • Protocolo: https
    • Domínio: api.example.com
    • Caminho: /v1/users
    • Query Params: id=123&active=true


    Ao chamar Serialize(), a função irá retornar:

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

  • Parse(const string url): Faz o oposto de FullUrl(void). Recebe uma URL completa como argumento e separa seus componentes de forma organizada. O objetivo é decompor uma URL em partes menores (protocolo, domínio, porta, caminho, parâmetros de consulta etc.), para que o programador possa trabalhar com esses elementos individualmente. Isso é especialmente útil se você está recebendo uma URL e precisa entender seus detalhes ou modificá-los programaticamente.

    Como funciona:

    • Recebe uma string contendo a URL completa.
    • Analisa (“parseia”) a string, identificando cada parte da URL: protocolo (http, https), domínio, porta (se houver), caminho (path) e quaisquer parâmetros de consulta.
    • Atribui esses valores aos atributos internos da classe, como protocolo, host, path, queryParams. - Lida corretamente com os separadores como ://, /, ? , & para dividir a URL em suas partes.


    Exemplo: Dada a URL:

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

    Ao chamar Parse(), a função atribuirá os seguintes valores:

    • Protocolo: https
    • Domínio: api.example.com
    • Porta: 8080
    • Caminho: /v1/users
    • Query Params: id=123 , active=true


    Isso permite acessar cada parte da URL programaticamente, facilitando a manipulação ou análise.

  • UrlProtocolToStr(ENUM_URL_PROTOCOL protocol): Retorna o protocolo em formato de string, útil para converter ENUM_URL_PROTOCOL em uma string simples, por exemplo:

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

Cada um desses métodos desempenha um papel essencial na construção e manipulação de URLs. Com esses recursos, a biblioteca Connexus se torna altamente flexível para atender às necessidades dinâmicas das APIs, seja criando URLs do zero ou analisando URLs já existentes. Ao implementar esses métodos, os desenvolvedores podem compor URLs programaticamente, evitando erros e otimizando a comunicação com servidores. Abaixo está o código com as funções 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 fim, vamos adicionar mais duas funções para ajudar os desenvolvedores. São elas:

  • ShowData(void): Exibe os elementos da URL separadamente, nos ajudando a depurar e entender quais dados estão armazenados na classe. Por exemplo:

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

A função deve retornar isto:

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

  • Compare(CURL &url): Esta função recebe outra instância da classe CURL. Ela deve retornar true se as URLs armazenadas em ambas as instâncias forem iguais, caso contrário, retorna false. Pode ser útil para evitar a comparação de URLs serializadas, economizando tempo. Exemplo sem a função Compare():
    // Exemplo sem usar o método Compare()
    CURl url1;
    CURl url2;
    
    if(url1.FullUrl() == url2.FullUrl())
      {
       Print("Equals URL");
      }
    
    // Exemplo usando o método Compare()
    CURl url1;
    CURl url2;
    
    if(url1.Compare(url2))
      {
       Print("Equals URL");
      }
        
    
        
    

Abaixo está o código para implementar cada uma dessas funções:

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

Finalizamos ambas as classes para trabalhar com URLs, vamos passar para os testes.


Testes

Agora que temos nossas classes iniciais prontas, vamos criar URLs através das classes e também fazer o processo reverso: a partir de uma URL, vamos separar os elementos usando a classe. Para realizar os testes, vou criar um arquivo chamado TestUrl.mq5 seguindo este caminho: 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);
  }

Ao executar o EA, temos os seguintes dados no terminal:

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


Conclusão

Neste artigo, exploramos em profundidade como o protocolo HTTP funciona, desde conceitos básicos como os verbos HTTP (GET, POST, PUT, DELETE) até os códigos de status de resposta que nos ajudam a interpretar o retorno das requisições. Para facilitar o gerenciamento de URLs em suas aplicações MQL5, construímos a classe CQueryParam, que oferece uma maneira simples e eficiente de manipular os query params. Além disso, implementamos a classe CURL, que permite a modificação dinâmica das partes da URL, tornando o processo de criação e manipulação de requisições HTTP mais flexível e robusto.

Com esses recursos em mãos, você já possui uma boa base para integrar suas aplicações com APIs externas, facilitando a comunicação entre seu código e servidores web. No entanto, estamos apenas começando. No próximo artigo, continuaremos nossa jornada pelo mundo HTTP, onde construiremos classes dedicadas para trabalhar com os headers e body das requisições, permitindo ainda mais controle sobre as interações HTTP.

Fique atento aos próximos posts enquanto construímos uma biblioteca essencial que vai elevar suas habilidades de integração com APIs para o próximo nível!

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15897

Últimos Comentários | Ir para discussão (1)
Denis Kirichenko
Denis Kirichenko | 28 abr. 2025 em 15:28

Material interessante, graças ao autor.

Uma pequena colherada de azedume. Na minha opinião, o nome da classe CURL é muito infeliz. Seria melhor usar algo como CiURL. Porque pode haver confusão com CURL.

Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Expert Advisor Auto-otimizável com MQL5 e Python (Parte IV): Empilhamento de Modelos Expert Advisor Auto-otimizável com MQL5 e Python (Parte IV): Empilhamento de Modelos
Hoje, vamos demonstrar como você pode construir aplicações de trading com IA capazes de aprender com os próprios erros. Vamos demonstrar uma técnica conhecida como stacking (empilhamento), na qual usamos 2 modelos para fazer 1 previsão. O primeiro modelo é tipicamente um aprendiz mais fraco, e o segundo modelo normalmente é um modelo mais poderoso que aprende com os resíduos do nosso aprendiz mais fraco. Nosso objetivo é criar um conjunto de modelos (ensemble), na esperança de alcançar maior acurácia.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Ganhe Vantagem em Qualquer Mercado (Parte IV): Índices de Volatilidade do Euro e do Ouro da CBOE Ganhe Vantagem em Qualquer Mercado (Parte IV): Índices de Volatilidade do Euro e do Ouro da CBOE
Vamos analisar dados alternativos selecionados pela Chicago Board Of Options Exchange (CBOE) para melhorar a precisão de nossas redes neurais profundas ao prever o símbolo XAUEUR.