
Клиент в Connexus (Часть 7): Добавление клиентского уровня
Введение
Настоящая статья является продолжением серии статей, в которых мы будем создавать библиотеку под названием Connexus. В первой статье мы разобрались с основами работы функции WebRequest, разобрались с каждым из ее параметров, а также создали пример кода, который демонстрирует использование данной функции и ее трудности. В прошлой статье мы разобрались, что такое http-методы, а также что такое возвращаемые сервером коды состояния, которые сообщают о том, был ли запрос обработан успешно или клиентом либо сервером сгенерирована ошибка.
В данной седьмой статье серии мы добавим самую ожидаемую часть всей библиотеки, мы сделаем запрос с помощью функции WebRequest, мы не будем напрямую создавать доступ к ней, в процессе будут задействованы некоторые классы и интерфейсы. Поехали!
Просто чтобы напомнить вам о текущем состоянии библиотеки, вот текущая схема:
Цель здесь состоит в том, чтобы получить объект CHttpRequest, то есть готовый HTTP-запрос, уже настроенный с заголовком, телом, URL-адресом, методом и таймаутом, и эффективно отправить HTTP-запрос с помощью функции WebRequest. Он также должен обработать запрос и вернуть объект CHttpResponse с данными ответа, такими как заголовок, тело, код состояния и общая продолжительность запроса.
Создание класса CHttpClient
Создадим самый ожидаемый класс из всех, последний класс библиотеки, который будет классом, инстанцированным пользователем библиотеки и будет им использоваться. Этот класс будет называться CHttpClient.mqh и будет расположен по следующему пути: Include/Connexus/Core/CHttpClient.mqh. Изначально файл будет похож на этот, я уже добавил соответствующий импорт://+------------------------------------------------------------------+ //| 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) { } //+------------------------------------------------------------------+Этот класс выполняет функцию преобразования объекта запроса, то есть CHttpRequest, в HTTP-запрос с помощью функции WebRequest и возвращает объект CHttpResponse с данными. Давайте создадим этот класс. Для этого мы создадим метод Send(), который должен получать два объекта, один CHttpRequest и другой CHttpResponse, и должен возвращать значение boolean.
class CHttpClient { public: CHttpClient(void); ~CHttpClient(void); //--- Basis function bool Send(CHttpRequest &request, CHttpResponse &response); };
Поработаем над реализацией этой функции. Сначала кратко рассмотрим параметры функции WebRequest. У неё есть 2 варианта, и изначально мы будем использовать тот, у которого меньше параметров:
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 );У нас уже есть все эти параметры, готовые и настроенные в полученных объектах, поэтому просто добавим их. Реализация будет выглядеть следующим образом:
//+------------------------------------------------------------------+ //| 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()); } //+------------------------------------------------------------------+
Я добавил несколько цифр в комментарии, просто чтобы объяснить каждый шаг ниже, и очень важно, чтобы вы понимали, что здесь происходит, поскольку это - центральная функция библиотеки:
- ЗАПРОС: Здесь мы создадим массив типа uchar с именем body_request. Мы имеем доступ к телу запроса и получаем тело запроса в двоичном формате путем передачи массива body_request. Таким образом, он изменит этот массив, вставив в него данные тела.
- ОТВЕТ: Мы создаем две переменные, которые будут использоваться функцией WebRequest для возврата тела и заголовка ответа с сервера.
- ОТПРАВИТЬ: Это главная часть всего, мы вызываем функцию WebRequest и передаем все параметры, включая URL и тайм-аут, а также переменные, созданные на шаге 2, чтобы получить тело и заголовок ответа. Здесь мы также создаем две другие вспомогательные переменные, которые используют функцию GetMicrosecondCount() для хранения времени до и после запроса. Таким образом, мы получаем время до и после запроса, чтобы рассчитать продолжительность запроса в миллисекундах.
- ДОБАВИМ ДАННЫЕ В ОТВЕТ: Здесь, после получения ответа на запрос, независимо от того, был ли он успешным или нет, мы используем функцию Clear() для сброса всех значений объекта response и добавляем длительность, используя следующую формулу: (конец - начало)/1000. Мы также определяем код состояния, возвращаемый функцией, и добавляем текст и заголовок к объекту response.
- ВОЗВРАТ: На последнем шаге мы проверяем, вернул ли запрос какой-либо код успешного выполнения (от 200 до 299). Таким образом, можно узнать, был ли запрос выполнен, и для получения более подробной информации просто проверьте содержимое объекта response, то есть CHttpResponse.
С созданием этого класса обновленная схема будет похожа на эту:
В конце концов, все классы находятся в HttpClient. Выполним простой тест.
Первый тест
Проведем первый и самый простой тест со всем, что мы создали до сих пор. Отправим запрос GET и запрос POST в httpbin.org, который представляет собой бесплатный онлайн-сервис, позволяющий тестировать HTTP-запросы. Его создал kennethreitz, это проект OpenSource (ссылка). Мы уже использовали этот общедоступный API в предыдущих статьях, но вкратце объясню, что httpbin работает как зеркальное отображение, то есть все, что мы отправляем на сервер, возвращается клиенту. Это очень полезно для тестирования приложений, где мы просто хотим знать, был ли выполнен запрос и какие данные получил сервер.
Перейдя к коду, давайте создадим новый файл в папке Experts с именем TestRequest.mq5 , конечным путем будет Experts/Connexus/Test/TestRequest.mq5 . Мы будем использовать только функцию OnInit из файла, поэтому откажемся от остальных. Код будет выглядеть так:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Импортируем библиотеку, добавив импорт с помощью #include
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Connexus/Core/HttpClient.mqh>
Теперь создадим три объекта:
- CHttpRequest : Где будут определены URL-адрес и HTTP-метод. Тайм-аут, тело и заголовок являются необязательными в запросах GET, значение тайм-аута по умолчанию составляет 5000 миллисекунд (5 секунд).
- CHttpResponse : Сохраняет результат запроса с кодом состояния и описанием, а также продолжительность запроса в миллисекундах.
- CHttpClient : Класс, который фактически выполняет запрос
//--- 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;
И, наконец, мы вызываем функцию Send(), чтобы отправить запрос и вывести данные на терминале. Полный код выглядит следующим образом:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+При выполнении кода мы получаем такой результат в терминале:
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" } ---------------
Это данные из объекта response, у нас есть код состояния с описанием, длительность запроса в миллисекундах, заголовок и текст ответа. Это значительно облегчает жизнь разработчикам при подключении их MQL5-программ к внешним API. Нам удалось сделать HTTP-запрос очень простым, просто чтобы сравнить эволюцию, я напомню вам, как работает запрос без использования библиотеки и другой, работающий с ее использованием:
- Без библиотеки 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); }
- С библиотекой 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); }
Гораздо проще работать с библиотекой, получать доступ к телу в различных форматах, таких как string, json или binary, и ко всем другим доступным ресурсам. Также можно изменить части запроса, такие как URL, тело и заголовок, и использовать тот же объект для выполнения другого запроса. Это позволяет вам делать несколько запросов подряд упрощенным способом.
У нас есть небольшая проблема, СВЯЗЬ (COUPLING)!
Кажется, что все работает корректно, и это действительно так, но все можно улучшить, и здесь, в библиотеке Connexus, это ничем не отличается. Но, в конце концов, в чем же заключается эта проблема? Используя функцию WebRequest непосредственно в классе CHttpClient, мы получаем очень связанный код, но что такое очень связанный код?
Представьте себе следующее: вы строите карточный домик. Каждая карта должна быть идеально сбалансирована, чтобы все оставалось в вертикальном положении. А теперь представьте, что вместо легкого и гибкого замка, в котором вы можете переместить карту, не разрушая всё, у вас есть нечто похожее на бетонный блок. Если вы возьмете карту или передвинете ее, угадайте, что произойдет? Весь замок рушится. Именно это происходит со связанным кодом.
Когда мы говорим о связи в программировании, это похоже на то, как если бы части вашего кода были настолько привязаны друг к другу, что, если вы перемещаете одну, вам нужно переместить и несколько других. Это как тот тугой узел, который вы случайно завязали на шнурке своего ботинка, и теперь его трудно развязать, не испортив все окончательно. Изменение функции, модификация класса или добавление чего-то нового становится головной болью, потому что все слипается воедино.
Например, если у вас есть функция, которая сильно зависит от другой функции, и эти функции не могут "жить" отдельно, то код тесно связан. И это плохо, потому что со временем его становится трудно поддерживать, добавлять новые функции или даже выполнять отладку без риска что-то сломать. Это именно то, что происходит внутри класса CHttpClient, где функция Send() напрямую зависит от WebRequest; в настоящее время невозможно отделить одно от другого. В идеальном мире нам нужен "развязанный" код, в котором каждая часть могла бы работать сама по себе, как детали конструктора Lego: можно обучать и наоборот без каких-либо серьезных проблем.
Чтобы создать менее связанный код, мы используем интерфейсы и внедрение зависимости. Идея состоит в том, чтобы создать интерфейс, определяющий необходимые операции, позволяя классу зависеть от этой абстракции, а не от конкретной реализации. Таким образом, CHttpClient может взаимодействовать с любым объектом, реализующим интерфейс, будь то WebRequest или функция mock ( FakeWebRequest). Давайте рассмотрим концепцию интерфейсов и внедрение зависимости:
- Роль интерфейсов: Интерфейсы позволяют нам определить контракт, который могут реализовать другие классы. В нашем случае мы можем создать интерфейс под названием IHttpTransport, который будет определять методы, необходимые CHttpClient для выполнения HTTP-запроса. Таким образом, CHttpClient будет зависеть от интерфейса IHttpTransport, а не от функции WebRequest, что уменьшает связь.
- Внедрение зависимости: Чтобы подключить CHttpClient к конкретной реализации Http-транспорта, мы будем использовать внедрение зависимости. Этот метод заключается в передаче зависимости (WebRequest или FakeWebRequest) в качестве параметра в CHttpClient вместо ее непосредственного инстанцирования внутри класса.
В чем преимущества того, чтобы не оставлять код связанным?
- Обслуживаемость: С развязанным кодом, если вам нужно что-то изменить, это все равно, что заменить деталь автомобиля без разборки всего двигателя. Части вашего кода независимы, поэтому изменение одной из них не повлияет на другие. Хотите исправить ошибку или улучшить какую-то функцию? Продолжайте и вносите изменения, не опасаясь нарушить работу остальной системы.
- Многоразовое использование: Представьте себе, что у вас есть детали Lego, с несвязанным кодом, вы можете брать функциональные блоки и использовать их в других проектах или частях системы, не привязываясь к сложным зависимостям.
- Тестируемость: Используя интерфейс, становится возможным заменить WebRequest мок-функцией, которая имитирует ожидаемое поведение, позволяя вам выполнять тесты, не завися от реальных HTTP-запросов. Эта концепция моделирования подводит нас к следующему разделу: моки.
Что такое Моки?
Моки - это имитированные версии реальных объектов или функций, используемые для тестирования поведения класса, не полагаясь на внешние реализации. Когда мы отделяем CHttpClient от WebRequest с помощью интерфейса, у нас есть возможность передать имитированную версию, которая выполняет тот же контракт, называемую "mock". Таким образом, мы можем протестировать CHttpClient в контролируемой среде и прогнозировать, как он будет реагировать в различных сценариях ответа, фактически не выполняя HTTP-вызовы.
В системе, где CHttpClient выполняет прямые вызовы WebRequest, тестирование ограничено и зависит от подключения и поведения сервера. Однако, внедрив мок функции WebRequest, которая возвращает имитированные результаты, мы можем протестировать различные сценарии и изолированно проверить ответ CHttpClient. Эти моки очень полезны для обеспечения того, чтобы библиотека вела себя в соответствии с ожиданиями, даже в ситуациях ошибок или неожиданных ответах.
Далее мы рассмотрим, как реализовать интерфейсы и моки для повышения гибкости и тестируемости нашего класса CHttpRequest.
Рука на коде
На практике, как мы собираемся сделать это в коде? Мы собираемся использовать некоторые более продвинутые языковые признаки, такие как классы и интерфейсы. Если вы зашли так далеко и прочитали предыдущие статьи, то вы уже в некоторой степени знакомы с классами, но давайте рассмотрим концепцию.
- Класс: В качестве “шаблона” для создания объекта внутри класса вы можете определить методы и атрибуты. Методы - это действия или поведение, в то время как атрибуты - это характеристики или данные объекта. Давайте рассмотрим пример в контексте MQL5. У вас есть класс под названием CPosition. В этом классе вы можете определить такие атрибуты, как цена, тип (покупка или продажа), тейк-профит, стоп-лосс, объем и т.д. Класс используется для создания объектов (экземпляров), и этот объект обладает своими уникальными характеристиками.
- Интерфейс: Интерфейс подобен “контракту”, определяющему только методы, которые должен реализовывать класс, но не говорит, как эти методы должны быть реализованы. Интерфейс не содержит реализации методов, только их сигнатуры (имена, параметры и типы возвращаемых данных).
Следуя этой концепции, рассмотрим практический пример в контексте библиотеки. Пойдем поэтапно. Мы хотим получить доступ к функции WebRequest, но я не хочу обращаться к ней напрямую, потому что мы видели, что это делает код слишком связанным. Я хочу, чтобы у вас была собственная функция WebRequest и чтобы библиотека могла использовать ее без осложнений. Давайте создадим слои между функцией и тем, кто обращается к этой функции. Этим слоем будет интерфейс.
Прежде чем перейти непосредственно к коду, я объясню это с помощью некоторых схем.
Как показано на схеме, класс HttpClient напрямую обращается к функции WebRequest. Добавим слой между классом и функцией, этим слоем будет интерфейс IHttpTransport.
С помощью этого интерфейса мы можем создать класс CHttpTransport для реализации этого интерфейса с помощью функции WebRequest. Как показано на схеме ниже:
Теперь давайте воспользуемся всем этим, мы можем создать несколько различных реализаций интерфейса и передать их классу CHttpClient. Изначально библиотека будет использовать CHttpTransport, который имеет свою реализацию с помощью WebRequest, но мы можем добавить столько других, сколько захотим, как показано на схеме:
|--- Connexus |--- |--- Core |--- |--- |--- HttpTransport.mqh |--- |--- Interface |--- |--- |--- IHttpTransport.mqh
Во-первых, создаём код интерфейса, который определяет входные и выходные данные функции:
//+------------------------------------------------------------------+ //| 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); }; //+------------------------------------------------------------------+Обратите внимание, что я только объявляю функцию, я не определяю тело функции. Я сообщаю компилятору, что функция должна возвращать целое число, имя метода будет Request, и я сообщаю ему ожидаемые параметры. Чтобы определить тело, необходимо использовать класс, и мы сообщаем компилятору, что класс должен реализовывать интерфейс, на практике это выглядит следующим образом:
//+------------------------------------------------------------------+ //| 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)); } //+------------------------------------------------------------------+
Используя интерфейсы и моки, мы отделили класс CHttpRequest от функции WebRequest, создав более гибкую и удобную в обслуживании библиотеку. Такой подход дает разработчикам, использующим Connexus, возможность гибко тестировать поведение библиотеки в различных сценариях без выполнения реальных запросов, обеспечивая более плавную интеграцию и возможность быстрой адаптации к новым потребностям и функциональности.
Теперь нам просто нужно использовать это в классе CHttpClient, поэтому мы импортируем IHttpTransport и создадим экземпляр:
//+------------------------------------------------------------------+ //| 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); }; //+------------------------------------------------------------------+
Чтобы выполнить внедрение зависимости, мы добавим в класс новый конструктор, который получит транспортный уровень, который будет использоваться, а также сохраним его в m_transport. Мы также изменим конструктор по умолчанию, чтобы, когда транспортный уровень не определен, он автоматически реализовывал его с помощью 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(); } //+------------------------------------------------------------------+
Теперь, когда у нас есть транспортный уровень, давайте используем его в функции “Send” (Отправить)
//+------------------------------------------------------------------+ //| 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()); } //+------------------------------------------------------------------+
Стоит помнить, что способ использования класса не меняется, то есть те же коды, которые мы использовали ранее в этой статье в разделе тестирования, продолжают действовать. Разница в том, что теперь мы можем изменить конечную функцию библиотеки.
Заключение
На этом завершается еще один этап работы библиотеки - создание клиентского уровня. Используя интерфейсы и моки, мы отделили класс CHttpClient от функции WebRequest, создав более гибкую и удобную в обслуживании библиотеку. Такой подход дает разработчикам, использующим Connexus, возможность гибко тестировать поведение библиотеки в различных сценариях без выполнения реальных запросов, обеспечивая более плавную интеграцию и возможность быстрой адаптации к новым потребностям и функционалу.
Практика разделения с использованием интерфейсов и мок, значительно повышает качество кода, способствуя расширяемости, тестируемости и долговременной поддержке. Такой подход особенно выгоден для библиотек, где разработчикам часто требуется гибкое тестирование и замена модулей, что обеспечивает дополнительную ценность для всех пользователей библиотеки Connexus.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16324
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования