
Client in Connexus (Part 7): Adding the Client Layer
Introduction
This article is the continuation of a series of articles where we will build a library called Connexus. In the first article, we understood the basic functioning of the WebRequest function, understanding each of its parameters and also creating an example code that demonstrates the use of this function and its difficulties. In the last article, we understood what the http methods are and also what the status codes are that are returned by the server, which inform whether a request was processed successfully or if an error was generated by the client or server.
In this seventh article of the series, we will add the most anticipated part of the entire library, we will make the request using the WebRequest function, we will not directly create access to it, there will be some classes and interfaces in the process. Let's go!
Just to remind you of the current state of the library, this is the current diagram:
The goal here is to receive a CHttpRequest object, that is, a ready HTTP request, already configured with header, body, url, method and timeout and effectively send an HTTP request using the WebRequest function. It must also process the request and return a CHttpResponse object, with the response data such as header, body, status code and total duration of the request.
Creating the CHttpClient class
Let's create the most anticipated class of all, the final class of the library, which will be the one that the library user will instantiate and use. This class will be called CHttpClient.mqh, and will be located in the following path: Include/Connexus/Core/CHttpClient.mqh. Initially the file will be similar to this, I have already added the appropriate imports://+------------------------------------------------------------------+ //| 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) { } //+------------------------------------------------------------------+This class has the role of transforming a request object, that is, CHttpRequest into an HTTP request using the WebRequest function, and returning a CHttpResponse object with the data. Let's create this class. To do this, we will create a Send() method, which must receive two objects, one CHttpRequest and another CHttpResponse, and must return boolean.
class CHttpClient { public: CHttpClient(void); ~CHttpClient(void); //--- Basis function bool Send(CHttpRequest &request, CHttpResponse &response); };
Let's work on implementing this function. First, let's briefly review the parameters of the WebRequest function. It has 2 variants and initially we'll use the one with fewer parameters:
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 );We already have all these parameters ready and configured in the objects that are received, so just add them. The implementation would be like this:
//+------------------------------------------------------------------+ //| 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()); } //+------------------------------------------------------------------+
I added some numbers in the comments just to explain each step below, and it is very important that you understand what is happening here, as this is the central function of the library:
- REQUEST: Here we create an array of type uchar called body_request. We access the body of the request and obtain the body of the request in binary by passing the array body_request, this way it will change this array by inserting the body data inside it.
- RESPONSE: We create two variables that will be used by the WebRequest function to return the body and the header of the response from the server.
- SEND: This is the heart of everything, we call the WebRequest function and pass all the parameters, including URL and timeout, and the variables created in step 2 to receive the body and header of the response. Here we also create two other auxiliary variables that use the GetMicrosecondCount() function to store the time before and after the request. This way we get the time before and after the request to calculate the duration of the request in milliseconds.
- ADD DATA IN RESPONSE: Here, after the request has responded, regardless of whether it was successful or not, we use the Clear() function to reset all the values of the response object, and we add the duration using the following formula: (end - start)/1000. We also define the status code returned by the function and add the body and header to the response object.
- RETURN: In the last step, we check whether the request returned any success code (between 200 and 299). This way, it is possible to know if the request was completed, and for more details, just check the content of the response object, that is, CHttpResponse .
With the creation of this class, the updated diagram would be similar to this:
In the end, all classes are in HttpClient. Let's perform a simple test.
First test
Let's perform the first and simplest test with everything we've built so far. Let's make a GET request and a POST request to httpbin.org, which is a free online service that allows you to test HTTP requests. It was created by kennethreitz, and it's an OpenSource project (link). We've already used this public API in previous articles, but to explain it briefly, httpbin works like a mirror, that is, everything we send to the server is returned to the client. This is very useful for testing applications, where we simply want to know if the request was completed and what data the server received.
Going to the code, let's create a new file inside the Experts folder called TestRequest.mq5 , the final path will be Experts/Connexus/Test/TestRequest.mq5 . We will only use the OnInit function from the file, so we discard the rest. The code will look like this:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Let's import the library, adding the import using #include
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Connexus/Core/HttpClient.mqh>
Now, let's create three objects:
- CHttpRequest : Where the URL and HTTP method will be defined. Timeout, body and header are optional in GET requests, the timeout has the default value of 5000 milliseconds (5 seconds).
- CHttpResponse : Stores the result of the request with the status code and description, and also the duration of the request in milliseconds.
- CHttpClient : Class that actually performs the request
//--- 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;
And finally, we call the Send() function to make the request and print the data on the terminal. The complete code looks like this:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+When executing the code we have this result in the 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" } ---------------
This is the data from the response object, we have the status code with description, duration of the request in milliseconds, header and response body. This makes the developer's life much easier when connecting their MQL5 programs with external APIs. We managed to make an HTTP request very simple, just to have a comparison of the evolution, I will remind you how a request works without using the library and another using it:
- Without the Connexus library:
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); }
- With the Connexus library:
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); }
It is much easier to work with the library, access the body in different formats, such as string, json or binary and all other available resources. It is also possible to change parts of the request such as URL, body and header and use the same object to make another request. This allows you to make several requests in a row in a simplified way.
We have a small problem, COUPLING!
It seems that everything is working correctly, and it really is, but everything can be improved and here in the Connexus library it is no different. But after all, what is this problem? Using the WebRequest function directly within the CHttpClient class, we have a very coupled code, but what is a very coupled code?
Imagine the following: you are building a house of cards. Each card needs to balance perfectly so that everything stays upright. Now, think that instead of a light and flexible castle, where you can move a card without knocking everything down, you have something that looks like a concrete block. If you take a card or move one, guess what? The whole castle comes down. That is what happens with coupled code.
When we talk about coupling in programming, it is as if the parts of your code were so tied to each other that, if you move one, you need to move several others too. It's like that tight knot you accidentally tied in your shoelace and now it's hard to untie without messing everything up. Changing a function, modifying a class or adding something new becomes a headache, because everything is stuck together.
For example, if you have a function that strongly depends on another function and these functions can't "live" separately, then the code is tightly coupled. And this is bad because, over time, it becomes difficult to maintain, add new features or even fix bugs without running the risk of breaking something. This is exactly what happens inside the CHttpClient class, where the Send() function directly depends on WebRequest ; it's not possible to separate one from the other currently. In an ideal world, we want "decoupled" code, where each part can work on its own, like Lego pieces: you can fit and unfit them without any major problems.
To create less coupled code, we use interfaces and dependency injection. The idea is to create an interface that defines the necessary operations, allowing the class to depend on this abstraction instead of a specific implementation. Thus, CHttpClient can communicate with any object that implements the interface, be it WebRequest or a mock function ( FakeWebRequest ). Let's look at the concept of interfaces and dependency injection:
- The Role of Interfaces: Interfaces allow us to define a contract that other classes can implement. In our case, we can create an interface called IHttpTransport , which will define the methods that CHttpClient needs to perform an HTTP request. This way, CHttpClient will depend on the IHttpTransport interface, and not on the WebRequest function, reducing coupling.
- Dependency Injection: To connect CHttpClient to the concrete implementation of IHttpTransport , we will use dependency injection. This technique consists of passing the dependency ( WebRequest or FakeWebRequest ) as a parameter to CHttpClient , instead of instantiating it directly inside the class.
What are the advantages of not leaving the code coupled?
- Maintainability: With decoupled code, if you need to change something, it's like replacing a part of a car without disassembling the entire engine. The parts of your code are independent, so changing one doesn't affect the others. Want to fix a bug or improve a feature? Go ahead and make the change without fear of breaking the rest of the system.
- Reusability: Think of it like having Lego pieces, with decoupled code, you can take blocks of functionality and use them in other projects or parts of the system without being tied to complicated dependencies.
- Testability: By using an interface, it becomes possible to replace WebRequest with a mock function that imitates the expected behavior, allowing you to perform tests without depending on real HTTP requests. This concept of simulation brings us to the next topic: mocks.
What are Mocks?
Mocks are simulated versions of real objects or functions, used to test the behavior of a class without relying on external implementations. When we separate CHttpClient from WebRequest using an interface, we have the option of passing a simulated version that fulfills the same contract, called a "mock". This way, we can test CHttpClient in a controlled environment and predict how it would react in different response scenarios, without actually making HTTP calls.
In a system where CHttpClient makes direct calls to WebRequest , testing is limited and dependent on the connectivity and behavior of the server. However, by injecting a mock of the WebRequest function that returns simulated results, we can test different scenarios and validate the response of CHttpClient in isolation. These mocks are very useful to ensure that the library behaves as expected, even in error situations or unexpected responses.
Next, we'll see how to implement interfaces and mocks to improve the flexibility and testability of our CHttpRequest class.
Hands on Code
In practice, how are we going to do this in code? We are going to use some more advanced language features such as classes and interfaces. If you have come this far and read the previous articles, you are already somewhat familiar with classes, but let's review the concept.
- Class: As a “template” for creating an object, within a class you can define methods and attributes. Methods are the actions or behaviors, while attributes are the characteristics or data of the object. Let's take an example in the context of MQL5. You have a class called CPosition. In this class, you can define attributes such as price, type (buy or sell), takeprofit, stop loss, volume, etc. The class is used to create objects (instances), and this object has its own unique characteristics.
- Interface: An interface is like a “contract” that defines only the methods that a class must implement, but does not say how these methods should be implemented. An interface does not contain the implementation of the methods, only their signatures (names, parameters and return types).
Following this concept, let's look at a practical example in the context of the library. Let's go step by step. We want to access the WebRequest function, but I don't want to access it directly, because we saw that this makes the code too coupled. I want to make it possible for you to have your own WebRequest function and for the library to use it without complications. Let's create layers between the function and whoever accesses this function. This layer will be the interface.
I'll explain using some diagrams before going directly to the code.
As shown in the diagram, the HttpClient class directly accesses the WebRequest function, let's add a layer between the class and the function, this layer will be the IHttpTransport interface.
With this interface we can create the CHttpTransport class to implement this interface using the WebRequest function. As in the diagram below:
Now let's take advantage of all this, we can create several different implementations for the interface and deliver them to the CHttpClient class, natively the library will use CHttpTransport which has its implementation with WebRequest, but we can add as many others as we want, as shown in the diagram:
|--- Connexus |--- |--- Core |--- |--- |--- HttpTransport.mqh |--- |--- Interface |--- |--- |--- IHttpTransport.mqh
First, let's create the interface code that defines the input and output of the function:
//+------------------------------------------------------------------+ //| 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 that I am only declaring the function, I am not defining the body of the function. I tell the compiler that the function should return an integer, the name of the method will be Request, and I tell it what the expected parameters are. To define the body, the class must be used, and we tell the compiler that the class must implement the interface, in practice it looks like this:
//+------------------------------------------------------------------+ //| 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)); } //+------------------------------------------------------------------+
By using interfaces and mocks, we have decoupled the CHttpRequest class from the WebRequest function, creating a more flexible and maintainable library. This approach gives developers using Connexus the flexibility to test the library's behavior in different scenarios without making real requests, allowing for smoother integration and the ability to quickly adapt to new needs and functionality.
Now we just need to use this in the CHttpClient class, so we will import the IHttpTransport interface and create an instance:
//+------------------------------------------------------------------+ //| 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); }; //+------------------------------------------------------------------+
To perform dependency injection, we will add a new constructor to the class that will receive the transport layer that will be used, and store it in m_transport. We will also change the default constructor so that when a transport layer is not defined, it automatically implements it using 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(); } //+------------------------------------------------------------------+
Now that we have the transport layer, let's use it in the “Send” function
//+------------------------------------------------------------------+ //| 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()); } //+------------------------------------------------------------------+
It is worth remembering that the way the class is used does not change, that is, the same codes that we used earlier in this article in the testing section continue to be valid. The difference is that now we can change the final function of the library.
Conclusion
This concludes another stage of the library, creating the client layer. By using interfaces and mocks, we decoupled the CHttpClient class from the WebRequest function, creating a more flexible and easy-to-maintain library. This approach gives developers using Connexus the flexibility to test the library's behavior in different scenarios without making real requests, allowing for smoother integration and the ability to quickly adapt to new needs and functionalities.
The practice of decoupling using interfaces and mocks significantly improves code quality, contributing to extensibility, testability, and long-term maintainability. This approach is particularly advantageous for libraries, where developers often need flexible testing and modular replacements, providing added value for all users of the Connexus library.





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use