English Русский 中文 Español Deutsch 日本語
preview
Cliente no Connexus (Parte 7): Adicionando a camada de cliente

Cliente no Connexus (Parte 7): Adicionando a camada de cliente

MetaTrader 5Exemplos |
21 0
joaopedrodev
joaopedrodev

Introdução

Este artigo é a continuação de uma série de textos em que estamos criando uma biblioteca chamada Connexus. No primeiro artigo, entendemos os fundamentos da função WebRequest, analisamos cada um de seus parâmetros e ainda criamos um exemplo de código que demonstrava o uso dessa função e suas dificuldades. No artigo anterior, estudamos o que são métodos http e também os códigos de status retornados pelo servidor, que informam se a requisição foi processada com sucesso ou se ocorreu algum erro gerado pelo cliente ou pelo servidor.

Neste sétimo artigo da série, vamos adicionar a parte mais esperada de toda a biblioteca, vamos fazer uma requisição usando a função WebRequest, não vamos criar o acesso direto a ela, e no processo serão utilizados alguns classes e interfaces. Vamos lá!

Só para lembrar o estado atual da biblioteca, aqui está o esquema atual:

O objetivo aqui é obter um objeto CHttpRequest, ou seja, uma requisição HTTP pronta, já configurada com cabeçalho, corpo, URL, método e tempo limite, e enviar a requisição HTTP de forma eficiente por meio da função WebRequest. Ele também deve processar a requisição e retornar um objeto CHttpResponse com os dados da resposta, como cabeçalho, corpo, código de status e a duração total da requisição.


Criação da classe CHttpClient

Vamos criar a classe mais esperada de todas, a última da biblioteca, que será a classe instanciada e utilizada pelo usuário da biblioteca. Essa classe se chamará CHttpClient.mqh e estará localizada no seguinte caminho: Include/Connexus/Core/CHttpClient.mqh. Inicialmente, o arquivo ficará parecido com este, já adicionei a importação correspondente:
//+------------------------------------------------------------------+
//|                                                   HttpClient.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "HttpRequest.mqh"
#include "HttpResponse.mqh"
#include "../Constants/HttpMethod.mqh"
//+------------------------------------------------------------------+
//| class : CHttpClient                                              |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CHttpClient                                        |
//| Heritage    : No heritage                                        |
//| Description : Class responsible for linking the request and      |
//|               response object with the transport layer.          |
//|                                                                  |
//+------------------------------------------------------------------+
class CHttpClient
  {
public:
                     CHttpClient(void);
                    ~CHttpClient(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHttpClient::CHttpClient(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CHttpClient::~CHttpClient(void)
  {
  }
//+------------------------------------------------------------------+
Esta classe tem a função de converter o objeto de requisição, ou seja, o CHttpRequest, em uma requisição HTTP utilizando a função WebRequest e devolver um objeto CHttpResponse com os dados. Vamos criar essa classe. Para isso, vamos criar o método Send(), que deve receber dois objetos, um CHttpRequest e outro CHttpResponse, e deve retornar um valor boolean.
class CHttpClient
  {
public:
                     CHttpClient(void);
                    ~CHttpClient(void);
   
   //--- Basis function
   bool              Send(CHttpRequest &request, CHttpResponse &response);
  };

Vamos trabalhar na implementação dessa função. Primeiro, vamos revisar rapidamente os parâmetros da função WebRequest. Ela possui 2 variações, e inicialmente vamos usar a que tem menos parâmetros:

int  WebRequest(
   const string      method,           // HTTP method
   const string      url,              // URL
   const string      headers,          // headers 
   int               timeout,          // timeout
   const char        &data[],          // the array of the HTTP message body
   char              &result[],        // an array containing server response data
   string            &result_headers   // headers of server response
);
Já temos todos esses parâmetros prontos e configurados nos objetos recebidos, portanto basta adicioná-los. A implementação ficará assim:
//+------------------------------------------------------------------+
//| Basis function                                                   |
//+------------------------------------------------------------------+
bool CHttpClient::Send(CHttpRequest &request, CHttpResponse &response)
  {
   //--- 1. Request
   uchar body_request[];
   request.Body().GetAsBinary(body_request);
   
   //--- 2. Response
   uchar body_response[];
   string headers_response;
   
   //--- 3. Send
   ulong start = GetMicrosecondCount();
   int status_code = WebRequest(
      request.Method().GetMethodDescription(),     // HTTP method
      request.Url().FullUrl(),                     // URL
      request.Header().Serialize(),                // Headers
      request.Timeout(),                           // Timeout
      body_request,                                // The array of the HTTP message body
      body_response,                               // An array containing server response data
      headers_response                             // Headers of server response
   );
   ulong end = GetMicrosecondCount();
   
   //--- 4. Add data in Response
   response.Clear();
   response.Duration((end-start)/1000);
   response.StatusCode() = status_code;
   response.Body().AddBinary(body_response);
   response.Header().Parse(headers_response);
   
   //--- 5. Return is success
   return(response.StatusCode().IsSuccess());
  }
//+------------------------------------------------------------------+

Adicionei alguns números nos comentários apenas para explicar cada etapa abaixo, e é muito importante que você entenda o que está acontecendo aqui, pois esta é a função central da biblioteca:

  1. REQUISIÇÃO: Aqui vamos criar um array do tipo uchar chamado body_request. Temos acesso ao corpo da requisição e obtemos esse corpo em formato binário passando o array body_request. Assim, esse array será alterado, recebendo os dados do corpo.
  2. RESPOSTA: Criamos duas variáveis que serão usadas pela função WebRequest para armazenar o corpo e o cabeçalho da resposta do servidor.
  3. ENVIAR: Esta é a parte principal de tudo, chamamos a função WebRequest e passamos todos os parâmetros, incluindo URL e timeout, além das variáveis criadas no passo 2 para receber o corpo e o cabeçalho da resposta. Aqui também criamos duas outras variáveis auxiliares que utilizam a função GetMicrosecondCount() para armazenar o tempo antes e depois da requisição. Assim, obtemos o tempo inicial e final para calcular a duração da requisição em milissegundos.
  4. ADICIONAR DADOS À RESPOSTA: Aqui, após receber a resposta da requisição, independentemente de ter sido bem-sucedida ou não, usamos a função Clear() para reinicializar todos os valores do objeto response e adicionamos a duração usando a fórmula: (fim - início)/1000. Também definimos o código de status retornado pela função e adicionamos o texto e o cabeçalho ao objeto response.
  5. RETORNO: No último passo, verificamos se a requisição retornou algum código de sucesso (de 200 a 299). Assim podemos saber se a requisição foi executada, e para obter informações mais detalhadas basta verificar o conteúdo do objeto response, ou seja, do CHttpResponse.

Com a criação desta classe, o esquema atualizado ficará parecido com este:

No fim, todas as classes estão dentro de HttpClient. Vamos executar um teste simples.



Primeiro teste

Faremos o primeiro e mais simples teste com tudo o que criamos até agora. Vamos enviar uma requisição GET e uma requisição POST para httpbin.org, que é um serviço online gratuito para testar requisições HTTP. Ele foi criado por kennethreitz, é um projeto OpenSource (link). Já usamos essa API pública em artigos anteriores, mas resumidamente explico que o httpbin funciona como um espelho, ou seja, tudo o que enviamos ao servidor é retornado para o cliente. Isso é muito útil para testar aplicações em que queremos apenas confirmar se a requisição foi executada e quais dados o servidor recebeu.

Passando ao código, vamos criar um novo arquivo na pasta Experts com o nome TestRequest.mq5, o caminho final será Experts/Connexus/Test/TestRequest.mq5. Usaremos apenas a função OnInit do arquivo, então descartaremos as demais. O código ficará assim:

//+------------------------------------------------------------------+
//|                                                  TestRequest.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Initialize
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Importamos a biblioteca, adicionando a importação com #include

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Connexus/Core/HttpClient.mqh>

Agora vamos criar três objetos:

  • CHttpRequest: Onde serão definidos o URL e o método HTTP. O tempo limite, o corpo e o cabeçalho são opcionais em requisições GET, o valor padrão do tempo limite é 5000 milissegundos (5 segundos).
  • CHttpResponse: Armazena o resultado da requisição com o código de status e a descrição, além da duração da requisição em milissegundos.
  • CHttpClient: Classe que de fato executa a requisição
//--- Request object
CHttpRequest request;
request.Method() = HTTP_METHOD_GET;
request.Url().Parse("https://httpbin.org/get");

//--- Response object
CHttpResponse response;

//--- Client http object
CHttpClient client;

E, por fim, chamamos a função Send() para enviar a requisição e exibir os dados no terminal. O código completo fica assim:

//+------------------------------------------------------------------+
//|                                                  TestRequest.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Connexus/Core/HttpClient.mqh>
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Request object
   CHttpRequest request;
   request.Method() = HTTP_METHOD_GET;
   request.Url().Parse("https://httpbin.org/get");
   
   //--- Response object
   CHttpResponse response;
   
   //--- Client http object
   CHttpClient client;
   client.Send(request,response);
   Print(response.FormatString());
   
   //--- Initialize
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
Ao executar o código, obtemos o seguinte resultado no terminal:
HTTP Response:
---------------
Status Code: HTTP_STATUS_OK [200] - OK
Duration: 845 ms

---------------
Headers:
Date: Fri, 18 Oct 2024 17:52:35 GMT
Content-Type: application/json
Content-Length: 380
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
---------------
Body:
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Accept-Language": "pt,en;q=0.5", 
    "Host": "httpbin.org", 
    "User-Agent": "MetaTrader 5 Terminal/5.4620 (Windows NT 11.0.22631; x64)", 
    "X-Amzn-Trace-Id": "Root=1-6712a063-5c19d0e03b85df9903cb0e91"
  }, 
  "origin": "XXX.XX.XX.XX", 
  "url": "https://httpbin.org/get"
}

---------------

Esses são os dados do objeto response; temos o código de status com a descrição, a duração da requisição em milissegundos, o cabeçalho e o texto da resposta. Isso facilita muito a vida dos desenvolvedores ao conectar seus programas MQL5 a APIs externas. Conseguimos tornar a requisição HTTP muito simples e, só para comparar a evolução, lembro como funciona uma requisição sem usar a biblioteca e outra usando-a:

  • Sem a biblioteca Connexus:
    int OnInit()
      {
    //--- Defining variables
       string method = "GET";                    // HTTP verb in string (GET, POST, etc...)
       string url = "https://httpbin.org/get";   // Destination URL
       string headers = "";                      // Request header
       int timeout = 5000;                       // Maximum waiting time 5 seconds
       char data[];                              // Data we will send (body) array of type char
       char result[];                            // Data received as an array of type char
       string result_headers;                    // String with response headers
       
       string body = "{\"key\":\"value\"}";
       StringToCharArray(body,data,0,WHOLE_ARRAY,CP_UTF8);
       ArrayRemove(data,ArraySize(data)-1);
       
    //--- Calling the function and getting the status_code
       int status_code = WebRequest(method,url,headers,timeout,data,result,result_headers);
       
    //--- Print the data
       Print("Status code: ",status_code);
       Print("Response: ",CharArrayToString(result)); // We use CharArrayToString to display the response in string form.
    
    //---
       return(INIT_SUCCEEDED);
      }
  • Com a biblioteca Connexus:
    int OnInit()
      {
       //--- Request object
       CHttpRequest request;
       request.Method() = HTTP_METHOD_GET;
       request.Url().Parse("https://httpbin.org/get");
       request.Body().AddString("{\"key\":\"value\"}");
       
       //--- Response object
       CHttpResponse response;
       
       //--- Client http object
       CHttpClient client;
       client.Send(request,response);
       Print(response.FormatString());
       
       //--- Initialize
       return(INIT_SUCCEEDED);
      }

É muito mais fácil trabalhar com a biblioteca, acessar o corpo em vários formatos, como string, json ou binary, e todos os outros recursos disponíveis. Também é possível alterar partes da requisição, como o URL, o corpo e o cabeçalho, e usar o mesmo objeto para executar outra requisição. Isso permite fazer várias requisições em sequência de forma simplificada.


Temos um pequeno problema, ACOPLAMENTO (COUPLING)!

Parece que tudo funciona corretamente, e de fato funciona, mas tudo pode ser melhorado e, aqui, na biblioteca Connexus, não é diferente. Mas, afinal, qual é esse problema? Ao usar a função WebRequest diretamente na classe CHttpClient, obtemos um código muito acoplado, mas o que é um código muito acoplado?

Imagine o seguinte: você está construindo um castelo de cartas. Cada carta precisa estar perfeitamente equilibrada para que tudo permaneça em pé. Agora imagine que, em vez de um castelo leve e flexível, no qual você pode mover uma carta sem derrubar tudo, você tem algo parecido com um bloco de concreto. Se você tirar uma carta ou movê-la, adivinha o que acontece? O castelo inteiro desaba. É exatamente isso que ocorre com um código acoplado.

Quando falamos de acoplamento em programação, é como se partes do seu código estivessem tão presas umas às outras que, ao mover uma, você precisasse mover várias outras também. É como aquele nó apertado que você faz sem querer no cadarço do sapato, e agora é difícil de desfazer sem estragar tudo de vez. Alterar uma função, modificar uma classe ou adicionar algo novo se torna uma dor de cabeça, porque tudo está grudado.

Por exemplo, se você tem uma função que depende fortemente de outra função, e essas funções não conseguem "viver" separadamente, o código está fortemente acoplado. E isso é ruim, porque com o tempo se torna difícil dar manutenção, adicionar novas funcionalidades ou até mesmo depurar sem correr o risco de quebrar algo. É exatamente isso que acontece dentro da classe CHttpClient, onde a função Send() depende diretamente da WebRequest; no estado atual, não é possível separar uma da outra. Em um mundo ideal, precisamos de um código "desacoplado", em que cada parte possa funcionar de forma independente, como peças de Lego: é possível encaixar e remover sem grandes problemas.

Para criar um código menos acoplado, usamos interfaces e injeção de dependência. A ideia é criar uma interface que defina as operações necessárias, permitindo que a classe dependa dessa abstração em vez de uma implementação específica. Dessa forma, o CHttpClient pode interagir com qualquer objeto que implemente a interface, seja o WebRequest ou uma função mock (FakeWebRequest). Vamos entender a ideia de interfaces e injeção de dependência:

  • Papel das interfaces: As interfaces permitem definir um contrato que outros classes podem implementar. No nosso caso, podemos criar uma interface chamada IHttpTransport, que definirá os métodos necessários para o CHttpClient executar a requisição HTTP. Assim, o CHttpClient passará a depender da interface IHttpTransport em vez da função WebRequest, o que reduz o acoplamento.
  • Injeção de dependência: Para conectar o CHttpClient a uma implementação específica de transporte HTTP, usaremos a injeção de dependência. Esse método consiste em passar a dependência (WebRequest ou FakeWebRequest) como parâmetro para o CHttpClient, em vez de instanciá-la diretamente dentro da classe.

Quais são as vantagens de não deixar o código acoplado?

  • Manutenibilidade: Com um código desacoplado, se você precisar mudar algo, é como substituir uma peça do carro sem desmontar todo o motor. As partes do seu código são independentes, então a alteração de uma não afeta as outras. Quer corrigir um erro ou melhorar alguma função? Vá em frente e faça as mudanças sem medo de comprometer o restante do sistema.
  • Reutilização: Imagine que você tem peças de Lego; com um código desacoplado, você pode pegar blocos funcionais e usá-los em outros projetos ou em outras partes do sistema, sem ficar preso a dependências complicadas.
  • Testabilidade: Ao usar uma interface, torna-se possível substituir o WebRequest por uma função mock, que simula o comportamento esperado, permitindo realizar testes sem depender de requisições HTTP reais. Essa conceito de simulação nos leva à próxima parte: mocks.


O que são Mocks?

Mocks são versões simuladas de objetos ou funções reais, usadas para testar o comportamento de uma classe sem depender de implementações externas. Quando separamos o CHttpClient do WebRequest por meio de uma interface, temos a possibilidade de passar uma versão simulada que cumpre o mesmo contrato, chamada de "mock". Assim, podemos testar o CHttpClient em um ambiente controlado e prever como ele reagirá em diferentes cenários de resposta, sem realmente executar chamadas HTTP.

Em um sistema onde o CHttpClient faz chamadas diretas ao WebRequest, os testes são limitados e dependem da conexão e do comportamento do servidor. No entanto, ao injetar uma função mock do WebRequest, que retorna resultados simulados, podemos testar diversos cenários e verificar isoladamente a resposta do CHttpClient. Esses mocks são extremamente úteis para garantir que a biblioteca se comporte conforme o esperado, mesmo em situações de erro ou respostas inesperadas.

A seguir, veremos como implementar interfaces e mocks para aumentar a flexibilidade e a testabilidade da nossa classe CHttpRequest.

Mão no código

Na prática, como vamos fazer isso no código? Vamos utilizar alguns recursos mais avançados da linguagem, como classes e interfaces. Se você chegou até aqui e leu os artigos anteriores, já está em certa medida familiarizado com classes, mas vamos revisar o conceito.

  • Classe: Como um "molde" para criar um objeto, dentro de uma classe você pode definir métodos e atributos. Métodos são ações ou comportamentos, enquanto atributos são características ou dados do objeto. Vamos considerar um exemplo no contexto do MQL5. Você tem uma classe chamada CPosition. Nessa classe, é possível definir atributos como preço, tipo (compra ou venda), take-profit, stop-loss, volume etc. A classe é usada para criar objetos (instâncias), e cada objeto possui suas próprias características únicas.
  • Interface: Uma interface é como um "contrato", que define apenas os métodos que a classe deve implementar, mas não especifica como esses métodos devem ser implementados. A interface não contém implementação, apenas a assinatura dos métodos (nomes, parâmetros e tipos de retorno).

Seguindo esse conceito, vamos analisar um exemplo prático dentro da biblioteca. Vamos por etapas. Queremos acessar a função WebRequest, mas não quero chamá-la diretamente, pois vimos que isso deixa o código excessivamente acoplado. Quero que você tenha a sua própria função WebRequest e que a biblioteca consiga usá-la sem complicações. Vamos criar camadas entre a função e quem a chama. Essa camada será a interface.

Antes de irmos para o código, vou explicar isso com a ajuda de alguns esquemas.

Como mostrado no esquema, a classe HttpClient chama diretamente a função WebRequest. Vamos adicionar uma camada entre a classe e a função, e essa camada será a interface IHttpTransport.

Com essa interface, podemos criar uma classe CHttpTransport para implementar essa interface utilizando a função WebRequest. Como mostrado no esquema abaixo:

Agora podemos aproveitar tudo isso: é possível criar várias implementações diferentes da interface e passá-las para a classe CHttpClient. Inicialmente, a biblioteca usará a CHttpTransport, que tem sua implementação baseada na WebRequest, mas podemos adicionar quantas outras quisermos, como mostrado no esquema:

Na prática, a estrutura de pastas ficará assim: criaremos uma nova pasta chamada "Interface" e dois outros arquivos, conforme indicado abaixo:
|--- Connexus
|--- |--- Core
|--- |--- |--- HttpTransport.mqh
|--- |--- Interface
|--- |--- |--- IHttpTransport.mqh

Primeiro, criamos o código da interface, que define as entradas e saídas da função:

//+------------------------------------------------------------------+
//| transport interface                                              |
//+------------------------------------------------------------------+
interface IHttpTransport
  {
   int               Request(const string method,const string url,const string cookie,const string referer,int timeout,const char &data[],int data_size,char &result[],string &result_headers);
   int               Request(const string method,const string url,const string headers,int timeout,const char &data[],char &result[],string &result_headers);
  };
//+------------------------------------------------------------------+
Note que aqui apenas declaro a função, não defino o corpo dela. Informo ao compilador que a função deve retornar um número inteiro, o nome do método será Request, e indico os parâmetros esperados. Para definir o corpo, precisamos usar uma classe, e então dizemos ao compilador que a classe deve implementar a interface. Na prática, isso fica assim:
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "../Interface/IHttpTransport.mqh"
//+------------------------------------------------------------------+
//| class : CHttpTransport                                           |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CHttpTransport                                     |
//| Heritage    : IHttpTransport                                     |
//| Description : class that implements the transport interface,     |
//|               works as an extra layer between the request and    |
//|               the final function and WebRequest.                 |
//|                                                                  |
//+------------------------------------------------------------------+
class CHttpTransport : public IHttpTransport
  {
public:
                     CHttpTransport(void);
                    ~CHttpTransport(void);

   int               Request(const string method,const string url,const string cookie,const string referer,int timeout,const char &data[],int data_size,char &result[],string &result_headers);
   int               Request(const string method,const string url,const string headers,int timeout,const char &data[],char &result[],string &result_headers);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHttpTransport::CHttpTransport(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CHttpTransport::~CHttpTransport(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CHttpTransport::Request(const string method,const string url,const string cookie,const string referer,int timeout,const char &data[],int data_size,char &result[],string &result_headers)
  {
   return(WebRequest(method,url,cookie,referer,timeout,data,data_size,result,result_headers));
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CHttpTransport::Request(const string method,const string url,const string headers,int timeout,const char &data[],char &result[],string &result_headers)
  {
   return(WebRequest(method,url,headers,timeout,data,result,result_headers));
  }
//+------------------------------------------------------------------+

Usando interfaces e mocks, separamos a classe CHttpRequest da função WebRequest, criando uma biblioteca mais flexível e de fácil manutenção. Essa abordagem oferece aos desenvolvedores que utilizam o Connexus a possibilidade de testar o comportamento da biblioteca em diferentes cenários sem precisar executar requisições reais, garantindo uma integração mais suave e a capacidade de adaptação rápida a novas necessidades e funcionalidades.

Agora só precisamos usar isso na classe CHttpClient, então importamos o IHttpTransport e criamos uma instância:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "../Interface/IHttpTransport.mqh"
//+------------------------------------------------------------------+
//| class : CHttpClient                                              |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CHttpClient                                        |
//| Heritage    : No heritage                                        |
//| Description : Class responsible for linking the request and      |
//|               response object with the transport layer.          |
//|                                                                  |
//+------------------------------------------------------------------+
class CHttpClient
  {
private:
   
   IHttpTransport    *m_transport;                       // Instance to store the transport implementation
   
public:
                     CHttpClient(void);
                    ~CHttpClient(void);
   
   //--- Basis function
   bool              Send(CHttpRequest &request, CHttpResponse &response);
  };
//+------------------------------------------------------------------+

Para realizar a injeção de dependência, adicionamos ao classe um novo construtor que receberá a camada de transporte a ser usada e a armazenará em m_transport. Também alteramos o construtor padrão para que, quando a camada de transporte não for definida, ele automaticamente seja implementado com CHttpTransport:

//+------------------------------------------------------------------+
//| class : CHttpClient                                              |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CHttpClient                                        |
//| Heritage    : No heritage                                        |
//| Description : Class responsible for linking the request and      |
//|               response object with the transport layer.          |
//|                                                                  |
//+------------------------------------------------------------------+
class CHttpClient
  {
private:
   
   IHttpTransport    *m_transport;                       // Instance to store the transport implementation
   
public:
                     CHttpClient(IHttpTransport *transport);
                     CHttpClient(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHttpClient::CHttpClient(IHttpTransport *transport)
  {
   m_transport = transport;
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHttpClient::CHttpClient(void)
  {
   m_transport = new CHttpTransport();
  }
//+------------------------------------------------------------------+

Agora que temos a camada de transporte, vamos utilizá-la na função "Send" (Enviar).

//+------------------------------------------------------------------+
//| Basis function                                                   |
//+------------------------------------------------------------------+
bool CHttpClient::Send(CHttpRequest &request, CHttpResponse &response)
  {
   //--- Request
   uchar body_request[];
   request.Body().GetAsBinary(body_request);
   
   //--- Response
   uchar body_response[];
   string headers_response;
   
   //--- Send
   ulong start = GetMicrosecondCount();
   int status_code = m_transport.Request(request.Method().GetMethodDescription(),request.Url().FullUrl(),request.Header().Serialize(),request.Timeout(),body_request,body_response,headers_response);
   ulong end = GetMicrosecondCount();
   
   //--- Add data in Response
   response.Clear();
   response.Duration((end-start)/1000);
   response.StatusCode() = status_code;
   response.Body().AddBinary(body_response);
   response.Header().Parse(headers_response);
   
   //--- Return is success
   return(response.StatusCode().IsSuccess());
  }
//+------------------------------------------------------------------+

É importante lembrar que a forma de usar a classe não muda, ou seja, os mesmos códigos que utilizamos anteriormente nesta série, na parte de testes, continuam funcionando. A diferença é que agora podemos substituir a função final da biblioteca.


Considerações finais

Com isso, concluímos mais uma etapa no desenvolvimento da biblioteca: a criação da camada de cliente. Usando interfaces e mocks, separamos a classe CHttpClient da função WebRequest, construindo uma biblioteca mais flexível e de fácil manutenção. Esse método oferece aos desenvolvedores que utilizam o Connexus a capacidade de testar o comportamento da biblioteca em diferentes cenários sem executar chamadas reais, garantindo uma integração mais fluida e a adaptação rápida a novas necessidades e funcionalidades.

A prática de desacoplamento com o uso de interfaces e mocks aumenta significativamente a qualidade do código, promovendo extensibilidade, testabilidade e manutenção de longo prazo. Essa abordagem é especialmente vantajosa em bibliotecas, onde os desenvolvedores frequentemente precisam de testes flexíveis e substituição de módulos, o que agrega ainda mais valor para todos os usuários da biblioteca Connexus.

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

Criando um painel de administração de trading em MQL5 (Parte VII): Usuário confiável, recuperação e criptografia Criando um painel de administração de trading em MQL5 (Parte VII): Usuário confiável, recuperação e criptografia
Alertas de segurança, como aqueles que aparecem sempre que o gráfico é atualizado, uma nova par é adicionada ao chat do painel administrativo do EA ou o terminal é reiniciado, podem se tornar cansativos. Nesta discussão, vamos analisar e implementar uma função que rastreia o número de tentativas de login para identificar um usuário confiável. Após um determinado número de tentativas malsucedidas, o aplicativo passará para um procedimento avançado de login, que também facilita a recuperação de senha para usuários que possam tê-la esquecido. Além disso, veremos como é possível integrar de forma eficiente a criptografia no painel administrativo para aumentar a segurança.
Simulação de mercado: Position View (XI) Simulação de mercado: Position View (XI)
Neste artigo, mostrarei como você, meu caro e estimado leitor, pode sem muito esforço. Conseguir modificar o indicador de posição a fim de que ele venha a ser capaz de fazer bem mais coisas, do que originalmente era capaz de fazer. Veremos como incluir a capacidade de podermos mover tanto os preços, quanto também criar as linhas de preço. E isto diretamente no gráfico. Algo que muitos imaginariam ser extremamente complicado e de difícil solução. Porém você notará que faremos tudo isto, com muita facilidade e com um mínimo de esforço. Tudo que será preciso fazer é parar e pensar um pouco.
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.
Do básico ao intermediário: Filas, Listas e Árvores (III) Do básico ao intermediário: Filas, Listas e Árvores (III)
Neste artigo iremos dar o que será o próximo passo a fim de implementar e entender o que seria e como funciona uma lista encadeada. Apesar do conteúdo aqui, ser de certa maneira bastante denso e confuso para quem está iniciando. Procurei deixar as coisas o mais didática possível. Assim, você conseguirá entender por que e quando usar uma lista encadeada.