English Русский Español Deutsch 日本語
preview
HTTP和Connexus(第2部分):理解HTTP架构和库设计

HTTP和Connexus(第2部分):理解HTTP架构和库设计

MetaTrader 5示例 | 19 五月 2025, 07:41
169 0
joaopedrodev
joaopedrodev

概述

本文是该系列文章的延续,我们将构建一个名为Connexus的库。在第一篇文章中,我们了解到WebRequest函数的基本工作原理,理解了它的每个参数,并且还创建了一个示例代码,展示了该函数的使用及其困难之处。在本文中,我们将继续深入了解HTTP协议,了解URL的工作原理以及构建URL所使用的元素,并创建两个初始类,分别是:

  • CQueryParam:用于管理URL中查询参数的类
  • CURL:包含URL的所有元素的类,包括CQueryParam的一个实例


什么是HTTP?

HTTP是一种用于在网页上传输数据的通信协议。它工作在TCP/IP协议之上,后者建立了客户端和服务器之间的连接。HTTP是一种无状态协议,这意味着每个请求都是独立的,之前的请求未知。一个HTTP请求和响应由三个主要部分组成:

1. 请求行

请求行包含三个元素:

  • HTTP方法:定义要执行的动作(GET、POST、PUT、DELETE等)。
  • URL:指定请求的资源。
  • HTTP版本:代表所使用的协议版本(HTTP/1.1、HTTP/2等)。

请求和响应示例:

REQUEST
GET /index.html HTTP/1.1

RESPONSE
HTTP/1.1 200 OK

2. 请求头

请求头提供了关于请求的附加信息,比如内容类型、用户代理(浏览器)以及认证信息。例如:

Host: www.exemplo.com
User-Agent: Mozilla/5.0
Accept-Language: en-US,en;q=0.5

3. 请求体

并非所有请求都包含请求体,但在像POST和PUT这样的方法中很常见,这些方法会将数据(如表单或文件)发送到服务器。


常见HTTP方法

HTTP方法对于确定客户端向服务器请求的操作类型至关重要。每种方法都定义了特定的用途,例如获取数据、发送信息或修改资源。下面我们深入探讨最常见的HTTP方法、其用途以及最佳实例。

  • GET:GET 方法是HTTP协议中最常用的方法。其用于从服务器检索或搜索数据,但不会改变服务器的状态。GET方法的主要特性是幂等的,也就是说,对同一资源发起多次 GET 请求不会改变应用程序的状态。
    • 特征
      • 无负面影响:仅读取数据。不在服务器上产生任何更改。
      • 空请求体:通常,GET 请求没有请求体。
      • 可缓存:浏览器支持GET响应缓存以提高性能。
    • 使用场景:
      • 获取静态数据,如HTML页面、图片、文件或JSON格式的信息。
      • 从数据库中检索信息而不修改数据。
  • POST:POST方法用于向服务器发送数据,通常用于创建新资源。与GET不同,它会改变服务器的状态。POST不是幂等的,这意味着如果您多次发送相同的请求,可能会创建多个资源。
    • 特征
      • 改变服务器状态:通常用于创建新资源。
      • 包含请求体:包含将发送到服务器的数据。
      • 不可缓存:通常,不支持POST请求缓存。
    • 使用场景:
      • 提交包含数据的表单。
      • 创建新资源,如向数据库中添加新条目。
  • PUT:PUT方法用于更新服务器上的资源。如果资源不存在,可以使用PUT创建。PUT方法的主要特性是其具有幂等性:使用相同的请求体发起多次 PUT请求将始终产生相同的结果。
    • 特征
      • 幂等:使用相同请求体的重复请求将具有相同的效果。
      • 发送完整资源:请求体通常包含已更新资源的完整表示。
      • 可创建或更新:如果资源不存在,可使用PUT创建。
    • 使用场景:
      • 完全更新或替换现有资源。
      • 如果资源不存在,则通过特定URL创建资源。
  • DELETE:使用DELETE方法从服务器上删除特定资源。与 PUT 类似,它也是幂等的,这意味着如果您对同一资源发起多次DELETE请求,结果将相同:资源将被删除(或者如果已经被删除,则保持缺失状态)。
    • 特征
      • 幂等:如果您删除一个已经被删除的资源,服务器将返回成功。
      • 无请求体:通常,DELETE请求没有请求体。
    • 使用场景:
      • 删除特定的服务器资源,例如从数据库中删除数据。
  • PATCH:PATCH方法用于对资源进行部分更新。与需要发送资源完整表示的PUT不同,PATCH允许您仅修改需要更新的字段。
  • HEAD:与GET类似,但没有响应体。其用于检查有关资源的信息。
  • OPTIONS:用于描述与服务器的通信选项,包括支持的方法。
  • TRACE:用于诊断,返回客户端发送到服务器的数据。


HTTP状态码

HTTP状态码是服务器发送给客户端的响应,用于告知客户端请求的结果。这些状态码是数字形式的,表明请求是成功还是失败,以及是否存在错误或重定向。它们被分为五个主要类别,每个类别都有特定的数字范围,为请求处理过程中发生的情况提供清晰且详细的信息。

下面是对每个类别以及一些最常用状态码的更深入分析。

1xx:信息性响应:1xx系列的状态码表明服务器已经收到了请求,客户端需要等待更多信息。这些响应在日常实践中很少使用,但在某些场景中可能很重要。

2xx:成功:2xx系列的状态码表明请求成功。这些是我们大多数情况下希望看到的状态码,因为它们表明客户端和服务器之间的交互如预期般顺利进行。

3xx:重定向:3xx系列的代码表明客户端需要采取一些额外的行动来完成请求,通常是一个重定向到另一个URL。

4xx:客户端错误:4xx系列的代码表明客户端发出的请求中存在错误。这些错误可能是由于数据格式不正确、缺少认证,或者是试图访问不存在的资源引起的。

5xx:服务器错误:5xx系列的代码表明服务器在尝试处理请求时发生了内部错误。这些错误通常是后端问题,例如内部服务故障、配置错误或系统过载。


构建URL

URL(统一资源定位符)是我们识别和访问网络上资源的方式。它由几个元素组成,这些元素提供了诸如服务器位置、可请求的资源以及可选的附加参数等基本信息,这些附加参数可用于过滤或定制响应。

下面,我们将详细说明URL的每个组成部分以及查询参数如何用于在HTTP请求中传递附加信息。

URL结构

典型的URL遵循以下格式:

protocol://domain:port/path?query=params#fragment

每个部分都有其特定的作用:

  1. 协议:表明将用于通信的协议,例如http或https。示例:https://。
  2. 域名:托管资源的服务器名称。它可以是一个域名(例如example.com)或一个IP地址(例如192.168.1.1)。
  3. 端口:一个可选的数字,指定用于通信的服务器端口。如果省略,浏览器将使用默认端口,例如,http的80端口和https的443端口。示例:8080。
  4. 路径:指定服务器上的资源或路由。它可以代表页面、API端点或文件。示例:/api/v1/users。
  5. 查询参数:用于向服务器传递附加信息。它们跟随问号(?)并且由键值对组成。多个参数由&分隔。示例:?name=John&age=30。
  6. 碎片:指示资源的特定部分,例如HTML页面内的锚点。跟随井号字符(#)。示例: #section2 。通常,碎片既没用处也不会用于API消费数据。这是因为碎片仅在客户端处理,也就是说,由浏览器或正在消费网页的应用程序的界面处理。服务器不会接收URL的碎片,因此它不能用于发送到服务器的HTTP请求,例如在消费API时。因此,我们的库将不支持碎片。


完整示例

让我们分析以下URL:

https://www.exemplo.com:8080/market/symbols?active=EURUSD&timeframe=h1

此处我们有:

  • 协议:https
  • 域名:www.example.com
  • 端口:8080
  • 路径:/market/symbols
  • 查询参数:active=EURUSD&timeframe=h1


亲身实战:开始构建Connexus库

为了开始构建Connexus库,我们将专注于负责构建和操作URL以及查询参数的类。我们将构建一个模块,从而动态且程序化地创建URL和添加查询参数。


类结构

我们将首先创建一个CURL类,它将负责构建和操作URL。它将允许用户轻松添加查询参数,构建基础URL,并高效处理URL的不同元素。为了以一种有组织且高效的方式管理URL的查询参数,我们将使用一个名为CJson的类。这个类的目标是将查询参数(通常作为字符串在URL中传递)转换成一个结构化且易于管理的格式:JSON


什么是JSON?

在深入了解CJson类的功能之前,如果您还不熟悉,那么了解JSON(JavaScript对象表示法)格式是很重要的。JSON是一种在网络上用于表示结构化数据的非常常见的数据格式。它由键值对组成,其中每个键都有一个关联的值。这些键值对用逗号分隔,并用大括号“{}”分组。

JSON对象的示例:

{
  "name": "John",
  "age": 30,
  "city": "New York"
}

此处,我们有一个包含三个键值对“name”、“age”和“city”的JSON对象,以及它们各自的值。在URL的查询参数的情况下,每个参数的工作方式类似:有一个键(参数名称)和一个值(与该键关联的值)。


CJson类的用途

CJson类将用于将URL查询参数组织成JSON格式。这使得在将它们包含在最终URL中之前,更容易操作、读取,甚至验证这些参数。与其处理像?name=John&age=30这样的参数字符串,不如处理一个结构化的对象,使代码更干净、更易于理解。CJson类也将有助于发送和接收数据,这一点将在后续文章中看到。


创建第一个类

我们首先在Metaeditor的includes中创建一个名为Connexus的文件夹。在Connexus文件夹内再创建另一个名为URL的文件夹和另一个名为Data的文件夹,在URL文件夹内创建一个名为URL和QueryParam的文件,我也会将CJson类附加到本文中,该类应添加到Data文件夹中。我不会过多涉及这个类的实现细节,但是请相信我,该类很容易使用。其结构应该看起来如下:

MQL5
 |--- include
 |--- |--- Connexus
 |--- |--- |--- Data
 |--- |--- |--- |--- Json.mqh
 |--- |--- |--- URL
 |--- |--- |--- |--- QueryParam.mqh
 |--- |--- |--- |--- URL.mqh


查询参数

让我们先从CQueryParam类开始着手。该类将负责添加、删除、搜索和序列化查询参数,同时提供诸如清理数据和解析查询字符串等辅助方法。我们首先创建这个类,并在其中包含一个私有的CJson类型对象,用于以键值对的形式存储查询参数。

//+------------------------------------------------------------------+
//| class : CQueryParam                                              |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CQueryParam                                        |
//| Heritage    : No heritage                                        |
//| Description : Manages query parameters for HTTP requests         |
//|                                                                  |
//+------------------------------------------------------------------+
class CQueryParam
  {
private:

   CJson             m_query_param;                      // Storage for query parameters

public:
                     CQueryParam(void);
                    ~CQueryParam(void);

   //--- Functions to manage query parameters
   void              AddParam(string key, string value); // Add a key-value pair
   void              AddParam(string param);             // Add a single key-value parameter
   void              AddParams(const string &params[]);  // Add multiple parameters
   void              RemoveParam(const string key);      // Remove a parameter by key
   string            GetParam(const string key) const;   // Retrieve a parameter value by key
   bool              HasParam(const string key);         // Check if parameter exists
   int               ParamSize(void);                    // Get the number of parameters

   //--- Auxiliary methods
   bool              Parse(const string query_param);    // Parse a query string
   string            Serialize(void);                    // Serialize parameters into a query string
   void              Clear(void);                        // Clear all parameters
  };

现在,让我们探索主要的方法,并了解每种方法是如何为类的功能做出贡献的。

  • AddParam(string key, string value):此方法负责向查询参数列表中添加一个新的参数。它接收键和值作为参数,并将它们存储于m_query_param对象中。
  • AddParam(string param):此方法添加一个已经以key=value格式化的参数。它会检查字符串中是否有=字符,如果存在,则将字符串拆分为两个值,一个作为键,一个作为值,并将它们存储起来。
  • AddParams(const string &params[]):此方法一次性添加多个参数。它接收一个以key=value格式的字符串数组,并为数组中的每个项目调用AddParam方法。
  • RemoveParam(const string key):此方法从查询参数列表中移除一个参数。它定位到该键并从m_query_param对象中将其移除。- GetParam(const string key):此方法返回特定参数的值,使用键作为输入。
  • HasParam(const string key):此方法检查是否已经添加了给定的参数。
  • ParamSize(void):此方法返回存储的查询参数的数量。
  • Parse(const string query_param):Parse()方法接收一个查询参数字符串,并将其转换为键值对,存储在m_query_param对象中。它通过&字符(用于分隔参数)和=字符(用于分隔键和值)来拆分字符串。
  • Serialize(void):Serialize()方法生成一个包含所有存储的查询参数的格式化字符串。它将参数以key=value格式连接起来,并用&分隔每对参数。
  • Clear(void):Clear()方法清除所有存储的参数,重置该对象。

以下是带有已实现函数的代码,记得添加CJSON的导入语句:

//+------------------------------------------------------------------+
//| Include the file CJson class                                     |
//+------------------------------------------------------------------+
#include "../Data/Json.mqh"
//+------------------------------------------------------------------+
//| class : CQueryParam                                              |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CQueryParam                                        |
//| Heritage    : No heritage                                        |
//| Description : Manages query parameters for HTTP requests         |
//|                                                                  |
//+------------------------------------------------------------------+
class CQueryParam
  {
private:

   CJson             m_query_param;                      // Storage for query parameters

public:
                     CQueryParam(void);
                    ~CQueryParam(void);

   //--- Functions to manage query parameters
   void              AddParam(string key, string value); // Add a key-value pair
   void              AddParam(string param);             // Add a single key-value parameter
   void              AddParams(const string &params[]);  // Add multiple parameters
   void              RemoveParam(const string key);      // Remove a parameter by key
   string            GetParam(const string key) const;   // Retrieve a parameter value by key
   bool              HasParam(const string key);         // Check if parameter exists
   int               ParamSize(void);                    // Get the number of parameters

   //--- Auxiliary methods
   bool              Parse(const string query_param);    // Parse a query string
   string            Serialize(void);                    // Serialize parameters into a query string
   void              Clear(void);                        // Clear all parameters
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CQueryParam::CQueryParam(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CQueryParam::~CQueryParam(void)
  {
  }
//+------------------------------------------------------------------+
//| Adds a key-value pair to the query parameters                    |
//+------------------------------------------------------------------+
void CQueryParam::AddParam(string key, string value)
  {
   m_query_param[key] = value;
  }
//+------------------------------------------------------------------+
//| Adds a single parameter from a formatted string                  |
//+------------------------------------------------------------------+
void CQueryParam::AddParam(string param)
  {
   //--- Check if the input string contains an "=" symbol, which indicates a key-value pair
   if(StringFind(param,"=") >= 0)
     {
      //--- Declare an array to hold the key and value after splitting the string
      string key_value[];
      
      //--- Split the input string using "=" as the delimiter and store the result in the key_value array
      int size = StringSplit(param,StringGetCharacter("=",0),key_value);
      
      //--- If the size of the split result is exactly 2 (meaning a valid key-value pair was found)
      if(size == 2)
        {
         // Add the key-value pair to the m_query_param map
         // key_value[0] is the key, key_value[1] is the value
         m_query_param[key_value[0]] = key_value[1];
        }
     }
  }
//+------------------------------------------------------------------+
//| Adds multiple parameters from an array of formatted strings      |
//+------------------------------------------------------------------+
void CQueryParam::AddParams(const string &params[])
  {
   //--- Get the size of the input array 'params'
   int size = ArraySize(params);
   
   //--- Loop through each element in the 'params' array.
   for(int i=0;i<size;i++)
     {
      //--- Call the AddParam function to add each parameter to the m_query_param map.
      this.AddParam(params[i]);
     }
  }
//+------------------------------------------------------------------+
//| Removes a parameter by key                                       |
//+------------------------------------------------------------------+
void CQueryParam::RemoveParam(const string key)
  {
   m_query_param.Remove(key);
  }
//+------------------------------------------------------------------+
//| Retrieves a parameter value by key                               |
//+------------------------------------------------------------------+
string CQueryParam::GetParam(const string key) const
  {
   return(m_query_param[key].ToString());
  }
//+------------------------------------------------------------------+
//| Checks if a parameter exists by key                              |
//+------------------------------------------------------------------+
bool CQueryParam::HasParam(const string key)
  {
   return(m_query_param.FindKey(key) != NULL);
  }
//+------------------------------------------------------------------+
//| Returns the number of parameters stored                          |
//+------------------------------------------------------------------+
int CQueryParam::ParamSize(void)
  {
   return(m_query_param.Size());
  }
//+------------------------------------------------------------------+
//| Parses a query string into parameters                            |
//| Input: query_param - A string formatted as a query parameter     |
//| Output: bool - Always returns true, indicating successful parsing|
//+------------------------------------------------------------------+
bool CQueryParam::Parse(const string query_param)
  {
   //--- Split the input string by '&', separating the individual parameters
   string params[];
   int size = StringSplit(query_param, StringGetCharacter("&",0), params);

   //--- Iterate through each parameter string
   for(int i=0; i<size; i++)
     {
      //--- Split each parameter string by '=', separating the key and value
      string key_value[];
      StringSplit(params[i], StringGetCharacter("=",0), key_value);

      //--- Check if the split resulted in exactly two parts: key and value
      if (ArraySize(key_value) == 2)
        {
         //--- Assign the value to the corresponding key in the map
         m_query_param[key_value[0]] = key_value[1];
        }
     }
   //--- Return true indicating that parsing was successful
   return(true);
  }
//+------------------------------------------------------------------+
//| Serializes the stored parameters into a query string             |
//| Output: string - A string representing the serialized parameters |
//+------------------------------------------------------------------+
string CQueryParam::Serialize(void)
  {
   //--- Initialize an empty string to build the query parameter string
   string query_param = "";

   //--- Iterate over each key-value pair in the parameter map
   for(int i=0; i<m_query_param.Size(); i++)
     {
      //--- Append a '?' at the beginning to indicate the start of parameters
      if(i == 0)
        {
         query_param = "?";
        }

      //--- Construct each key-value pair as 'key=value'
      if(i == m_query_param.Size()-1)
        {
         //--- If it's the last pair, don't append '&'
         query_param += m_query_param[i].m_key + "=" + m_query_param[i].ToString();
        }
      else
        {
         //--- Otherwise, append '&' after each pair
         query_param += m_query_param[i].m_key + "=" + m_query_param[i].ToString() + "&";
        }
     }

   //--- Return the constructed query parameter string
   return(query_param);
  }
//+------------------------------------------------------------------+
//| Clears all stored parameters                                     |
//+------------------------------------------------------------------+
void CQueryParam::Clear(void)
  {
   m_query_param.Clear();
  }
//+------------------------------------------------------------------+


URL

既然我们已经有了一个负责处理查询参数的类,那么就来着手处理将使用协议、主机、端口等用于完成其余工作的CURL类。以下是MQL5中CURL类的初步实现,记得导入CQueryParam类:

//+------------------------------------------------------------------+
//|                                                          URL.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#include "QueryParam.mqh"
class CURL
  {
public:
                     CURL(void);
                    ~CURL(void);
  };
CURL::CURL(void)
  {
  }
CURL::~CURL(void)
  {
  }
//+------------------------------------------------------------------+

让我们创建一个枚举(ENUM),其中包含最流行的协议。

//+------------------------------------------------------------------+
//| Enum to represent different URL protocol                         |
//+------------------------------------------------------------------+
enum ENUM_URL_PROTOCOL
  {
   URL_PROTOCOL_NULL = 0,  // No protocol defined
   URL_PROTOCOL_HTTP,      // HTTP protocol
   URL_PROTOCOL_HTTPS,     // HTTPS protocol
   URL_PROTOCOL_WS,        // WebSocket (WS) protocol
   URL_PROTOCOL_WSS,       // Secure WebSocket (WSS) protocol
   URL_PROTOCOL_FTP        // FTP protocol
  };

在类的私有字段中,我们将添加一个结构体,用于构成URL的基本元素,以及一个名为m_url的该结构体的实例。

private:
   
   //--- Structure to hold components of a URL
   struct MqlURL
     {
      ENUM_URL_PROTOCOL protocol;      // URL protocol
      string            host;          // Host name or IP
      uint              port;          // Port number
      string            path;          // Path after the host
      CQueryParam       query_param;   // Query parameters as key-value pairs
     };
   MqlURL            m_url;            // Instance of MqlURL to store the URL details

我们创建了设置器(setters)和获取器(getters),具体实现如下:

//+------------------------------------------------------------------+
//| class : CURL                                                     |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CURL                                               |
//| Heritage    : No heritage                                        |
//| Description : Define a class CURL to manage and manipulate URLs  |
//|                                                                  |
//+------------------------------------------------------------------+
class CURL
  {
public:
                     CURL(void);
                    ~CURL(void);
   
   
   //--- Methods to access and modify URL components
   ENUM_URL_PROTOCOL Protocol(void) const;                  // Get the protocol
   void              Protocol(ENUM_URL_PROTOCOL protocol);  // Set the protocol
   string            Host(void) const;                      // Get the host
   void              Host(const string host);               // Set the host
   uint              Port(void) const;                      // Get the port
   void              Port(const uint port);                 // Set the port
   string            Path(void) const;                      // Get the path
   void              Path(const string path);               // Set the path
   CQueryParam       *QueryParam(void);                     // Access query parameters
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CURL::CURL(void)
  {
   this.Clear();
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CURL::~CURL(void)
  {
  }
//+------------------------------------------------------------------+
//| Getter for protocol                                              |
//+------------------------------------------------------------------+
ENUM_URL_PROTOCOL CURL::Protocol(void) const
  {
   return(m_url.protocol);
  }
//+------------------------------------------------------------------+
//| Setter for protocol                                              |
//+------------------------------------------------------------------+
void CURL::Protocol(ENUM_URL_PROTOCOL protocol)
  {
   m_url.protocol = protocol;
  }
//+------------------------------------------------------------------+
//| Getter for host                                                  |
//+------------------------------------------------------------------+
string CURL::Host(void) const
  {
   return(m_url.host);
  }
//+------------------------------------------------------------------+
//| Setter for host                                                  |
//+------------------------------------------------------------------+
void CURL::Host(const string host)
  {
   m_url.host = host;
  }
//+------------------------------------------------------------------+
//| Getter for port                                                  |
//+------------------------------------------------------------------+
uint CURL::Port(void) const
  {
   return(m_url.port);
  }
//+------------------------------------------------------------------+
//| Setter for port                                                  |
//+------------------------------------------------------------------+
void CURL::Port(const uint port)
  {
   m_url.port = port;
  }
//+------------------------------------------------------------------+
//| Getter for path                                                  |
//+------------------------------------------------------------------+
string CURL::Path(void) const
  {
   return(m_url.path);
  }
//+------------------------------------------------------------------+
//| Setter for path                                                  |
//+------------------------------------------------------------------+
void CURL::Path(const string path)
  {
   m_url.path = path;
  }
//+------------------------------------------------------------------+
//| Accessor for query parameters (returns a pointer)                |
//+------------------------------------------------------------------+
CQueryParam *CURL::QueryParam(void)
  {
   return(GetPointer(m_url.query_param));
  }
//+------------------------------------------------------------------+

现在我们将着手为类打造核心功能,添加用于处理这些数据的新函数,它们分别是:

  • Clear(void):Clear()方法负责清除类中存储的所有数据,将其属性重置为空或默认值。该方法在您想要重用类实例来构建一个新的URL时,或者需要确保在新的操作中不会意外包含旧数据时非常有用。换句话说,其“重置”了类,移除了所有之前存储的信息。

    其工作原理:

    • 根据数据类型,将类的属性设置为空或null(例如,协议、域名等设置为空字符串)。
    • 移除所有查询参数,并将路径重置为默认值。
    • 调用Clear()之后,类实例将处于初始状态,就像其刚被创建一样。


    例如:

    如果之前存储过类:

    • 协议:https
    • 域名:api.example.com
    • 路径:/v1/users
    • 查询参数:id=123&active=true


    在调用Clear()之后,将所有这些值重置为:

    • 协议:""(空字符串)
    • 域名:""(空字符串)
    • 路径:""(空字符串)
    • 查询参数:""(空字符串)


    这使得类准备好从头开始构建一个新的URL。

  • BaseUrl(void):该方法负责生成并返回URL的基础部分,它由协议(例如http、https)、域名(例如www.example.com)以及可选的端口(例如:8080)组成。该方法确保用于与服务器通信的基本要素是正确的。该方法让您可以灵活地构建动态URL,始终从基础部分开始。当您想要重用URL的基础部分来访问同一服务器上的不同资源时,它就会非常有用。

  • PathAndQuery(void):该方法负责生成资源的路径部分,并将您之前添加的查询参数进行拼接。路径通常指定您想要在服务器上访问的资源,而查询参数允许您提供附加情况,例如过滤条件或分页信息。通过将路径和查询参数与基础URL分开,您可以以更有序的方式构建URL的不同部分。该方法返回一个可以直接用于HTTP请求或其他需要这种结构的方法的字符串。

  • FullUrl(void):这是“编译”URL所有部分并返回完整的、可以直接使用的URL的方法。它将BaseURL()PathAndQuery()结合起来,形成您可以直接在HTTP请求中使用的最终URL。如果您需要完整的URL来发送HTTP请求,这就是最简单的方式,可以确保URL格式正确。其能够预防诸如忘记将基础部分和查询参数拼接起来的错误。

    示例:如果类存储了以下值:

    • 协议:https
    • 域名:api.example.com
    • 路径:/v1/users
    • 查询参数:id=123&active=true


    当调用Serialize()时,该函数将返回:

    https://api.exemplo.com/v1/users?id=123&active=true

  • Parse(const string url):与FullUrl(void)的功能相反。它接收一个完整的URL作为参数,并将其组件以有组织的方式分开。其目标是将一个URL分解为更小的部分(协议、域名、端口、路径、查询参数等),以便程序员可以单独操作这些要素。如果您接收到一个URL并需要了解其细节或通过编程方式进行修改,这样会非常有用。

    其工作原理:

    • 接收一个包含完整URL的字符串。
    • 分析(或“解析”)该字符串,识别URL的每一部分:协议(http、https)、域名、端口(如果有)、路径以及任何查询参数。
    • 将这些值分配给类的内部属性,例如protocol、host、path、queryParams。- 正确处理诸如://、/、?、&等分隔符,以便将URL拆分为各个部分。


    示例: 给定以下URL:

    https://api.example.com:8080/v1/users?id=123&active=true

    当调用Parse()时,该函数将分配以下值:

    • 协议:https
    • 域名:api.example.com
    • 端口:8080
    • 路径:/v1/users
    • 查询参数:id=123,active=true


    这使得您可以通过编程方式访问URL的每个部分,从而更容易对其进行操作或解析。

  • UrlProtocolToStr(ENUM_URL_PROTOCOL protocol):将协议以字符串形式返回,这在将ENUM_URL_PROTOCOL转换为简单字符串时显得非常有用,例如:

    • URL_PROTOCOL_HTTP → “http”
    • URL_PROTOCOL_HTTPS → “httpS”
    • URL_PROTOCOL_WSS → “wss”
    • 等等…

这些方法在构建和操作URL方面都发挥着重要的作用。凭借这些功能,Connexus库变得高度灵活,能够满足API的动态需求,无论是从头开始创建URL还是解析现有的URL。通过实现这些方法,开发人员可以以编程方式构建URL,避免错误并优化与服务器的通信。以下是包含已实现函数的代码:

//+------------------------------------------------------------------+
//| Define constants for different URL protocols                     |
//+------------------------------------------------------------------+
#define HTTP "http"
#define HTTPS "https"
#define WS "ws"
#define WSS "wss"
#define FTP "ftp"
//+------------------------------------------------------------------+
//| class : CURL                                                     |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CURL                                               |
//| Heritage    : No heritage                                        |
//| Description : Define a class CURL to manage and manipulate URLs  |
//|                                                                  |
//+------------------------------------------------------------------+
class CURL
  {
private:
   string            UrlProtocolToStr(ENUM_URL_PROTOCOL protocol); // Helper method to convert protocol enum to string
   
public:
   //--- Methods to parse and serialize the URL
   void              Clear(void);                           // Clear/reset the URL
   string            BaseUrl(void);                         // Return the base URL (protocol, host, port)
   string            PathAndQuery(void);                    // Return the path and query part of the URL
   string            FullUrl(void);                         // Return the complete URL
   bool              Parse(const string url);               // Parse a URL string into components
  };
//+------------------------------------------------------------------+
//| Convert URL protocol enum to string                              |
//+------------------------------------------------------------------+
string CURL::UrlProtocolToStr(ENUM_URL_PROTOCOL protocol)
  {
   if(protocol == URL_PROTOCOL_HTTP)   { return(HTTP);   }
   if(protocol == URL_PROTOCOL_HTTPS)  { return(HTTPS);  }
   if(protocol == URL_PROTOCOL_WS)     { return(WS);     }
   if(protocol == URL_PROTOCOL_WSS)    { return(WSS);    }
   if(protocol == URL_PROTOCOL_FTP)    { return(FTP);    }
   return(NULL);
  }
//+------------------------------------------------------------------+
//| Clear or reset the URL structure                                 |
//+------------------------------------------------------------------+
void CURL::Clear(void)
  {
   m_url.protocol = URL_PROTOCOL_NULL;
   m_url.host = "";
   m_url.port = 0;
   m_url.path = "";
   m_url.query_param.Clear();
  }
//+------------------------------------------------------------------+
//| Construct the base URL from protocol, host, and port             |
//+------------------------------------------------------------------+
string CURL::BaseUrl(void)
  {
   //--- Checks if host is not null or empty
   if(m_url.host != "" && m_url.host != NULL)
     {
      MqlURL url = m_url;
      
      //--- Set default protocol if not defined
      if(url.protocol == URL_PROTOCOL_NULL)
        {
         url.protocol = URL_PROTOCOL_HTTPS;
        }
      
      //--- Set default port based on the protocol
      if(url.port == 0)
        {
         url.port = (url.protocol == URL_PROTOCOL_HTTPS) ? 443 : 80;
        }
      
      //--- Construct base URL (protocol + host)
      string serialized_url = this.UrlProtocolToStr(url.protocol) + "://" + url.host;
      
      //--- Include port in URL only if it's not the default port for the protocol
      if(!(url.protocol == URL_PROTOCOL_HTTP && url.port == 80) &&
         !(url.protocol == URL_PROTOCOL_HTTPS && url.port == 443))
        {
         serialized_url += ":" + IntegerToString(m_url.port);
        }
      
      return(serialized_url);
     }
   else
     {
      return("Error: Invalid host");
     }
  }
//+------------------------------------------------------------------+
//| Construct path and query string from URL components              |
//+------------------------------------------------------------------+
string CURL::PathAndQuery(void)
  {
   MqlURL url = m_url;
   
   //--- Ensure path starts with a "/"
   if(url.path == "")
     {
      url.path = "/";
     }
   else if(StringGetCharacter(url.path,0) != '/')
     {
      url.path = "/" + url.path;
     }
   
   //--- Remove any double slashes from the path
   StringReplace(url.path,"//","/");
   
   //--- Check for invalid spaces in the path
   if(StringFind(url.path," ") >= 0)
     {
      return("Error: Invalid characters in path");
     }
   
   //--- Return the full path and query string
   return(url.path + url.query_param.Serialize());
  }
//+------------------------------------------------------------------+
//| Return the complete URL (base URL + path + query)                |
//+------------------------------------------------------------------+
string CURL::FullUrl(void)
  {
   return(this.BaseUrl() + this.PathAndQuery());
  }
//+------------------------------------------------------------------+
//| Parse a URL string and extract its components                    |
//+------------------------------------------------------------------+
bool CURL::Parse(const string url)
  {
   //--- Create an instance of MqlURL to hold the parsed data
   MqlURL urlObj;
   
   //--- Parse protocol from the URL
   int index_end_protocol = 0;
   
   //--- Check if the URL starts with "http://"
   if(StringFind(url,"http://") >= 0)
     {
      urlObj.protocol = URL_PROTOCOL_HTTP;
      index_end_protocol = 7;
     }
   else if(StringFind(url,"https://") >= 0)
     {
      urlObj.protocol = URL_PROTOCOL_HTTPS;
      index_end_protocol = 8;
     }
   else if(StringFind(url,"ws://") >= 0)
     {
      urlObj.protocol = URL_PROTOCOL_WS;
      index_end_protocol = 5;
     }
   else if(StringFind(url,"wss://") >= 0)
     {
      urlObj.protocol = URL_PROTOCOL_WSS;
      index_end_protocol = 6;
     }
   else if(StringFind(url,"ftp://") >= 0)
     {
      urlObj.protocol = URL_PROTOCOL_FTP;
      index_end_protocol = 6;
     }
   else
     {
      return(false); // Unsupported protocol
     }
   
   //--- Separate the endpoint part after the protocol
   string endpoint = StringSubstr(url,index_end_protocol);  // Get the URL part after the protocol
   string parts[];                                          // Array to hold the split components of the URL
   
   //--- Split the endpoint by the "/" character to separate path and query components
   int size = StringSplit(endpoint,StringGetCharacter("/",0),parts);
   
   //--- Handle the host and port part of the URL
   string host_port[];
   
   //--- If the first part (host) contains a colon (":"), split it into host and port
   if(StringSplit(parts[0],StringGetCharacter(":",0),host_port) > 1)
     {
      urlObj.host = host_port[0];                        // Set the host
      urlObj.port = (uint)StringToInteger(host_port[1]); // Convert and set the port
     }
   else
     {
      urlObj.host = parts[0];
      
      //--- Set default port based on the protocol
      if(urlObj.protocol == URL_PROTOCOL_HTTP)
        {
         urlObj.port = 80;
        }
      if(urlObj.protocol == URL_PROTOCOL_HTTPS)
        {
         urlObj.port = 443;
        }
     }
   
   //--- If there's no path, default to "/"
   if(size == 1)
     {
      urlObj.path += "/"; // Add a default root path "/"
     }
   
   //--- Loop through the remaining parts of the URL (after the host)
   for(int i=1;i<size;i++)
     {
      //--- If the path contains an empty part, return false (invalid URL)
      if(parts[i] == "")
        {
         return(false);
        }
      //--- If the part contains a "?" (indicating query parameters)
      else if(StringFind(parts[i],"?") >= 0)
        {
         string resource_query[];
         
         //--- Split the part by "?" to separate the resource and query
         if(StringSplit(parts[i],StringGetCharacter("?",0),resource_query) > 0)
           {
            urlObj.path += "/"+resource_query[0];
            urlObj.query_param.Parse(resource_query[1]);
           }
        }
      else
        {
         //--- Otherwise, add to the path as part of the URL
         urlObj.path += "/"+parts[i];
        }
     }
   
   //--- Assign the parsed URL object to the member variable
   m_url = urlObj;
   return(true);
  }
//+------------------------------------------------------------------+

最后,我们将再添加两个新函数来帮助开发人员,它们分别是:

  • ShowData(void):分别打印URL的各个要素,帮助我们进行调试并了解类中存储了哪些数据。例如:

https://api.exemplo.com/v1/users?id=123&active=true

函数应返回以下内容:

Protocol: https
Host: api.exemplo.com
Port: 443
Path: /v1/users
Query Param: {
   "id":123,
   "active":true
}

  • Compare(CURL &url):该函数接收另一个CURL类的实例。如果两个实例中存储的URL相同,则应返回true;否则返回false。可以用它来避免比较序列化后的URL,从而节省时间。不使用Compare()函数的示例
    // Example without using the Compare() method
    CURl url1;
    CURl url2;
    
    if(url1.FullUrl() == url2.FullUrl())
      {
       Print("Equals URL");
      }
    
    
    // Example with method Compare()
    CURl url1;
    CURl url2;
    
    if(url1.Compare(url2))
      {
       Print("Equals URL");
      }
        
    
        
    

以下是实现这些函数的代码:

//+------------------------------------------------------------------+
//| class : CURL                                                     |
//|                                                                  |
//| [PROPERTY]                                                       |
//| Name        : CURL                                               |
//| Heritage    : No heritage                                        |
//| Description : Define a class CURL to manage and manipulate URLs  |
//|                                                                  |
//+------------------------------------------------------------------+
class CURL
  {
public:
   //--- Auxiliary methods
   bool              Compare(CURL &url);                    // Compare two URLs
   string            ShowData();                            // Show URL details as a string
  };
//+------------------------------------------------------------------+
//| Compare the current URL with another URL                         |
//+------------------------------------------------------------------+
bool CURL::Compare(CURL &url)
  {
   return (m_url.protocol == url.Protocol() &&
           m_url.host == url.Host() &&
           m_url.port == url.Port() &&
           m_url.path == url.Path() &&
           m_url.query_param.Serialize() == url.QueryParam().Serialize());
  }
//+------------------------------------------------------------------+
//| Display the components of the URL as a formatted string          |
//+------------------------------------------------------------------+
string CURL::ShowData(void)
  {
   return(
      "Protocol: "+EnumToString(m_url.protocol)+"\n"+
      "Host: "+m_url.host+"\n"+
      "Port: "+IntegerToString(m_url.port)+"\n"+
      "Path: "+m_url.path+"\n"+
      "Query Param: "+m_url.query_param.Serialize()+"\n"
   );
  }
//+------------------------------------------------------------------+

我们已经完成了用于处理URL的两个类,接下来要进行测试。


测试

现在初始类已经准备好了,让我们通过这些类来创建URL,同时也进行反向操作,即通过类将URL的要素拆分出来。为了进行测试,我将创建一个名为TestUrl.mq5的文件,路径为Experts/Connexus/TestUrl.mq5。

int OnInit()
  {
   //--- Creating URL
   CURL url;
   url.Host("example.com");
   url.Path("/api/v1/data");
   Print("Test1 | # ",url.FullUrl() == "https://example.com/api/v1/data");
   
   //--- Changing parts of the URL
   url.Host("api.example.com");
   Print("Test2 | # ",url.FullUrl() == "https://api.example.com/api/v1/data");
   
   //--- Parse URL
   url.Clear();
   string url_str = "https://api.example.com/api/v1/data";
   Print("Test3 | # ",url.Parse(url_str));
   Print("Test3 | - Protocol # ",url.Protocol() == URL_PROTOCOL_HTTPS);
   Print("Test3 | - Host # ",url.Host() == "api.example.com");
   Print("Test3 | - Port # ",url.Port() == 443);
   Print("Test3 | - Path # ",url.Path() == "/api/v1/data");
   
//---
   return(INIT_SUCCEEDED);
  }

运行EA时,终端中包含以下数据:

Test1 | # true
Test2 | # true
Test3 | # true
Test3 | - Protocol # true
Test3 | - Host # true
Test3 | - Port # true
Test3 | - Path # true


结论

在本文中,我们深入探讨了HTTP协议的工作原理,从基本概念(如HTTP动词GET、POST、PUT、DELETE)到帮助我们解读请求返回结果的响应状态码。为了便于在您在MQL5应用程序中管理URL,我们构建了CQueryParam类,它提供了一种简单高效的方式来操作查询参数。此外,我们还实现了CURL类,它允许动态修改URL的各个部分,使得创建和处理HTTP请求的过程更加灵活和健壮。

凭借这些资源,您已经为应用程序与外部API的集成打下了良好的基础,从而方便代码与网络服务器之间的通信。然而,我们才刚刚开始。在下一篇文章中,我们将继续深入HTTP世界,构建专门用于处理请求的头部和正文的类,从而在与HTTP交互时拥有更多的控制权。

请关注后续文章,因为我们正在构建一个重要的库,它将把您的API集成技能提升到一个新的水平!

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15897

交易中的神经网络:层次化向量变换器(HiVT) 交易中的神经网络:层次化向量变换器(HiVT)
我们邀请您来领略层次化矢量转换器(HiVT)方法,其专为快速、准确地预测多模态时间序列而开发。
基于MQL5的订单剥头皮交易系统 基于MQL5的订单剥头皮交易系统
这款MetaTrader 5 EA实现了基于订单流的剥头皮交易策略,并配备了高级风险管理功能。它使用多种技术指标,通过订单的不平衡性来识别交易机会。回测结果显示该策略具有潜在的盈利能力,但同时也突显了需要进一步优化的必要性,尤其是在风险管理和交易结果比率方面。该策略适合经验丰富的交易者,但在实际部署之前,需要进行彻底的测试和深入理解。
使用Python和MQL5进行多交易品种分析(第一部分):纳斯达克集成电路制造商 使用Python和MQL5进行多交易品种分析(第一部分):纳斯达克集成电路制造商
加入我们的讨论,了解如何利用人工智能(AI)优化您的仓位规模和订单数量,以最大化您的投资组合回报。我们将展示如何通过算法识别一个最优的投资组合,并根据您的回报预期或风险承受能力来调整投资组合。在本次讨论中,我们将使用SciPy库和MQL5语言,利用所拥有的全部数据创建一个最优且多样化的投资组合。
掌握 MQL5:从入门到精通(第五部分):基本控制流操作符 掌握 MQL5:从入门到精通(第五部分):基本控制流操作符
本文探讨了用于修改程序执行流程的关键操作符:条件语句、循环和 switch 语句。利用这些操作符将使我们创建的函数表现得更加“智能”。
OSZAR »