
HTTP e Connexus (Parte 2): Entendendo a Arquitetura HTTP e o Design de Bibliotecas
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:
- Protocolo: Indica qual protocolo será usado na comunicação, como http ou https. Exemplo: https:// .
- 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).
- 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 .
- Caminho: Especifica o recurso ou rota no servidor. Pode representar páginas, endpoints de API ou arquivos. Exemplo: /api/v1/users .
- 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 .
- 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 ¶ms[]); // Add multiple parameters void RemoveParam(const string key); // Remove a parameter by key string GetParam(const string key) const; // Retrieve a parameter value by key bool HasParam(const string key); // Check if parameter exists int ParamSize(void); // Get the number of parameters //--- Auxiliary methods bool Parse(const string query_param); // Parse a query string string Serialize(void); // Serialize parameters into a query string void Clear(void); // Clear all parameters };
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 ¶ms[]): 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 ¶ms[]); // Add multiple parameters void RemoveParam(const string key); // Remove a parameter by key string GetParam(const string key) const; // Retrieve a parameter value by key bool HasParam(const string key); // Check if parameter exists int ParamSize(void); // Get the number of parameters //--- Auxiliary methods bool Parse(const string query_param); // Parse a query string string Serialize(void); // Serialize parameters into a query string void Clear(void); // Clear all parameters }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CQueryParam::CQueryParam(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CQueryParam::~CQueryParam(void) { } //+------------------------------------------------------------------+ //| Adds a key-value pair to the query parameters | //+------------------------------------------------------------------+ void CQueryParam::AddParam(string key, string value) { m_query_param[key] = value; } //+------------------------------------------------------------------+ //| Adds a single parameter from a formatted string | //+------------------------------------------------------------------+ void CQueryParam::AddParam(string param) { //--- Check if the input string contains an "=" symbol, which indicates a key-value pair if(StringFind(param,"=") >= 0) { //--- Declare an array to hold the key and value after splitting the string string key_value[]; //--- Split the input string using "=" as the delimiter and store the result in the key_value array int size = StringSplit(param,StringGetCharacter("=",0),key_value); //--- If the size of the split result is exactly 2 (meaning a valid key-value pair was found) if(size == 2) { // Add the key-value pair to the m_query_param map // key_value[0] is the key, key_value[1] is the value m_query_param[key_value[0]] = key_value[1]; } } } //+------------------------------------------------------------------+ //| Adds multiple parameters from an array of formatted strings | //+------------------------------------------------------------------+ void CQueryParam::AddParams(const string ¶ms[]) { //--- Get the size of the input array 'params' int size = ArraySize(params); //--- Loop through each element in the 'params' array. for(int i=0;i<size;i++) { //--- Call the AddParam function to add each parameter to the m_query_param map. this.AddParam(params[i]); } } //+------------------------------------------------------------------+ //| Removes a parameter by key | //+------------------------------------------------------------------+ void CQueryParam::RemoveParam(const string key) { m_query_param.Remove(key); } //+------------------------------------------------------------------+ //| Retrieves a parameter value by key | //+------------------------------------------------------------------+ string CQueryParam::GetParam(const string key) const { return(m_query_param[key].ToString()); } //+------------------------------------------------------------------+ //| Checks if a parameter exists by key | //+------------------------------------------------------------------+ bool CQueryParam::HasParam(const string key) { return(m_query_param.FindKey(key) != NULL); } //+------------------------------------------------------------------+ //| Returns the number of parameters stored | //+------------------------------------------------------------------+ int CQueryParam::ParamSize(void) { return(m_query_param.Size()); } //+------------------------------------------------------------------+ //| Parses a query string into parameters | //| Input: query_param - A string formatted as a query parameter | //| Output: bool - Always returns true, indicating successful parsing| //+------------------------------------------------------------------+ bool CQueryParam::Parse(const string query_param) { //--- Split the input string by '&', separating the individual parameters string params[]; int size = StringSplit(query_param, StringGetCharacter("&",0), params); //--- Iterate through each parameter string for(int i=0; i<size; i++) { //--- Split each parameter string by '=', separating the key and value string key_value[]; StringSplit(params[i], StringGetCharacter("=",0), key_value); //--- Check if the split resulted in exactly two parts: key and value if (ArraySize(key_value) == 2) { //--- Assign the value to the corresponding key in the map m_query_param[key_value[0]] = key_value[1]; } } //--- Return true indicating that parsing was successful return(true); } //+------------------------------------------------------------------+ //| Serializes the stored parameters into a query string | //| Output: string - A string representing the serialized parameters | //+------------------------------------------------------------------+ string CQueryParam::Serialize(void) { //--- Initialize an empty string to build the query parameter string string query_param = ""; //--- Iterate over each key-value pair in the parameter map for(int i=0; i<m_query_param.Size(); i++) { //--- Append a '?' at the beginning to indicate the start of parameters if(i == 0) { query_param = "?"; } //--- Construct each key-value pair as 'key=value' if(i == m_query_param.Size()-1) { //--- If it's the last pair, don't append '&' query_param += m_query_param[i].m_key + "=" + m_query_param[i].ToString(); } else { //--- Otherwise, append '&' after each pair query_param += m_query_param[i].m_key + "=" + m_query_param[i].ToString() + "&"; } } //--- Return the constructed query parameter string return(query_param); } //+------------------------------------------------------------------+ //| Clears all stored parameters | //+------------------------------------------------------------------+ void CQueryParam::Clear(void) { m_query_param.Clear(); } //+------------------------------------------------------------------+
URL
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





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
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.