
Cliente en Connexus (Parte 7): Añadir la capa de cliente
Introducción
Este artículo es la continuación de una serie de artículos donde construiremos una biblioteca llamada Connexus. En el primer artículo, comprendimos el funcionamiento básico de la función WebRequest, entendiendo cada uno de sus parámetros y creando también un código de ejemplo que muestra el uso de esta función y sus dificultades. En el último artículo, entendimos qué son los métodos http y también cuáles son los códigos de estado que devuelve el servidor, los cuales informan si una solicitud se procesó correctamente o si se generó un error por parte del cliente o del servidor.
En este séptimo artículo de la serie, añadiremos la parte más esperada de toda la biblioteca, realizaremos la solicitud utilizando la función WebRequest, no crearemos directamente el acceso a ella, habrá algunas clases e interfaces en el proceso. ¡Vamos!
Solo para recordarles el estado actual de la biblioteca, este es el diagrama actual:
El objetivo aquí es recibir un objeto CHttpRequest, es decir, una solicitud HTTP lista, ya configurada con encabezado, cuerpo, URL, método y tiempo de espera, y enviar efectivamente una solicitud HTTP utilizando la función WebRequest. También debe procesar la solicitud y devolver un objeto CHttpResponse, con los datos de la respuesta, como el encabezado, el cuerpo, el código de estado y la duración total de la solicitud.
Creación de la clase CHttpClient
Creemos la clase más esperada de todas, la clase final de la biblioteca, que será la que el usuario de la biblioteca instanciará y utilizará. Esta clase se llamará CHttpClient.mqh y se ubicará en la siguiente ruta: Include/Connexus/Core/CHttpClient.mqh. Inicialmente, el archivo será similar a este. Ya he añadido las importaciones adecuadas://+------------------------------------------------------------------+ //| 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 clase tiene la función de transformar un objeto de solicitud, es decir, CHttpRequest, en una solicitud HTTP utilizando la función WebRequest, y devolver un objeto CHttpResponse con los datos. Vamos a crear esta clase. Para ello, crearemos un método Send(), el cual deberá recibir dos objetos, uno CHttpRequest y otro CHttpResponse, y deberá devolver un booleano.
class CHttpClient { public: CHttpClient(void); ~CHttpClient(void); //--- Basis function bool Send(CHttpRequest &request, CHttpResponse &response); };
Trabajemos en la implementación de esta función. Primero, revisemos brevemente los parámetros de la función WebRequest. Tiene 2 variantes e inicialmente usaremos la que tiene 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 );Ya tenemos todos estos parámetros listos y configurados en los objetos que se reciben, solo falta agregarlos. La implementación sería así:
//+------------------------------------------------------------------+ //| 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()); } //+------------------------------------------------------------------+
Agregué algunos números en los comentarios solo para explicar cada paso a continuación, y es muy importante que entiendas lo que está sucediendo aquí, ya que esta es la función central de la biblioteca:
- REQUEST: Aquí creamos una matriz de tipo uchar llamada body_request. Accedemos al cuerpo de la solicitud y obtenemos el cuerpo de la solicitud en formato binario pasando la matriz body_request, de esta forma se modificará esta matriz insertando los datos del cuerpo en su interior.
- RESPONSE: Creamos dos variables que serán utilizadas por la función WebRequest para devolver el cuerpo y el encabezado de la respuesta del servidor.
- SEND: Este es el núcleo de todo, llamamos a la función WebRequest y pasamos todos los parámetros, incluyendo la URL y el tiempo de espera, así como las variables creadas en el paso 2 para recibir el cuerpo y el encabezado de la respuesta. Aquí también creamos otras dos variables auxiliares que utilizan la función GetMicrosecondCount() para almacenar el tiempo antes y después de la solicitud.. De esta manera obtenemos el tiempo antes y después de la solicitud para calcular la duración de la solicitud en milisegundos.
- ADD DATA IN RESPONSE: Aquí, después de que la solicitud haya respondido, independientemente de si ha tenido éxito o no, utilizamos la función Clear() para restablecer todos los valores del objeto de respuesta y añadimos la duración utilizando la siguiente fórmula: (end - start)/1000. También definimos el código de estado devuelto por la función y agregamos el cuerpo y el encabezado al objeto de respuesta.
- RETURN: En el último paso, comprobamos si la solicitud ha devuelto algún código de éxito (entre 200 y 299). De esta manera, es posible saber si la solicitud se completó y, para obtener más detalles, solo hay que comprobar el contenido del objeto de respuesta, es decir, CHttpResponse.
Con la creación de esta clase, el diagrama actualizado sería similar a este:
Al final, todas las clases están en HttpClient. Hagamos una prueba sencilla.
Primera prueba
Realicemos la primera y más sencilla prueba con todo lo que hemos construido hasta ahora. Hagamos una solicitud GET y una solicitud POST a httpbin.org, que es un servicio online gratuito que permite probar solicitudes HTTP. Fue creado por kennethreitz y es un proyecto de código abierto (enlace). Ya hemos utilizado esta API pública en artículos anteriores, pero para explicarlo brevemente, httpbin funciona como un espejo, es decir, todo lo que enviamos al servidor se devuelve al cliente. Esto resulta muy útil para probar aplicaciones, en las que simplemente queremos saber si la solicitud se ha completado y qué datos ha recibido el servidor.
Pasando al código, creemos un nuevo archivo dentro de la carpeta Experts llamado TestRequest.mq5. La ruta final será Experts/Connexus/Test/TestRequest.mq5. Solo utilizaremos la función OnInit del archivo, por lo que descartamos el resto. El código tendrá el siguiente aspecto:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
Importemos la biblioteca añadiendo la importación con #include.
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Connexus/Core/HttpClient.mqh>
Ahora, creemos tres objetos:
- CHttpRequest: Donde se definirán la URL y el método HTTP. El tiempo de espera, el cuerpo y el encabezado son opcionales en las solicitudes GET. El tiempo de espera tiene un valor predeterminado de 5000 milisegundos (5 segundos).
- CHttpResponse: Almacena el resultado de la solicitud con el código de estado y la descripción, así como la duración de la solicitud en milisegundos.
- CHttpClient: Clase que realmente realiza la solicitud.
//--- 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;
Y, por último, llamamos a la función Send() para realizar la solicitud e imprimir los datos en el terminal. El código completo tiene el siguiente aspecto:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+Al ejecutar el código, obtenemos este resultado en la 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" } ---------------
Estos son los datos del objeto de respuesta: tenemos el código de estado con su descripción, la duración de la solicitud en milisegundos, el encabezado y el cuerpo de la respuesta. Esto facilita mucho la vida de los desarrolladores a la hora de conectar sus programas MQL5 con API externas. Hemos conseguido que una solicitud HTTP sea muy sencilla, solo para comparar la evolución. Te recordaré cómo funciona una solicitud sin utilizar la biblioteca y otra utilizándola:
- Sin la 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); }
- Con la 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); }
Es mucho más fácil trabajar con la biblioteca, acceder al cuerpo en diferentes formatos, como string, json o binario, y a todos los demás recursos disponibles. También es posible modificar partes de la solicitud, como la URL, el cuerpo y el encabezado, y utilizar el mismo objeto para realizar otra solicitud. Esto le permite realizar varias solicitudes seguidas de forma simplificada.
Tenemos un pequeño problema, ¡EL ACOPLAMIENTO!
Parece que todo funciona correctamente, y realmente es así, pero todo se puede mejorar y aquí en la biblioteca Connexus no es diferente. Pero al fin y al cabo ¿cuál es el problema? Al utilizar la función WebRequest directamente dentro de la clase CHttpClient, tenemos un código muy acoplado, pero ¿qué es un código muy acoplado?
Imagínate lo siguiente: estás construyendo un castillo de naipes. Cada carta debe estar perfectamente equilibrada para que todo permanezca en posición vertical. Ahora, imagina que en lugar de un castillo ligero y flexible, donde puedes mover una carta sin derribar todo, tienes algo que parece un bloque de hormigón. Si tomas una carta o mueves una, ¿adivina qué? Todo el castillo se derrumba. Esto es lo que sucede con el código acoplado.
Cuando hablamos de acoplamiento en programación, es como si las partes de tu código estuvieran tan ligadas entre sí que, si mueves una, necesitas mover varias otras también. Es como ese nudo apretado que accidentalmente ataste en el cordón de tu zapato y ahora es difícil de desatarlo sin estropearlo todo. Cambiar una función, modificar una clase o agregar algo nuevo se convierte en un dolor de cabeza, porque todo está pegado.
Por ejemplo, si tienes una función que depende en gran medida de otra función y estas funciones no pueden "vivir" por separado, entonces el código está estrechamente acoplado. Y esto es malo porque, con el tiempo, se vuelve difícil realizar mantenimiento, agregar nuevas características o incluso corregir errores sin correr el riesgo de romper algo. Esto es exactamente lo que sucede dentro de la clase CHttpClient, donde la función Send() depende directamente de WebRequest; actualmente no es posible separar una de la otra. En un mundo ideal, queremos un código "desacoplado", donde cada parte pueda funcionar por sí sola, como piezas de Lego: puedes encajarlas y desencajarlas sin mayores problemas.
Para crear código menos acoplado, utilizamos interfaces y inyección de dependencias. La idea es crear una interfaz que defina las operaciones necesarias, permitiendo que la clase dependa de esta abstracción en lugar de una implementación específica. Por lo tanto, CHttpClient puede comunicarse con cualquier objeto que implemente la interfaz, ya sea WebRequest o una función simulada (FakeWebRequest). Veamos el concepto de interfaces e inyección de dependencias:
- El papel de las interfaces: Las interfaces nos permiten definir un contrato que otras clases pueden implementar. En nuestro caso, podemos crear una interfaz llamada IHttpTransport, que definirá los métodos que CHttpClient necesita para realizar una solicitud HTTP. De esta forma, CHttpClient dependerá de la interfaz IHttpTransport y no de la función WebRequest, lo que reducirá el acoplamiento.
- Inyección de dependencias: Para conectar CHttpClient a la implementación concreta de IHttpTransport, utilizaremos la inyección de dependencias. Esta técnica consiste en pasar la dependencia (WebRequest o FakeWebRequest) como parámetro a CHttpClient, en lugar de instanciarla directamente dentro de la clase.
¿Cuáles son las ventajas de no dejar el código acoplado?
- Mantenibilidad: Con el código desacoplado, si necesitas cambiar algo, es como sustituir una pieza de un coche sin desmontar todo el motor. Las partes de tu código son independientes, por lo que cambiar una no afecta a las demás. ¿Quieres corregir un error o mejorar una función? Adelante, haz el cambio sin miedo a romper el resto del sistema.
- Reutilización: Piensa en ello como si tuvieras piezas de Lego: con código desacoplado, puedes tomar bloques de funcionalidad y utilizarlos en otros proyectos o partes del sistema sin estar atado a complicadas dependencias.
- Testabilidad: Al utilizar una interfaz, es posible sustituir WebRequest por una función simulada que imita el comportamiento esperado, lo que permite realizar pruebas sin depender de solicitudes HTTP reales. Este concepto de simulación nos lleva al siguiente tema: mocks.
¿Qué son los simulacros?
Las simulaciones son versiones simuladas de objetos o funciones reales, que se utilizan para probar el comportamiento de una clase sin depender de implementaciones externas. Cuando separamos CHttpClient de WebRequest utilizando una interfaz, tenemos la opción de pasar una versión simulada que cumple el mismo contrato, denominada «mock». De esta manera, podemos probar CHttpClient en un entorno controlado y predecir cómo reaccionaría en diferentes escenarios de respuesta, sin realizar llamadas HTTP reales.
En un sistema en el que CHttpClient realiza llamadas directas a WebRequest, las pruebas son limitadas y dependen de la conectividad y el comportamiento del servidor. Sin embargo, al inyectar una simulación de la función WebRequest que devuelve resultados simulados, podemos probar diferentes escenarios y validar la respuesta de CHttpClient de forma aislada. Estas simulaciones son muy útiles para garantizar que la biblioteca se comporte según lo esperado, incluso en situaciones de error o respuestas inesperadas.
A continuación, veremos cómo implementar interfaces y simulaciones para mejorar la flexibilidad y la capacidad de prueba de nuestra clase CHttpRequest.
Manos a la obra con el código
En la práctica, ¿cómo vamos a hacer esto en el código? Vamos a utilizar algunas características lingüísticas más avanzadas, como clases e interfaces. Si has llegado hasta aquí y has leído los artículos anteriores, ya estás algo familiarizado con las clases, pero repasemos el concepto.
- Class: Como «plantilla» para crear un objeto, dentro de una clase se pueden definir métodos y atributos. Los métodos son las acciones o comportamientos, mientras que los atributos son las características o datos del objeto. Tomemos un ejemplo en el contexto de MQL5. Tienes una clase llamada CPosition. En esta clase, puedes definir atributos como precio, tipo (compra o venta), take profit, stop loss, volumen, etc. La clase se utiliza para crear objetos (instancias), y este objeto tiene sus propias características únicas.
- Interface: Una interfaz es como un «contrato» que define únicamente los métodos que debe implementar una clase, pero no especifica cómo deben implementarse dichos métodos. Una interfaz no contiene la implementación de los métodos, solo sus firmas (nombres, parámetros y tipos de retorno).
Siguiendo este concepto, veamos un ejemplo práctico en el contexto de la biblioteca. Vamos paso a paso. Queremos acceder a la función WebRequest, pero no quiero acceder a ella directamente, porque vimos que esto hace que el código esté demasiado acoplado. Quiero hacer posible que usted tenga su propia función WebRequest y que la biblioteca la utilice sin complicaciones. Creemos capas entre la función y quien accede a esta función. Esta capa será la interfaz.
Lo explicaré usando algunos diagramas antes de ir directamente al código.
Como se muestra en el diagrama, la clase HttpClient accede directamente a la función WebRequest, agreguemos una capa entre la clase y la función, esta capa será la interfaz IHttpTransport.
Con esta interfaz podemos crear la clase CHttpTransport para implementar esta interfaz utilizando la función WebRequest. Como en el diagrama a continuación:
Ahora aprovechemos todo esto, podemos crear varias implementaciones diferentes para la interfaz y entregarlas a la clase CHttpClient, de forma nativa la librería utilizará CHttpTransport que tiene su implementación con WebRequest, pero podemos agregar tantas otras como queramos, como se muestra en el diagrama:
|--- Connexus |--- |--- Core |--- |--- |--- HttpTransport.mqh |--- |--- Interface |--- |--- |--- IHttpTransport.mqh
Primero, creemos el código de interfaz que define la entrada y la salida de la función:
//+------------------------------------------------------------------+ //| 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); }; //+------------------------------------------------------------------+Tenga en cuenta que solo estoy declarando la función, no estoy definiendo el cuerpo de la función. Le digo al compilador que la función debe devolver un entero, el nombre del método será Request y le digo cuáles son los parámetros esperados. Para definir el cuerpo se debe utilizar la clase y le decimos al compilador que la clase debe implementar la interfaz, en la práctica se ve así:
//+------------------------------------------------------------------+ //| 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)); } //+------------------------------------------------------------------+
Al utilizar interfaces y simulacros, hemos desacoplado la clase CHttpRequest de la función WebRequest, creando una biblioteca más flexible y fácil de mantener. Este enfoque brinda a los desarrolladores que utilizan Connexus la flexibilidad de probar el comportamiento de la biblioteca en diferentes escenarios sin realizar solicitudes reales, lo que permite una integración más fluida y la capacidad de adaptarse rápidamente a nuevas necesidades y funcionalidades.
Ahora solo necesitamos usar esto en la clase CHttpClient, por lo que importaremos la interfaz IHttpTransport y crearemos una instancia:
//+------------------------------------------------------------------+ //| 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 la inyección de dependencia, agregaremos un nuevo constructor a la clase que recibirá la capa de transporte que se utilizará y lo almacenaremos en m_transport. También cambiaremos el constructor predeterminado para que cuando no se defina una capa de transporte, la implemente automáticamente utilizando 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(); } //+------------------------------------------------------------------+
Ahora que tenemos la capa de transporte, usémosla en la función “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()); } //+------------------------------------------------------------------+
Vale la pena recordar que la forma de uso de la clase no cambia, es decir, los mismos códigos que utilizamos anteriormente en este artículo en la sección de pruebas siguen siendo válidos. La diferencia es que ahora podemos cambiar la función final de la biblioteca.
Conclusión
Con esto concluye otra etapa de la biblioteca, la creación de la capa cliente. Al utilizar interfaces y simulaciones, desacoplamos la clase CHttpClient de la función WebRequest, creando una biblioteca más flexible y fácil de mantener. Este enfoque brinda a los desarrolladores que utilizan Connexus la flexibilidad de probar el comportamiento de la biblioteca en diferentes escenarios sin realizar solicitudes reales, lo que permite una integración más fluida y la capacidad de adaptarse rápidamente a nuevas necesidades y funcionalidades.
La práctica de desacoplar el uso de interfaces y simulacros mejora significativamente la calidad del código, lo que contribuye a la extensibilidad, la capacidad de prueba y el mantenimiento a largo plazo. Este enfoque es particularmente ventajoso para las bibliotecas, donde los desarrolladores a menudo necesitan pruebas flexibles y reemplazos modulares, proporcionando valor agregado para todos los usuarios de la biblioteca Connexus.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16324





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso